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