+ writel(0x0, pd_vio_shift_con_reg);
+ writel(0x0, pd_vio_shift_sel_reg);
+ writel(0x1 << shift_bit, pd_vio_shift_sta_reg);
+
+ return sync_done;
+}
+
+static void devapc_vio_info_print(struct mtk_devapc_context *devapc_ctx)
+{
+ struct mtk_devapc_vio_info *vio_info = devapc_ctx->vio_info;
+
+ /* Print violation information */
+ if (vio_info->write)
+ pr_info(PFX "Write Violation\n");
+ else if (vio_info->read)
+ pr_info(PFX "Read Violation\n");
+
+ pr_info(PFX "%s%x, %s%x, %s%x, %s%x\n",
+ "Vio Addr:0x", vio_info->vio_addr,
+ "High:0x", vio_info->vio_addr_high,
+ "Bus ID:0x", vio_info->master_id,
+ "Dom ID:0x", vio_info->domain_id);
+}
+
+static void devapc_extract_vio_dbg(struct mtk_devapc_context *devapc_ctx,
+ int slave_type)
+{
+ void __iomem *vio_dbg0_reg, *vio_dbg1_reg;
+ struct mtk_devapc_vio_dbgs_desc *vio_dbgs;
+ struct mtk_devapc_vio_info *vio_info;
+ u32 dbg0;
+
+ vio_dbg0_reg = mtk_devapc_pd_get(devapc_ctx, slave_type, VIO_DBG0, 0);
+ vio_dbg1_reg = mtk_devapc_pd_get(devapc_ctx, slave_type, VIO_DBG1, 0);
+
+ vio_dbgs = devapc_ctx->vio_dbgs_desc;
+ vio_info = devapc_ctx->vio_info;
+
+ /* Extract violation information */
+ dbg0 = readl(vio_dbg0_reg);
+ vio_info->vio_addr = readl(vio_dbg1_reg);
+
+ vio_info->master_id = (dbg0 & vio_dbgs[MSTID].mask) >>
+ vio_dbgs[MSTID].start_bit;
+ vio_info->domain_id = (dbg0 & vio_dbgs[DMNID].mask) >>
+ vio_dbgs[DMNID].start_bit;
+ vio_info->write = ((dbg0 & vio_dbgs[VIO_W].mask) >>
+ vio_dbgs[VIO_W].start_bit) == 1;
+ vio_info->read = ((dbg0 & vio_dbgs[VIO_R].mask) >>
+ vio_dbgs[VIO_R].start_bit) == 1;
+ vio_info->vio_addr_high = (dbg0 & vio_dbgs[ADDR_H].mask) >>
+ vio_dbgs[ADDR_H].start_bit;
+
+ devapc_vio_info_print(devapc_ctx);
+}
+
+/*
+ * mtk_devapc_dump_vio_dbg - shift & dump the violation debug information.
+ */
+static bool mtk_devapc_dump_vio_dbg(struct mtk_devapc_context *devapc_ctx,
+ int slave_type, int *vio_idx)
+{
+ const struct mtk_device_info **device_info;
+ u32 shift_bit;
+ int i;
+
+ device_info = devapc_ctx->device_info;
+
+ for (i = 0; i < get_vio_slave_num(slave_type); i++) {
+ *vio_idx = device_info[slave_type][i].vio_index;
+
+ if (check_vio_mask(devapc_ctx, slave_type, *vio_idx))
+ continue;
+
+ if (!check_vio_status(devapc_ctx, slave_type, *vio_idx))
+ continue;
+
+ shift_bit = get_shift_group(devapc_ctx, slave_type, *vio_idx);
+
+ if (!sync_vio_dbg(devapc_ctx, slave_type, shift_bit))
+ continue;
+
+ devapc_extract_vio_dbg(devapc_ctx, slave_type);
+
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * devapc_violation_irq - the devapc Interrupt Service Routine (ISR) will dump
+ * violation information including which master violates
+ * access slave.
+ */
+static irqreturn_t devapc_violation_irq(int irq_number,
+ struct mtk_devapc_context *devapc_ctx)
+{
+ const struct mtk_device_info **device_info;
+ int slave_type_num;
+ int vio_idx = -1;
+ int slave_type;
+
+ slave_type_num = devapc_ctx->slave_type_num;
+ device_info = devapc_ctx->device_info;
+
+ for (slave_type = 0; slave_type < slave_type_num; slave_type++) {
+ if (!mtk_devapc_dump_vio_dbg(devapc_ctx, slave_type, &vio_idx))
+ continue;
+
+ /* Ensure that violation info are written before
+ * further operations
+ */
+ smp_mb();
+
+ mask_module_irq(devapc_ctx, slave_type, vio_idx, true);
+
+ clear_vio_status(devapc_ctx, slave_type, vio_idx);
+
+ mask_module_irq(devapc_ctx, slave_type, vio_idx, false);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * start_devapc - initialize devapc status and start receiving interrupt
+ * while devapc violation is triggered.
+ */
+static void start_devapc(struct mtk_devapc_context *devapc_ctx)
+{
+ const struct mtk_device_info **device_info;
+ void __iomem *pd_vio_shift_sta_reg;
+ void __iomem *pd_apc_con_reg;
+ u32 vio_shift_sta;
+ int slave_type, slave_type_num;
+ int i, vio_idx;
+
+ device_info = devapc_ctx->device_info;
+ slave_type_num = devapc_ctx->slave_type_num;
+
+ for (slave_type = 0; slave_type < slave_type_num; slave_type++) {
+ pd_apc_con_reg = mtk_devapc_pd_get(devapc_ctx, slave_type,
+ APC_CON, 0);
+ pd_vio_shift_sta_reg = mtk_devapc_pd_get(devapc_ctx, slave_type,
+ VIO_SHIFT_STA, 0);
+ if (!pd_apc_con_reg || !pd_vio_shift_sta_reg)
+ return;
+
+ /* Clear devapc violation status */
+ writel(BIT(31), pd_apc_con_reg);
+
+ /* Clear violation shift status */
+ vio_shift_sta = readl(pd_vio_shift_sta_reg);
+ if (vio_shift_sta)
+ writel(vio_shift_sta, pd_vio_shift_sta_reg);
+
+ /* Clear slave violation status */
+ for (i = 0; i < get_vio_slave_num(slave_type); i++) {
+ vio_idx = device_info[slave_type][i].vio_index;
+
+ clear_vio_status(devapc_ctx, slave_type, vio_idx);
+
+ mask_module_irq(devapc_ctx, slave_type, vio_idx, false);
+ }
+ }
+}
+
+static int mtk_devapc_probe(struct platform_device *pdev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct mtk_devapc_context *devapc_ctx;
+ struct clk *devapc_infra_clk;
+ u32 vio_dbgs_num, pds_num;
+ u8 slave_type_num;
+ u32 devapc_irq;
+ size_t size;
+ int i, ret;
+
+ if (IS_ERR(node))
+ return -ENODEV;
+
+ devapc_ctx = devm_kzalloc(&pdev->dev, sizeof(struct mtk_devapc_context),
+ GFP_KERNEL);
+ if (!devapc_ctx)
+ return -ENOMEM;
+
+ if (of_property_read_u8(node, "mediatek-slv_type_num", &slave_type_num))
+ return -ENXIO;
+
+ devapc_ctx->slave_type_num = slave_type_num;
+
+ size = slave_type_num * sizeof(void *);
+ devapc_ctx->devapc_pd_base = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
+ if (!devapc_ctx->devapc_pd_base)
+ return -ENOMEM;
+
+ size = slave_type_num * sizeof(struct mtk_device_info *);
+ devapc_ctx->device_info = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
+ if (!devapc_ctx->device_info)
+ return -ENOMEM;
+
+ for (i = 0; i < slave_type_num; i++) {
+ devapc_ctx->devapc_pd_base[i] = of_iomap(node, i);
+ if (!devapc_ctx->devapc_pd_base[i])
+ return -EINVAL;
+
+ if (i == 0)
+ devapc_ctx->device_info[i] = mtk_devices_infra;
+ }
+
+ size = sizeof(struct mtk_devapc_vio_info);
+ devapc_ctx->vio_info = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
+ if (!devapc_ctx->vio_info)
+ return -ENOMEM;
+
+ vio_dbgs_num = of_property_count_u32_elems(node, "mediatek-vio_dbgs");
+ if (vio_dbgs_num <= 0)
+ return -ENXIO;
+
+ size = (vio_dbgs_num / 2) * sizeof(struct mtk_devapc_vio_dbgs_desc);
+ devapc_ctx->vio_dbgs_desc = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
+ if (!devapc_ctx->vio_dbgs_desc)
+ return -ENOMEM;
+
+ for (i = 0; i < vio_dbgs_num / 2; i++) {
+ if (of_property_read_u32_index(node, "mediatek-vio_dbgs",
+ i * 2,
+ &devapc_ctx->vio_dbgs_desc[i].mask))
+ return -ENXIO;
+
+ if (of_property_read_u32_index(node, "mediatek-vio_dbgs",
+ (i * 2) + 1,
+ &devapc_ctx->vio_dbgs_desc[i].start_bit))
+ return -ENXIO;
+ }
+
+ pds_num = of_property_count_u32_elems(node, "mediatek-pds_offset");
+ if (pds_num <= 0)
+ return -ENXIO;
+
+ size = pds_num * sizeof(u32);
+ devapc_ctx->pds_offset = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
+ if (!devapc_ctx->pds_offset)
+ return -ENOMEM;
+
+ for (i = 0; i < pds_num; i++) {
+ if (of_property_read_u32_index(node, "mediatek-pds_offset", i,
+ &devapc_ctx->pds_offset[i]))
+ return -ENXIO;
+ }
+
+ devapc_irq = irq_of_parse_and_map(node, 0);
+ if (!devapc_irq)
+ return -EINVAL;
+
+ devapc_infra_clk = devm_clk_get(&pdev->dev, "devapc-infra-clock");
+ if (IS_ERR(devapc_infra_clk))
+ return -EINVAL;
+
+ if (clk_prepare_enable(devapc_infra_clk))
+ return -EINVAL;
+
+ start_devapc(devapc_ctx);
+
+ ret = devm_request_irq(&pdev->dev, devapc_irq,
+ (irq_handler_t)devapc_violation_irq,
+ IRQF_TRIGGER_NONE, "devapc", devapc_ctx);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int mtk_devapc_remove(struct platform_device *dev)
+{
+ return 0;
+}
+
+static const struct of_device_id mtk_devapc_dt_match[] = {
+ { .compatible = "mediatek,mt6779-devapc" },
+ {},
+};
+
+static struct platform_driver mtk_devapc_driver = {
+ .probe = mtk_devapc_probe,
+ .remove = mtk_devapc_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = mtk_devapc_dt_match,
+ },
+};
+
+module_platform_driver(mtk_devapc_driver);
+
+MODULE_DESCRIPTION("Mediatek Device APC Driver");
+MODULE_AUTHOR("Neal Liu <neal.liu@xxxxxxxxxxxx>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/soc/mediatek/mtk-devapc.h b/drivers/soc/mediatek/mtk-devapc.h
new file mode 100644
index 0000000..ab2cb14
--- /dev/null
+++ b/drivers/soc/mediatek/mtk-devapc.h
@@ -0,0 +1,670 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 MediaTek Inc.
+ */
+
+#ifndef __MTK_DEVAPC_H__
+#define __MTK_DEVAPC_H__
+
+#define PFX "[DEVAPC]: "