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