1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Copyright (C) 2023 Loongson Technology Corporation Limited |
4 | */ |
5 | |
6 | #include <linux/delay.h> |
7 | |
8 | #include <drm/drm_file.h> |
9 | #include <drm/drm_managed.h> |
10 | #include <drm/drm_print.h> |
11 | |
12 | #include "lsdc_drv.h" |
13 | |
14 | /* |
15 | * GFX PLL is the PLL used by DC, GMC and GPU, the structure of the GFX PLL |
16 | * may suffer from change across chip variants. |
17 | * |
18 | * |
19 | * +-------------+ sel_out_dc |
20 | * +----| / div_out_0 | _____/ _____ DC |
21 | * | +-------------+ |
22 | * refclk +---------+ +-------+ | +-------------+ sel_out_gmc |
23 | * ---+---> | div_ref | ---> | loopc | --+--> | / div_out_1 | _____/ _____ GMC |
24 | * | +---------+ +-------+ | +-------------+ |
25 | * | / * | +-------------+ sel_out_gpu |
26 | * | +----| / div_out_2 | _____/ _____ GPU |
27 | * | +-------------+ |
28 | * | ^ |
29 | * | | |
30 | * +--------------------------- bypass ----------------------+ |
31 | */ |
32 | |
33 | struct loongson_gfxpll_bitmap { |
34 | /* Byte 0 ~ Byte 3 */ |
35 | unsigned div_out_dc : 7; /* 6 : 0 DC output clock divider */ |
36 | unsigned div_out_gmc : 7; /* 13 : 7 GMC output clock divider */ |
37 | unsigned div_out_gpu : 7; /* 20 : 14 GPU output clock divider */ |
38 | unsigned loopc : 9; /* 29 : 21 clock multiplier */ |
39 | unsigned _reserved_1_ : 2; /* 31 : 30 */ |
40 | |
41 | /* Byte 4 ~ Byte 7 */ |
42 | unsigned div_ref : 7; /* 38 : 32 Input clock divider */ |
43 | unsigned locked : 1; /* 39 PLL locked indicator */ |
44 | unsigned sel_out_dc : 1; /* 40 dc output clk enable */ |
45 | unsigned sel_out_gmc : 1; /* 41 gmc output clk enable */ |
46 | unsigned sel_out_gpu : 1; /* 42 gpu output clk enable */ |
47 | unsigned set_param : 1; /* 43 Trigger the update */ |
48 | unsigned bypass : 1; /* 44 */ |
49 | unsigned powerdown : 1; /* 45 */ |
50 | unsigned _reserved_2_ : 18; /* 46 : 63 no use */ |
51 | }; |
52 | |
53 | union loongson_gfxpll_reg_bitmap { |
54 | struct loongson_gfxpll_bitmap bitmap; |
55 | u32 w[2]; |
56 | u64 d; |
57 | }; |
58 | |
59 | static void __gfxpll_rreg(struct loongson_gfxpll *this, |
60 | union loongson_gfxpll_reg_bitmap *reg) |
61 | { |
62 | #if defined(CONFIG_64BIT) |
63 | reg->d = readq(addr: this->mmio); |
64 | #else |
65 | reg->w[0] = readl(this->mmio); |
66 | reg->w[1] = readl(this->mmio + 4); |
67 | #endif |
68 | } |
69 | |
70 | /* Update new parameters to the hardware */ |
71 | |
72 | static int loongson_gfxpll_update(struct loongson_gfxpll * const this, |
73 | struct loongson_gfxpll_parms const *pin) |
74 | { |
75 | /* None, TODO */ |
76 | |
77 | return 0; |
78 | } |
79 | |
80 | static void loongson_gfxpll_get_rates(struct loongson_gfxpll * const this, |
81 | unsigned int *dc, |
82 | unsigned int *gmc, |
83 | unsigned int *gpu) |
84 | { |
85 | struct loongson_gfxpll_parms *pparms = &this->parms; |
86 | union loongson_gfxpll_reg_bitmap gfxpll_reg; |
87 | unsigned int pre_output; |
88 | unsigned int dc_mhz; |
89 | unsigned int gmc_mhz; |
90 | unsigned int gpu_mhz; |
91 | |
92 | __gfxpll_rreg(this, reg: &gfxpll_reg); |
93 | |
94 | pparms->div_ref = gfxpll_reg.bitmap.div_ref; |
95 | pparms->loopc = gfxpll_reg.bitmap.loopc; |
96 | |
97 | pparms->div_out_dc = gfxpll_reg.bitmap.div_out_dc; |
98 | pparms->div_out_gmc = gfxpll_reg.bitmap.div_out_gmc; |
99 | pparms->div_out_gpu = gfxpll_reg.bitmap.div_out_gpu; |
100 | |
101 | pre_output = pparms->ref_clock / pparms->div_ref * pparms->loopc; |
102 | |
103 | dc_mhz = pre_output / pparms->div_out_dc / 1000; |
104 | gmc_mhz = pre_output / pparms->div_out_gmc / 1000; |
105 | gpu_mhz = pre_output / pparms->div_out_gpu / 1000; |
106 | |
107 | if (dc) |
108 | *dc = dc_mhz; |
109 | |
110 | if (gmc) |
111 | *gmc = gmc_mhz; |
112 | |
113 | if (gpu) |
114 | *gpu = gpu_mhz; |
115 | } |
116 | |
117 | static void loongson_gfxpll_print(struct loongson_gfxpll * const this, |
118 | struct drm_printer *p, |
119 | bool verbose) |
120 | { |
121 | struct loongson_gfxpll_parms *parms = &this->parms; |
122 | unsigned int dc, gmc, gpu; |
123 | |
124 | if (verbose) { |
125 | drm_printf(p, f: "reference clock: %u\n" , parms->ref_clock); |
126 | drm_printf(p, f: "div_ref = %u\n" , parms->div_ref); |
127 | drm_printf(p, f: "loopc = %u\n" , parms->loopc); |
128 | |
129 | drm_printf(p, f: "div_out_dc = %u\n" , parms->div_out_dc); |
130 | drm_printf(p, f: "div_out_gmc = %u\n" , parms->div_out_gmc); |
131 | drm_printf(p, f: "div_out_gpu = %u\n" , parms->div_out_gpu); |
132 | } |
133 | |
134 | this->funcs->get_rates(this, &dc, &gmc, &gpu); |
135 | |
136 | drm_printf(p, f: "dc: %uMHz, gmc: %uMHz, gpu: %uMHz\n" , dc, gmc, gpu); |
137 | } |
138 | |
139 | /* GFX (DC, GPU, GMC) PLL initialization and destroy function */ |
140 | |
141 | static void loongson_gfxpll_fini(struct drm_device *ddev, void *data) |
142 | { |
143 | struct loongson_gfxpll *this = (struct loongson_gfxpll *)data; |
144 | |
145 | iounmap(addr: this->mmio); |
146 | |
147 | kfree(objp: this); |
148 | } |
149 | |
150 | static int loongson_gfxpll_init(struct loongson_gfxpll * const this) |
151 | { |
152 | struct loongson_gfxpll_parms *pparms = &this->parms; |
153 | struct drm_printer printer = drm_info_printer(dev: this->ddev->dev); |
154 | |
155 | pparms->ref_clock = LSDC_PLL_REF_CLK_KHZ; |
156 | |
157 | this->mmio = ioremap(offset: this->reg_base, size: this->reg_size); |
158 | if (IS_ERR_OR_NULL(ptr: this->mmio)) |
159 | return -ENOMEM; |
160 | |
161 | this->funcs->print(this, &printer, false); |
162 | |
163 | return 0; |
164 | } |
165 | |
166 | static const struct loongson_gfxpll_funcs lsdc_gmc_gpu_funcs = { |
167 | .init = loongson_gfxpll_init, |
168 | .update = loongson_gfxpll_update, |
169 | .get_rates = loongson_gfxpll_get_rates, |
170 | .print = loongson_gfxpll_print, |
171 | }; |
172 | |
173 | int loongson_gfxpll_create(struct drm_device *ddev, |
174 | struct loongson_gfxpll **ppout) |
175 | { |
176 | struct lsdc_device *ldev = to_lsdc(ddev); |
177 | const struct loongson_gfx_desc *gfx = to_loongson_gfx(dcp: ldev->descp); |
178 | struct loongson_gfxpll *this; |
179 | int ret; |
180 | |
181 | this = kzalloc(size: sizeof(*this), GFP_KERNEL); |
182 | if (IS_ERR_OR_NULL(ptr: this)) |
183 | return -ENOMEM; |
184 | |
185 | this->ddev = ddev; |
186 | this->reg_size = gfx->gfxpll.reg_size; |
187 | this->reg_base = gfx->conf_reg_base + gfx->gfxpll.reg_offset; |
188 | this->funcs = &lsdc_gmc_gpu_funcs; |
189 | |
190 | ret = this->funcs->init(this); |
191 | if (unlikely(ret)) { |
192 | kfree(objp: this); |
193 | return ret; |
194 | } |
195 | |
196 | *ppout = this; |
197 | |
198 | return drmm_add_action_or_reset(ddev, loongson_gfxpll_fini, this); |
199 | } |
200 | |