pinctrl: exynos5440: add gpio interrupt support
Exynos5440 supports gpio interrupts on gpios 16 to 23. The eight interrupt lines originating from the pin-controller are connected to the gic. Add irq-chip support for these interrupts. Signed-off-by: Thomas Abraham <thomas.ab@samsung.com> Cc: Linus Walleij <linus.walleij@linaro.org> Signed-off-by: Kukjin Kim <kgene.kim@samsung.com> Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
This commit is contained in:
parent
f981973942
commit
8dc3568d55
1 changed files with 142 additions and 0 deletions
|
@ -20,6 +20,9 @@
|
|||
#include <linux/pinctrl/pinctrl.h>
|
||||
#include <linux/pinctrl/pinmux.h>
|
||||
#include <linux/pinctrl/pinconf.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include "core.h"
|
||||
|
||||
/* EXYNOS5440 GPIO and Pinctrl register offsets */
|
||||
|
@ -37,6 +40,7 @@
|
|||
#define GPIO_DS1 0x2C
|
||||
|
||||
#define EXYNOS5440_MAX_PINS 23
|
||||
#define EXYNOS5440_MAX_GPIO_INT 8
|
||||
#define PIN_NAME_LENGTH 10
|
||||
|
||||
#define GROUP_SUFFIX "-grp"
|
||||
|
@ -109,6 +113,7 @@ struct exynos5440_pmx_func {
|
|||
struct exynos5440_pinctrl_priv_data {
|
||||
void __iomem *reg_base;
|
||||
struct gpio_chip *gc;
|
||||
struct irq_domain *irq_domain;
|
||||
|
||||
const struct exynos5440_pin_group *pin_groups;
|
||||
unsigned int nr_groups;
|
||||
|
@ -116,6 +121,16 @@ struct exynos5440_pinctrl_priv_data {
|
|||
unsigned int nr_functions;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct exynos5440_gpio_intr_data: private data for gpio interrupts.
|
||||
* @priv: driver's private runtime data.
|
||||
* @gpio_int: gpio interrupt number.
|
||||
*/
|
||||
struct exynos5440_gpio_intr_data {
|
||||
struct exynos5440_pinctrl_priv_data *priv;
|
||||
unsigned int gpio_int;
|
||||
};
|
||||
|
||||
/* list of all possible config options supported */
|
||||
static struct pin_config {
|
||||
char *prop_cfg;
|
||||
|
@ -598,6 +613,22 @@ static int exynos5440_gpio_direction_output(struct gpio_chip *gc, unsigned offse
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* gpiolib gpio_to_irq callback function */
|
||||
static int exynos5440_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
|
||||
{
|
||||
struct exynos5440_pinctrl_priv_data *priv = dev_get_drvdata(gc->dev);
|
||||
unsigned int virq;
|
||||
|
||||
if (offset < 16 || offset > 23)
|
||||
return -ENXIO;
|
||||
|
||||
if (!priv->irq_domain)
|
||||
return -ENXIO;
|
||||
|
||||
virq = irq_create_mapping(priv->irq_domain, offset - 16);
|
||||
return virq ? : -ENXIO;
|
||||
}
|
||||
|
||||
/* parse the pin numbers listed in the 'samsung,exynos5440-pins' property */
|
||||
static int exynos5440_pinctrl_parse_dt_pins(struct platform_device *pdev,
|
||||
struct device_node *cfg_np, unsigned int **pin_list,
|
||||
|
@ -821,6 +852,7 @@ static int exynos5440_gpiolib_register(struct platform_device *pdev,
|
|||
gc->get = exynos5440_gpio_get;
|
||||
gc->direction_input = exynos5440_gpio_direction_input;
|
||||
gc->direction_output = exynos5440_gpio_direction_output;
|
||||
gc->to_irq = exynos5440_gpio_to_irq;
|
||||
gc->label = "gpiolib-exynos5440";
|
||||
gc->owner = THIS_MODULE;
|
||||
ret = gpiochip_add(gc);
|
||||
|
@ -845,6 +877,110 @@ static int exynos5440_gpiolib_unregister(struct platform_device *pdev,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void exynos5440_gpio_irq_unmask(struct irq_data *irqd)
|
||||
{
|
||||
struct exynos5440_pinctrl_priv_data *d;
|
||||
unsigned long gpio_int;
|
||||
|
||||
d = irq_data_get_irq_chip_data(irqd);
|
||||
gpio_int = readl(d->reg_base + GPIO_INT);
|
||||
gpio_int |= 1 << irqd->hwirq;
|
||||
writel(gpio_int, d->reg_base + GPIO_INT);
|
||||
}
|
||||
|
||||
static void exynos5440_gpio_irq_mask(struct irq_data *irqd)
|
||||
{
|
||||
struct exynos5440_pinctrl_priv_data *d;
|
||||
unsigned long gpio_int;
|
||||
|
||||
d = irq_data_get_irq_chip_data(irqd);
|
||||
gpio_int = readl(d->reg_base + GPIO_INT);
|
||||
gpio_int &= ~(1 << irqd->hwirq);
|
||||
writel(gpio_int, d->reg_base + GPIO_INT);
|
||||
}
|
||||
|
||||
/* irq_chip for gpio interrupts */
|
||||
static struct irq_chip exynos5440_gpio_irq_chip = {
|
||||
.name = "exynos5440_gpio_irq_chip",
|
||||
.irq_unmask = exynos5440_gpio_irq_unmask,
|
||||
.irq_mask = exynos5440_gpio_irq_mask,
|
||||
};
|
||||
|
||||
/* interrupt handler for GPIO interrupts 0..7 */
|
||||
static irqreturn_t exynos5440_gpio_irq(int irq, void *data)
|
||||
{
|
||||
struct exynos5440_gpio_intr_data *intd = data;
|
||||
struct exynos5440_pinctrl_priv_data *d = intd->priv;
|
||||
int virq;
|
||||
|
||||
virq = irq_linear_revmap(d->irq_domain, intd->gpio_int);
|
||||
if (!virq)
|
||||
return IRQ_NONE;
|
||||
generic_handle_irq(virq);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int exynos5440_gpio_irq_map(struct irq_domain *h, unsigned int virq,
|
||||
irq_hw_number_t hw)
|
||||
{
|
||||
struct exynos5440_pinctrl_priv_data *d = h->host_data;
|
||||
|
||||
irq_set_chip_data(virq, d);
|
||||
irq_set_chip_and_handler(virq, &exynos5440_gpio_irq_chip,
|
||||
handle_level_irq);
|
||||
set_irq_flags(virq, IRQF_VALID);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* irq domain callbacks for gpio interrupt controller */
|
||||
static const struct irq_domain_ops exynos5440_gpio_irqd_ops = {
|
||||
.map = exynos5440_gpio_irq_map,
|
||||
.xlate = irq_domain_xlate_twocell,
|
||||
};
|
||||
|
||||
/* setup handling of gpio interrupts */
|
||||
static int exynos5440_gpio_irq_init(struct platform_device *pdev,
|
||||
struct exynos5440_pinctrl_priv_data *priv)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct exynos5440_gpio_intr_data *intd;
|
||||
int i, irq, ret;
|
||||
|
||||
intd = devm_kzalloc(dev, sizeof(*intd) * EXYNOS5440_MAX_GPIO_INT,
|
||||
GFP_KERNEL);
|
||||
if (!intd) {
|
||||
dev_err(dev, "failed to allocate memory for gpio intr data\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
for (i = 0; i < EXYNOS5440_MAX_GPIO_INT; i++) {
|
||||
irq = irq_of_parse_and_map(dev->of_node, i);
|
||||
if (irq <= 0) {
|
||||
dev_err(dev, "irq parsing failed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
intd->gpio_int = i;
|
||||
intd->priv = priv;
|
||||
ret = devm_request_irq(dev, irq, exynos5440_gpio_irq,
|
||||
0, dev_name(dev), intd++);
|
||||
if (ret) {
|
||||
dev_err(dev, "irq request failed\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
}
|
||||
|
||||
priv->irq_domain = irq_domain_add_linear(dev->of_node,
|
||||
EXYNOS5440_MAX_GPIO_INT,
|
||||
&exynos5440_gpio_irqd_ops, priv);
|
||||
if (!priv->irq_domain) {
|
||||
dev_err(dev, "failed to create irq domain\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos5440_pinctrl_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
|
@ -883,6 +1019,12 @@ static int exynos5440_pinctrl_probe(struct platform_device *pdev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
ret = exynos5440_gpio_irq_init(pdev, priv);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to setup gpio interrupts\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
dev_info(dev, "EXYNOS5440 pinctrl driver registered\n");
|
||||
return 0;
|
||||
|
|
Loading…
Reference in a new issue