xref: /linux/drivers/irqchip/irq-gic-v5-iwb.c (revision 7d2fed1f3ce46f7b161e33626b2cffedfa21767f)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (C) 2024-2025 ARM Limited, All Rights Reserved.
4  */
5 #define pr_fmt(fmt)	"GICv5 IWB: " fmt
6 
7 #include <linux/init.h>
8 #include <linux/kernel.h>
9 #include <linux/msi.h>
10 #include <linux/of.h>
11 #include <linux/of_address.h>
12 #include <linux/of_platform.h>
13 
14 #include <linux/irqchip.h>
15 #include <linux/irqchip/arm-gic-v5.h>
16 
17 struct gicv5_iwb_chip_data {
18 	void __iomem	*iwb_base;
19 	u16		nr_regs;
20 };
21 
iwb_readl_relaxed(struct gicv5_iwb_chip_data * iwb_node,const u32 reg_offset)22 static u32 iwb_readl_relaxed(struct gicv5_iwb_chip_data *iwb_node, const u32 reg_offset)
23 {
24 	return readl_relaxed(iwb_node->iwb_base + reg_offset);
25 }
26 
iwb_writel_relaxed(struct gicv5_iwb_chip_data * iwb_node,const u32 val,const u32 reg_offset)27 static void iwb_writel_relaxed(struct gicv5_iwb_chip_data *iwb_node, const u32 val,
28 			       const u32 reg_offset)
29 {
30 	writel_relaxed(val, iwb_node->iwb_base + reg_offset);
31 }
32 
gicv5_iwb_wait_for_wenabler(struct gicv5_iwb_chip_data * iwb_node)33 static int gicv5_iwb_wait_for_wenabler(struct gicv5_iwb_chip_data *iwb_node)
34 {
35 	return gicv5_wait_for_op_atomic(iwb_node->iwb_base, GICV5_IWB_WENABLE_STATUSR,
36 					GICV5_IWB_WENABLE_STATUSR_IDLE, NULL);
37 }
38 
__gicv5_iwb_set_wire_enable(struct gicv5_iwb_chip_data * iwb_node,u32 iwb_wire,bool enable)39 static int __gicv5_iwb_set_wire_enable(struct gicv5_iwb_chip_data *iwb_node,
40 				       u32 iwb_wire, bool enable)
41 {
42 	u32 n = iwb_wire / 32;
43 	u8 i = iwb_wire % 32;
44 	u32 val;
45 
46 	if (n >= iwb_node->nr_regs) {
47 		pr_err("IWB_WENABLER<n> is invalid for n=%u\n", n);
48 		return -EINVAL;
49 	}
50 
51 	/*
52 	 * Enable IWB wire/pin at this point
53 	 * Note: This is not the same as enabling the interrupt
54 	 */
55 	val = iwb_readl_relaxed(iwb_node, GICV5_IWB_WENABLER + (4 * n));
56 	if (enable)
57 		val |= BIT(i);
58 	else
59 		val &= ~BIT(i);
60 	iwb_writel_relaxed(iwb_node, val, GICV5_IWB_WENABLER + (4 * n));
61 
62 	return gicv5_iwb_wait_for_wenabler(iwb_node);
63 }
64 
gicv5_iwb_enable_wire(struct gicv5_iwb_chip_data * iwb_node,u32 iwb_wire)65 static int gicv5_iwb_enable_wire(struct gicv5_iwb_chip_data *iwb_node,
66 				 u32 iwb_wire)
67 {
68 	return __gicv5_iwb_set_wire_enable(iwb_node, iwb_wire, true);
69 }
70 
gicv5_iwb_disable_wire(struct gicv5_iwb_chip_data * iwb_node,u32 iwb_wire)71 static int gicv5_iwb_disable_wire(struct gicv5_iwb_chip_data *iwb_node,
72 				  u32 iwb_wire)
73 {
74 	return __gicv5_iwb_set_wire_enable(iwb_node, iwb_wire, false);
75 }
76 
gicv5_iwb_irq_disable(struct irq_data * d)77 static void gicv5_iwb_irq_disable(struct irq_data *d)
78 {
79 	struct gicv5_iwb_chip_data *iwb_node = irq_data_get_irq_chip_data(d);
80 
81 	gicv5_iwb_disable_wire(iwb_node, d->hwirq);
82 	irq_chip_disable_parent(d);
83 }
84 
gicv5_iwb_irq_enable(struct irq_data * d)85 static void gicv5_iwb_irq_enable(struct irq_data *d)
86 {
87 	struct gicv5_iwb_chip_data *iwb_node = irq_data_get_irq_chip_data(d);
88 
89 	gicv5_iwb_enable_wire(iwb_node, d->hwirq);
90 	irq_chip_enable_parent(d);
91 }
92 
gicv5_iwb_set_type(struct irq_data * d,unsigned int type)93 static int gicv5_iwb_set_type(struct irq_data *d, unsigned int type)
94 {
95 	struct gicv5_iwb_chip_data *iwb_node = irq_data_get_irq_chip_data(d);
96 	u32 iwb_wire, n, wtmr;
97 	u8 i;
98 
99 	iwb_wire = d->hwirq;
100 	i = iwb_wire % 32;
101 	n = iwb_wire / 32;
102 
103 	if (n >= iwb_node->nr_regs) {
104 		pr_err_once("reg %u out of range\n", n);
105 		return -EINVAL;
106 	}
107 
108 	wtmr = iwb_readl_relaxed(iwb_node, GICV5_IWB_WTMR + (4 * n));
109 
110 	switch (type) {
111 	case IRQ_TYPE_LEVEL_HIGH:
112 	case IRQ_TYPE_LEVEL_LOW:
113 		wtmr |= BIT(i);
114 		break;
115 	case IRQ_TYPE_EDGE_RISING:
116 	case IRQ_TYPE_EDGE_FALLING:
117 		wtmr &= ~BIT(i);
118 		break;
119 	default:
120 		pr_debug("unexpected wire trigger mode");
121 		return -EINVAL;
122 	}
123 
124 	iwb_writel_relaxed(iwb_node, wtmr, GICV5_IWB_WTMR + (4 * n));
125 
126 	return 0;
127 }
128 
gicv5_iwb_domain_set_desc(msi_alloc_info_t * alloc_info,struct msi_desc * desc)129 static void gicv5_iwb_domain_set_desc(msi_alloc_info_t *alloc_info, struct msi_desc *desc)
130 {
131 	alloc_info->desc = desc;
132 	alloc_info->hwirq = (u32)desc->data.icookie.value;
133 }
134 
gicv5_iwb_irq_domain_translate(struct irq_domain * d,struct irq_fwspec * fwspec,irq_hw_number_t * hwirq,unsigned int * type)135 static int gicv5_iwb_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,
136 					  irq_hw_number_t *hwirq,
137 					  unsigned int *type)
138 {
139 	if (!is_of_node(fwspec->fwnode))
140 		return -EINVAL;
141 
142 	if (fwspec->param_count < 2)
143 		return -EINVAL;
144 
145 	/*
146 	 * param[0] is be the wire
147 	 * param[1] is the interrupt type
148 	 */
149 	*hwirq = fwspec->param[0];
150 	*type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
151 
152 	return 0;
153 }
154 
gicv5_iwb_write_msi_msg(struct irq_data * d,struct msi_msg * msg)155 static void gicv5_iwb_write_msi_msg(struct irq_data *d, struct msi_msg *msg) {}
156 
157 static const struct msi_domain_template iwb_msi_template = {
158 	.chip = {
159 		.name			= "GICv5-IWB",
160 		.irq_mask		= irq_chip_mask_parent,
161 		.irq_unmask		= irq_chip_unmask_parent,
162 		.irq_enable		= gicv5_iwb_irq_enable,
163 		.irq_disable		= gicv5_iwb_irq_disable,
164 		.irq_eoi		= irq_chip_eoi_parent,
165 		.irq_set_type		= gicv5_iwb_set_type,
166 		.irq_write_msi_msg	= gicv5_iwb_write_msi_msg,
167 		.irq_set_affinity	= irq_chip_set_affinity_parent,
168 		.irq_get_irqchip_state	= irq_chip_get_parent_state,
169 		.irq_set_irqchip_state	= irq_chip_set_parent_state,
170 		.flags			= IRQCHIP_SET_TYPE_MASKED |
171 					  IRQCHIP_SKIP_SET_WAKE |
172 					  IRQCHIP_MASK_ON_SUSPEND,
173 	},
174 
175 	.ops = {
176 		.set_desc		= gicv5_iwb_domain_set_desc,
177 		.msi_translate		= gicv5_iwb_irq_domain_translate,
178 	},
179 
180 	.info = {
181 		.bus_token		= DOMAIN_BUS_WIRED_TO_MSI,
182 		.flags			= MSI_FLAG_USE_DEV_FWNODE,
183 	},
184 
185 	.alloc_info = {
186 		.flags			= MSI_ALLOC_FLAGS_FIXED_MSG_DATA,
187 	},
188 };
189 
gicv5_iwb_create_device_domain(struct device * dev,unsigned int size,struct gicv5_iwb_chip_data * iwb_node)190 static bool gicv5_iwb_create_device_domain(struct device *dev, unsigned int size,
191 				     struct gicv5_iwb_chip_data *iwb_node)
192 {
193 	if (WARN_ON_ONCE(!dev->msi.domain))
194 		return false;
195 
196 	return msi_create_device_irq_domain(dev, MSI_DEFAULT_DOMAIN,
197 					    &iwb_msi_template, size,
198 					    NULL, iwb_node);
199 }
200 
201 static struct gicv5_iwb_chip_data *
gicv5_iwb_init_bases(void __iomem * iwb_base,struct platform_device * pdev)202 gicv5_iwb_init_bases(void __iomem *iwb_base, struct platform_device *pdev)
203 {
204 	u32 nr_wires, idr0, cr0;
205 	unsigned int n;
206 	int ret;
207 
208 	struct gicv5_iwb_chip_data *iwb_node __free(kfree) = kzalloc(sizeof(*iwb_node),
209 								     GFP_KERNEL);
210 	if (!iwb_node)
211 		return ERR_PTR(-ENOMEM);
212 
213 	iwb_node->iwb_base = iwb_base;
214 
215 	idr0 = iwb_readl_relaxed(iwb_node, GICV5_IWB_IDR0);
216 	nr_wires = (FIELD_GET(GICV5_IWB_IDR0_IW_RANGE, idr0) + 1) * 32;
217 
218 	cr0 = iwb_readl_relaxed(iwb_node, GICV5_IWB_CR0);
219 	if (!FIELD_GET(GICV5_IWB_CR0_IWBEN, cr0)) {
220 		dev_err(&pdev->dev, "IWB must be enabled in firmware\n");
221 		return ERR_PTR(-EINVAL);
222 	}
223 
224 	iwb_node->nr_regs = FIELD_GET(GICV5_IWB_IDR0_IW_RANGE, idr0) + 1;
225 
226 	for (n = 0; n < iwb_node->nr_regs; n++)
227 		iwb_writel_relaxed(iwb_node, 0, GICV5_IWB_WENABLER + (sizeof(u32) * n));
228 
229 	ret = gicv5_iwb_wait_for_wenabler(iwb_node);
230 	if (ret)
231 		return ERR_PTR(ret);
232 
233 	if (!gicv5_iwb_create_device_domain(&pdev->dev, nr_wires, iwb_node))
234 		return ERR_PTR(-ENOMEM);
235 
236 	return_ptr(iwb_node);
237 }
238 
gicv5_iwb_device_probe(struct platform_device * pdev)239 static int gicv5_iwb_device_probe(struct platform_device *pdev)
240 {
241 	struct gicv5_iwb_chip_data *iwb_node;
242 	void __iomem *iwb_base;
243 	struct resource *res;
244 
245 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
246 	if (!res)
247 		return -EINVAL;
248 
249 	iwb_base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
250 	if (!iwb_base) {
251 		dev_err(&pdev->dev, "failed to ioremap %pR\n", res);
252 		return -ENOMEM;
253 	}
254 
255 	iwb_node = gicv5_iwb_init_bases(iwb_base, pdev);
256 	if (IS_ERR(iwb_node))
257 		return PTR_ERR(iwb_node);
258 
259 	return 0;
260 }
261 
262 static const struct of_device_id gicv5_iwb_of_match[] = {
263 	{ .compatible = "arm,gic-v5-iwb" },
264 	{ /* END */ }
265 };
266 MODULE_DEVICE_TABLE(of, gicv5_iwb_of_match);
267 
268 static struct platform_driver gicv5_iwb_platform_driver = {
269 	.driver = {
270 		.name			= "GICv5 IWB",
271 		.of_match_table		= gicv5_iwb_of_match,
272 		.suppress_bind_attrs	= true,
273 	},
274 	.probe				= gicv5_iwb_device_probe,
275 };
276 
277 module_platform_driver(gicv5_iwb_platform_driver);
278