| 1 | |
| 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 |
|
| 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 |
|
| 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), reg + 4 * i);
|
| 68 |
|
| 69 | writel(GENMASK(VIO_MOD_TO_REG_OFF(ctx->data->vio_idx_num) - 1, 0),
|
| 70 | 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, reg + 4 * i);
|
| 88 |
|
| 89 | val = readl(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, 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 |
|
| 129 | val = readl(pd_vio_shift_sta_reg);
|
| 130 | if (!val)
|
| 131 | return false;
|
| 132 |
|
| 133 | min_shift_group = __ffs(val);
|
| 134 |
|
| 135 |
|
| 136 | writel(0x1 << min_shift_group, pd_vio_shift_sel_reg);
|
| 137 |
|
| 138 |
|
| 139 | writel(0x1, 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 |
|
| 149 | writel(0x0, pd_vio_shift_con_reg);
|
| 150 |
|
| 151 |
|
| 152 | writel(0x1 << min_shift_group, 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 devapc_extract_vio_dbg(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(vio_dbg0_reg);
|
| 171 | vio_dbgs.vio_dbg1 = readl(vio_dbg1_reg);
|
| 172 |
|
| 173 |
|
| 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), ctx->infra_base + ctx->data->regs_ofs->apc_con_offset);
|
| 207 |
|
| 208 | mask_module_irq(ctx, 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, true);
|
| 217 |
|
| 218 | writel(BIT(2), 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(node))
|
| 262 | return -ENODEV;
|
| 263 |
|
| 264 | ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
|
| 265 | if (!ctx)
|
| 266 | return -ENOMEM;
|
| 267 |
|
| 268 | ctx->data = of_device_get_match_data(&pdev->dev);
|
| 269 | ctx->dev = &pdev->dev;
|
| 270 |
|
| 271 | ctx->infra_base = of_iomap(node, 0);
|
| 272 | if (!ctx->infra_base)
|
| 273 | return -EINVAL;
|
| 274 |
|
| 275 | devapc_irq = irq_of_parse_and_map(node, 0);
|
| 276 | if (!devapc_irq) {
|
| 277 | ret = -EINVAL;
|
| 278 | goto err;
|
| 279 | }
|
| 280 |
|
| 281 | ctx->infra_clk = devm_clk_get_enabled(&pdev->dev, "devapc-infra-clock");
|
| 282 | if (IS_ERR(ctx->infra_clk)) {
|
| 283 | ret = -EINVAL;
|
| 284 | goto err;
|
| 285 | }
|
| 286 |
|
| 287 | ret = devm_request_irq(&pdev->dev, devapc_irq, devapc_violation_irq,
|
| 288 | IRQF_TRIGGER_NONE, "devapc", ctx);
|
| 289 | if (ret)
|
| 290 | goto err;
|
| 291 |
|
| 292 | platform_set_drvdata(pdev, ctx);
|
| 293 |
|
| 294 | start_devapc(ctx);
|
| 295 |
|
| 296 | return 0;
|
| 297 |
|
| 298 | err:
|
| 299 | iounmap(ctx->infra_base);
|
| 300 | return ret;
|
| 301 | }
|
| 302 |
|
| 303 | static void mtk_devapc_remove(struct platform_device *pdev)
|
| 304 | {
|
| 305 | struct mtk_devapc_context *ctx = platform_get_drvdata(pdev);
|
| 306 |
|
| 307 | stop_devapc(ctx);
|
| 308 | iounmap(ctx->infra_base);
|
| 309 | }
|
| 310 |
|
| 311 | static struct platform_driver mtk_devapc_driver = {
|
| 312 | .probe = mtk_devapc_probe,
|
| 313 | .remove = mtk_devapc_remove,
|
| 314 | .driver = {
|
| 315 | .name = "mtk-devapc",
|
| 316 | .of_match_table = mtk_devapc_dt_match,
|
| 317 | },
|
| 318 | };
|
| 319 |
|
| 320 | module_platform_driver(mtk_devapc_driver);
|
| 321 |
|
| 322 | MODULE_DESCRIPTION("Mediatek Device APC Driver");
|
| 323 | MODULE_AUTHOR("Neal Liu <[email protected]>");
|
| 324 | MODULE_LICENSE("GPL");
|