Commit | Line | Data |
---|---|---|
5a729246 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
2722e56d | 2 | /* |
c10d5c9e | 3 | * OMAP L3 Interconnect error handling driver |
ed0e3520 | 4 | * |
e7309c26 | 5 | * Copyright (C) 2011-2015 Texas Instruments Incorporated - http://www.ti.com/ |
ed0e3520 | 6 | * Santosh Shilimkar <santosh.shilimkar@ti.com> |
7 | * Sricharan <r.sricharan@ti.com> | |
ed0e3520 | 8 | */ |
2722e56d | 9 | #include <linux/init.h> |
2722e56d | 10 | #include <linux/interrupt.h> |
0659452d | 11 | #include <linux/io.h> |
2722e56d | 12 | #include <linux/kernel.h> |
0659452d S |
13 | #include <linux/module.h> |
14 | #include <linux/of_device.h> | |
15 | #include <linux/of.h> | |
16 | #include <linux/platform_device.h> | |
2722e56d SS |
17 | #include <linux/slab.h> |
18 | ||
19 | #include "omap_l3_noc.h" | |
20 | ||
e4be3f3a NM |
21 | /** |
22 | * l3_handle_target() - Handle Target specific parse and reporting | |
23 | * @l3: pointer to l3 struct | |
24 | * @base: base address of clkdm | |
25 | * @flag_mux: flagmux corresponding to the event | |
26 | * @err_src: error source index of the slave (target) | |
2722e56d | 27 | * |
e4be3f3a NM |
28 | * This does the second part of the error interrupt handling: |
29 | * 3) Parse in the slave information | |
30 | * 4) Print the logged information. | |
31 | * 5) Add dump stack to provide kernel trace. | |
32 | * 6) Clear the source if known. | |
33 | * | |
34 | * This handles two types of errors: | |
2722e56d SS |
35 | * 1) Custom errors in L3 : |
36 | * Target like DMM/FW/EMIF generates SRESP=ERR error | |
37 | * 2) Standard L3 error: | |
38 | * - Unsupported CMD. | |
39 | * L3 tries to access target while it is idle | |
40 | * - OCP disconnect. | |
41 | * - Address hole error: | |
42 | * If DSS/ISS/FDIF/USBHOSTFS access a target where they | |
43 | * do not have connectivity, the error is logged in | |
44 | * their default target which is DMM2. | |
45 | * | |
46 | * On High Secure devices, firewall errors are possible and those | |
47 | * can be trapped as well. But the trapping is implemented as part | |
48 | * secure software and hence need not be implemented here. | |
49 | */ | |
e4be3f3a NM |
50 | static int l3_handle_target(struct omap_l3 *l3, void __iomem *base, |
51 | struct l3_flagmux_data *flag_mux, int err_src) | |
2722e56d | 52 | { |
e4be3f3a NM |
53 | int k; |
54 | u32 std_err_main, clear, masterid; | |
cf52b2ec | 55 | u8 op_code, m_req_info; |
e4be3f3a | 56 | void __iomem *l3_targ_base; |
9e224c8f | 57 | void __iomem *l3_targ_stderr, *l3_targ_slvofslsb, *l3_targ_mstaddr; |
cf52b2ec | 58 | void __iomem *l3_targ_hdr, *l3_targ_info; |
3ae9af7c | 59 | struct l3_target_data *l3_targ_inst; |
0659452d | 60 | struct l3_masters_data *master; |
e4be3f3a | 61 | char *target_name, *master_name = "UN IDENTIFIED"; |
c98aa7aa NM |
62 | char *err_description; |
63 | char err_string[30] = { 0 }; | |
cf52b2ec | 64 | char info_string[60] = { 0 }; |
2722e56d | 65 | |
e4be3f3a NM |
66 | /* We DONOT expect err_src to go out of bounds */ |
67 | BUG_ON(err_src > MAX_CLKDM_TARGETS); | |
68 | ||
69 | if (err_src < flag_mux->num_targ_data) { | |
70 | l3_targ_inst = &flag_mux->l3_targ[err_src]; | |
71 | target_name = l3_targ_inst->name; | |
72 | l3_targ_base = base + l3_targ_inst->offset; | |
73 | } else { | |
74 | target_name = L3_TARGET_NOT_SUPPORTED; | |
75 | } | |
76 | ||
77 | if (target_name == L3_TARGET_NOT_SUPPORTED) | |
78 | return -ENODEV; | |
79 | ||
80 | /* Read the stderrlog_main_source from clk domain */ | |
81 | l3_targ_stderr = l3_targ_base + L3_TARG_STDERRLOG_MAIN; | |
82 | l3_targ_slvofslsb = l3_targ_base + L3_TARG_STDERRLOG_SLVOFSLSB; | |
83 | ||
84 | std_err_main = readl_relaxed(l3_targ_stderr); | |
85 | ||
86 | switch (std_err_main & CUSTOM_ERROR) { | |
87 | case STANDARD_ERROR: | |
88 | err_description = "Standard"; | |
89 | snprintf(err_string, sizeof(err_string), | |
90 | ": At Address: 0x%08X ", | |
91 | readl_relaxed(l3_targ_slvofslsb)); | |
92 | ||
93 | l3_targ_mstaddr = l3_targ_base + L3_TARG_STDERRLOG_MSTADDR; | |
7f9de02d | 94 | l3_targ_hdr = l3_targ_base + L3_TARG_STDERRLOG_HDR; |
cf52b2ec | 95 | l3_targ_info = l3_targ_base + L3_TARG_STDERRLOG_INFO; |
e4be3f3a NM |
96 | break; |
97 | ||
98 | case CUSTOM_ERROR: | |
99 | err_description = "Custom"; | |
100 | ||
101 | l3_targ_mstaddr = l3_targ_base + | |
102 | L3_TARG_STDERRLOG_CINFO_MSTADDR; | |
7f9de02d | 103 | l3_targ_hdr = l3_targ_base + L3_TARG_STDERRLOG_CINFO_OPCODE; |
cf52b2ec | 104 | l3_targ_info = l3_targ_base + L3_TARG_STDERRLOG_CINFO_INFO; |
e4be3f3a NM |
105 | break; |
106 | ||
107 | default: | |
108 | /* Nothing to be handled here as of now */ | |
109 | return 0; | |
110 | } | |
111 | ||
112 | /* STDERRLOG_MSTADDR Stores the NTTP master address. */ | |
113 | masterid = (readl_relaxed(l3_targ_mstaddr) & | |
114 | l3->mst_addr_mask) >> __ffs(l3->mst_addr_mask); | |
115 | ||
116 | for (k = 0, master = l3->l3_masters; k < l3->num_masters; | |
117 | k++, master++) { | |
118 | if (masterid == master->id) { | |
119 | master_name = master->name; | |
120 | break; | |
121 | } | |
122 | } | |
123 | ||
7f9de02d NM |
124 | op_code = readl_relaxed(l3_targ_hdr) & 0x7; |
125 | ||
cf52b2ec NM |
126 | m_req_info = readl_relaxed(l3_targ_info) & 0xF; |
127 | snprintf(info_string, sizeof(info_string), | |
128 | ": %s in %s mode during %s access", | |
129 | (m_req_info & BIT(0)) ? "Opcode Fetch" : "Data Access", | |
130 | (m_req_info & BIT(1)) ? "Supervisor" : "User", | |
131 | (m_req_info & BIT(3)) ? "Debug" : "Functional"); | |
132 | ||
e4be3f3a | 133 | WARN(true, |
cf52b2ec | 134 | "%s:L3 %s Error: MASTER %s TARGET %s (%s)%s%s\n", |
e4be3f3a NM |
135 | dev_name(l3->dev), |
136 | err_description, | |
137 | master_name, target_name, | |
7f9de02d | 138 | l3_transaction_type[op_code], |
cf52b2ec | 139 | err_string, info_string); |
e4be3f3a NM |
140 | |
141 | /* clear the std error log*/ | |
142 | clear = std_err_main | CLEAR_STDERR_LOG; | |
143 | writel_relaxed(clear, l3_targ_stderr); | |
144 | ||
145 | return 0; | |
146 | } | |
147 | ||
148 | /** | |
149 | * l3_interrupt_handler() - interrupt handler for l3 events | |
150 | * @irq: irq number | |
151 | * @_l3: pointer to l3 structure | |
152 | * | |
153 | * Interrupt Handler for L3 error detection. | |
154 | * 1) Identify the L3 clockdomain partition to which the error belongs to. | |
155 | * 2) Identify the slave where the error information is logged | |
156 | * ... handle the slave event.. | |
157 | * 7) if the slave is unknown, mask out the slave. | |
158 | */ | |
159 | static irqreturn_t l3_interrupt_handler(int irq, void *_l3) | |
160 | { | |
161 | struct omap_l3 *l3 = _l3; | |
162 | int inttype, i, ret; | |
163 | int err_src = 0; | |
164 | u32 err_reg, mask_val; | |
165 | void __iomem *base, *mask_reg; | |
166 | struct l3_flagmux_data *flag_mux; | |
167 | ||
2722e56d | 168 | /* Get the Type of interrupt */ |
35f7b961 | 169 | inttype = irq == l3->app_irq ? L3_APPLICATION_ERROR : L3_DEBUG_ERROR; |
2722e56d | 170 | |
0659452d | 171 | for (i = 0; i < l3->num_modules; i++) { |
2722e56d SS |
172 | /* |
173 | * Read the regerr register of the clock domain | |
174 | * to determine the source | |
175 | */ | |
6616aac6 | 176 | base = l3->l3_base[i]; |
97708c08 NM |
177 | flag_mux = l3->l3_flagmux[i]; |
178 | err_reg = readl_relaxed(base + flag_mux->offset + | |
9e224c8f | 179 | L3_FLAGMUX_REGERR0 + (inttype << 3)); |
2722e56d | 180 | |
2100b595 AM |
181 | err_reg &= ~(inttype ? flag_mux->mask_app_bits : |
182 | flag_mux->mask_dbg_bits); | |
183 | ||
2722e56d SS |
184 | /* Get the corresponding error and analyse */ |
185 | if (err_reg) { | |
186 | /* Identify the source from control status register */ | |
342fd144 | 187 | err_src = __ffs(err_reg); |
3340d739 | 188 | |
e4be3f3a | 189 | ret = l3_handle_target(l3, base, flag_mux, err_src); |
2722e56d | 190 | |
3340d739 | 191 | /* |
e4be3f3a NM |
192 | * Certain plaforms may have "undocumented" status |
193 | * pending on boot. So dont generate a severe warning | |
194 | * here. Just mask it off to prevent the error from | |
195 | * reoccuring and locking up the system. | |
3340d739 | 196 | */ |
e4be3f3a | 197 | if (ret) { |
3340d739 RN |
198 | dev_err(l3->dev, |
199 | "L3 %s error: target %d mod:%d %s\n", | |
200 | inttype ? "debug" : "application", | |
201 | err_src, i, "(unclearable)"); | |
202 | ||
97708c08 | 203 | mask_reg = base + flag_mux->offset + |
3340d739 RN |
204 | L3_FLAGMUX_MASK0 + (inttype << 3); |
205 | mask_val = readl_relaxed(mask_reg); | |
206 | mask_val &= ~(1 << err_src); | |
207 | writel_relaxed(mask_val, mask_reg); | |
2100b595 AM |
208 | |
209 | /* Mark these bits as to be ignored */ | |
210 | if (inttype) | |
211 | flag_mux->mask_app_bits |= 1 << err_src; | |
212 | else | |
213 | flag_mux->mask_dbg_bits |= 1 << err_src; | |
3340d739 RN |
214 | } |
215 | ||
c98aa7aa | 216 | /* Error found so break the for loop */ |
c4cf0935 | 217 | return IRQ_HANDLED; |
2722e56d SS |
218 | } |
219 | } | |
c4cf0935 K |
220 | |
221 | dev_err(l3->dev, "L3 %s IRQ not handled!!\n", | |
222 | inttype ? "debug" : "application"); | |
223 | ||
224 | return IRQ_NONE; | |
2722e56d SS |
225 | } |
226 | ||
0659452d | 227 | static const struct of_device_id l3_noc_match[] = { |
e7309c26 SA |
228 | {.compatible = "ti,omap4-l3-noc", .data = &omap4_l3_data}, |
229 | {.compatible = "ti,omap5-l3-noc", .data = &omap5_l3_data}, | |
53a848be | 230 | {.compatible = "ti,dra7-l3-noc", .data = &dra_l3_data}, |
27b7d5f3 | 231 | {.compatible = "ti,am4372-l3-noc", .data = &am4372_l3_data}, |
0659452d S |
232 | {}, |
233 | }; | |
234 | MODULE_DEVICE_TABLE(of, l3_noc_match); | |
235 | ||
c10d5c9e | 236 | static int omap_l3_probe(struct platform_device *pdev) |
2722e56d | 237 | { |
0659452d | 238 | const struct of_device_id *of_id; |
c10d5c9e | 239 | static struct omap_l3 *l3; |
f33ddf74 | 240 | int ret, i, res_idx; |
2722e56d | 241 | |
0659452d S |
242 | of_id = of_match_device(l3_noc_match, &pdev->dev); |
243 | if (!of_id) { | |
244 | dev_err(&pdev->dev, "OF data missing\n"); | |
245 | return -EINVAL; | |
246 | } | |
247 | ||
bae74510 | 248 | l3 = devm_kzalloc(&pdev->dev, sizeof(*l3), GFP_KERNEL); |
2722e56d | 249 | if (!l3) |
7529b703 | 250 | return -ENOMEM; |
2722e56d | 251 | |
0659452d | 252 | memcpy(l3, of_id->data, sizeof(*l3)); |
ca6a3493 | 253 | l3->dev = &pdev->dev; |
2722e56d | 254 | platform_set_drvdata(pdev, l3); |
2722e56d | 255 | |
56c4a022 | 256 | /* Get mem resources */ |
f33ddf74 NM |
257 | for (i = 0, res_idx = 0; i < l3->num_modules; i++) { |
258 | struct resource *res; | |
259 | ||
260 | if (l3->l3_base[i] == L3_BASE_IS_SUBMODULE) { | |
261 | /* First entry cannot be submodule */ | |
262 | BUG_ON(i == 0); | |
263 | l3->l3_base[i] = l3->l3_base[i - 1]; | |
264 | continue; | |
265 | } | |
266 | res = platform_get_resource(pdev, IORESOURCE_MEM, res_idx); | |
56c4a022 PU |
267 | l3->l3_base[i] = devm_ioremap_resource(&pdev->dev, res); |
268 | if (IS_ERR(l3->l3_base[i])) { | |
ca6a3493 | 269 | dev_err(l3->dev, "ioremap %d failed\n", i); |
56c4a022 PU |
270 | return PTR_ERR(l3->l3_base[i]); |
271 | } | |
f33ddf74 | 272 | res_idx++; |
2722e56d SS |
273 | } |
274 | ||
275 | /* | |
276 | * Setup interrupt Handlers | |
277 | */ | |
c1df2dcc | 278 | l3->debug_irq = platform_get_irq(pdev, 0); |
ca6a3493 | 279 | ret = devm_request_irq(l3->dev, l3->debug_irq, l3_interrupt_handler, |
7d7275b3 | 280 | IRQF_NO_THREAD, "l3-dbg-irq", l3); |
2722e56d | 281 | if (ret) { |
ca6a3493 | 282 | dev_err(l3->dev, "request_irq failed for %d\n", |
ae22598a | 283 | l3->debug_irq); |
56c4a022 | 284 | return ret; |
2722e56d | 285 | } |
2722e56d | 286 | |
c1df2dcc | 287 | l3->app_irq = platform_get_irq(pdev, 1); |
ca6a3493 | 288 | ret = devm_request_irq(l3->dev, l3->app_irq, l3_interrupt_handler, |
7d7275b3 | 289 | IRQF_NO_THREAD, "l3-app-irq", l3); |
a0ef78f3 | 290 | if (ret) |
ca6a3493 | 291 | dev_err(l3->dev, "request_irq failed for %d\n", l3->app_irq); |
7529b703 | 292 | |
2722e56d SS |
293 | return ret; |
294 | } | |
295 | ||
258d2a10 | 296 | #ifdef CONFIG_PM_SLEEP |
61b43d4e K |
297 | |
298 | /** | |
299 | * l3_resume_noirq() - resume function for l3_noc | |
300 | * @dev: pointer to l3_noc device structure | |
301 | * | |
302 | * We only have the resume handler only since we | |
303 | * have already maintained the delta register | |
304 | * configuration as part of configuring the system | |
305 | */ | |
306 | static int l3_resume_noirq(struct device *dev) | |
307 | { | |
308 | struct omap_l3 *l3 = dev_get_drvdata(dev); | |
309 | int i; | |
310 | struct l3_flagmux_data *flag_mux; | |
311 | void __iomem *base, *mask_regx = NULL; | |
312 | u32 mask_val; | |
313 | ||
314 | for (i = 0; i < l3->num_modules; i++) { | |
315 | base = l3->l3_base[i]; | |
316 | flag_mux = l3->l3_flagmux[i]; | |
317 | if (!flag_mux->mask_app_bits && !flag_mux->mask_dbg_bits) | |
318 | continue; | |
319 | ||
320 | mask_regx = base + flag_mux->offset + L3_FLAGMUX_MASK0 + | |
321 | (L3_APPLICATION_ERROR << 3); | |
322 | mask_val = readl_relaxed(mask_regx); | |
323 | mask_val &= ~(flag_mux->mask_app_bits); | |
324 | ||
325 | writel_relaxed(mask_val, mask_regx); | |
326 | mask_regx = base + flag_mux->offset + L3_FLAGMUX_MASK0 + | |
327 | (L3_DEBUG_ERROR << 3); | |
328 | mask_val = readl_relaxed(mask_regx); | |
329 | mask_val &= ~(flag_mux->mask_dbg_bits); | |
330 | ||
331 | writel_relaxed(mask_val, mask_regx); | |
332 | } | |
333 | ||
334 | /* Dummy read to force OCP barrier */ | |
335 | if (mask_regx) | |
336 | (void)readl(mask_regx); | |
337 | ||
338 | return 0; | |
339 | } | |
340 | ||
341 | static const struct dev_pm_ops l3_dev_pm_ops = { | |
258d2a10 | 342 | SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, l3_resume_noirq) |
61b43d4e K |
343 | }; |
344 | ||
345 | #define L3_DEV_PM_OPS (&l3_dev_pm_ops) | |
346 | #else | |
347 | #define L3_DEV_PM_OPS NULL | |
348 | #endif | |
349 | ||
c10d5c9e S |
350 | static struct platform_driver omap_l3_driver = { |
351 | .probe = omap_l3_probe, | |
d039c5b9 BC |
352 | .driver = { |
353 | .name = "omap_l3_noc", | |
61b43d4e | 354 | .pm = L3_DEV_PM_OPS, |
0659452d | 355 | .of_match_table = of_match_ptr(l3_noc_match), |
2722e56d SS |
356 | }, |
357 | }; | |
358 | ||
c10d5c9e | 359 | static int __init omap_l3_init(void) |
2722e56d | 360 | { |
c10d5c9e | 361 | return platform_driver_register(&omap_l3_driver); |
2722e56d | 362 | } |
c10d5c9e | 363 | postcore_initcall_sync(omap_l3_init); |
2722e56d | 364 | |
c10d5c9e | 365 | static void __exit omap_l3_exit(void) |
2722e56d | 366 | { |
c10d5c9e | 367 | platform_driver_unregister(&omap_l3_driver); |
2722e56d | 368 | } |
c10d5c9e | 369 | module_exit(omap_l3_exit); |
be60566e AB |
370 | |
371 | MODULE_AUTHOR("Santosh Shilimkar"); | |
372 | MODULE_AUTHOR("Sricharan R"); | |
373 | MODULE_DESCRIPTION("OMAP L3 Interconnect error handling driver"); | |
374 | MODULE_LICENSE("GPL v2"); |