File Index
Files and directories
Sign in
FILE

irq-gic-v5-iwb.c

File preview with syntax highlighting.

linux-6.19.14/drivers/irqchip/irq-gic-v5-iwb.c C
  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, 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, 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, d->hwirq);
 82	irq_chip_disable_parent(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, d->hwirq);
 90	irq_chip_enable_parent(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, 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(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, MSI_DEFAULT_DOMAIN,
197					    &iwb_msi_template, size,
198					    NULL, 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(-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
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(&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
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);
~/ Read-only file preview.