mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-07 19:30:30 +09:00
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 <xingyu.chen@amlogic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
489
drivers/amlogic/irqchip/irq-meson-gpio-double-edge.c
Normal file
489
drivers/amlogic/irqchip/irq-meson-gpio-double-edge.c
Normal file
@@ -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);
|
||||
@@ -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)) {
|
||||
|
||||
Reference in New Issue
Block a user