[PATCH 07/10] ARM: clps711x: Add CLPS711X irqchip driver
Mark Rutland
mark.rutland at arm.com
Fri Aug 2 06:57:54 EDT 2013
On Thu, Jul 18, 2013 at 07:34:58PM +0100, Alexander Shiyan wrote:
> This adds the irqchip driver for Cirrus Logic CLPS711X series SoCs.
> Designed primarily for migration CLPS711X subarch for multiplatform & DT,
> for this as the "OF" and "not-OF" calls implemented.
>
> Signed-off-by: Alexander Shiyan <shc_work at mail.ru>
> ---
> .../interrupt-controller/cirrus,clps711x-intc.txt | 42 ++++
> arch/arm/Kconfig | 2 -
> drivers/irqchip/Kconfig | 6 +
> drivers/irqchip/Makefile | 1 +
> drivers/irqchip/irq-clps711x.c | 277 +++++++++++++++++++++
> 5 files changed, 326 insertions(+), 2 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/interrupt-controller/cirrus,clps711x-intc.txt
> create mode 100644 drivers/irqchip/irq-clps711x.c
>
> diff --git a/Documentation/devicetree/bindings/interrupt-controller/cirrus,clps711x-intc.txt b/Documentation/devicetree/bindings/interrupt-controller/cirrus,clps711x-intc.txt
> new file mode 100644
> index 0000000..26f8983
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/interrupt-controller/cirrus,clps711x-intc.txt
> @@ -0,0 +1,42 @@
> +Cirrus Logic CLPS711X Interrupt Controller
> +
> +Required properties:
> +
> +- compatible: Should be "cirrus,clps711x-intc"
> +- reg: Specifies base physical address of the registers set
> +- 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 1.
> +
> +The interrupt sources are as follows:
> +ID Name Description
> +---------------------------
> +1: BLINT Battery low (FIQ)
> +3: MCINT Media changed (FIQ)
> +4: CSINT CODEC sound
> +5: EINT1 External 1
> +6: EINT2 External 2
> +7: EINT3 External 3
> +8: TC1OI TC1 under flow
> +9: TC2OI TC2 under flow
> +10: RTCMI RTC compare match
> +11: TINT 64Hz tick
> +12: UTXINT1 UART1 transmit FIFO half empty
> +13: URXINT1 UART1 receive FIFO half full
> +14: UMSINT UART1 modem status changed
> +15: SSEOTI SSI1 end of transfer
> +16: KBDINT Keyboard
> +17: SS2RX SSI2 receive FIFO half or greater full
> +18: SS2TX SSI2 transmit FIFO less than half empty
> +28: UTXINT2 UART2 transmit FIFO half empty
> +29: URXINT2 UART2 receive FIFO half full
> +32: DAIINT DAI interface (FIQ)
Surely that's specific to the SoC the interrupt controller is used in,
not the interrupt controller IP itself?
> +
> +Example:
> +
> +intc: interrupt-controller {
> + compatible = "cirrus,clps711x-intc";
> + reg = <0x80000000 0>;
Zero length?
> + interrupt-controller;
> + #interrupt-cells = <1>;
> +};
[...]
> diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
> index 5c60cb7..6f0d238 100644
> --- a/arch/arm/Kconfig
> +++ b/arch/arm/Kconfig
> @@ -373,8 +373,6 @@ config ARCH_CLPS711X
> select COMMON_CLK
> select CPU_ARM720T
> select MFD_SYSCON
> - select MULTI_IRQ_HANDLER
> - select SPARSE_IRQ
> help
> Support for Cirrus Logic 711x/721x/731x based boards.
>
> diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
> index 1fea003..c94d00a 100644
> --- a/drivers/irqchip/Kconfig
> +++ b/drivers/irqchip/Kconfig
> @@ -30,6 +30,12 @@ config ARM_VIC_NR
> The maximum number of VICs available in the system, for
> power management.
>
> +config CLPS711X_IRQCHIP
> + def_bool y if ARCH_CLPS711X
> + select IRQ_DOMAIN
> + select MULTI_IRQ_HANDLER
> + select SPARSE_IRQ
> +
> config ORION_IRQCHIP
> bool
> select IRQ_DOMAIN
> diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> index e65c41a..a80ff5b 100644
> --- a/drivers/irqchip/Makefile
> +++ b/drivers/irqchip/Makefile
> @@ -8,6 +8,7 @@ obj-$(CONFIG_ARCH_S3C24XX) += irq-s3c24xx.o
> obj-$(CONFIG_METAG) += irq-metag-ext.o
> obj-$(CONFIG_METAG_PERFCOUNTER_IRQS) += irq-metag.o
> obj-$(CONFIG_ARCH_MOXART) += irq-moxart.o
> +obj-$(CONFIG_CLPS711X_IRQCHIP) += irq-clps711x.o
> obj-$(CONFIG_ORION_IRQCHIP) += irq-orion.o
> obj-$(CONFIG_ARCH_SUNXI) += irq-sun4i.o
> obj-$(CONFIG_ARCH_SPEAR3XX) += spear-shirq.o
> diff --git a/drivers/irqchip/irq-clps711x.c b/drivers/irqchip/irq-clps711x.c
> new file mode 100644
> index 0000000..819baa9
> --- /dev/null
> +++ b/drivers/irqchip/irq-clps711x.c
> @@ -0,0 +1,277 @@
> +/*
> + * CLPS711X IRQ driver
> + *
> + * Copyright (C) 2013 Alexander Shiyan <shc_work at mail.ru>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#include <linux/io.h>
> +#include <linux/irq.h>
> +#include <linux/irqdomain.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/slab.h>
> +
> +#include <asm/exception.h>
> +#include <asm/mach/irq.h>
> +
> +#include "irqchip.h"
> +
> +#define CLPS711X_INTSR1 (0x0240)
> +#define CLPS711X_INTMR1 (0x0280)
> +#define CLPS711X_BLEOI (0x0600)
> +#define CLPS711X_MCEOI (0x0640)
> +#define CLPS711X_TEOI (0x0680)
> +#define CLPS711X_TC1EOI (0x06c0)
> +#define CLPS711X_TC2EOI (0x0700)
> +#define CLPS711X_RTCEOI (0x0740)
> +#define CLPS711X_UMSEOI (0x0780)
> +#define CLPS711X_COEOI (0x07c0)
> +#define CLPS711X_INTSR2 (0x1240)
> +#define CLPS711X_INTMR2 (0x1280)
> +#define CLPS711X_SRXEOF (0x1600)
> +#define CLPS711X_KBDEOI (0x1700)
> +#define CLPS711X_INTSR3 (0x2240)
> +#define CLPS711X_INTMR3 (0x2280)
> +
> +static const struct {
> +#define CLPS711X_FLAG_EN (1 << 0)
> +#define CLPS711X_FLAG_FIQ (1 << 1)
> + unsigned int flags;
> + phys_addr_t phys_eoi;
> +} clps711x_irqs[] __initconst = {
> + [1] = { CLPS711X_FLAG_FIQ, CLPS711X_BLEOI, },
> + [3] = { CLPS711X_FLAG_FIQ, CLPS711X_MCEOI, },
> + [4] = { CLPS711X_FLAG_EN, CLPS711X_COEOI, },
> + [5] = { CLPS711X_FLAG_EN, },
> + [6] = { CLPS711X_FLAG_EN, },
> + [7] = { CLPS711X_FLAG_EN, },
> + [8] = { CLPS711X_FLAG_EN, CLPS711X_TC1EOI, },
> + [9] = { CLPS711X_FLAG_EN, CLPS711X_TC2EOI, },
> + [10] = { CLPS711X_FLAG_EN, CLPS711X_RTCEOI, },
> + [11] = { CLPS711X_FLAG_EN, CLPS711X_TEOI, },
> + [12] = { CLPS711X_FLAG_EN, },
> + [13] = { CLPS711X_FLAG_EN, },
> + [14] = { CLPS711X_FLAG_EN, CLPS711X_UMSEOI, },
> + [15] = { CLPS711X_FLAG_EN, CLPS711X_SRXEOF, },
> + [16] = { CLPS711X_FLAG_EN, CLPS711X_KBDEOI, },
> + [17] = { CLPS711X_FLAG_EN, },
> + [18] = { CLPS711X_FLAG_EN, },
> + [28] = { CLPS711X_FLAG_EN, },
> + [29] = { CLPS711X_FLAG_EN, },
> + [32] = { CLPS711X_FLAG_FIQ, },
> +};
Isn't that SoC specific?
> +
> +static struct {
> + struct irq_domain *domain;
> + phys_addr_t phys_base;
> + void __iomem *intmr[3];
> + void __iomem *intsr[3];
> + void __iomem *eoi[ARRAY_SIZE(clps711x_irqs)];
> +} *clps711x_intc;
> +
> +
> +static inline u32 fls16(u32 x)
> +{
> + u32 r = 15;
> +
> + if (!(x & 0xff00)) {
> + x <<= 8;
> + r -= 8;
> + }
> + if (!(x & 0xf000)) {
> + x <<= 4;
> + r -= 4;
> + }
> + if (!(x & 0xc000)) {
> + x <<= 2;
> + r -= 2;
> + }
> + if (!(x & 0x8000))
> + r--;
> +
> + return r;
> +}
Why hand-roll this? There's already a generic fls in bitops.h (see
include/asm-generic/bitops/fls.h).
> +
> +static asmlinkage void __exception_irq_entry
> +clps711x_handle_irq(struct pt_regs *regs)
> +{
> + do {
> + u32 irqnr, irqstat;
> +
> + irqstat = readw(clps711x_intc->intmr[0]) &
> + readw(clps711x_intc->intsr[0]);
> + if (irqstat) {
> + irqnr = irq_find_mapping(clps711x_intc->domain,
> + fls16(irqstat));
> + handle_IRQ(irqnr, regs);
> + }
> +
> + irqstat = readw(clps711x_intc->intmr[1]) &
> + readw(clps711x_intc->intsr[1]);
> + if (irqstat) {
> + irqnr = irq_find_mapping(clps711x_intc->domain,
> + fls16(irqstat) + 16);
> + handle_IRQ(irqnr, regs);
> + continue;
> + }
> +
> + break;
> + } while (1);
> +}
> +
> +static void clps711x_intc_eoi(struct irq_data *d)
> +{
> + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> +
> + writel(0, clps711x_intc->eoi[hwirq]);
> +}
> +
> +static void clps711x_intc_mask(struct irq_data *d)
> +{
> + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> + void __iomem *intmr = clps711x_intc->intmr[hwirq / 16];
> + u32 tmp;
> +
> + tmp = readl(intmr);
> + tmp &= ~(1 << (hwirq % 16));
> + writel(tmp, intmr);
> +}
> +
> +static void clps711x_intc_unmask(struct irq_data *d)
> +{
> + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> + void __iomem *intmr = clps711x_intc->intmr[hwirq / 16];
> + u32 tmp;
> +
> + tmp = readl(intmr);
> + tmp |= 1 << (hwirq % 16);
> + writel(tmp, intmr);
> +}
> +
> +static struct irq_chip clps711x_intc_chip = {
> + .name = "clps711x-intc",
> + .irq_eoi = clps711x_intc_eoi,
> + .irq_mask = clps711x_intc_mask,
> + .irq_unmask = clps711x_intc_unmask,
> +};
> +
> +static void __iomem __init *clps711x_ioremap_one(phys_addr_t reg)
> +{
> + void __iomem *ret;
> +
> + if (!request_mem_region(clps711x_intc->phys_base + reg, SZ_4, NULL))
> + return ERR_PTR(-EBUSY);
> +
> + ret = ioremap(clps711x_intc->phys_base + reg, SZ_4);
> + if (!ret)
> + return ERR_PTR(-ENOMEM);
> +
> + return ret;
> +}
> +
> +static int __init clps711x_intc_irq_map(struct irq_domain *h, unsigned int virq,
> + irq_hw_number_t hw)
> +{
> + irq_flow_handler_t handler = handle_level_irq;
> + unsigned int flags = IRQF_VALID | IRQF_PROBE;
> +
> + if (!clps711x_irqs[hw].flags)
> + return 0;
> +
> + if (clps711x_irqs[hw].flags & CLPS711X_FLAG_FIQ) {
> + handler = handle_bad_irq;
> + flags |= IRQF_NOAUTOEN;
> + } else if (clps711x_irqs[hw].phys_eoi) {
> + handler = handle_fasteoi_irq;
> +
> + clps711x_intc->eoi[hw] =
> + clps711x_ioremap_one(clps711x_irqs[hw].phys_eoi);
> + if (IS_ERR_OR_NULL(clps711x_intc->eoi[hw]))
> + return PTR_ERR(clps711x_intc->eoi[hw]);
Wrong. What if it's NULL?
> +
> + /* Clear down pending interrupt */
> + writel(0, clps711x_intc->eoi[hw]);
> + }
> +
> + irq_set_chip_and_handler(virq, &clps711x_intc_chip, handler);
> + set_irq_flags(virq, flags);
> +
> + return 0;
> +}
> +
> +static struct irq_domain_ops clps711x_intc_ops = {
> + .map = clps711x_intc_irq_map,
> + .xlate = irq_domain_xlate_onecell,
> +};
> +
> +static void __init _clps711x_intc_init(phys_addr_t phys_base,
> + struct device_node *np)
> +{
> + clps711x_intc = kzalloc(sizeof(*clps711x_intc), GFP_KERNEL);
> + BUG_ON(!clps711x_intc);
> +
> + clps711x_intc->phys_base = phys_base;
> +
> + clps711x_intc->intsr[0] = clps711x_ioremap_one(CLPS711X_INTSR1);
> + BUG_ON(IS_ERR_OR_NULL(clps711x_intc->intsr[0]));
> +
> + clps711x_intc->intmr[0] = clps711x_ioremap_one(CLPS711X_INTMR1);
> + BUG_ON(IS_ERR_OR_NULL(clps711x_intc->intmr[0]));
> +
> + clps711x_intc->intsr[1] = clps711x_ioremap_one(CLPS711X_INTSR2);
> + BUG_ON(IS_ERR_OR_NULL(clps711x_intc->intsr[1]));
> +
> + clps711x_intc->intmr[1] = clps711x_ioremap_one(CLPS711X_INTMR2);
> + BUG_ON(IS_ERR_OR_NULL(clps711x_intc->intmr[1]));
> +
> + clps711x_intc->intsr[2] = clps711x_ioremap_one(CLPS711X_INTSR3);
> + BUG_ON(IS_ERR_OR_NULL(clps711x_intc->intsr[2]));
> +
> + clps711x_intc->intmr[2] = clps711x_ioremap_one(CLPS711X_INTMR3);
> + BUG_ON(IS_ERR_OR_NULL(clps711x_intc->intmr[2]));
> +
> + /* Disable interrupts */
> + writel(0, clps711x_intc->intmr[0]);
> + writel(0, clps711x_intc->intmr[1]);
> + writel(0, clps711x_intc->intmr[2]);
> +
> + BUG_ON(IS_ERR_VALUE(irq_alloc_descs(-1, 0, ARRAY_SIZE(clps711x_irqs),
> + numa_node_id())));
> +
> + clps711x_intc->domain =
> + irq_domain_add_legacy(np, ARRAY_SIZE(clps711x_irqs),
> + 0, 0, &clps711x_intc_ops, NULL);
> + BUG_ON(!clps711x_intc->domain);
> +
> + irq_set_default_host(clps711x_intc->domain);
> + set_handle_irq(clps711x_handle_irq);
> +
> +#ifdef CONFIG_FIQ
> + init_FIQ(0);
> +#endif
> +}
> +
> +void __init clps711x_intc_init(phys_addr_t phys_base)
> +{
> + _clps711x_intc_init(phys_base, NULL);
> +}
> +
> +#ifdef CONFIG_IRQCHIP
> +static int __init clps711x_intc_init_dt(struct device_node *np,
> + struct device_node *parent)
> +{
> + struct resource res;
> +
> + BUG_ON(of_address_to_resource(np, 0, &res));
> +
> + _clps711x_intc_init(res.start, np);
Why not pass the whole resource?
> +
> + return 0;
> +}
> +IRQCHIP_DECLARE(clps711x, "cirrus,clps711x-intc", clps711x_intc_init_dt);
> +#endif
Thanks,
Mark.
More information about the linux-arm-kernel
mailing list