Skip to content
Permalink
Browse files
drivers: irqchip: add irq-type-changer
Irq type changer is a virtual irqchip useful to support boards that
change (e.g. invert) interrupt signal between producer and consumer.

Usage example, for Kingfisher extension board for Renesas Gen-3 Soc,
that has WiFi interrupt delivered over inverting level-shifter:

/ {
	gpio1_25_inverted: inverter {
		compatible = "linux,irq-type-changer";
		interrupt-controller;
		#interrupt-cells = <2>;
		interrupt-parent = <&gpio1>;
		interrupts = <25 IRQ_TYPE_EDGE_FALLING>;
	};
};

&wlcore {
        interrupt-parent = <&gpio1_25_inverted>;
	interrupts = <0 IRQ_TYPE_EDGE_RISING>;
};

Then, wlcore driver observes IRQ_TYPE_EDGE_RISING trigger type and
configures interrupt output as such. At the same time, gpio-rcar driver
gets interrupt configured for IRQ_TYPE_EDGE_FALLING.

This version uses hierarchical irq_domain API, and works only with
parent interrupt domains compatible with that API.

Signed-off-by: Nikita Yushchenko <nikita.yoush@cogentembedded.com>
  • Loading branch information
nikita-yoush authored and intel-lab-lkp committed Jan 19, 2022
1 parent 67d50b5 commit 08692091f6fa9d1ee51baef78ce2adf983b2248a
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 0 deletions.
@@ -617,4 +617,22 @@ config MCHP_EIC
help
Support for Microchip External Interrupt Controller.

config IRQ_TYPE_CHANGER
bool "Interrupt trigger type changer"
select IRQ_DOMAIN
select IRQ_DOMAIN_HIERARCHY
help
Interrupt trigger type changer is designed to support boards that
modify (e.g. invert) signal between interrupt source and interrupt
controller input. So trigger type configured by a driver for some
interrupt output pin does not match trigger type that shall be used
to configure interrupt controller's input where that pin is connected.

In this case, board device tree shall add an interrupt trigger
type changer node and use it as the interrupt parent for the node
representing interrupt source. Then, interrupt trigger type defined
in the interrupt source node will be visible for the interrupt source
driver, and (different) trigger type defined inside the changer node
will be used to configure the interrupt controller.

endmenu
@@ -117,3 +117,4 @@ obj-$(CONFIG_WPCM450_AIC) += irq-wpcm450-aic.o
obj-$(CONFIG_IRQ_IDT3243X) += irq-idt3243x.o
obj-$(CONFIG_APPLE_AIC) += irq-apple-aic.o
obj-$(CONFIG_MCHP_EIC) += irq-mchp-eic.o
obj-$(CONFIG_IRQ_TYPE_CHANGER) += irq-type-changer.o
@@ -0,0 +1,162 @@
// SPDX-License-Identifier: GPL-2.0

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/irqchip.h>
#include <linux/irqdomain.h>
#include <linux/of_irq.h>

struct changer {
unsigned long count;
struct {
struct irq_fwspec fwspec;
unsigned int type;
} out[0];
};

static int changer_set_type(struct irq_data *data, unsigned int type)
{
struct changer *ch = data->domain->host_data;
struct irq_data *parent_data = data->parent_data;

return parent_data->chip->irq_set_type(parent_data,
ch->out[data->hwirq].type);
}

static struct irq_chip changer_chip = {
.name = "type-changer",
.irq_mask = irq_chip_mask_parent,
.irq_unmask = irq_chip_unmask_parent,
.irq_eoi = irq_chip_eoi_parent,
.irq_set_type = changer_set_type,
.irq_retrigger = irq_chip_retrigger_hierarchy,
.irq_set_affinity = irq_chip_set_affinity_parent,
.irq_set_wake = irq_chip_set_wake_parent,
};

static int changer_domain_translate(struct irq_domain *domain,
struct irq_fwspec *fwspec,
unsigned long *hwirq,
unsigned int *type)
{
struct changer *ch = domain->host_data;

if (fwspec->param_count != 2)
return -EINVAL;
if (fwspec->param[0] >= ch->count)
return -ENXIO;

*hwirq = fwspec->param[0];
*type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
return 0;
}

static int changer_domain_alloc(struct irq_domain *domain, unsigned int virq,
unsigned int nr_irqs, void *arg)
{
struct changer *ch = domain->host_data;
struct irq_fwspec *fwspec = arg;
irq_hw_number_t hwirq;
unsigned int type;
int ret;

if (WARN_ON(nr_irqs != 1))
return -EINVAL;

ret = changer_domain_translate(domain, fwspec, &hwirq, &type);
if (ret)
return ret;

irq_domain_set_hwirq_and_chip(domain, virq, hwirq, &changer_chip, ch);

return irq_domain_alloc_irqs_parent(domain, virq, 1,
&ch->out[hwirq].fwspec);
}

static const struct irq_domain_ops changer_domain_ops = {
.translate = changer_domain_translate,
.alloc = changer_domain_alloc,
.free = irq_domain_free_irqs_common,
};

static int __init changer_of_init(struct device_node *node,
struct device_node *parent)
{
struct irq_domain *domain, *parent_domain;
int count, i, ret;
struct changer *ch;
struct of_phandle_args pargs;
irq_hw_number_t unused;

if (!parent) {
pr_err("%pOF: no parent node\n", node);
return -EINVAL;
}

parent_domain = irq_find_host(parent);
if (!parent_domain) {
pr_err("%pOF: no parent domain\n", node);
return -EINVAL;
}

if (WARN_ON(!parent_domain->ops->translate))
return -EINVAL;

count = of_irq_count(node);
if (count < 1) {
pr_err("%pOF: no interrupts defined\n", node);
return -EINVAL;
}

ch = kzalloc(GFP_KERNEL, sizeof(*ch) + count * sizeof(ch->out[0]));
if (!ch)
return -ENOMEM;
ch->count = count;

for (i = 0; i < count; i++) {
ret = of_irq_parse_one(node, i, &pargs);
if (ret) {
pr_err("%pOF: interrupt %d: error %d parsing\n",
node, i, ret);
goto out_free;
}
of_phandle_args_to_fwspec(pargs.np, pargs.args,
pargs.args_count,
&ch->out[i].fwspec);
ret = parent_domain->ops->translate(parent_domain,
&ch->out[i].fwspec,
&unused,
&ch->out[i].type);
if (ret) {
pr_err("%pOF: interrupt %d: error %d extracting type\n",
node, i, ret);
goto out_free;
}
if (ch->out[i].type == IRQ_TYPE_NONE) {
pr_err("%pOF: interrupt %d: no type\n", node, i);
ret = -ENXIO;
goto out_free;
}
}

domain = irq_domain_create_hierarchy(parent_domain, 0, count,
of_node_to_fwnode(node),
&changer_domain_ops, ch);
if (!domain) {
ret = -ENOMEM;
goto out_free;
}

return 0;

out_free:
kfree(ch);
return ret;
}

IRQCHIP_PLATFORM_DRIVER_BEGIN(changer)
IRQCHIP_MATCH("linux,irq-type-changer", changer_of_init)
IRQCHIP_PLATFORM_DRIVER_END(changer)
MODULE_AUTHOR("Nikita Yushchenko <nikita.yoush.cogentembedded.com>");
MODULE_DESCRIPTION("Virtual irqchip to support trigger type change in route");
MODULE_LICENSE("GPL v2");

0 comments on commit 0869209

Please sign in to comment.