irqchip: add a new gpio IRQ driver to support double-edge detection [1/1]
authorXingyu Chen <xingyu.chen@amlogic.com>
Thu, 18 Oct 2018 07:09:52 +0000 (15:09 +0800)
committerJianxin Pan <jianxin.pan@amlogic.com>
Mon, 29 Oct 2018 02:40:06 +0000 (19:40 -0700)
PD#SWPL-922

Problem:
the existing gpio IRQ driver(porting from the upstream) does not
double-edge detection

Solution:
add a new gpio IRQ driver to support the double-edge detection,
the new driver is with different software structure but compatible
with functions of the existing driver

Verify:
test pass on s400

Change-Id: Id69dd0b4459eef20a6755947c7d95a24d7b17fa9
Signed-off-by: Xingyu Chen <xingyu.chen@amlogic.com>
MAINTAINERS
drivers/amlogic/irqchip/Makefile
drivers/amlogic/irqchip/irq-meson-gpio-double-edge.c [new file with mode: 0644]
drivers/amlogic/pinctrl/pinctrl-meson.c

index 6c5077b..168cddd 100644 (file)
@@ -14281,6 +14281,7 @@ F:      sound/soc/amlogic/meson/dmic.h
 AMLOGIC GPIO IRQ
 M:     Xingyu Chen <xingyu.chen@amlogic.com>
 F:     drivers/amlogic/irqchip/*
+F:     drivers/amlogic/irqchip/irq-meson-gpio-double-edge.c
 F:     Documentation/devicetree/bindings/interrupt-controller/amlogic,meson-gpio-intc.txt
 
 AMLOGIC PINCTRL DRIVER
index d11b37a..ef4b22e 100644 (file)
@@ -1 +1,2 @@
- obj-$(CONFIG_AMLOGIC_GPIO_IRQ) += irq-meson-gpio.o
+ obj-$(CONFIG_AMLOGIC_GPIO_IRQ) += irq-meson-gpio.o \
+                                       irq-meson-gpio-double-edge.o
diff --git a/drivers/amlogic/irqchip/irq-meson-gpio-double-edge.c b/drivers/amlogic/irqchip/irq-meson-gpio-double-edge.c
new file mode 100644 (file)
index 0000000..41cf4ee
--- /dev/null
@@ -0,0 +1,489 @@
+/*
+ * drivers/amlogic/irqchip/irq-meson-gpio-double-edge.c
+ *
+ * Copyright (C) 2017 Amlogic, Inc. All rights reserved.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ */
+
+#include <linux/of_address.h>
+#include <linux/of.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqchip.h>
+
+#ifndef NO_IRQ
+#define NO_IRQ ((unsigned int)(-1))
+#endif
+
+#define DRIVER_NAME "GPIO-INTC"
+#define MESON_GPIO_BIND_PARENT_IRQ_NUM_MAX 2
+
+#define REG_EDGE_POL   0x0
+#define REG_PIN_03_SEL 0x4
+#define REG_PIN_47_SEL 0x8
+#define REG_FILTER_SEL 0xc
+
+#define REG_EDGE_POL_MASK(x) (BIT(x) | BIT(16 + x))
+#define REG_EDGE_SET(x)       BIT(x)
+#define REG_POL_SET(x)        BIT(16+x)
+/**
+ * struct gpio_parent_irq - describe the parent irq for gpio
+ *
+ * @virq: virtual interrupt number of parent irq
+ * @owner: hwirq for gpio
+ */
+struct gpio_parent_irq {
+       int virq;
+       unsigned int owner;
+};
+
+struct meson_gpio_irq_data {
+       unsigned int nr_hwirq;
+};
+
+struct meson_gpio_intc {
+       unsigned char nr_gicirq;
+       spinlock_t lock;
+       void __iomem *base;
+       struct irq_domain *irqdomain;
+       struct gpio_parent_irq *parent_irqs;
+       const struct meson_gpio_irq_data *data;
+};
+
+static const struct meson_gpio_irq_data meson8_data = {
+       .nr_hwirq = 134,
+};
+
+static const struct meson_gpio_irq_data meson8b_data = {
+       .nr_hwirq = 119,
+};
+
+static const struct meson_gpio_irq_data gxbb_data = {
+       .nr_hwirq = 133,
+};
+
+static const struct meson_gpio_irq_data gxl_data = {
+       .nr_hwirq = 110,
+};
+
+static const struct meson_gpio_irq_data axg_data = {
+       .nr_hwirq = 100,
+};
+
+static const struct meson_gpio_irq_data txlx_data = {
+       .nr_hwirq = 119,
+};
+
+static const struct meson_gpio_irq_data g12a_data = {
+       .nr_hwirq = 100,
+};
+
+static const struct meson_gpio_irq_data txl_data = {
+       .nr_hwirq = 93,
+};
+
+static const struct meson_gpio_irq_data tl1_data = {
+       .nr_hwirq = 102,
+};
+
+static const struct of_device_id meson_gpio_irq_matches[] = {
+       { .compatible = "amlogic,meson8-gpio-intc", .data = &meson8_data },
+       { .compatible = "amlogic,meson8b-gpio-intc", .data = &meson8b_data },
+       { .compatible = "amlogic,meson-gxbb-gpio-intc", .data = &gxbb_data },
+       { .compatible = "amlogic,meson-gxl-gpio-intc", .data = &gxl_data },
+       { .compatible = "amlogic,meson-axg-gpio-intc", .data = &axg_data },
+       { .compatible = "amlogic,meson-txlx-gpio-intc", .data = &txlx_data },
+       { .compatible = "amlogic,meson-g12a-gpio-intc", .data = &g12a_data },
+       { .compatible = "amlogic,meson-txl-gpio-intc", .data = &txl_data },
+       { .compatible = "amlogic,meson-tl1-gpio-intc", .data = &tl1_data },
+       { }
+};
+
+static void meson_reg_update_bits(void __iomem *base, unsigned int regoff,
+       unsigned int mask, const unsigned int val)
+{
+       unsigned int orig;
+
+       orig = readl_relaxed(base + regoff);
+       orig = orig & (~mask);
+       orig |= val;
+       writel_relaxed(orig, base + regoff);
+}
+
+static int meson_gpio_irq_find_by_parent_virq(struct irq_data *irqd)
+{
+       struct irq_desc *desc = irq_data_to_desc(irqd);
+       struct meson_gpio_intc *intc = irq_desc_get_handler_data(desc);
+       int idx;
+
+       for (idx = 0; idx < intc->nr_gicirq; idx++)
+               if (intc->parent_irqs[idx].virq == irqd->irq)
+                       return idx;
+
+       return -EINVAL;
+}
+
+static int meson_gpio_parent_irq_find_by_hwirq(struct irq_data *irqd,
+       int *data, int dlen)
+{
+       struct meson_gpio_intc *intc = irq_data_get_irq_chip_data(irqd);
+       int nr = 0;
+       int idx;
+
+       for (idx = 0; idx < intc->nr_gicirq; idx++) {
+               if (nr >= dlen)
+                       break;
+
+               if (intc->parent_irqs[idx].owner == irqd->hwirq)
+                       data[nr++] = idx;
+       }
+
+       return nr;
+}
+
+/*
+ * NOP functions
+ */
+static void noop(struct irq_data *irqd) { }
+
+static void meson_gpio_parent_irq_unmask(int virq)
+{
+       struct irq_data *parent_data;
+
+       parent_data = irq_get_irq_data(virq);
+
+       /*enable the interrupt line of gpio in GIC controller*/
+       parent_data->chip->irq_unmask(parent_data);
+}
+static void meson_gpio_parent_irq_mask(int virq)
+{
+       struct irq_data *parent_data;
+
+       parent_data = irq_get_irq_data(virq);
+
+       /*disable the interrupt line of gpio in GIC controller*/
+       parent_data->chip->irq_mask(parent_data);
+}
+
+static int meson_gpio_parent_irq_request(struct irq_data *irqd,
+       unsigned int type)
+{
+       struct meson_gpio_intc *intc = irq_data_get_irq_chip_data(irqd);
+       struct irq_data *parent_data;
+       unsigned int idx;
+
+       for (idx = 0; idx < intc->nr_gicirq; idx++) {
+               if (intc->parent_irqs[idx].owner == NO_IRQ) {
+                       intc->parent_irqs[idx].owner = irqd->hwirq;
+                       break;
+               }
+       }
+
+       if (idx == intc->nr_gicirq) {
+               pr_warn("%s: no more gpio irqs available\n", DRIVER_NAME);
+               return -EINVAL;
+       }
+
+       parent_data = irq_get_irq_data(intc->parent_irqs[idx].virq);
+
+       /*set trigger type of gpio in GIC controller*/
+       if (type & IRQ_TYPE_EDGE_BOTH)
+               parent_data->chip->irq_set_type(parent_data,
+                       IRQ_TYPE_EDGE_RISING);
+       else
+               parent_data->chip->irq_set_type(parent_data,
+                       IRQ_TYPE_LEVEL_HIGH);
+
+       pr_info("%s: gpio virq[%d] connect to GIC hwirq[%ld]\n", DRIVER_NAME,
+               irqd->irq, parent_data->hwirq);
+
+       return idx;
+}
+
+static int meson_gpio_parent_irq_release(struct irq_data *irqd)
+{
+       struct meson_gpio_intc *intc = irq_data_get_irq_chip_data(irqd);
+       int idx[MESON_GPIO_BIND_PARENT_IRQ_NUM_MAX];
+       int nr;
+
+       nr = meson_gpio_parent_irq_find_by_hwirq(irqd, idx, ARRAY_SIZE(idx));
+       while (nr--)
+               intc->parent_irqs[idx[nr]].owner = NO_IRQ;
+
+       return 0;
+}
+
+static void meson_gpio_irq_regs_config(struct irq_data *irqd,
+       int idx, unsigned int type)
+{
+       struct meson_gpio_intc *intc = irq_data_get_irq_chip_data(irqd);
+       unsigned int val = 0;
+       unsigned long flags;
+       int regoff;
+       int shift;
+
+       spin_lock_irqsave(&intc->lock, flags);
+
+       if (type & IRQ_TYPE_EDGE_BOTH)
+               val |= REG_EDGE_SET(idx);
+
+       if (type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_LEVEL_LOW))
+               val |= REG_POL_SET(idx);
+
+       meson_reg_update_bits(intc->base, REG_EDGE_POL,
+                       REG_EDGE_POL_MASK(idx), val);
+
+       /*set the  filter registers*/
+       shift = idx << 2;
+       meson_reg_update_bits(intc->base, REG_FILTER_SEL,
+                       0x7 << shift, 0x7 << shift);
+
+       /*set pin select register*/
+       shift = (idx << 3) % 32;
+       regoff = (idx < 4) ? REG_PIN_03_SEL : REG_PIN_47_SEL;
+       meson_reg_update_bits(intc->base, regoff,
+                       0xff << shift, irqd->hwirq << shift);
+
+       spin_unlock_irqrestore(&intc->lock, flags);
+}
+
+static void meson_gpio_irq_enable(struct irq_data *irqd)
+{
+       struct meson_gpio_intc *intc = irq_data_get_irq_chip_data(irqd);
+       int idx[MESON_GPIO_BIND_PARENT_IRQ_NUM_MAX];
+       unsigned long flags;
+       int nr;
+
+       spin_lock_irqsave(&intc->lock, flags);
+
+       nr = meson_gpio_parent_irq_find_by_hwirq(irqd,
+                       idx, ARRAY_SIZE(idx));
+
+       while (nr--)
+               meson_gpio_parent_irq_unmask(intc->parent_irqs[idx[nr]].virq);
+
+       spin_unlock_irqrestore(&intc->lock, flags);
+}
+
+static void meson_gpio_irq_disable(struct irq_data *irqd)
+{
+       struct meson_gpio_intc *intc = irq_data_get_irq_chip_data(irqd);
+       int idx[MESON_GPIO_BIND_PARENT_IRQ_NUM_MAX];
+       unsigned long flags;
+       int nr;
+
+       spin_lock_irqsave(&intc->lock, flags);
+
+       nr = meson_gpio_parent_irq_find_by_hwirq(irqd, idx, ARRAY_SIZE(idx));
+       while (nr--)
+               meson_gpio_parent_irq_mask(intc->parent_irqs[idx[nr]].virq);
+
+       spin_unlock_irqrestore(&intc->lock, flags);
+}
+
+/**
+ *free gpio irq when free_irq() is called, and another pin can use it again.
+ */
+static void meson_gpio_irq_shutdown(struct irq_data *irqd)
+{
+       struct meson_gpio_intc *intc = irq_data_get_irq_chip_data(irqd);
+       unsigned long flags;
+
+       spin_lock_irqsave(&intc->lock, flags);
+       meson_gpio_parent_irq_release(irqd);
+       spin_unlock_irqrestore(&intc->lock, flags);
+
+       meson_gpio_irq_disable(irqd);
+}
+
+static int meson_gpio_irq_type(struct irq_data *irqd, unsigned int type)
+{
+       struct meson_gpio_intc *intc = irq_data_get_irq_chip_data(irqd);
+       unsigned long flags;
+       int nr_parent_irq;
+       int idx;
+
+       type = type & IRQ_TYPE_SENSE_MASK;
+       nr_parent_irq = (type == IRQ_TYPE_EDGE_BOTH) ? 2 : 1;
+
+       while (nr_parent_irq--) {
+               if (type == IRQ_TYPE_EDGE_BOTH)
+                       type = IRQ_TYPE_EDGE_FALLING;
+
+               spin_lock_irqsave(&intc->lock, flags);
+               idx = meson_gpio_parent_irq_request(irqd, type);
+               spin_unlock_irqrestore(&intc->lock, flags);
+               if (idx < 0) {
+                       meson_gpio_irq_shutdown(irqd);
+                       return -EINVAL;
+               }
+
+               meson_gpio_irq_regs_config(irqd, idx, type);
+
+               if (nr_parent_irq > 0)
+                       type = IRQ_TYPE_EDGE_RISING;
+       }
+
+       return 0;
+}
+
+static struct irq_chip meson_gpio_intc_chip = {
+       .name           = "meson-gpio-irqchip",
+       .irq_enable     = meson_gpio_irq_enable,
+       .irq_disable    = meson_gpio_irq_disable,
+       .irq_set_type   = meson_gpio_irq_type,
+       .irq_mask       = noop,
+       .irq_unmask     = noop,
+       .irq_shutdown   = meson_gpio_irq_shutdown,
+};
+
+static int meson_gpio_intc_domain_map(struct irq_domain *d, unsigned int virq,
+                        irq_hw_number_t hwirq)
+{
+       struct meson_gpio_intc *intc = d->host_data;
+
+       irq_set_chip_and_handler(virq,
+               &meson_gpio_intc_chip, handle_simple_irq);
+       irq_set_chip_data(virq, intc);
+
+       return 0;
+}
+
+static const struct irq_domain_ops meson_gpio_intc_domain_ops = {
+       .map = meson_gpio_intc_domain_map,
+       .xlate = irq_domain_xlate_twocell,
+};
+
+static void meson_gpio_irq_handler(struct irq_desc *desc)
+{
+       struct irq_chip *chip = irq_desc_get_chip(desc);
+       struct irq_data *parent_data = irq_desc_get_irq_data(desc);
+       struct meson_gpio_intc *intc = irq_desc_get_handler_data(desc);
+       int idx;
+
+       chained_irq_enter(chip, desc);
+
+       idx = meson_gpio_irq_find_by_parent_virq(parent_data);
+       if (idx >= 0 && intc->parent_irqs[idx].owner != NO_IRQ)
+               generic_handle_irq(irq_find_mapping(intc->irqdomain,
+                       intc->parent_irqs[idx].owner));
+
+       chained_irq_exit(chip, desc);
+}
+
+static int __init meson_gpio_intc_init(struct device_node *node,
+       struct device_node *parent)
+{
+       struct meson_gpio_intc *intc;
+       const struct of_device_id *match;
+       struct irq_fwspec fwspec;
+       int parent_hwirq;
+       int parent_virq;
+       int ret;
+       int idx;
+
+       intc = kzalloc(sizeof(struct meson_gpio_intc), GFP_KERNEL);
+       if (!intc)
+               return -ENOMEM;
+
+       match = of_match_node(meson_gpio_irq_matches, node);
+       if (!match) {
+               ret = -ENODEV;
+               pr_err("%s: fail to match device node\n", DRIVER_NAME);
+               goto alloc_err1;
+       }
+       intc->data = match->data;
+
+       ret = of_property_count_elems_of_size(node,
+                               "amlogic,channel-interrupts", sizeof(u32));
+       if (ret <= 0) {
+               pr_err("%s: fail to get the number of elements\n", DRIVER_NAME);
+               ret = -EINVAL;
+               goto alloc_err1;
+       }
+       intc->nr_gicirq = ret;
+
+       intc->parent_irqs = kcalloc(intc->nr_gicirq,
+                       sizeof(struct gpio_parent_irq), GFP_KERNEL);
+       if (!intc->parent_irqs) {
+               ret = -ENOMEM;
+               goto alloc_err1;
+       }
+
+       spin_lock_init(&intc->lock);
+
+       intc->base = of_iomap(node, 0);
+       if (IS_ERR_OR_NULL(intc->base)) {
+               ret = -ENOMEM;
+               goto alloc_err2;
+       }
+
+       intc->irqdomain = irq_domain_add_linear(node, intc->data->nr_hwirq,
+                       &meson_gpio_intc_domain_ops, intc);
+       if (IS_ERR_OR_NULL(intc->irqdomain)) {
+               ret = -ENOMEM;
+               goto iomap_err;
+       }
+
+       for (idx = 0; idx < intc->nr_gicirq; idx++) {
+               ret = of_property_read_u32_index(node,
+                               "amlogic,channel-interrupts", idx,
+                               &parent_hwirq);
+               if (ret < 0) {
+                       pr_err("%s: fail to read property value\n",
+                                       DRIVER_NAME);
+                       goto iomap_err;
+               }
+
+               fwspec.fwnode = of_node_to_fwnode(parent);
+               fwspec.param_count = 3;
+               fwspec.param[0] = 0;
+               fwspec.param[1] = parent_hwirq;
+               fwspec.param[2] = IRQ_TYPE_EDGE_RISING;
+
+               parent_virq = irq_create_fwspec_mapping(&fwspec);
+
+               intc->parent_irqs[idx].virq = parent_virq;
+               intc->parent_irqs[idx].owner = NO_IRQ;
+               irq_set_handler_data(parent_virq, intc);
+               irq_set_chained_handler(parent_virq, meson_gpio_irq_handler);
+
+               /*disable the interrupt line of gpio in GIC controller*/
+               meson_gpio_parent_irq_mask(parent_virq);
+       }
+
+       pr_info("%s: support to detect double-edge trigger signal\n",
+                       DRIVER_NAME);
+
+       return 0;
+
+iomap_err:
+       iounmap(intc->base);
+
+alloc_err2:
+       kfree(intc->parent_irqs);
+
+alloc_err1:
+       kfree(intc);
+
+       return ret;
+}
+
+/*
+ * if you want to use the Meson GPIO IRQ which support the
+ * double-edge detection, please set the compatible property
+ * to "amlogic,meson-gpio-intc-ext" in dts
+ */
+IRQCHIP_DECLARE(meson_gpio, "amlogic,meson-gpio-intc-ext",
+               meson_gpio_intc_init);
index a44eae5..8347f15 100644 (file)
@@ -654,6 +654,9 @@ static int meson_pinctrl_parse_dt(struct meson_pinctrl *pc,
 
        pc->of_irq = of_find_compatible_node(NULL,
                                        NULL, "amlogic,meson-gpio-intc");
+       if (!pc->of_irq)
+               pc->of_irq = of_find_compatible_node(NULL,
+                                       NULL, "amlogic,meson-gpio-intc-ext");
 
        pc->reg_mux = meson_map_resource(pc, gpio_np, "mux");
        if (IS_ERR(pc->reg_mux)) {