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
17struct gicv5_iwb_chip_data {
18 void __iomem *iwb_base;
19 u16 nr_regs;
20};
21
22static 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
27static 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
33static 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
39static 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
65static 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, enable: true);
69}
70
71static 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, enable: false);
75}
76
77static 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, iwb_wire: d->hwirq);
82 irq_chip_disable_parent(data: d);
83}
84
85static 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, iwb_wire: d->hwirq);
90 irq_chip_enable_parent(data: d);
91}
92
93static 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, val: wtmr, GICV5_IWB_WTMR + (4 * n));
125
126 return 0;
127}
128
129static 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
135static 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(fwnode: 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
155static void gicv5_iwb_write_msi_msg(struct irq_data *d, struct msi_msg *msg) {}
156
157static 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
190static 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, domid: MSI_DEFAULT_DOMAIN,
197 template: &iwb_msi_template, hwsize: size,
198 NULL, chip_data: iwb_node);
199}
200
201static struct gicv5_iwb_chip_data *
202gicv5_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(error: -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(error: -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, val: 0, GICV5_IWB_WENABLER + (sizeof(u32) * n));
228
229 ret = gicv5_iwb_wait_for_wenabler(iwb_node);
230 if (ret)
231 return ERR_PTR(error: ret);
232
233 if (!gicv5_iwb_create_device_domain(dev: &pdev->dev, size: nr_wires, iwb_node))
234 return ERR_PTR(error: -ENOMEM);
235
236 return_ptr(iwb_node);
237}
238
239static 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(dev: &pdev->dev, offset: res->start, size: 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(ptr: iwb_node))
257 return PTR_ERR(ptr: iwb_node);
258
259 return 0;
260}
261
262static const struct of_device_id gicv5_iwb_of_match[] = {
263 { .compatible = "arm,gic-v5-iwb" },
264 { /* END */ }
265};
266MODULE_DEVICE_TABLE(of, gicv5_iwb_of_match);
267
268static 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
277module_platform_driver(gicv5_iwb_platform_driver);
278

source code of linux/drivers/irqchip/irq-gic-v5-iwb.c