diff options
Diffstat (limited to 'drivers/pinctrl/pinctrl-exynos5440.c')
-rw-r--r-- | drivers/pinctrl/pinctrl-exynos5440.c | 159 |
1 files changed, 152 insertions, 7 deletions
diff --git a/drivers/pinctrl/pinctrl-exynos5440.c b/drivers/pinctrl/pinctrl-exynos5440.c index 1376eb7305db..6038503ed929 100644 --- a/drivers/pinctrl/pinctrl-exynos5440.c +++ b/drivers/pinctrl/pinctrl-exynos5440.c @@ -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; @@ -286,7 +301,7 @@ static void exynos5440_dt_free_map(struct pinctrl_dev *pctldev, } /* list of pinctrl callbacks for the pinctrl core */ -static struct pinctrl_ops exynos5440_pctrl_ops = { +static const struct pinctrl_ops exynos5440_pctrl_ops = { .get_groups_count = exynos5440_get_group_count, .get_group_name = exynos5440_get_group_name, .get_group_pins = exynos5440_get_group_pins, @@ -374,7 +389,7 @@ static int exynos5440_pinmux_gpio_set_direction(struct pinctrl_dev *pctldev, } /* list of pinmux callbacks for the pinmux vertical in pinctrl core */ -static struct pinmux_ops exynos5440_pinmux_ops = { +static const struct pinmux_ops exynos5440_pinmux_ops = { .get_functions_count = exynos5440_get_functions_count, .get_function_name = exynos5440_pinmux_get_fname, .get_function_groups = exynos5440_pinmux_get_groups, @@ -523,7 +538,7 @@ static int exynos5440_pinconf_group_get(struct pinctrl_dev *pctldev, } /* list of pinconfig callbacks for pinconfig vertical in the pinctrl code */ -static struct pinconf_ops exynos5440_pinconf_ops = { +static const struct pinconf_ops exynos5440_pinconf_ops = { .pin_config_get = exynos5440_pinconf_get, .pin_config_set = exynos5440_pinconf_set, .pin_config_group_get = exynos5440_pinconf_group_get, @@ -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, @@ -670,8 +701,10 @@ static int exynos5440_pinctrl_parse_dt(struct platform_device *pdev, ret = exynos5440_pinctrl_parse_dt_pins(pdev, cfg_np, &pin_list, &npins); - if (ret) - return ret; + if (ret) { + gname = NULL; + goto skip_to_pin_function; + } /* derive pin group name from the node name */ gname = devm_kzalloc(dev, strlen(cfg_np->name) + GSUFFIX_LEN, @@ -687,6 +720,7 @@ static int exynos5440_pinctrl_parse_dt(struct platform_device *pdev, grp->num_pins = npins; grp++; +skip_to_pin_function: ret = of_property_read_u32(cfg_np, "samsung,exynos5440-pin-function", &function); if (ret) @@ -709,7 +743,7 @@ static int exynos5440_pinctrl_parse_dt(struct platform_device *pdev, return -ENOMEM; } func->groups[0] = gname; - func->num_groups = 1; + func->num_groups = gname ? 1 : 0; func->function = function; func++; func_idx++; @@ -818,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); @@ -842,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; @@ -854,7 +993,7 @@ static int exynos5440_pinctrl_probe(struct platform_device *pdev) return -ENODEV; } - priv = devm_kzalloc(dev, sizeof(priv), GFP_KERNEL); + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) { dev_err(dev, "could not allocate memory for private data\n"); return -ENOMEM; @@ -880,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; |