Merge tag 'pinctrl-v4.19-1' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw...
[muen/linux.git] / drivers / pinctrl / actions / pinctrl-owl.c
index b5c880b50bb371f5fb5eeddcd0799cd9d7057289..9d18c02f192b3ef8cc5e5ea4b109458ce28744de 100644 (file)
@@ -13,6 +13,7 @@
 #include <linux/err.h>
 #include <linux/gpio/driver.h>
 #include <linux/io.h>
+#include <linux/irq.h>
 #include <linux/module.h>
 #include <linux/of.h>
 #include <linux/platform_device.h>
@@ -45,6 +46,9 @@ struct owl_pinctrl {
        struct clk *clk;
        const struct owl_pinctrl_soc_data *soc;
        void __iomem *base;
+       struct irq_chip irq_chip;
+       unsigned int num_irq;
+       unsigned int *irq;
 };
 
 static void owl_update_bits(void __iomem *base, u32 mask, u32 val)
@@ -701,10 +705,213 @@ static int owl_gpio_direction_output(struct gpio_chip *chip,
        return 0;
 }
 
+static void irq_set_type(struct owl_pinctrl *pctrl, int gpio, unsigned int type)
+{
+       const struct owl_gpio_port *port;
+       void __iomem *gpio_base;
+       unsigned long flags;
+       unsigned int offset, value, irq_type = 0;
+
+       switch (type) {
+       case IRQ_TYPE_EDGE_BOTH:
+               /*
+                * Since the hardware doesn't support interrupts on both edges,
+                * emulate it in the software by setting the single edge
+                * interrupt and switching to the opposite edge while ACKing
+                * the interrupt
+                */
+               if (owl_gpio_get(&pctrl->chip, gpio))
+                       irq_type = OWL_GPIO_INT_EDGE_FALLING;
+               else
+                       irq_type = OWL_GPIO_INT_EDGE_RISING;
+               break;
+
+       case IRQ_TYPE_EDGE_RISING:
+               irq_type = OWL_GPIO_INT_EDGE_RISING;
+               break;
+
+       case IRQ_TYPE_EDGE_FALLING:
+               irq_type = OWL_GPIO_INT_EDGE_FALLING;
+               break;
+
+       case IRQ_TYPE_LEVEL_HIGH:
+               irq_type = OWL_GPIO_INT_LEVEL_HIGH;
+               break;
+
+       case IRQ_TYPE_LEVEL_LOW:
+               irq_type = OWL_GPIO_INT_LEVEL_LOW;
+               break;
+
+       default:
+               break;
+       }
+
+       port = owl_gpio_get_port(pctrl, &gpio);
+       if (WARN_ON(port == NULL))
+               return;
+
+       gpio_base = pctrl->base + port->offset;
+
+       raw_spin_lock_irqsave(&pctrl->lock, flags);
+
+       offset = (gpio < 16) ? 4 : 0;
+       value = readl_relaxed(gpio_base + port->intc_type + offset);
+       value &= ~(OWL_GPIO_INT_MASK << ((gpio % 16) * 2));
+       value |= irq_type << ((gpio % 16) * 2);
+       writel_relaxed(value, gpio_base + port->intc_type + offset);
+
+       raw_spin_unlock_irqrestore(&pctrl->lock, flags);
+}
+
+static void owl_gpio_irq_mask(struct irq_data *data)
+{
+       struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+       struct owl_pinctrl *pctrl = gpiochip_get_data(gc);
+       const struct owl_gpio_port *port;
+       void __iomem *gpio_base;
+       unsigned long flags;
+       unsigned int gpio = data->hwirq;
+       u32 val;
+
+       port = owl_gpio_get_port(pctrl, &gpio);
+       if (WARN_ON(port == NULL))
+               return;
+
+       gpio_base = pctrl->base + port->offset;
+
+       raw_spin_lock_irqsave(&pctrl->lock, flags);
+
+       owl_gpio_update_reg(gpio_base + port->intc_msk, gpio, false);
+
+       /* disable port interrupt if no interrupt pending bit is active */
+       val = readl_relaxed(gpio_base + port->intc_msk);
+       if (val == 0)
+               owl_gpio_update_reg(gpio_base + port->intc_ctl,
+                                       OWL_GPIO_CTLR_ENABLE, false);
+
+       raw_spin_unlock_irqrestore(&pctrl->lock, flags);
+}
+
+static void owl_gpio_irq_unmask(struct irq_data *data)
+{
+       struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+       struct owl_pinctrl *pctrl = gpiochip_get_data(gc);
+       const struct owl_gpio_port *port;
+       void __iomem *gpio_base;
+       unsigned long flags;
+       unsigned int gpio = data->hwirq;
+       u32 value;
+
+       port = owl_gpio_get_port(pctrl, &gpio);
+       if (WARN_ON(port == NULL))
+               return;
+
+       gpio_base = pctrl->base + port->offset;
+       raw_spin_lock_irqsave(&pctrl->lock, flags);
+
+       /* enable port interrupt */
+       value = readl_relaxed(gpio_base + port->intc_ctl);
+       value |= BIT(OWL_GPIO_CTLR_ENABLE) | BIT(OWL_GPIO_CTLR_SAMPLE_CLK_24M);
+       writel_relaxed(value, gpio_base + port->intc_ctl);
+
+       /* enable GPIO interrupt */
+       owl_gpio_update_reg(gpio_base + port->intc_msk, gpio, true);
+
+       raw_spin_unlock_irqrestore(&pctrl->lock, flags);
+}
+
+static void owl_gpio_irq_ack(struct irq_data *data)
+{
+       struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+       struct owl_pinctrl *pctrl = gpiochip_get_data(gc);
+       const struct owl_gpio_port *port;
+       void __iomem *gpio_base;
+       unsigned long flags;
+       unsigned int gpio = data->hwirq;
+
+       /*
+        * Switch the interrupt edge to the opposite edge of the interrupt
+        * which got triggered for the case of emulating both edges
+        */
+       if (irqd_get_trigger_type(data) == IRQ_TYPE_EDGE_BOTH) {
+               if (owl_gpio_get(gc, gpio))
+                       irq_set_type(pctrl, gpio, IRQ_TYPE_EDGE_FALLING);
+               else
+                       irq_set_type(pctrl, gpio, IRQ_TYPE_EDGE_RISING);
+       }
+
+       port = owl_gpio_get_port(pctrl, &gpio);
+       if (WARN_ON(port == NULL))
+               return;
+
+       gpio_base = pctrl->base + port->offset;
+
+       raw_spin_lock_irqsave(&pctrl->lock, flags);
+
+       owl_gpio_update_reg(gpio_base + port->intc_ctl,
+                               OWL_GPIO_CTLR_PENDING, true);
+
+       raw_spin_unlock_irqrestore(&pctrl->lock, flags);
+}
+
+static int owl_gpio_irq_set_type(struct irq_data *data, unsigned int type)
+{
+       struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+       struct owl_pinctrl *pctrl = gpiochip_get_data(gc);
+
+       if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH))
+               irq_set_handler_locked(data, handle_level_irq);
+       else
+               irq_set_handler_locked(data, handle_edge_irq);
+
+       irq_set_type(pctrl, data->hwirq, type);
+
+       return 0;
+}
+
+static void owl_gpio_irq_handler(struct irq_desc *desc)
+{
+       struct owl_pinctrl *pctrl = irq_desc_get_handler_data(desc);
+       struct irq_chip *chip = irq_desc_get_chip(desc);
+       struct irq_domain *domain = pctrl->chip.irq.domain;
+       unsigned int parent = irq_desc_get_irq(desc);
+       const struct owl_gpio_port *port;
+       void __iomem *base;
+       unsigned int pin, irq, offset = 0, i;
+       unsigned long pending_irq;
+
+       chained_irq_enter(chip, desc);
+
+       for (i = 0; i < pctrl->soc->nports; i++) {
+               port = &pctrl->soc->ports[i];
+               base = pctrl->base + port->offset;
+
+               /* skip ports that are not associated with this irq */
+               if (parent != pctrl->irq[i])
+                       goto skip;
+
+               pending_irq = readl_relaxed(base + port->intc_pd);
+
+               for_each_set_bit(pin, &pending_irq, port->pins) {
+                       irq = irq_find_mapping(domain, offset + pin);
+                       generic_handle_irq(irq);
+
+                       /* clear pending interrupt */
+                       owl_gpio_update_reg(base + port->intc_pd, pin, true);
+               }
+
+skip:
+               offset += port->pins;
+       }
+
+       chained_irq_exit(chip, desc);
+}
+
 static int owl_gpio_init(struct owl_pinctrl *pctrl)
 {
        struct gpio_chip *chip;
-       int ret;
+       struct gpio_irq_chip *gpio_irq;
+       int ret, i, j, offset;
 
        chip = &pctrl->chip;
        chip->base = -1;
@@ -714,6 +921,35 @@ static int owl_gpio_init(struct owl_pinctrl *pctrl)
        chip->owner = THIS_MODULE;
        chip->of_node = pctrl->dev->of_node;
 
+       pctrl->irq_chip.name = chip->of_node->name;
+       pctrl->irq_chip.irq_ack = owl_gpio_irq_ack;
+       pctrl->irq_chip.irq_mask = owl_gpio_irq_mask;
+       pctrl->irq_chip.irq_unmask = owl_gpio_irq_unmask;
+       pctrl->irq_chip.irq_set_type = owl_gpio_irq_set_type;
+
+       gpio_irq = &chip->irq;
+       gpio_irq->chip = &pctrl->irq_chip;
+       gpio_irq->handler = handle_simple_irq;
+       gpio_irq->default_type = IRQ_TYPE_NONE;
+       gpio_irq->parent_handler = owl_gpio_irq_handler;
+       gpio_irq->parent_handler_data = pctrl;
+       gpio_irq->num_parents = pctrl->num_irq;
+       gpio_irq->parents = pctrl->irq;
+
+       gpio_irq->map = devm_kcalloc(pctrl->dev, chip->ngpio,
+                               sizeof(*gpio_irq->map), GFP_KERNEL);
+       if (!gpio_irq->map)
+               return -ENOMEM;
+
+       for (i = 0, offset = 0; i < pctrl->soc->nports; i++) {
+               const struct owl_gpio_port *port = &pctrl->soc->ports[i];
+
+               for (j = 0; j < port->pins; j++)
+                       gpio_irq->map[offset + j] = gpio_irq->parents[i];
+
+               offset += port->pins;
+       }
+
        ret = gpiochip_add_data(&pctrl->chip, pctrl);
        if (ret) {
                dev_err(pctrl->dev, "failed to register gpiochip\n");
@@ -728,7 +964,7 @@ int owl_pinctrl_probe(struct platform_device *pdev,
 {
        struct resource *res;
        struct owl_pinctrl *pctrl;
-       int ret;
+       int ret, i;
 
        pctrl = devm_kzalloc(&pdev->dev, sizeof(*pctrl), GFP_KERNEL);
        if (!pctrl)
@@ -772,14 +1008,40 @@ int owl_pinctrl_probe(struct platform_device *pdev,
                                        &owl_pinctrl_desc, pctrl);
        if (IS_ERR(pctrl->pctrldev)) {
                dev_err(&pdev->dev, "could not register Actions OWL pinmux driver\n");
-               return PTR_ERR(pctrl->pctrldev);
+               ret = PTR_ERR(pctrl->pctrldev);
+               goto err_exit;
+       }
+
+       ret = platform_irq_count(pdev);
+       if (ret < 0)
+               goto err_exit;
+
+       pctrl->num_irq = ret;
+
+       pctrl->irq = devm_kcalloc(&pdev->dev, pctrl->num_irq,
+                                       sizeof(*pctrl->irq), GFP_KERNEL);
+       if (!pctrl->irq) {
+               ret = -ENOMEM;
+               goto err_exit;
+       }
+
+       for (i = 0; i < pctrl->num_irq ; i++) {
+               ret = platform_get_irq(pdev, i);
+               if (ret < 0)
+                       goto err_exit;
+               pctrl->irq[i] = ret;
        }
 
        ret = owl_gpio_init(pctrl);
        if (ret)
-               return ret;
+               goto err_exit;
 
        platform_set_drvdata(pdev, pctrl);
 
        return 0;
+
+err_exit:
+       clk_disable_unprepare(pctrl->clk);
+
+       return ret;
 }