1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2020 MediaTek Inc. |
4 | */ |
5 | |
6 | #include <linux/clk.h> |
7 | #include <linux/interrupt.h> |
8 | #include <linux/iopoll.h> |
9 | #include <linux/module.h> |
10 | #include <linux/platform_device.h> |
11 | #include <linux/of.h> |
12 | #include <linux/of_irq.h> |
13 | #include <linux/of_address.h> |
14 | |
15 | #define VIO_MOD_TO_REG_IND(m) ((m) / 32) |
16 | #define VIO_MOD_TO_REG_OFF(m) ((m) % 32) |
17 | |
18 | struct mtk_devapc_vio_dbgs { |
19 | union { |
20 | u32 vio_dbg0; |
21 | struct { |
22 | u32 mstid:16; |
23 | u32 dmnid:6; |
24 | u32 vio_w:1; |
25 | u32 vio_r:1; |
26 | u32 addr_h:4; |
27 | u32 resv:4; |
28 | } dbg0_bits; |
29 | }; |
30 | |
31 | u32 vio_dbg1; |
32 | }; |
33 | |
34 | struct mtk_devapc_regs_ofs { |
35 | /* reg offset */ |
36 | u32 vio_mask_offset; |
37 | u32 vio_sta_offset; |
38 | u32 vio_dbg0_offset; |
39 | u32 vio_dbg1_offset; |
40 | u32 apc_con_offset; |
41 | u32 vio_shift_sta_offset; |
42 | u32 vio_shift_sel_offset; |
43 | u32 vio_shift_con_offset; |
44 | }; |
45 | |
46 | struct mtk_devapc_data { |
47 | /* numbers of violation index */ |
48 | u32 vio_idx_num; |
49 | const struct mtk_devapc_regs_ofs *regs_ofs; |
50 | }; |
51 | |
52 | struct mtk_devapc_context { |
53 | struct device *dev; |
54 | void __iomem *infra_base; |
55 | struct clk *infra_clk; |
56 | const struct mtk_devapc_data *data; |
57 | }; |
58 | |
59 | static void clear_vio_status(struct mtk_devapc_context *ctx) |
60 | { |
61 | void __iomem *reg; |
62 | int i; |
63 | |
64 | reg = ctx->infra_base + ctx->data->regs_ofs->vio_sta_offset; |
65 | |
66 | for (i = 0; i < VIO_MOD_TO_REG_IND(ctx->data->vio_idx_num) - 1; i++) |
67 | writel(GENMASK(31, 0), addr: reg + 4 * i); |
68 | |
69 | writel(GENMASK(VIO_MOD_TO_REG_OFF(ctx->data->vio_idx_num) - 1, 0), |
70 | addr: reg + 4 * i); |
71 | } |
72 | |
73 | static void mask_module_irq(struct mtk_devapc_context *ctx, bool mask) |
74 | { |
75 | void __iomem *reg; |
76 | u32 val; |
77 | int i; |
78 | |
79 | reg = ctx->infra_base + ctx->data->regs_ofs->vio_mask_offset; |
80 | |
81 | if (mask) |
82 | val = GENMASK(31, 0); |
83 | else |
84 | val = 0; |
85 | |
86 | for (i = 0; i < VIO_MOD_TO_REG_IND(ctx->data->vio_idx_num) - 1; i++) |
87 | writel(val, addr: reg + 4 * i); |
88 | |
89 | val = readl(addr: reg + 4 * i); |
90 | if (mask) |
91 | val |= GENMASK(VIO_MOD_TO_REG_OFF(ctx->data->vio_idx_num) - 1, |
92 | 0); |
93 | else |
94 | val &= ~GENMASK(VIO_MOD_TO_REG_OFF(ctx->data->vio_idx_num) - 1, |
95 | 0); |
96 | |
97 | writel(val, addr: reg + 4 * i); |
98 | } |
99 | |
100 | #define PHY_DEVAPC_TIMEOUT 0x10000 |
101 | |
102 | /* |
103 | * devapc_sync_vio_dbg - do "shift" mechansim" to get full violation information. |
104 | * shift mechanism is depends on devapc hardware design. |
105 | * Mediatek devapc set multiple slaves as a group. |
106 | * When violation is triggered, violation info is kept |
107 | * inside devapc hardware. |
108 | * Driver should do shift mechansim to sync full violation |
109 | * info to VIO_DBGs registers. |
110 | * |
111 | */ |
112 | static int devapc_sync_vio_dbg(struct mtk_devapc_context *ctx) |
113 | { |
114 | void __iomem *pd_vio_shift_sta_reg; |
115 | void __iomem *pd_vio_shift_sel_reg; |
116 | void __iomem *pd_vio_shift_con_reg; |
117 | int min_shift_group; |
118 | int ret; |
119 | u32 val; |
120 | |
121 | pd_vio_shift_sta_reg = ctx->infra_base + |
122 | ctx->data->regs_ofs->vio_shift_sta_offset; |
123 | pd_vio_shift_sel_reg = ctx->infra_base + |
124 | ctx->data->regs_ofs->vio_shift_sel_offset; |
125 | pd_vio_shift_con_reg = ctx->infra_base + |
126 | ctx->data->regs_ofs->vio_shift_con_offset; |
127 | |
128 | /* Find the minimum shift group which has violation */ |
129 | val = readl(addr: pd_vio_shift_sta_reg); |
130 | if (!val) |
131 | return false; |
132 | |
133 | min_shift_group = __ffs(val); |
134 | |
135 | /* Assign the group to sync */ |
136 | writel(val: 0x1 << min_shift_group, addr: pd_vio_shift_sel_reg); |
137 | |
138 | /* Start syncing */ |
139 | writel(val: 0x1, addr: pd_vio_shift_con_reg); |
140 | |
141 | ret = readl_poll_timeout(pd_vio_shift_con_reg, val, val == 0x3, 0, |
142 | PHY_DEVAPC_TIMEOUT); |
143 | if (ret) { |
144 | dev_err(ctx->dev, "%s: Shift violation info failed\n" , __func__); |
145 | return false; |
146 | } |
147 | |
148 | /* Stop syncing */ |
149 | writel(val: 0x0, addr: pd_vio_shift_con_reg); |
150 | |
151 | /* Write clear */ |
152 | writel(val: 0x1 << min_shift_group, addr: pd_vio_shift_sta_reg); |
153 | |
154 | return true; |
155 | } |
156 | |
157 | /* |
158 | * devapc_extract_vio_dbg - extract full violation information after doing |
159 | * shift mechanism. |
160 | */ |
161 | static void (struct mtk_devapc_context *ctx) |
162 | { |
163 | struct mtk_devapc_vio_dbgs vio_dbgs; |
164 | void __iomem *vio_dbg0_reg; |
165 | void __iomem *vio_dbg1_reg; |
166 | |
167 | vio_dbg0_reg = ctx->infra_base + ctx->data->regs_ofs->vio_dbg0_offset; |
168 | vio_dbg1_reg = ctx->infra_base + ctx->data->regs_ofs->vio_dbg1_offset; |
169 | |
170 | vio_dbgs.vio_dbg0 = readl(addr: vio_dbg0_reg); |
171 | vio_dbgs.vio_dbg1 = readl(addr: vio_dbg1_reg); |
172 | |
173 | /* Print violation information */ |
174 | if (vio_dbgs.dbg0_bits.vio_w) |
175 | dev_info(ctx->dev, "Write Violation\n" ); |
176 | else if (vio_dbgs.dbg0_bits.vio_r) |
177 | dev_info(ctx->dev, "Read Violation\n" ); |
178 | |
179 | dev_info(ctx->dev, "Bus ID:0x%x, Dom ID:0x%x, Vio Addr:0x%x\n" , |
180 | vio_dbgs.dbg0_bits.mstid, vio_dbgs.dbg0_bits.dmnid, |
181 | vio_dbgs.vio_dbg1); |
182 | } |
183 | |
184 | /* |
185 | * devapc_violation_irq - the devapc Interrupt Service Routine (ISR) will dump |
186 | * violation information including which master violates |
187 | * access slave. |
188 | */ |
189 | static irqreturn_t devapc_violation_irq(int irq_number, void *data) |
190 | { |
191 | struct mtk_devapc_context *ctx = data; |
192 | |
193 | while (devapc_sync_vio_dbg(ctx)) |
194 | devapc_extract_vio_dbg(ctx); |
195 | |
196 | clear_vio_status(ctx); |
197 | |
198 | return IRQ_HANDLED; |
199 | } |
200 | |
201 | /* |
202 | * start_devapc - unmask slave's irq to start receiving devapc violation. |
203 | */ |
204 | static void start_devapc(struct mtk_devapc_context *ctx) |
205 | { |
206 | writel(BIT(31), addr: ctx->infra_base + ctx->data->regs_ofs->apc_con_offset); |
207 | |
208 | mask_module_irq(ctx, mask: false); |
209 | } |
210 | |
211 | /* |
212 | * stop_devapc - mask slave's irq to stop service. |
213 | */ |
214 | static void stop_devapc(struct mtk_devapc_context *ctx) |
215 | { |
216 | mask_module_irq(ctx, mask: true); |
217 | |
218 | writel(BIT(2), addr: ctx->infra_base + ctx->data->regs_ofs->apc_con_offset); |
219 | } |
220 | |
221 | static const struct mtk_devapc_regs_ofs devapc_regs_ofs_mt6779 = { |
222 | .vio_mask_offset = 0x0, |
223 | .vio_sta_offset = 0x400, |
224 | .vio_dbg0_offset = 0x900, |
225 | .vio_dbg1_offset = 0x904, |
226 | .apc_con_offset = 0xF00, |
227 | .vio_shift_sta_offset = 0xF10, |
228 | .vio_shift_sel_offset = 0xF14, |
229 | .vio_shift_con_offset = 0xF20, |
230 | }; |
231 | |
232 | static const struct mtk_devapc_data devapc_mt6779 = { |
233 | .vio_idx_num = 511, |
234 | .regs_ofs = &devapc_regs_ofs_mt6779, |
235 | }; |
236 | |
237 | static const struct mtk_devapc_data devapc_mt8186 = { |
238 | .vio_idx_num = 519, |
239 | .regs_ofs = &devapc_regs_ofs_mt6779, |
240 | }; |
241 | |
242 | static const struct of_device_id mtk_devapc_dt_match[] = { |
243 | { |
244 | .compatible = "mediatek,mt6779-devapc" , |
245 | .data = &devapc_mt6779, |
246 | }, { |
247 | .compatible = "mediatek,mt8186-devapc" , |
248 | .data = &devapc_mt8186, |
249 | }, { |
250 | }, |
251 | }; |
252 | MODULE_DEVICE_TABLE(of, mtk_devapc_dt_match); |
253 | |
254 | static int mtk_devapc_probe(struct platform_device *pdev) |
255 | { |
256 | struct device_node *node = pdev->dev.of_node; |
257 | struct mtk_devapc_context *ctx; |
258 | u32 devapc_irq; |
259 | int ret; |
260 | |
261 | if (IS_ERR(ptr: node)) |
262 | return -ENODEV; |
263 | |
264 | ctx = devm_kzalloc(dev: &pdev->dev, size: sizeof(*ctx), GFP_KERNEL); |
265 | if (!ctx) |
266 | return -ENOMEM; |
267 | |
268 | ctx->data = of_device_get_match_data(dev: &pdev->dev); |
269 | ctx->dev = &pdev->dev; |
270 | |
271 | ctx->infra_base = of_iomap(node, index: 0); |
272 | if (!ctx->infra_base) |
273 | return -EINVAL; |
274 | |
275 | devapc_irq = irq_of_parse_and_map(node, index: 0); |
276 | if (!devapc_irq) |
277 | return -EINVAL; |
278 | |
279 | ctx->infra_clk = devm_clk_get_enabled(dev: &pdev->dev, id: "devapc-infra-clock" ); |
280 | if (IS_ERR(ptr: ctx->infra_clk)) |
281 | return -EINVAL; |
282 | |
283 | ret = devm_request_irq(dev: &pdev->dev, irq: devapc_irq, handler: devapc_violation_irq, |
284 | IRQF_TRIGGER_NONE, devname: "devapc" , dev_id: ctx); |
285 | if (ret) |
286 | return ret; |
287 | |
288 | platform_set_drvdata(pdev, data: ctx); |
289 | |
290 | start_devapc(ctx); |
291 | |
292 | return 0; |
293 | } |
294 | |
295 | static void mtk_devapc_remove(struct platform_device *pdev) |
296 | { |
297 | struct mtk_devapc_context *ctx = platform_get_drvdata(pdev); |
298 | |
299 | stop_devapc(ctx); |
300 | } |
301 | |
302 | static struct platform_driver mtk_devapc_driver = { |
303 | .probe = mtk_devapc_probe, |
304 | .remove_new = mtk_devapc_remove, |
305 | .driver = { |
306 | .name = "mtk-devapc" , |
307 | .of_match_table = mtk_devapc_dt_match, |
308 | }, |
309 | }; |
310 | |
311 | module_platform_driver(mtk_devapc_driver); |
312 | |
313 | MODULE_DESCRIPTION("Mediatek Device APC Driver" ); |
314 | MODULE_AUTHOR("Neal Liu <neal.liu@mediatek.com>" ); |
315 | MODULE_LICENSE("GPL" ); |
316 | |