FILE
irq-gic-v5-iwb.c
File preview with syntax highlighting.
linux-6.19.14/drivers/irqchip/irq-gic-v5-iwb.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.