1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (c) 2022 Amlogic, Inc. All rights reserved. |
4 | */ |
5 | |
6 | #include <linux/err.h> |
7 | #include <linux/io.h> |
8 | #include <linux/kernel.h> |
9 | #include <linux/module.h> |
10 | #include <linux/of.h> |
11 | #include <linux/perf_event.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/printk.h> |
14 | #include <linux/types.h> |
15 | |
16 | #include <soc/amlogic/meson_ddr_pmu.h> |
17 | |
18 | #define PORT_MAJOR 32 |
19 | #define DEFAULT_XTAL_FREQ 24000000UL |
20 | |
21 | #define DMC_QOS_IRQ BIT(30) |
22 | |
23 | /* DMC bandwidth monitor register address offset */ |
24 | #define DMC_MON_G12_CTRL0 (0x0 << 2) |
25 | #define DMC_MON_G12_CTRL1 (0x1 << 2) |
26 | #define DMC_MON_G12_CTRL2 (0x2 << 2) |
27 | #define DMC_MON_G12_CTRL3 (0x3 << 2) |
28 | #define DMC_MON_G12_CTRL4 (0x4 << 2) |
29 | #define DMC_MON_G12_CTRL5 (0x5 << 2) |
30 | #define DMC_MON_G12_CTRL6 (0x6 << 2) |
31 | #define DMC_MON_G12_CTRL7 (0x7 << 2) |
32 | #define DMC_MON_G12_CTRL8 (0x8 << 2) |
33 | |
34 | #define DMC_MON_G12_ALL_REQ_CNT (0x9 << 2) |
35 | #define DMC_MON_G12_ALL_GRANT_CNT (0xa << 2) |
36 | #define DMC_MON_G12_ONE_GRANT_CNT (0xb << 2) |
37 | #define DMC_MON_G12_SEC_GRANT_CNT (0xc << 2) |
38 | #define DMC_MON_G12_THD_GRANT_CNT (0xd << 2) |
39 | #define DMC_MON_G12_FOR_GRANT_CNT (0xe << 2) |
40 | #define DMC_MON_G12_TIMER (0xf << 2) |
41 | |
42 | /* Each bit represent a axi line */ |
43 | PMU_FORMAT_ATTR(event, "config:0-7" ); |
44 | PMU_FORMAT_ATTR(arm, "config1:0" ); |
45 | PMU_FORMAT_ATTR(gpu, "config1:1" ); |
46 | PMU_FORMAT_ATTR(pcie, "config1:2" ); |
47 | PMU_FORMAT_ATTR(hdcp, "config1:3" ); |
48 | PMU_FORMAT_ATTR(hevc_front, "config1:4" ); |
49 | PMU_FORMAT_ATTR(usb3_0, "config1:6" ); |
50 | PMU_FORMAT_ATTR(device, "config1:7" ); |
51 | PMU_FORMAT_ATTR(hevc_back, "config1:8" ); |
52 | PMU_FORMAT_ATTR(h265enc, "config1:9" ); |
53 | PMU_FORMAT_ATTR(vpu_read1, "config1:16" ); |
54 | PMU_FORMAT_ATTR(vpu_read2, "config1:17" ); |
55 | PMU_FORMAT_ATTR(vpu_read3, "config1:18" ); |
56 | PMU_FORMAT_ATTR(vpu_write1, "config1:19" ); |
57 | PMU_FORMAT_ATTR(vpu_write2, "config1:20" ); |
58 | PMU_FORMAT_ATTR(vdec, "config1:21" ); |
59 | PMU_FORMAT_ATTR(hcodec, "config1:22" ); |
60 | PMU_FORMAT_ATTR(ge2d, "config1:23" ); |
61 | |
62 | PMU_FORMAT_ATTR(spicc1, "config1:32" ); |
63 | PMU_FORMAT_ATTR(usb0, "config1:33" ); |
64 | PMU_FORMAT_ATTR(dma, "config1:34" ); |
65 | PMU_FORMAT_ATTR(arb0, "config1:35" ); |
66 | PMU_FORMAT_ATTR(sd_emmc_b, "config1:36" ); |
67 | PMU_FORMAT_ATTR(usb1, "config1:37" ); |
68 | PMU_FORMAT_ATTR(audio, "config1:38" ); |
69 | PMU_FORMAT_ATTR(aififo, "config1:39" ); |
70 | PMU_FORMAT_ATTR(parser, "config1:41" ); |
71 | PMU_FORMAT_ATTR(ao_cpu, "config1:42" ); |
72 | PMU_FORMAT_ATTR(sd_emmc_c, "config1:43" ); |
73 | PMU_FORMAT_ATTR(spicc2, "config1:44" ); |
74 | PMU_FORMAT_ATTR(ethernet, "config1:45" ); |
75 | PMU_FORMAT_ATTR(sana, "config1:46" ); |
76 | |
77 | /* for sm1 and g12b */ |
78 | PMU_FORMAT_ATTR(nna, "config1:10" ); |
79 | |
80 | /* for g12b only */ |
81 | PMU_FORMAT_ATTR(gdc, "config1:11" ); |
82 | PMU_FORMAT_ATTR(mipi_isp, "config1:12" ); |
83 | PMU_FORMAT_ATTR(arm1, "config1:13" ); |
84 | PMU_FORMAT_ATTR(sd_emmc_a, "config1:40" ); |
85 | |
86 | static struct attribute *g12_pmu_format_attrs[] = { |
87 | &format_attr_event.attr, |
88 | &format_attr_arm.attr, |
89 | &format_attr_gpu.attr, |
90 | &format_attr_nna.attr, |
91 | &format_attr_gdc.attr, |
92 | &format_attr_arm1.attr, |
93 | &format_attr_mipi_isp.attr, |
94 | &format_attr_sd_emmc_a.attr, |
95 | &format_attr_pcie.attr, |
96 | &format_attr_hdcp.attr, |
97 | &format_attr_hevc_front.attr, |
98 | &format_attr_usb3_0.attr, |
99 | &format_attr_device.attr, |
100 | &format_attr_hevc_back.attr, |
101 | &format_attr_h265enc.attr, |
102 | &format_attr_vpu_read1.attr, |
103 | &format_attr_vpu_read2.attr, |
104 | &format_attr_vpu_read3.attr, |
105 | &format_attr_vpu_write1.attr, |
106 | &format_attr_vpu_write2.attr, |
107 | &format_attr_vdec.attr, |
108 | &format_attr_hcodec.attr, |
109 | &format_attr_ge2d.attr, |
110 | &format_attr_spicc1.attr, |
111 | &format_attr_usb0.attr, |
112 | &format_attr_dma.attr, |
113 | &format_attr_arb0.attr, |
114 | &format_attr_sd_emmc_b.attr, |
115 | &format_attr_usb1.attr, |
116 | &format_attr_audio.attr, |
117 | &format_attr_aififo.attr, |
118 | &format_attr_parser.attr, |
119 | &format_attr_ao_cpu.attr, |
120 | &format_attr_sd_emmc_c.attr, |
121 | &format_attr_spicc2.attr, |
122 | &format_attr_ethernet.attr, |
123 | &format_attr_sana.attr, |
124 | NULL, |
125 | }; |
126 | |
127 | /* calculate ddr clock */ |
128 | static unsigned long dmc_g12_get_freq_quick(struct dmc_info *info) |
129 | { |
130 | unsigned int val; |
131 | unsigned int n, m, od1; |
132 | unsigned int od_div = 0xfff; |
133 | unsigned long freq = 0; |
134 | |
135 | val = readl(addr: info->pll_reg); |
136 | val = val & 0xfffff; |
137 | switch ((val >> 16) & 7) { |
138 | case 0: |
139 | od_div = 2; |
140 | break; |
141 | |
142 | case 1: |
143 | od_div = 3; |
144 | break; |
145 | |
146 | case 2: |
147 | od_div = 4; |
148 | break; |
149 | |
150 | case 3: |
151 | od_div = 6; |
152 | break; |
153 | |
154 | case 4: |
155 | od_div = 8; |
156 | break; |
157 | |
158 | default: |
159 | break; |
160 | } |
161 | |
162 | m = val & 0x1ff; |
163 | n = ((val >> 10) & 0x1f); |
164 | od1 = (((val >> 19) & 0x1)) == 1 ? 2 : 1; |
165 | freq = DEFAULT_XTAL_FREQ / 1000; /* avoid overflow */ |
166 | if (n) |
167 | freq = ((((freq * m) / n) >> od1) / od_div) * 1000; |
168 | |
169 | return freq; |
170 | } |
171 | |
172 | #ifdef DEBUG |
173 | static void g12_dump_reg(struct dmc_info *db) |
174 | { |
175 | int s = 0, i; |
176 | unsigned int r; |
177 | |
178 | for (i = 0; i < 9; i++) { |
179 | r = readl(db->ddr_reg[0] + (DMC_MON_G12_CTRL0 + (i << 2))); |
180 | pr_notice("DMC_MON_CTRL%d: %08x\n" , i, r); |
181 | } |
182 | r = readl(db->ddr_reg[0] + DMC_MON_G12_ALL_REQ_CNT); |
183 | pr_notice("DMC_MON_ALL_REQ_CNT: %08x\n" , r); |
184 | r = readl(db->ddr_reg[0] + DMC_MON_G12_ALL_GRANT_CNT); |
185 | pr_notice("DMC_MON_ALL_GRANT_CNT:%08x\n" , r); |
186 | r = readl(db->ddr_reg[0] + DMC_MON_G12_ONE_GRANT_CNT); |
187 | pr_notice("DMC_MON_ONE_GRANT_CNT:%08x\n" , r); |
188 | r = readl(db->ddr_reg[0] + DMC_MON_G12_SEC_GRANT_CNT); |
189 | pr_notice("DMC_MON_SEC_GRANT_CNT:%08x\n" , r); |
190 | r = readl(db->ddr_reg[0] + DMC_MON_G12_THD_GRANT_CNT); |
191 | pr_notice("DMC_MON_THD_GRANT_CNT:%08x\n" , r); |
192 | r = readl(db->ddr_reg[0] + DMC_MON_G12_FOR_GRANT_CNT); |
193 | pr_notice("DMC_MON_FOR_GRANT_CNT:%08x\n" , r); |
194 | r = readl(db->ddr_reg[0] + DMC_MON_G12_TIMER); |
195 | pr_notice("DMC_MON_TIMER: %08x\n" , r); |
196 | } |
197 | #endif |
198 | |
199 | static void dmc_g12_counter_enable(struct dmc_info *info) |
200 | { |
201 | unsigned int val; |
202 | unsigned long clock_count = dmc_g12_get_freq_quick(info) / 10; /* 100ms */ |
203 | |
204 | writel(val: clock_count, addr: info->ddr_reg[0] + DMC_MON_G12_TIMER); |
205 | |
206 | val = readl(addr: info->ddr_reg[0] + DMC_MON_G12_CTRL0); |
207 | |
208 | /* enable all channel */ |
209 | val = BIT(31) | /* enable bit */ |
210 | BIT(20) | /* use timer */ |
211 | 0x0f; /* 4 channels */ |
212 | |
213 | writel(val, addr: info->ddr_reg[0] + DMC_MON_G12_CTRL0); |
214 | |
215 | #ifdef DEBUG |
216 | g12_dump_reg(info); |
217 | #endif |
218 | } |
219 | |
220 | static void dmc_g12_config_fiter(struct dmc_info *info, |
221 | int port, int channel) |
222 | { |
223 | u32 val; |
224 | u32 rp[MAX_CHANNEL_NUM] = {DMC_MON_G12_CTRL1, DMC_MON_G12_CTRL3, |
225 | DMC_MON_G12_CTRL5, DMC_MON_G12_CTRL7}; |
226 | u32 rs[MAX_CHANNEL_NUM] = {DMC_MON_G12_CTRL2, DMC_MON_G12_CTRL4, |
227 | DMC_MON_G12_CTRL6, DMC_MON_G12_CTRL8}; |
228 | int subport = -1; |
229 | |
230 | /* clear all port mask */ |
231 | if (port < 0) { |
232 | writel(val: 0, addr: info->ddr_reg[0] + rp[channel]); |
233 | writel(val: 0, addr: info->ddr_reg[0] + rs[channel]); |
234 | return; |
235 | } |
236 | |
237 | if (port >= PORT_MAJOR) |
238 | subport = port - PORT_MAJOR; |
239 | |
240 | if (subport < 0) { |
241 | val = readl(addr: info->ddr_reg[0] + rp[channel]); |
242 | val |= (1 << port); |
243 | writel(val, addr: info->ddr_reg[0] + rp[channel]); |
244 | val = 0xffff; |
245 | writel(val, addr: info->ddr_reg[0] + rs[channel]); |
246 | } else { |
247 | val = BIT(23); /* select device */ |
248 | writel(val, addr: info->ddr_reg[0] + rp[channel]); |
249 | val = readl(addr: info->ddr_reg[0] + rs[channel]); |
250 | val |= (1 << subport); |
251 | writel(val, addr: info->ddr_reg[0] + rs[channel]); |
252 | } |
253 | } |
254 | |
255 | static void dmc_g12_set_axi_filter(struct dmc_info *info, int axi_id, int channel) |
256 | { |
257 | if (channel > info->hw_info->chann_nr) |
258 | return; |
259 | |
260 | dmc_g12_config_fiter(info, port: axi_id, channel); |
261 | } |
262 | |
263 | static void dmc_g12_counter_disable(struct dmc_info *info) |
264 | { |
265 | int i; |
266 | |
267 | /* clear timer */ |
268 | writel(val: 0, addr: info->ddr_reg[0] + DMC_MON_G12_CTRL0); |
269 | writel(val: 0, addr: info->ddr_reg[0] + DMC_MON_G12_TIMER); |
270 | |
271 | writel(val: 0, addr: info->ddr_reg[0] + DMC_MON_G12_ALL_REQ_CNT); |
272 | writel(val: 0, addr: info->ddr_reg[0] + DMC_MON_G12_ALL_GRANT_CNT); |
273 | writel(val: 0, addr: info->ddr_reg[0] + DMC_MON_G12_ONE_GRANT_CNT); |
274 | writel(val: 0, addr: info->ddr_reg[0] + DMC_MON_G12_SEC_GRANT_CNT); |
275 | writel(val: 0, addr: info->ddr_reg[0] + DMC_MON_G12_THD_GRANT_CNT); |
276 | writel(val: 0, addr: info->ddr_reg[0] + DMC_MON_G12_FOR_GRANT_CNT); |
277 | |
278 | /* clear port channel mapping */ |
279 | for (i = 0; i < info->hw_info->chann_nr; i++) |
280 | dmc_g12_config_fiter(info, port: -1, channel: i); |
281 | } |
282 | |
283 | static void dmc_g12_get_counters(struct dmc_info *info, |
284 | struct dmc_counter *counter) |
285 | { |
286 | int i; |
287 | unsigned int reg; |
288 | |
289 | counter->all_cnt = readl(addr: info->ddr_reg[0] + DMC_MON_G12_ALL_GRANT_CNT); |
290 | counter->all_req = readl(addr: info->ddr_reg[0] + DMC_MON_G12_ALL_REQ_CNT); |
291 | |
292 | for (i = 0; i < info->hw_info->chann_nr; i++) { |
293 | reg = DMC_MON_G12_ONE_GRANT_CNT + (i << 2); |
294 | counter->channel_cnt[i] = readl(addr: info->ddr_reg[0] + reg); |
295 | } |
296 | } |
297 | |
298 | static int dmc_g12_irq_handler(struct dmc_info *info, |
299 | struct dmc_counter *counter) |
300 | { |
301 | unsigned int val; |
302 | int ret = -EINVAL; |
303 | |
304 | val = readl(addr: info->ddr_reg[0] + DMC_MON_G12_CTRL0); |
305 | if (val & DMC_QOS_IRQ) { |
306 | dmc_g12_get_counters(info, counter); |
307 | /* clear irq flags */ |
308 | writel(val, addr: info->ddr_reg[0] + DMC_MON_G12_CTRL0); |
309 | ret = 0; |
310 | } |
311 | return ret; |
312 | } |
313 | |
314 | static const struct dmc_hw_info g12a_dmc_info = { |
315 | .enable = dmc_g12_counter_enable, |
316 | .disable = dmc_g12_counter_disable, |
317 | .irq_handler = dmc_g12_irq_handler, |
318 | .get_counters = dmc_g12_get_counters, |
319 | .set_axi_filter = dmc_g12_set_axi_filter, |
320 | |
321 | .dmc_nr = 1, |
322 | .chann_nr = 4, |
323 | .capability = {0X7EFF00FF03DF, 0}, |
324 | .fmt_attr = g12_pmu_format_attrs, |
325 | }; |
326 | |
327 | static const struct dmc_hw_info g12b_dmc_info = { |
328 | .enable = dmc_g12_counter_enable, |
329 | .disable = dmc_g12_counter_disable, |
330 | .irq_handler = dmc_g12_irq_handler, |
331 | .get_counters = dmc_g12_get_counters, |
332 | .set_axi_filter = dmc_g12_set_axi_filter, |
333 | |
334 | .dmc_nr = 1, |
335 | .chann_nr = 4, |
336 | .capability = {0X7FFF00FF3FDF, 0}, |
337 | .fmt_attr = g12_pmu_format_attrs, |
338 | }; |
339 | |
340 | static const struct dmc_hw_info sm1_dmc_info = { |
341 | .enable = dmc_g12_counter_enable, |
342 | .disable = dmc_g12_counter_disable, |
343 | .irq_handler = dmc_g12_irq_handler, |
344 | .get_counters = dmc_g12_get_counters, |
345 | .set_axi_filter = dmc_g12_set_axi_filter, |
346 | |
347 | .dmc_nr = 1, |
348 | .chann_nr = 4, |
349 | .capability = {0X7EFF00FF07DF, 0}, |
350 | .fmt_attr = g12_pmu_format_attrs, |
351 | }; |
352 | |
353 | static int g12_ddr_pmu_probe(struct platform_device *pdev) |
354 | { |
355 | return meson_ddr_pmu_create(pdev); |
356 | } |
357 | |
358 | static void g12_ddr_pmu_remove(struct platform_device *pdev) |
359 | { |
360 | meson_ddr_pmu_remove(pdev); |
361 | } |
362 | |
363 | static const struct of_device_id meson_ddr_pmu_dt_match[] = { |
364 | { |
365 | .compatible = "amlogic,g12a-ddr-pmu" , |
366 | .data = &g12a_dmc_info, |
367 | }, |
368 | { |
369 | .compatible = "amlogic,g12b-ddr-pmu" , |
370 | .data = &g12b_dmc_info, |
371 | }, |
372 | { |
373 | .compatible = "amlogic,sm1-ddr-pmu" , |
374 | .data = &sm1_dmc_info, |
375 | }, |
376 | {} |
377 | }; |
378 | MODULE_DEVICE_TABLE(of, meson_ddr_pmu_dt_match); |
379 | |
380 | static struct platform_driver g12_ddr_pmu_driver = { |
381 | .probe = g12_ddr_pmu_probe, |
382 | .remove_new = g12_ddr_pmu_remove, |
383 | |
384 | .driver = { |
385 | .name = "meson-g12-ddr-pmu" , |
386 | .of_match_table = meson_ddr_pmu_dt_match, |
387 | }, |
388 | }; |
389 | |
390 | module_platform_driver(g12_ddr_pmu_driver); |
391 | MODULE_AUTHOR("Jiucheng Xu" ); |
392 | MODULE_LICENSE("GPL" ); |
393 | MODULE_DESCRIPTION("Amlogic G12 series SoC DDR PMU" ); |
394 | |