blob: 41cf4eebd58207416bacb0a6c33cc2d29c9a95ae [file] [log] [blame]
/*
* 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);