From: maxime.ripard@free-electrons.com (Maxime Ripard)
To: linux-arm-kernel@lists.infradead.org
Subject: [PATCH v3 1/3] ARM: sun7i/sun6i: irqchip: Add irqchip driver for NMI controller
Date: Thu, 16 Jan 2014 13:39:40 +0100 [thread overview]
Message-ID: <20140116123940.GE31779@lukather> (raw)
In-Reply-To: <1389453548-10665-2-git-send-email-carlo.caione@gmail.com>
Hi Carlo,
On Sat, Jan 11, 2014 at 04:19:06PM +0100, Carlo Caione wrote:
> Allwinner A20/A31 SoCs have special registers to control / (un)mask /
> acknowledge NMI. This NMI controller is separated and independent from GIC.
> This patch adds a new irqchip to manage NMI.
>
> Signed-off-by: Carlo Caione <carlo.caione@gmail.com>
> ---
> drivers/irqchip/Makefile | 1 +
> drivers/irqchip/irq-sunxi-nmi.c | 228 ++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 229 insertions(+)
> create mode 100644 drivers/irqchip/irq-sunxi-nmi.c
>
> diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> index c60b901..e31d4d6 100644
> --- a/drivers/irqchip/Makefile
> +++ b/drivers/irqchip/Makefile
> @@ -11,6 +11,7 @@ obj-$(CONFIG_METAG_PERFCOUNTER_IRQS) += irq-metag.o
> obj-$(CONFIG_ARCH_MOXART) += irq-moxart.o
> obj-$(CONFIG_ORION_IRQCHIP) += irq-orion.o
> obj-$(CONFIG_ARCH_SUNXI) += irq-sun4i.o
> +obj-$(CONFIG_ARCH_SUNXI) += irq-sunxi-nmi.o
> obj-$(CONFIG_ARCH_SPEAR3XX) += spear-shirq.o
> obj-$(CONFIG_ARM_GIC) += irq-gic.o
> obj-$(CONFIG_ARM_NVIC) += irq-nvic.o
> diff --git a/drivers/irqchip/irq-sunxi-nmi.c b/drivers/irqchip/irq-sunxi-nmi.c
> new file mode 100644
> index 0000000..a2b7373
> --- /dev/null
> +++ b/drivers/irqchip/irq-sunxi-nmi.c
> @@ -0,0 +1,228 @@
> +/*
> + * Allwinner A20/A31 SoCs NMI IRQ chip driver.
> + *
> + * Carlo Caione <carlo.caione@gmail.com>
> + *
> + * This file is licensed under the terms of the GNU General Public
> + * License version 2. This program is licensed "as is" without any
> + * warranty of any kind, whether express or implied.
> + */
> +
> +#include <linux/bitops.h>
> +#include <linux/device.h>
> +#include <linux/io.h>
> +#include <linux/irq.h>
> +#include <linux/interrupt.h>
> +#include <linux/irqdomain.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_address.h>
> +#include <linux/of_platform.h>
> +#include <linux/irqchip/chained_irq.h>
> +#include "irqchip.h"
> +
> +#define SUNXI_NMI_SRC_TYPE_MASK 0x00000003
> +
> +enum {
> + SUNXI_SRC_TYPE_LEVEL_LOW = 0,
> + SUNXI_SRC_TYPE_EDGE_FALLING,
> + SUNXI_SRC_TYPE_LEVEL_HIGH,
> + SUNXI_SRC_TYPE_EDGE_RISING,
> +};
> +
> +struct sunxi_sc_nmi_reg_offs {
> + u32 ctrl;
> + u32 pend;
> + u32 enable;
> +};
> +
> +static struct sunxi_sc_nmi_reg_offs sun7i_reg_offs = {
> + .ctrl = 0x00,
> + .pend = 0x04,
> + .enable = 0x08,
> +};
> +
> +static struct sunxi_sc_nmi_reg_offs sun6i_reg_offs = {
> + .ctrl = 0x00,
> + .pend = 0x04,
> + .enable = 0x34,
> +};
> +
> +/*
> + * Ack level interrupts right before unmask
> + *
> + * In case of level-triggered interrupt, IRQ line must be acked before it
> + * is unmasked or else a double-interrupt is triggered
> + */
> +
> +static void sunxi_sc_nmi_ack_and_unmask(struct irq_data *d)
> +{
> + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
> + struct irq_chip_type *ct = irq_data_get_chip_type(d);
> + u32 mask = d->mask;
> +
> + if (irqd_get_trigger_type(d) & IRQ_TYPE_LEVEL_MASK)
> + ct->chip.irq_ack(d);
> +
> + irq_gc_lock(gc);
> + irq_reg_writel(mask, gc->reg_base + ct->regs.mask);
> + irq_gc_unlock(gc);
> +}
Hmmm, handle_level_irq seems to be doing exactly that already. It
first masks and acks the interrupts, and then unmask it, so we should
be fine, don't we?
> +static inline void sunxi_sc_nmi_write(struct irq_chip_generic *gc, u32 off,
> + u32 val)
> +{
> + irq_reg_writel(val, gc->reg_base + off);
> +}
> +
> +static inline u32 sunxi_sc_nmi_read(struct irq_chip_generic *gc, u32 off)
> +{
> + return irq_reg_readl(gc->reg_base + off);
> +}
> +
> +static void sunxi_sc_nmi_handle_irq(unsigned int irq, struct irq_desc *desc)
> +{
> + struct irq_domain *domain = irq_desc_get_handler_data(desc);
> + struct irq_chip *chip = irq_get_chip(irq);
> + unsigned int virq = irq_find_mapping(domain, 0);
> +
> + chained_irq_enter(chip, desc);
> + generic_handle_irq(virq);
> + chained_irq_exit(chip, desc);
> +}
> +
> +static int sunxi_sc_nmi_set_type(struct irq_data *data, unsigned int flow_type)
> +{
> + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data);
> + struct irq_chip_type *ct = gc->chip_types;
> + u32 src_type_reg;
> + u32 ctrl_off = ct->regs.type;
> + unsigned int src_type;
> + unsigned int i;
> +
> + irq_gc_lock(gc);
> +
> + switch (flow_type & IRQF_TRIGGER_MASK) {
> + case IRQ_TYPE_EDGE_FALLING:
> + src_type = SUNXI_SRC_TYPE_EDGE_FALLING;
> + break;
> + case IRQ_TYPE_EDGE_RISING:
> + src_type = SUNXI_SRC_TYPE_EDGE_RISING;
> + break;
> + case IRQ_TYPE_LEVEL_HIGH:
> + src_type = SUNXI_SRC_TYPE_LEVEL_HIGH;
> + break;
> + case IRQ_TYPE_NONE:
> + case IRQ_TYPE_LEVEL_LOW:
> + src_type = SUNXI_SRC_TYPE_LEVEL_LOW;
> + break;
> + default:
> + irq_gc_unlock(gc);
> + pr_err("%s: Cannot assign multiple trigger modes to IRQ %d.\n",
> + __func__, data->irq);
> + return -EBADR;
> + }
> +
> + irqd_set_trigger_type(data, flow_type);
> + irq_setup_alt_chip(data, flow_type);
> +
> + for (i = 0; i <= gc->num_ct; i++, ct++)
> + if (ct->type & flow_type)
> + ctrl_off = ct->regs.type;
> +
> + src_type_reg = sunxi_sc_nmi_read(gc, ctrl_off);
> + src_type_reg &= ~SUNXI_NMI_SRC_TYPE_MASK;
> + src_type_reg |= src_type;
> + sunxi_sc_nmi_write(gc, ctrl_off, src_type_reg);
> +
> + irq_gc_unlock(gc);
> +
> + return IRQ_SET_MASK_OK;
> +}
> +
> +static int __init sunxi_sc_nmi_irq_init(struct device_node *node,
> + struct sunxi_sc_nmi_reg_offs *reg_offs)
> +{
> + struct irq_domain *domain;
> + struct irq_chip_generic *gc;
> + unsigned int irq;
> + unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN;
> + int ret;
> +
> +
> + domain = irq_domain_add_linear(node, 1, &irq_generic_chip_ops, NULL);
> + if (!domain) {
> + pr_err("%s: Could not register interrupt domain.\n", node->name);
> + return -ENOMEM;
> + }
> +
> + ret = irq_alloc_domain_generic_chips(domain, 1, 2, node->name,
> + handle_level_irq, clr, 0,
> + IRQ_GC_INIT_MASK_CACHE);
> + if (ret) {
> + pr_err("%s: Could not allocate generic interrupt chip.\n",
> + node->name);
> + goto fail_irqd_remove;
> + }
> +
> + irq = irq_of_parse_and_map(node, 0);
> + if (irq <= 0) {
> + pr_err("%s: unable to parse irq\n", node->name);
> + ret = -EINVAL;
> + goto fail_irqd_remove;
> + }
> +
> + gc = irq_get_domain_generic_chip(domain, 0);
> + gc->reg_base = of_iomap(node, 0);
> + if (!gc->reg_base) {
> + pr_err("%s: unable to map resource\n", node->name);
> + ret = -ENOMEM;
> + goto fail_irqd_remove;
> + }
> +
> + gc->chip_types[0].type = IRQ_TYPE_LEVEL_MASK;
> + gc->chip_types[0].chip.irq_ack = irq_gc_ack_set_bit;
> + gc->chip_types[0].chip.irq_mask = irq_gc_mask_clr_bit;
> + gc->chip_types[0].chip.irq_unmask = sunxi_sc_nmi_ack_and_unmask;
> + gc->chip_types[0].chip.irq_set_type = sunxi_sc_nmi_set_type;
> + gc->chip_types[0].regs.ack = reg_offs->pend;
> + gc->chip_types[0].regs.mask = reg_offs->enable;
> + gc->chip_types[0].regs.type = reg_offs->ctrl;
> +
> + gc->chip_types[1].type = IRQ_TYPE_EDGE_BOTH;
> + gc->chip_types[1].chip.name = gc->chip_types[0].chip.name;
> + gc->chip_types[1].chip.irq_ack = irq_gc_ack_set_bit;
> + gc->chip_types[1].chip.irq_mask = irq_gc_mask_clr_bit;
> + gc->chip_types[1].chip.irq_unmask = sunxi_sc_nmi_ack_and_unmask;
> + gc->chip_types[1].chip.irq_set_type = sunxi_sc_nmi_set_type;
> + gc->chip_types[1].regs.ack = reg_offs->pend;
> + gc->chip_types[1].regs.mask = reg_offs->enable;
> + gc->chip_types[1].regs.type = reg_offs->ctrl;
> + gc->chip_types[1].handler = handle_edge_irq;
> +
> + irq_set_handler_data(irq, domain);
> + irq_set_chained_handler(irq, sunxi_sc_nmi_handle_irq);
> +
> + sunxi_sc_nmi_write(gc, reg_offs->enable, 0);
> + sunxi_sc_nmi_write(gc, reg_offs->pend, 0x1);
I really wonder whether it makes sense to have a generic chip here. It
seems to be much more complicated than it should. It's only about a
single interrupt interrupt chip here.
> +
> + return 0;
> +
> +fail_irqd_remove:
> + irq_domain_remove(domain);
> +
> + return ret;
> +}
> +
> +static int __init sun6i_sc_nmi_irq_init(struct device_node *node,
> + struct device_node *parent)
> +{
> + return sunxi_sc_nmi_irq_init(node, &sun6i_reg_offs);
> +}
> +IRQCHIP_DECLARE(sun6i_sc_nmi, "allwinner,sun6i-sc-nmi", sun6i_sc_nmi_irq_init);
I'm curious, where did you get these infos on the A31? :)
> +static int __init sun7i_sc_nmi_irq_init(struct device_node *node,
> + struct device_node *parent)
> +{
> + return sunxi_sc_nmi_irq_init(node, &sun7i_reg_offs);
> +}
> +IRQCHIP_DECLARE(sun7i_sc_nmi, "allwinner,sun7i-sc-nmi", sun7i_sc_nmi_irq_init);
The compatibles should be sun6i-a31-* and sun7i-a20-*.
Thanks for your work!
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20140116/0150fad3/attachment.sig>
next prev parent reply other threads:[~2014-01-16 12:39 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-01-11 15:19 [PATCH v3 0/3] ARM: sun7i/sun6i: irqchip: Irqchip driver for NMI Carlo Caione
2014-01-11 15:19 ` [PATCH v3 1/3] ARM: sun7i/sun6i: irqchip: Add irqchip driver for NMI controller Carlo Caione
2014-01-16 12:39 ` Maxime Ripard [this message]
2014-01-17 8:54 ` Carlo Caione
2014-01-28 10:40 ` Maxime Ripard
2014-01-28 11:02 ` Hans de Goede
2014-01-28 16:41 ` Maxime Ripard
2014-01-28 21:02 ` Carlo Caione
2014-01-29 12:58 ` Maxime Ripard
2014-01-29 13:21 ` Carlo Caione
2014-01-28 19:41 ` Carlo Caione
2014-01-11 15:19 ` [PATCH v3 2/3] ARM: sun7i/sun6i: dts: Add NMI irqchip support Carlo Caione
2014-01-11 15:19 ` [PATCH v3 3/3] ARM: sun7i/sun6i: irqchip: Update the documentation Carlo Caione
2014-01-13 19:01 ` [PATCH v3 0/3] ARM: sun7i/sun6i: irqchip: Irqchip driver for NMI Carlo Caione
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20140116123940.GE31779@lukather \
--to=maxime.ripard@free-electrons.com \
--cc=linux-arm-kernel@lists.infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.