| /* |
| * OMAP3XXX L3 Interconnect Driver |
| * |
| * Copyright (C) 2011 Texas Corporation |
| * Felipe Balbi <balbi@ti.com> |
| * Santosh Shilimkar <santosh.shilimkar@ti.com> |
| * Sricharan <r.sricharan@ti.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
| * USA |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/slab.h> |
| #include <linux/platform_device.h> |
| #include <linux/interrupt.h> |
| #include <linux/io.h> |
| #include "omap_l3_smx.h" |
| |
| static inline u64 omap3_l3_readll(void __iomem *base, u16 reg) |
| { |
| return __raw_readll(base + reg); |
| } |
| |
| static inline void omap3_l3_writell(void __iomem *base, u16 reg, u64 value) |
| { |
| __raw_writell(value, base + reg); |
| } |
| |
| static inline enum omap3_l3_code omap3_l3_decode_error_code(u64 error) |
| { |
| return (error & 0x0f000000) >> L3_ERROR_LOG_CODE; |
| } |
| |
| static inline u32 omap3_l3_decode_addr(u64 error_addr) |
| { |
| return error_addr & 0xffffffff; |
| } |
| |
| static inline unsigned omap3_l3_decode_cmd(u64 error) |
| { |
| return (error & 0x07) >> L3_ERROR_LOG_CMD; |
| } |
| |
| static inline enum omap3_l3_initiator_id omap3_l3_decode_initid(u64 error) |
| { |
| return (error & 0xff00) >> L3_ERROR_LOG_INITID; |
| } |
| |
| static inline unsigned omap3_l3_decode_req_info(u64 error) |
| { |
| return (error >> 32) & 0xffff; |
| } |
| |
| static char *omap3_l3_code_string(u8 code) |
| { |
| switch (code) { |
| case OMAP_L3_CODE_NOERROR: |
| return "No Error"; |
| case OMAP_L3_CODE_UNSUP_CMD: |
| return "Unsupported Command"; |
| case OMAP_L3_CODE_ADDR_HOLE: |
| return "Address Hole"; |
| case OMAP_L3_CODE_PROTECT_VIOLATION: |
| return "Protection Violation"; |
| case OMAP_L3_CODE_IN_BAND_ERR: |
| return "In-band Error"; |
| case OMAP_L3_CODE_REQ_TOUT_NOT_ACCEPT: |
| return "Request Timeout Not Accepted"; |
| case OMAP_L3_CODE_REQ_TOUT_NO_RESP: |
| return "Request Timeout, no response"; |
| default: |
| return "UNKNOWN error"; |
| } |
| } |
| |
| static char *omap3_l3_initiator_string(u8 initid) |
| { |
| switch (initid) { |
| case OMAP_L3_LCD: |
| return "LCD"; |
| case OMAP_L3_SAD2D: |
| return "SAD2D"; |
| case OMAP_L3_IA_MPU_SS_1: |
| case OMAP_L3_IA_MPU_SS_2: |
| case OMAP_L3_IA_MPU_SS_3: |
| case OMAP_L3_IA_MPU_SS_4: |
| case OMAP_L3_IA_MPU_SS_5: |
| return "MPU"; |
| case OMAP_L3_IA_IVA_SS_1: |
| case OMAP_L3_IA_IVA_SS_2: |
| case OMAP_L3_IA_IVA_SS_3: |
| return "IVA_SS"; |
| case OMAP_L3_IA_IVA_SS_DMA_1: |
| case OMAP_L3_IA_IVA_SS_DMA_2: |
| case OMAP_L3_IA_IVA_SS_DMA_3: |
| case OMAP_L3_IA_IVA_SS_DMA_4: |
| case OMAP_L3_IA_IVA_SS_DMA_5: |
| case OMAP_L3_IA_IVA_SS_DMA_6: |
| return "IVA_SS_DMA"; |
| case OMAP_L3_IA_SGX: |
| return "SGX"; |
| case OMAP_L3_IA_CAM_1: |
| case OMAP_L3_IA_CAM_2: |
| case OMAP_L3_IA_CAM_3: |
| return "CAM"; |
| case OMAP_L3_IA_DAP: |
| return "DAP"; |
| case OMAP_L3_SDMA_WR_1: |
| case OMAP_L3_SDMA_WR_2: |
| return "SDMA_WR"; |
| case OMAP_L3_SDMA_RD_1: |
| case OMAP_L3_SDMA_RD_2: |
| case OMAP_L3_SDMA_RD_3: |
| case OMAP_L3_SDMA_RD_4: |
| return "SDMA_RD"; |
| case OMAP_L3_USBOTG: |
| return "USB_OTG"; |
| case OMAP_L3_USBHOST: |
| return "USB_HOST"; |
| default: |
| return "UNKNOWN Initiator"; |
| } |
| } |
| |
| /** |
| * omap3_l3_block_irq - handles a register block's irq |
| * @l3: struct omap3_l3 * |
| * @base: register block base address |
| * @error: L3_ERROR_LOG register of our block |
| * |
| * Called in hard-irq context. Caller should take care of locking |
| * |
| * OMAP36xx TRM gives, on page 2001, Figure 9-10, the Typical Error |
| * Analysis Sequence, we are following that sequence here, please |
| * refer to that Figure for more information on the subject. |
| */ |
| static irqreturn_t omap3_l3_block_irq(struct omap3_l3 *l3, |
| u64 error, int error_addr) |
| { |
| u8 code = omap3_l3_decode_error_code(error); |
| u8 initid = omap3_l3_decode_initid(error); |
| u8 multi = error & L3_ERROR_LOG_MULTI; |
| u32 address = omap3_l3_decode_addr(error_addr); |
| |
| WARN(true, "%s Error seen by %s %s at address %x\n", |
| omap3_l3_code_string(code), |
| omap3_l3_initiator_string(initid), |
| multi ? "Multiple Errors" : "", |
| address); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t omap3_l3_app_irq(int irq, void *_l3) |
| { |
| struct omap3_l3 *l3 = _l3; |
| |
| u64 status, clear; |
| u64 error; |
| u64 error_addr; |
| u64 err_source = 0; |
| void __iomem *base; |
| int int_type; |
| |
| irqreturn_t ret = IRQ_NONE; |
| |
| if (irq == l3->app_irq) |
| int_type = L3_APPLICATION_ERROR; |
| else |
| int_type = L3_DEBUG_ERROR; |
| |
| if (!int_type) { |
| status = omap3_l3_readll(l3->rt, L3_SI_FLAG_STATUS_0); |
| /* |
| * if we have a timeout error, there's nothing we can |
| * do besides rebooting the board. So let's BUG on any |
| * of such errors and handle the others. timeout error |
| * is severe and not expected to occur. |
| */ |
| BUG_ON(status & L3_STATUS_0_TIMEOUT_MASK); |
| } else { |
| status = omap3_l3_readll(l3->rt, L3_SI_FLAG_STATUS_1); |
| /* No timeout error for debug sources */ |
| } |
| |
| base = ((l3->rt) + (*(omap3_l3_bases[int_type] + err_source))); |
| |
| /* identify the error source */ |
| for (err_source = 0; !(status & (1 << err_source)); err_source++) |
| ; |
| error = omap3_l3_readll(base, L3_ERROR_LOG); |
| |
| if (error) { |
| error_addr = omap3_l3_readll(base, L3_ERROR_LOG_ADDR); |
| |
| ret |= omap3_l3_block_irq(l3, error, error_addr); |
| } |
| |
| /* Clear the status register */ |
| clear = ((L3_AGENT_STATUS_CLEAR_IA << int_type) | |
| (L3_AGENT_STATUS_CLEAR_TA)); |
| |
| omap3_l3_writell(base, L3_AGENT_STATUS, clear); |
| |
| /* clear the error log register */ |
| omap3_l3_writell(base, L3_ERROR_LOG, error); |
| |
| return ret; |
| } |
| |
| static int __init omap3_l3_probe(struct platform_device *pdev) |
| { |
| struct omap3_l3 *l3; |
| struct resource *res; |
| int ret; |
| |
| l3 = kzalloc(sizeof(*l3), GFP_KERNEL); |
| if (!l3) { |
| ret = -ENOMEM; |
| goto err0; |
| } |
| |
| platform_set_drvdata(pdev, l3); |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| if (!res) { |
| dev_err(&pdev->dev, "couldn't find resource\n"); |
| ret = -ENODEV; |
| goto err1; |
| } |
| l3->rt = ioremap(res->start, resource_size(res)); |
| if (!(l3->rt)) { |
| dev_err(&pdev->dev, "ioremap failed\n"); |
| ret = -ENOMEM; |
| goto err2; |
| } |
| |
| l3->debug_irq = platform_get_irq(pdev, 0); |
| ret = request_irq(l3->debug_irq, omap3_l3_app_irq, |
| IRQF_DISABLED | IRQF_TRIGGER_RISING, |
| "l3-debug-irq", l3); |
| if (ret) { |
| dev_err(&pdev->dev, "couldn't request debug irq\n"); |
| goto err3; |
| } |
| |
| l3->app_irq = platform_get_irq(pdev, 1); |
| ret = request_irq(l3->app_irq, omap3_l3_app_irq, |
| IRQF_DISABLED | IRQF_TRIGGER_RISING, |
| "l3-app-irq", l3); |
| |
| if (ret) { |
| dev_err(&pdev->dev, "couldn't request app irq\n"); |
| goto err4; |
| } |
| |
| goto err0; |
| |
| err4: |
| err3: |
| iounmap(l3->rt); |
| err2: |
| err1: |
| kfree(l3); |
| err0: |
| return ret; |
| } |
| |
| static int __exit omap3_l3_remove(struct platform_device *pdev) |
| { |
| struct omap3_l3 *l3 = platform_get_drvdata(pdev); |
| |
| free_irq(l3->app_irq, l3); |
| free_irq(l3->debug_irq, l3); |
| iounmap(l3->rt); |
| kfree(l3); |
| |
| return 0; |
| } |
| |
| static struct platform_driver omap3_l3_driver = { |
| .remove = __exit_p(omap3_l3_remove), |
| .driver = { |
| .name = "omap_l3_smx", |
| }, |
| }; |
| |
| static int __init omap3_l3_init(void) |
| { |
| return platform_driver_probe(&omap3_l3_driver, omap3_l3_probe); |
| } |
| postcore_initcall_sync(omap3_l3_init); |
| |
| static void __exit omap3_l3_exit(void) |
| { |
| platform_driver_unregister(&omap3_l3_driver); |
| } |
| module_exit(omap3_l3_exit); |