// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2024-2025 ARM Limited, All Rights Reserved. */ #define pr_fmt(fmt) "GICv5 IWB: " fmt #include #include #include #include #include #include #include #include struct gicv5_iwb_chip_data { void __iomem *iwb_base; u16 nr_regs; }; static u32 iwb_readl_relaxed(struct gicv5_iwb_chip_data *iwb_node, const u32 reg_offset) { return readl_relaxed(iwb_node->iwb_base + reg_offset); } static void iwb_writel_relaxed(struct gicv5_iwb_chip_data *iwb_node, const u32 val, const u32 reg_offset) { writel_relaxed(val, iwb_node->iwb_base + reg_offset); } static int gicv5_iwb_wait_for_wenabler(struct gicv5_iwb_chip_data *iwb_node) { return gicv5_wait_for_op_atomic(iwb_node->iwb_base, GICV5_IWB_WENABLE_STATUSR, GICV5_IWB_WENABLE_STATUSR_IDLE, NULL); } static int __gicv5_iwb_set_wire_enable(struct gicv5_iwb_chip_data *iwb_node, u32 iwb_wire, bool enable) { u32 n = iwb_wire / 32; u8 i = iwb_wire % 32; u32 val; if (n >= iwb_node->nr_regs) { pr_err("IWB_WENABLER is invalid for n=%u\n", n); return -EINVAL; } /* * Enable IWB wire/pin at this point * Note: This is not the same as enabling the interrupt */ val = iwb_readl_relaxed(iwb_node, GICV5_IWB_WENABLER + (4 * n)); if (enable) val |= BIT(i); else val &= ~BIT(i); iwb_writel_relaxed(iwb_node, val, GICV5_IWB_WENABLER + (4 * n)); return gicv5_iwb_wait_for_wenabler(iwb_node); } static int gicv5_iwb_enable_wire(struct gicv5_iwb_chip_data *iwb_node, u32 iwb_wire) { return __gicv5_iwb_set_wire_enable(iwb_node, iwb_wire, true); } static int gicv5_iwb_disable_wire(struct gicv5_iwb_chip_data *iwb_node, u32 iwb_wire) { return __gicv5_iwb_set_wire_enable(iwb_node, iwb_wire, false); } static void gicv5_iwb_irq_disable(struct irq_data *d) { struct gicv5_iwb_chip_data *iwb_node = irq_data_get_irq_chip_data(d); gicv5_iwb_disable_wire(iwb_node, d->hwirq); irq_chip_disable_parent(d); } static void gicv5_iwb_irq_enable(struct irq_data *d) { struct gicv5_iwb_chip_data *iwb_node = irq_data_get_irq_chip_data(d); gicv5_iwb_enable_wire(iwb_node, d->hwirq); irq_chip_enable_parent(d); } static int gicv5_iwb_set_type(struct irq_data *d, unsigned int type) { struct gicv5_iwb_chip_data *iwb_node = irq_data_get_irq_chip_data(d); u32 iwb_wire, n, wtmr; u8 i; iwb_wire = d->hwirq; i = iwb_wire % 32; n = iwb_wire / 32; if (n >= iwb_node->nr_regs) { pr_err_once("reg %u out of range\n", n); return -EINVAL; } wtmr = iwb_readl_relaxed(iwb_node, GICV5_IWB_WTMR + (4 * n)); switch (type) { case IRQ_TYPE_LEVEL_HIGH: case IRQ_TYPE_LEVEL_LOW: wtmr |= BIT(i); break; case IRQ_TYPE_EDGE_RISING: case IRQ_TYPE_EDGE_FALLING: wtmr &= ~BIT(i); break; default: pr_debug("unexpected wire trigger mode"); return -EINVAL; } iwb_writel_relaxed(iwb_node, wtmr, GICV5_IWB_WTMR + (4 * n)); return 0; } static void gicv5_iwb_domain_set_desc(msi_alloc_info_t *alloc_info, struct msi_desc *desc) { alloc_info->desc = desc; alloc_info->hwirq = (u32)desc->data.icookie.value; } static int gicv5_iwb_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, irq_hw_number_t *hwirq, unsigned int *type) { if (!is_of_node(fwspec->fwnode)) return -EINVAL; if (fwspec->param_count < 2) return -EINVAL; /* * param[0] is be the wire * param[1] is the interrupt type */ *hwirq = fwspec->param[0]; *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK; return 0; } static void gicv5_iwb_write_msi_msg(struct irq_data *d, struct msi_msg *msg) {} static const struct msi_domain_template iwb_msi_template = { .chip = { .name = "GICv5-IWB", .irq_mask = irq_chip_mask_parent, .irq_unmask = irq_chip_unmask_parent, .irq_enable = gicv5_iwb_irq_enable, .irq_disable = gicv5_iwb_irq_disable, .irq_eoi = irq_chip_eoi_parent, .irq_set_type = gicv5_iwb_set_type, .irq_write_msi_msg = gicv5_iwb_write_msi_msg, .irq_set_affinity = irq_chip_set_affinity_parent, .irq_get_irqchip_state = irq_chip_get_parent_state, .irq_set_irqchip_state = irq_chip_set_parent_state, .flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_SKIP_SET_WAKE | IRQCHIP_MASK_ON_SUSPEND, }, .ops = { .set_desc = gicv5_iwb_domain_set_desc, .msi_translate = gicv5_iwb_irq_domain_translate, }, .info = { .bus_token = DOMAIN_BUS_WIRED_TO_MSI, .flags = MSI_FLAG_USE_DEV_FWNODE, }, .alloc_info = { .flags = MSI_ALLOC_FLAGS_FIXED_MSG_DATA, }, }; static bool gicv5_iwb_create_device_domain(struct device *dev, unsigned int size, struct gicv5_iwb_chip_data *iwb_node) { if (WARN_ON_ONCE(!dev->msi.domain)) return false; return msi_create_device_irq_domain(dev, MSI_DEFAULT_DOMAIN, &iwb_msi_template, size, NULL, iwb_node); } static struct gicv5_iwb_chip_data * gicv5_iwb_init_bases(void __iomem *iwb_base, struct platform_device *pdev) { u32 nr_wires, idr0, cr0; unsigned int n; int ret; struct gicv5_iwb_chip_data *iwb_node __free(kfree) = kzalloc(sizeof(*iwb_node), GFP_KERNEL); if (!iwb_node) return ERR_PTR(-ENOMEM); iwb_node->iwb_base = iwb_base; idr0 = iwb_readl_relaxed(iwb_node, GICV5_IWB_IDR0); nr_wires = (FIELD_GET(GICV5_IWB_IDR0_IW_RANGE, idr0) + 1) * 32; cr0 = iwb_readl_relaxed(iwb_node, GICV5_IWB_CR0); if (!FIELD_GET(GICV5_IWB_CR0_IWBEN, cr0)) { dev_err(&pdev->dev, "IWB must be enabled in firmware\n"); return ERR_PTR(-EINVAL); } iwb_node->nr_regs = FIELD_GET(GICV5_IWB_IDR0_IW_RANGE, idr0) + 1; for (n = 0; n < iwb_node->nr_regs; n++) iwb_writel_relaxed(iwb_node, 0, GICV5_IWB_WENABLER + (sizeof(u32) * n)); ret = gicv5_iwb_wait_for_wenabler(iwb_node); if (ret) return ERR_PTR(ret); if (!gicv5_iwb_create_device_domain(&pdev->dev, nr_wires, iwb_node)) return ERR_PTR(-ENOMEM); return_ptr(iwb_node); } static int gicv5_iwb_device_probe(struct platform_device *pdev) { struct gicv5_iwb_chip_data *iwb_node; void __iomem *iwb_base; struct resource *res; int ret; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) return -EINVAL; iwb_base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); if (!iwb_base) { dev_err(&pdev->dev, "failed to ioremap %pR\n", res); return -ENOMEM; } iwb_node = gicv5_iwb_init_bases(iwb_base, pdev); if (IS_ERR(iwb_node)) { ret = PTR_ERR(iwb_node); goto out_unmap; } return 0; out_unmap: iounmap(iwb_base); return ret; } static const struct of_device_id gicv5_iwb_of_match[] = { { .compatible = "arm,gic-v5-iwb" }, { /* END */ } }; MODULE_DEVICE_TABLE(of, gicv5_iwb_of_match); static struct platform_driver gicv5_iwb_platform_driver = { .driver = { .name = "GICv5 IWB", .of_match_table = gicv5_iwb_of_match, .suppress_bind_attrs = true, }, .probe = gicv5_iwb_device_probe, }; module_platform_driver(gicv5_iwb_platform_driver);