1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * OMAP3XXX L3 Interconnect Driver |
4 | * |
5 | * Copyright (C) 2011 Texas Corporation |
6 | * Felipe Balbi <balbi@ti.com> |
7 | * Santosh Shilimkar <santosh.shilimkar@ti.com> |
8 | * Sricharan <r.sricharan@ti.com> |
9 | */ |
10 | |
11 | #include <linux/kernel.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/interrupt.h> |
15 | #include <linux/io.h> |
16 | #include <linux/module.h> |
17 | #include <linux/of.h> |
18 | |
19 | #include "omap_l3_smx.h" |
20 | |
21 | static inline u64 omap3_l3_readll(void __iomem *base, u16 reg) |
22 | { |
23 | return __raw_readll(base + reg); |
24 | } |
25 | |
26 | static inline void omap3_l3_writell(void __iomem *base, u16 reg, u64 value) |
27 | { |
28 | __raw_writell(value, base + reg); |
29 | } |
30 | |
31 | static inline enum omap3_l3_code omap3_l3_decode_error_code(u64 error) |
32 | { |
33 | return (error & 0x0f000000) >> L3_ERROR_LOG_CODE; |
34 | } |
35 | |
36 | static inline u32 omap3_l3_decode_addr(u64 error_addr) |
37 | { |
38 | return error_addr & 0xffffffff; |
39 | } |
40 | |
41 | static inline unsigned omap3_l3_decode_cmd(u64 error) |
42 | { |
43 | return (error & 0x07) >> L3_ERROR_LOG_CMD; |
44 | } |
45 | |
46 | static inline enum omap3_l3_initiator_id omap3_l3_decode_initid(u64 error) |
47 | { |
48 | return (error & 0xff00) >> L3_ERROR_LOG_INITID; |
49 | } |
50 | |
51 | static inline unsigned omap3_l3_decode_req_info(u64 error) |
52 | { |
53 | return (error >> 32) & 0xffff; |
54 | } |
55 | |
56 | static char *omap3_l3_code_string(u8 code) |
57 | { |
58 | switch (code) { |
59 | case OMAP_L3_CODE_NOERROR: |
60 | return "No Error" ; |
61 | case OMAP_L3_CODE_UNSUP_CMD: |
62 | return "Unsupported Command" ; |
63 | case OMAP_L3_CODE_ADDR_HOLE: |
64 | return "Address Hole" ; |
65 | case OMAP_L3_CODE_PROTECT_VIOLATION: |
66 | return "Protection Violation" ; |
67 | case OMAP_L3_CODE_IN_BAND_ERR: |
68 | return "In-band Error" ; |
69 | case OMAP_L3_CODE_REQ_TOUT_NOT_ACCEPT: |
70 | return "Request Timeout Not Accepted" ; |
71 | case OMAP_L3_CODE_REQ_TOUT_NO_RESP: |
72 | return "Request Timeout, no response" ; |
73 | default: |
74 | return "UNKNOWN error" ; |
75 | } |
76 | } |
77 | |
78 | static char *omap3_l3_initiator_string(u8 initid) |
79 | { |
80 | switch (initid) { |
81 | case OMAP_L3_LCD: |
82 | return "LCD" ; |
83 | case OMAP_L3_SAD2D: |
84 | return "SAD2D" ; |
85 | case OMAP_L3_IA_MPU_SS_1: |
86 | case OMAP_L3_IA_MPU_SS_2: |
87 | case OMAP_L3_IA_MPU_SS_3: |
88 | case OMAP_L3_IA_MPU_SS_4: |
89 | case OMAP_L3_IA_MPU_SS_5: |
90 | return "MPU" ; |
91 | case OMAP_L3_IA_IVA_SS_1: |
92 | case OMAP_L3_IA_IVA_SS_2: |
93 | case OMAP_L3_IA_IVA_SS_3: |
94 | return "IVA_SS" ; |
95 | case OMAP_L3_IA_IVA_SS_DMA_1: |
96 | case OMAP_L3_IA_IVA_SS_DMA_2: |
97 | case OMAP_L3_IA_IVA_SS_DMA_3: |
98 | case OMAP_L3_IA_IVA_SS_DMA_4: |
99 | case OMAP_L3_IA_IVA_SS_DMA_5: |
100 | case OMAP_L3_IA_IVA_SS_DMA_6: |
101 | return "IVA_SS_DMA" ; |
102 | case OMAP_L3_IA_SGX: |
103 | return "SGX" ; |
104 | case OMAP_L3_IA_CAM_1: |
105 | case OMAP_L3_IA_CAM_2: |
106 | case OMAP_L3_IA_CAM_3: |
107 | return "CAM" ; |
108 | case OMAP_L3_IA_DAP: |
109 | return "DAP" ; |
110 | case OMAP_L3_SDMA_WR_1: |
111 | case OMAP_L3_SDMA_WR_2: |
112 | return "SDMA_WR" ; |
113 | case OMAP_L3_SDMA_RD_1: |
114 | case OMAP_L3_SDMA_RD_2: |
115 | case OMAP_L3_SDMA_RD_3: |
116 | case OMAP_L3_SDMA_RD_4: |
117 | return "SDMA_RD" ; |
118 | case OMAP_L3_USBOTG: |
119 | return "USB_OTG" ; |
120 | case OMAP_L3_USBHOST: |
121 | return "USB_HOST" ; |
122 | default: |
123 | return "UNKNOWN Initiator" ; |
124 | } |
125 | } |
126 | |
127 | /* |
128 | * omap3_l3_block_irq - handles a register block's irq |
129 | * @l3: struct omap3_l3 * |
130 | * @base: register block base address |
131 | * @error: L3_ERROR_LOG register of our block |
132 | * |
133 | * Called in hard-irq context. Caller should take care of locking |
134 | * |
135 | * OMAP36xx TRM gives, on page 2001, Figure 9-10, the Typical Error |
136 | * Analysis Sequence, we are following that sequence here, please |
137 | * refer to that Figure for more information on the subject. |
138 | */ |
139 | static irqreturn_t omap3_l3_block_irq(struct omap3_l3 *l3, |
140 | u64 error, int error_addr) |
141 | { |
142 | u8 code = omap3_l3_decode_error_code(error); |
143 | u8 initid = omap3_l3_decode_initid(error); |
144 | u8 multi = error & L3_ERROR_LOG_MULTI; |
145 | u32 address = omap3_l3_decode_addr(error_addr); |
146 | |
147 | pr_err("%s seen by %s %s at address %x\n" , |
148 | omap3_l3_code_string(code), |
149 | omap3_l3_initiator_string(initid), |
150 | multi ? "Multiple Errors" : "" , address); |
151 | WARN_ON(1); |
152 | |
153 | return IRQ_HANDLED; |
154 | } |
155 | |
156 | static irqreturn_t omap3_l3_app_irq(int irq, void *_l3) |
157 | { |
158 | struct omap3_l3 *l3 = _l3; |
159 | u64 status, clear; |
160 | u64 error; |
161 | u64 error_addr; |
162 | u64 err_source = 0; |
163 | void __iomem *base; |
164 | int int_type; |
165 | irqreturn_t ret = IRQ_NONE; |
166 | |
167 | int_type = irq == l3->app_irq ? L3_APPLICATION_ERROR : L3_DEBUG_ERROR; |
168 | if (!int_type) |
169 | status = omap3_l3_readll(base: l3->rt, L3_SI_FLAG_STATUS_0); |
170 | else |
171 | status = omap3_l3_readll(base: l3->rt, L3_SI_FLAG_STATUS_1); |
172 | |
173 | /* identify the error source */ |
174 | err_source = __ffs(status); |
175 | |
176 | base = l3->rt + omap3_l3_bases[int_type][err_source]; |
177 | error = omap3_l3_readll(base, L3_ERROR_LOG); |
178 | if (error) { |
179 | error_addr = omap3_l3_readll(base, L3_ERROR_LOG_ADDR); |
180 | ret |= omap3_l3_block_irq(l3, error, error_addr); |
181 | } |
182 | |
183 | /* |
184 | * if we have a timeout error, there's nothing we can |
185 | * do besides rebooting the board. So let's BUG on any |
186 | * of such errors and handle the others. timeout error |
187 | * is severe and not expected to occur. |
188 | */ |
189 | BUG_ON(!int_type && status & L3_STATUS_0_TIMEOUT_MASK); |
190 | |
191 | /* Clear the status register */ |
192 | clear = (L3_AGENT_STATUS_CLEAR_IA << int_type) | |
193 | L3_AGENT_STATUS_CLEAR_TA; |
194 | omap3_l3_writell(base, L3_AGENT_STATUS, value: clear); |
195 | |
196 | /* clear the error log register */ |
197 | omap3_l3_writell(base, L3_ERROR_LOG, value: error); |
198 | |
199 | return ret; |
200 | } |
201 | |
202 | #if IS_BUILTIN(CONFIG_OF) |
203 | static const struct of_device_id omap3_l3_match[] = { |
204 | { |
205 | .compatible = "ti,omap3-l3-smx" , |
206 | }, |
207 | { }, |
208 | }; |
209 | MODULE_DEVICE_TABLE(of, omap3_l3_match); |
210 | #endif |
211 | |
212 | static int omap3_l3_probe(struct platform_device *pdev) |
213 | { |
214 | struct omap3_l3 *l3; |
215 | struct resource *res; |
216 | int ret; |
217 | |
218 | l3 = kzalloc(size: sizeof(*l3), GFP_KERNEL); |
219 | if (!l3) |
220 | return -ENOMEM; |
221 | |
222 | platform_set_drvdata(pdev, data: l3); |
223 | |
224 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
225 | if (!res) { |
226 | dev_err(&pdev->dev, "couldn't find resource\n" ); |
227 | ret = -ENODEV; |
228 | goto err0; |
229 | } |
230 | l3->rt = ioremap(offset: res->start, size: resource_size(res)); |
231 | if (!l3->rt) { |
232 | dev_err(&pdev->dev, "ioremap failed\n" ); |
233 | ret = -ENOMEM; |
234 | goto err0; |
235 | } |
236 | |
237 | l3->debug_irq = platform_get_irq(pdev, 0); |
238 | ret = request_irq(irq: l3->debug_irq, handler: omap3_l3_app_irq, IRQF_TRIGGER_RISING, |
239 | name: "l3-debug-irq" , dev: l3); |
240 | if (ret) { |
241 | dev_err(&pdev->dev, "couldn't request debug irq\n" ); |
242 | goto err1; |
243 | } |
244 | |
245 | l3->app_irq = platform_get_irq(pdev, 1); |
246 | ret = request_irq(irq: l3->app_irq, handler: omap3_l3_app_irq, IRQF_TRIGGER_RISING, |
247 | name: "l3-app-irq" , dev: l3); |
248 | if (ret) { |
249 | dev_err(&pdev->dev, "couldn't request app irq\n" ); |
250 | goto err2; |
251 | } |
252 | |
253 | return 0; |
254 | |
255 | err2: |
256 | free_irq(l3->debug_irq, l3); |
257 | err1: |
258 | iounmap(addr: l3->rt); |
259 | err0: |
260 | kfree(objp: l3); |
261 | return ret; |
262 | } |
263 | |
264 | static int omap3_l3_remove(struct platform_device *pdev) |
265 | { |
266 | struct omap3_l3 *l3 = platform_get_drvdata(pdev); |
267 | |
268 | free_irq(l3->app_irq, l3); |
269 | free_irq(l3->debug_irq, l3); |
270 | iounmap(addr: l3->rt); |
271 | kfree(objp: l3); |
272 | |
273 | return 0; |
274 | } |
275 | |
276 | static struct platform_driver omap3_l3_driver = { |
277 | .probe = omap3_l3_probe, |
278 | .remove = omap3_l3_remove, |
279 | .driver = { |
280 | .name = "omap_l3_smx" , |
281 | .of_match_table = of_match_ptr(omap3_l3_match), |
282 | }, |
283 | }; |
284 | |
285 | static int __init omap3_l3_init(void) |
286 | { |
287 | return platform_driver_register(&omap3_l3_driver); |
288 | } |
289 | postcore_initcall_sync(omap3_l3_init); |
290 | |
291 | static void __exit omap3_l3_exit(void) |
292 | { |
293 | platform_driver_unregister(&omap3_l3_driver); |
294 | } |
295 | module_exit(omap3_l3_exit); |
296 | |
297 | MODULE_AUTHOR("Felipe Balbi" ); |
298 | MODULE_AUTHOR("Santosh Shilimkar" ); |
299 | MODULE_AUTHOR("Sricharan R" ); |
300 | MODULE_DESCRIPTION("OMAP3XXX L3 Interconnect Driver" ); |
301 | MODULE_LICENSE("GPL" ); |
302 | |