Subject: [PATCH v3 5/6] irqchip: s3c24xx: add devicetree support

From: [email-masked] (Rob Herring)
Date: Mon, 18 Mar 2013 13:59:16 -0500

On 03/17/2013 08:07 AM, Heiko St?bner wrote:
> Add the necessary code to initialize the interrupt controller
> thru devicetree data using the irqchip infrastructure.
> 
> On dt machines the eint-type interrupts in the main interrupt controller
> get mapped as regular edge-types, as their wakeup and interrupt type
> properties will be handled by the upcoming pinctrl driver.
> 
> Signed-off-by: Heiko Stuebner <[email-masked]>
> ---
>  .../interrupt-controller/samsung,s3c24xx-irq.txt   |   54 +++++
>  drivers/irqchip/irq-s3c24xx.c                      |  222 ++++++++++++++++++++
>  2 files changed, 276 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/interrupt-controller/samsung,s3c24xx-irq.txt
> 
> diff --git a/Documentation/devicetree/bindings/interrupt-controller/samsung,s3c24xx-irq.txt b/Documentation/devicetree/bindings/interrupt-controller/samsung,s3c24xx-irq.txt
> new file mode 100644
> index 0000000..be5dead
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/interrupt-controller/samsung,s3c24xx-irq.txt
> @@ -0,0 +1,54 @@
> +Samsung S3C24XX Interrupt Controllers
> +
> +The S3C24XX SoCs contain a custom set of interrupt controllers providing a
> +varying number of interrupt sources. The set consists of a main- and sub-
> +controller and on newer SoCs even a second main controller.
> +
> +Required properties:
> +- compatible: Compatible property value should be one of "samsung,s3c2410-irq",
> +  "samsung,s3c2412-irq", "samsung,s3c2416-irq", "samsung,s3c2440-irq",
> +  "samsung,s3c2442-irq", "samsung,s3c2443-irq" depending on the SoC variant.
> +
> +- reg: Physical base address of the controller and length of memory mapped
> +  region.
> +
> +- interrupt-controller : Identifies the node as an interrupt controller
> +
> +Sub-controllers as child nodes:
> +  The interrupt controllers that should be referenced by device nodes are
> +  represented by child nodes. Valid names are intc, subintc and intc2.
> +  The interrupt values in device nodes are then mapped directly to the
> +  bit-numbers of the pending register of the named interrupt controller.
> +
> +Required properties:
> +- interrupt-controller : Identifies the node as an interrupt controller
> +
> +- #interrupt-cells : Specifies the number of cells needed to encode an
> +  interrupt source. The value shall be 2.
> +
> +Example:
> +
> +	interrupt-controller at 4a000000 {
> +		compatible = "samsung,s3c2416-irq";
> +		reg = <0x4a000000 0x100>;
> +		interrupt-controller;
> +
> +		intc:intc {
> +			interrupt-controller;
> +			#interrupt-cells = <2>;
> +		};
> +
> +		subintc:subintc {
> +			interrupt-controller;
> +			#interrupt-cells = <2>;
> +		};
> +	};
> +
> +	[...]
> +
> +	serial at 50000000 {
> +		compatible = "samsung,s3c2410-uart";
> +		reg = <0x50000000 0x4000>;
> +		interrupt-parent = <&subintc>;
> +		interrupts = <0 0>, <1 0>;
> +	};
> diff --git a/drivers/irqchip/irq-s3c24xx.c b/drivers/irqchip/irq-s3c24xx.c
> index 1eba289..55cb363 100644
> --- a/drivers/irqchip/irq-s3c24xx.c
> +++ b/drivers/irqchip/irq-s3c24xx.c
> @@ -25,6 +25,9 @@
>  #include <linux/ioport.h>
>  #include <linux/device.h>
>  #include <linux/irqdomain.h>
> +#include <linux/of.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_address.h>
>  
>  #include <asm/exception.h>
>  #include <asm/mach/irq.h>
> @@ -36,6 +39,8 @@
>  #include <plat/regs-irqtype.h>
>  #include <plat/pm.h>
>  
> +#include "irqchip.h"
> +
>  #define S3C_IRQTYPE_NONE	0
>  #define S3C_IRQTYPE_EINT	1
>  #define S3C_IRQTYPE_EDGE	2
> @@ -380,6 +385,10 @@ static int s3c24xx_irq_map(struct irq_domain *h, unsigned int virq,
>  
>  	parent_intc = intc->parent;
>  
> +	/* on dt platforms the extints get handled by the pinctrl driver */
> +	if (h->of_node && irq_data->type == S3C_IRQTYPE_EINT)
> +		irq_data->type = S3C_IRQTYPE_EDGE;
> +
>  	/* set handler and flags */
>  	switch (irq_data->type) {
>  	case S3C_IRQTYPE_NONE:
> @@ -1104,3 +1113,216 @@ void __init s3c2443_init_irq(void)
>  	s3c24xx_init_intc(NULL, &init_s3c2443subint[0], main_intc, 0x4a000018);
>  }
>  #endif
> +
> +#ifdef CONFIG_OF
> +struct s3c24xx_irq_of_ctrl {
> +	char			*name;
> +	unsigned long		offset;
> +	struct s3c_irq_data	*irq_data;
> +	struct s3c_irq_intc	**handle;
> +	struct s3c_irq_intc	**parent;
> +};
> +
> +#define S3C24XX_IRQCTRL(n, o, d, h, p)		\
> +{						\
> +	.name		= n,			\
> +	.offset		= o,			\
> +	.irq_data	= d,			\
> +	.handle		= h,			\
> +	.parent		= p,			\
> +}
> +
> +struct s3c24xx_irq_of_data {
> +	struct s3c24xx_irq_of_ctrl	*irq_ctrl;
> +	int				num_ctrl;
> +};
> +
> +#ifdef CONFIG_CPU_S3C2410
> +static struct s3c24xx_irq_of_ctrl s3c2410_ctrl[] = {
> +	S3C24XX_IRQCTRL("intc", 0, init_s3c2410base, &main_intc, NULL),
> +	S3C24XX_IRQCTRL("subintc", 0x18, init_s3c2410subint, NULL, &main_intc),
> +};
> +
> +static struct s3c24xx_irq_of_data s3c2410_irq_data = {
> +	.irq_ctrl = s3c2410_ctrl,
> +	.num_ctrl = ARRAY_SIZE(s3c2410_ctrl),
> +};
> +#endif
> +
> +#ifdef CONFIG_CPU_S3C2412
> +static struct s3c24xx_irq_of_ctrl s3c2412_ctrl[] = {
> +	S3C24XX_IRQCTRL("intc", 0, init_s3c2412base, &main_intc, NULL),
> +	S3C24XX_IRQCTRL("subintc", 0x18, init_s3c2412subint, NULL, &main_intc),
> +};
> +
> +static struct s3c24xx_irq_of_data s3c2412_irq_data = {
> +	.irq_ctrl = s3c2412_ctrl,
> +	.num_ctrl = ARRAY_SIZE(s3c2412_ctrl),
> +};
> +#endif
> +
> +#ifdef CONFIG_CPU_S3C2416
> +static struct s3c24xx_irq_of_ctrl s3c2416_ctrl[] = {
> +	S3C24XX_IRQCTRL("intc", 0, init_s3c2416base, &main_intc, NULL),
> +	S3C24XX_IRQCTRL("subintc", 0x18, init_s3c2416subint, NULL, &main_intc),
> +	S3C24XX_IRQCTRL("intc2", 0x40, init_s3c2416_second, &main_intc2, NULL),
> +};
> +
> +static struct s3c24xx_irq_of_data s3c2416_irq_data = {
> +	.irq_ctrl = s3c2416_ctrl,
> +	.num_ctrl = ARRAY_SIZE(s3c2416_ctrl),
> +};
> +#endif
> +
> +#ifdef CONFIG_CPU_S3C2440
> +static struct s3c24xx_irq_of_ctrl s3c2440_ctrl[] = {
> +	S3C24XX_IRQCTRL("intc", 0, init_s3c2440base, &main_intc, NULL),
> +	S3C24XX_IRQCTRL("subintc", 0x18, init_s3c2440subint, NULL, &main_intc),
> +};
> +
> +static struct s3c24xx_irq_of_data s3c2440_irq_data = {
> +	.irq_ctrl = s3c2440_ctrl,
> +	.num_ctrl = ARRAY_SIZE(s3c2440_ctrl),
> +};
> +#endif
> +
> +#ifdef CONFIG_CPU_S3C2442
> +static struct s3c24xx_irq_of_ctrl s3c2442_ctrl[] = {
> +	S3C24XX_IRQCTRL("intc", 0, init_s3c2442base, &main_intc, NULL),
> +	S3C24XX_IRQCTRL("subintc", 0x18, init_s3c2442subint, NULL, &main_intc),
> +};
> +
> +static struct s3c24xx_irq_of_data s3c2442_irq_data = {
> +	.irq_ctrl = s3c2442_ctrl,
> +	.num_ctrl = ARRAY_SIZE(s3c2442_ctrl),
> +};
> +#endif
> +
> +#ifdef CONFIG_CPU_S3C2443
> +static struct s3c24xx_irq_of_ctrl s3c2443_ctrl[] = {
> +	S3C24XX_IRQCTRL("intc", 0, init_s3c2443base, &main_intc, NULL),
> +	S3C24XX_IRQCTRL("subintc", 0x18, init_s3c2443subint, NULL, &main_intc),
> +};
> +
> +static struct s3c24xx_irq_of_data s3c2443_irq_data = {
> +	.irq_ctrl = s3c2443_ctrl,
> +	.num_ctrl = ARRAY_SIZE(s3c2443_ctrl),
> +};
> +#endif
> +
> +static const struct of_device_id intc_list[] = {
> +#ifdef CONFIG_CPU_S3C2410
> +	{ .compatible = "samsung,s3c2410-irq", .data = &s3c2410_irq_data },
> +#endif
> +#ifdef CONFIG_CPU_S3C2412
> +	{ .compatible = "samsung,s3c2412-irq", .data = &s3c2412_irq_data },
> +#endif
> +#ifdef CONFIG_CPU_S3C2416
> +	{ .compatible = "samsung,s3c2416-irq", .data = &s3c2416_irq_data },
> +#endif
> +#ifdef CONFIG_CPU_S3C2440
> +	{ .compatible = "samsung,s3c2440-irq", .data = &s3c2440_irq_data },
> +#endif
> +#ifdef CONFIG_CPU_S3C2442
> +	{ .compatible = "samsung,s3c2442-irq", .data = &s3c2442_irq_data },
> +#endif
> +#ifdef CONFIG_CPU_S3C2443
> +	{ .compatible = "samsung,s3c2443-irq", .data = &s3c2443_irq_data },
> +#endif
> +	{},
> +};
> +
> +int __init s3c24xx_init_intc_of(struct device_node *np,
> +				 struct device_node *interrupt_parent)
> +{
> +	const struct of_device_id *match;
> +	const struct s3c24xx_irq_of_data *ctrl_data;
> +	struct device_node *intc_node;
> +	struct s3c24xx_irq_of_ctrl *ctrl;
> +	struct s3c_irq_intc *intc;
> +	void __iomem *reg_base;
> +	int i;
> +
> +	match = of_match_node(intc_list, np);

Rather than matching twice, create a wrapper function that passes in the
necessary data pointer. Something like this:

s3c2410_init_intc_of(struct device_node *np,
				 struct device_node *interrupt_parent)
{
	return s3c24xx_init_intc_of(np, interrupt_parent, &s3c2410_irq_data);
}

> +	if (!match) {
> +		pr_err("irq-s3c24xx: could not find matching irqdata\n");
> +		return -EINVAL;
> +	}
> +
> +	ctrl_data = match->data;
> +
> +	reg_base = of_iomap(np, 0);
> +	if (!reg_base) {
> +		pr_err("irq-s3c24xx: could not map irq memory\n");
> +		return -EINVAL;
> +	}
> +
> +	for (i = 0; i < ctrl_data->num_ctrl; i++) {
> +		ctrl = &ctrl_data->irq_ctrl[i];
> +
> +		intc_node = of_find_node_by_name(np, ctrl->name);
> +		if (!intc_node) {
> +			pr_debug("irq: no device node for %s\n",
> +				ctrl->name);
> +			continue;
> +		}
> +
> +		intc = kzalloc(sizeof(struct s3c_irq_intc), GFP_KERNEL);
> +		if (!intc) {
> +			of_node_put(intc_node);
> +			return -ENOMEM;
> +		}
> +
> +		pr_debug("irq: found controller %s\n", ctrl->name);
> +
> +		intc->irqs = ctrl->irq_data;
> +
> +		if (ctrl->parent) {
> +			if (*(ctrl->parent)) {
> +				intc->parent = *(ctrl->parent);
> +			} else {
> +				pr_warn("irq: parent of %s missing\n",
> +					ctrl->name);
> +				kfree(intc);
> +				of_node_put(intc_node);
> +				continue;
> +			}
> +		}
> +
> +		if (!ctrl->parent) {
> +			intc->reg_pending = reg_base + ctrl->offset;
> +			intc->reg_mask = reg_base + ctrl->offset + 0x08;
> +			intc->reg_intpnd = reg_base + ctrl->offset + 0x10;
> +		} else {
> +			intc->reg_pending = reg_base + ctrl->offset;
> +			intc->reg_mask = reg_base + ctrl->offset + 0x4;
> +		}

These if statements could be simplified some (move this else to the "if
(ctrl->parent)" above).

> +
> +		/* now that all the data is complete, init the irq-domain */
> +		s3c24xx_clear_intc(intc);
> +		intc->domain = irq_domain_add_linear(intc_node, 32,
> +						     &s3c24xx_irq_ops, intc);
> +		if (!intc->domain) {
> +			pr_err("irq: could not create irq-domain\n");
> +			kfree(intc);
> +			of_node_put(intc_node);
> +			continue;
> +		}
> +
> +		if (ctrl->handle)
> +			*(ctrl->handle) = intc;
> +	}
> +
> +	set_handle_irq(s3c24xx_handle_irq);
> +
> +	return 0;
> +}
> +
> +IRQCHIP_DECLARE(s3c2410_irq, "samsung,s3c2410-irq", s3c24xx_init_intc_of);
> +IRQCHIP_DECLARE(s3c2412_irq, "samsung,s3c2412-irq", s3c24xx_init_intc_of);
> +IRQCHIP_DECLARE(s3c2416_irq, "samsung,s3c2416-irq", s3c24xx_init_intc_of);
> +IRQCHIP_DECLARE(s3c2440_irq, "samsung,s3c2440-irq", s3c24xx_init_intc_of);
> +IRQCHIP_DECLARE(s3c2442_irq, "samsung,s3c2442-irq", s3c24xx_init_intc_of);
> +IRQCHIP_DECLARE(s3c2443_irq, "samsung,s3c2443-irq", s3c24xx_init_intc_of);
> +IRQCHIP_DECLARE(s3c2450_irq, "samsung,s3c2450-irq", s3c24xx_init_intc_of);
> +#endif
>