From dd4a0b513ebc171cd8e62da9970e761423e72e98 Mon Sep 17 00:00:00 2001 From: Xingyu Chen Date: Thu, 18 Oct 2018 15:09:52 +0800 Subject: [PATCH] irqchip: add a new gpio IRQ driver to support double-edge detection [1/1] 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 --- MAINTAINERS | 1 + drivers/amlogic/irqchip/Makefile | 3 +- .../amlogic/irqchip/irq-meson-gpio-double-edge.c | 489 +++++++++++++++++++++ drivers/amlogic/pinctrl/pinctrl-meson.c | 3 + 4 files changed, 495 insertions(+), 1 deletion(-) create mode 100644 drivers/amlogic/irqchip/irq-meson-gpio-double-edge.c diff --git a/MAINTAINERS b/MAINTAINERS index 6c5077b..168cddd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14281,6 +14281,7 @@ F: sound/soc/amlogic/meson/dmic.h AMLOGIC GPIO IRQ M: Xingyu Chen 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 diff --git a/drivers/amlogic/irqchip/Makefile b/drivers/amlogic/irqchip/Makefile index d11b37a..ef4b22e 100644 --- a/drivers/amlogic/irqchip/Makefile +++ b/drivers/amlogic/irqchip/Makefile @@ -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 index 0000000..41cf4ee --- /dev/null +++ b/drivers/amlogic/irqchip/irq-meson-gpio-double-edge.c @@ -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 +#include +#include +#include +#include +#include + +#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); diff --git a/drivers/amlogic/pinctrl/pinctrl-meson.c b/drivers/amlogic/pinctrl/pinctrl-meson.c index a44eae5..8347f15 100644 --- a/drivers/amlogic/pinctrl/pinctrl-meson.c +++ b/drivers/amlogic/pinctrl/pinctrl-meson.c @@ -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)) { -- 2.7.4