diff options
Diffstat (limited to 'drivers/irqchip/irq-ls-extirq.c')
| -rw-r--r-- | drivers/irqchip/irq-ls-extirq.c | 197 | 
1 files changed, 197 insertions, 0 deletions
diff --git a/drivers/irqchip/irq-ls-extirq.c b/drivers/irqchip/irq-ls-extirq.c new file mode 100644 index 000000000000..4d1179fed77c --- /dev/null +++ b/drivers/irqchip/irq-ls-extirq.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define pr_fmt(fmt) "irq-ls-extirq: " fmt + +#include <linux/irq.h> +#include <linux/irqchip.h> +#include <linux/irqdomain.h> +#include <linux/of.h> +#include <linux/mfd/syscon.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#include <dt-bindings/interrupt-controller/arm-gic.h> + +#define MAXIRQ 12 +#define LS1021A_SCFGREVCR 0x200 + +struct ls_extirq_data { +	struct regmap		*syscon; +	u32			intpcr; +	bool			bit_reverse; +	u32			nirq; +	struct irq_fwspec	map[MAXIRQ]; +}; + +static int +ls_extirq_set_type(struct irq_data *data, unsigned int type) +{ +	struct ls_extirq_data *priv = data->chip_data; +	irq_hw_number_t hwirq = data->hwirq; +	u32 value, mask; + +	if (priv->bit_reverse) +		mask = 1U << (31 - hwirq); +	else +		mask = 1U << hwirq; + +	switch (type) { +	case IRQ_TYPE_LEVEL_LOW: +		type = IRQ_TYPE_LEVEL_HIGH; +		value = mask; +		break; +	case IRQ_TYPE_EDGE_FALLING: +		type = IRQ_TYPE_EDGE_RISING; +		value = mask; +		break; +	case IRQ_TYPE_LEVEL_HIGH: +	case IRQ_TYPE_EDGE_RISING: +		value = 0; +		break; +	default: +		return -EINVAL; +	} +	regmap_update_bits(priv->syscon, priv->intpcr, mask, value); + +	return irq_chip_set_type_parent(data, type); +} + +static struct irq_chip ls_extirq_chip = { +	.name			= "ls-extirq", +	.irq_mask		= irq_chip_mask_parent, +	.irq_unmask		= irq_chip_unmask_parent, +	.irq_eoi		= irq_chip_eoi_parent, +	.irq_set_type		= ls_extirq_set_type, +	.irq_retrigger		= irq_chip_retrigger_hierarchy, +	.irq_set_affinity	= irq_chip_set_affinity_parent, +	.flags                  = IRQCHIP_SET_TYPE_MASKED, +}; + +static int +ls_extirq_domain_alloc(struct irq_domain *domain, unsigned int virq, +		       unsigned int nr_irqs, void *arg) +{ +	struct ls_extirq_data *priv = domain->host_data; +	struct irq_fwspec *fwspec = arg; +	irq_hw_number_t hwirq; + +	if (fwspec->param_count != 2) +		return -EINVAL; + +	hwirq = fwspec->param[0]; +	if (hwirq >= priv->nirq) +		return -EINVAL; + +	irq_domain_set_hwirq_and_chip(domain, virq, hwirq, &ls_extirq_chip, +				      priv); + +	return irq_domain_alloc_irqs_parent(domain, virq, 1, &priv->map[hwirq]); +} + +static const struct irq_domain_ops extirq_domain_ops = { +	.xlate		= irq_domain_xlate_twocell, +	.alloc		= ls_extirq_domain_alloc, +	.free		= irq_domain_free_irqs_common, +}; + +static int +ls_extirq_parse_map(struct ls_extirq_data *priv, struct device_node *node) +{ +	const __be32 *map; +	u32 mapsize; +	int ret; + +	map = of_get_property(node, "interrupt-map", &mapsize); +	if (!map) +		return -ENOENT; +	if (mapsize % sizeof(*map)) +		return -EINVAL; +	mapsize /= sizeof(*map); + +	while (mapsize) { +		struct device_node *ipar; +		u32 hwirq, intsize, j; + +		if (mapsize < 3) +			return -EINVAL; +		hwirq = be32_to_cpup(map); +		if (hwirq >= MAXIRQ) +			return -EINVAL; +		priv->nirq = max(priv->nirq, hwirq + 1); + +		ipar = of_find_node_by_phandle(be32_to_cpup(map + 2)); +		map += 3; +		mapsize -= 3; +		if (!ipar) +			return -EINVAL; +		priv->map[hwirq].fwnode = &ipar->fwnode; +		ret = of_property_read_u32(ipar, "#interrupt-cells", &intsize); +		if (ret) +			return ret; + +		if (intsize > mapsize) +			return -EINVAL; + +		priv->map[hwirq].param_count = intsize; +		for (j = 0; j < intsize; ++j) +			priv->map[hwirq].param[j] = be32_to_cpup(map++); +		mapsize -= intsize; +	} +	return 0; +} + +static int __init +ls_extirq_of_init(struct device_node *node, struct device_node *parent) +{ + +	struct irq_domain *domain, *parent_domain; +	struct ls_extirq_data *priv; +	int ret; + +	parent_domain = irq_find_host(parent); +	if (!parent_domain) { +		pr_err("Cannot find parent domain\n"); +		return -ENODEV; +	} + +	priv = kzalloc(sizeof(*priv), GFP_KERNEL); +	if (!priv) +		return -ENOMEM; + +	priv->syscon = syscon_node_to_regmap(node->parent); +	if (IS_ERR(priv->syscon)) { +		ret = PTR_ERR(priv->syscon); +		pr_err("Failed to lookup parent regmap\n"); +		goto out; +	} +	ret = of_property_read_u32(node, "reg", &priv->intpcr); +	if (ret) { +		pr_err("Missing INTPCR offset value\n"); +		goto out; +	} + +	ret = ls_extirq_parse_map(priv, node); +	if (ret) +		goto out; + +	if (of_device_is_compatible(node, "fsl,ls1021a-extirq")) { +		u32 revcr; + +		ret = regmap_read(priv->syscon, LS1021A_SCFGREVCR, &revcr); +		if (ret) +			goto out; +		priv->bit_reverse = (revcr != 0); +	} + +	domain = irq_domain_add_hierarchy(parent_domain, 0, priv->nirq, node, +					  &extirq_domain_ops, priv); +	if (!domain) +		ret = -ENOMEM; + +out: +	if (ret) +		kfree(priv); +	return ret; +} + +IRQCHIP_DECLARE(ls1021a_extirq, "fsl,ls1021a-extirq", ls_extirq_of_init);  |