linux/drivers/irqchip/irq-gic-v5-iwb.c

285 lines
6.9 KiB
C
Raw Permalink Normal View History

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2024-2025 ARM Limited, All Rights Reserved.
*/
#define pr_fmt(fmt) "GICv5 IWB: " fmt
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/msi.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/irqchip.h>
#include <linux/irqchip/arm-gic-v5.h>
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<n> 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);