pinctrl: qcom: Handle broken/missing PDC dual edge IRQs on sc7180
[linux-2.6-microblaze.git] / drivers / pinctrl / qcom / pinctrl-msm.c
index 83b7d64..c322f30 100644 (file)
@@ -832,6 +832,52 @@ static void msm_gpio_irq_unmask(struct irq_data *d)
        msm_gpio_irq_clear_unmask(d, false);
 }
 
+/**
+ * msm_gpio_update_dual_edge_parent() - Prime next edge for IRQs handled by parent.
+ * @d: The irq dta.
+ *
+ * This is much like msm_gpio_update_dual_edge_pos() but for IRQs that are
+ * normally handled by the parent irqchip.  The logic here is slightly
+ * different due to what's easy to do with our parent, but in principle it's
+ * the same.
+ */
+static void msm_gpio_update_dual_edge_parent(struct irq_data *d)
+{
+       struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+       struct msm_pinctrl *pctrl = gpiochip_get_data(gc);
+       const struct msm_pingroup *g = &pctrl->soc->groups[d->hwirq];
+       int loop_limit = 100;
+       unsigned int val;
+       unsigned int type;
+
+       /* Read the value and make a guess about what edge we need to catch */
+       val = msm_readl_io(pctrl, g) & BIT(g->in_bit);
+       type = val ? IRQ_TYPE_EDGE_FALLING : IRQ_TYPE_EDGE_RISING;
+
+       do {
+               /* Set the parent to catch the next edge */
+               irq_chip_set_type_parent(d, type);
+
+               /*
+                * Possibly the line changed between when we last read "val"
+                * (and decided what edge we needed) and when set the edge.
+                * If the value didn't change (or changed and then changed
+                * back) then we're done.
+                */
+               val = msm_readl_io(pctrl, g) & BIT(g->in_bit);
+               if (type == IRQ_TYPE_EDGE_RISING) {
+                       if (!val)
+                               return;
+                       type = IRQ_TYPE_EDGE_FALLING;
+               } else if (type == IRQ_TYPE_EDGE_FALLING) {
+                       if (val)
+                               return;
+                       type = IRQ_TYPE_EDGE_RISING;
+               }
+       } while (loop_limit-- > 0);
+       dev_warn_once(pctrl->dev, "dual-edge irq failed to stabilize\n");
+}
+
 static void msm_gpio_irq_ack(struct irq_data *d)
 {
        struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
@@ -840,8 +886,11 @@ static void msm_gpio_irq_ack(struct irq_data *d)
        unsigned long flags;
        u32 val;
 
-       if (test_bit(d->hwirq, pctrl->skip_wake_irqs))
+       if (test_bit(d->hwirq, pctrl->skip_wake_irqs)) {
+               if (test_bit(d->hwirq, pctrl->dual_edge_irqs))
+                       msm_gpio_update_dual_edge_parent(d);
                return;
+       }
 
        g = &pctrl->soc->groups[d->hwirq];
 
@@ -860,6 +909,17 @@ static void msm_gpio_irq_ack(struct irq_data *d)
        raw_spin_unlock_irqrestore(&pctrl->lock, flags);
 }
 
+static bool msm_gpio_needs_dual_edge_parent_workaround(struct irq_data *d,
+                                                      unsigned int type)
+{
+       struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+       struct msm_pinctrl *pctrl = gpiochip_get_data(gc);
+
+       return type == IRQ_TYPE_EDGE_BOTH &&
+              pctrl->soc->wakeirq_dual_edge_errata && d->parent_data &&
+              test_bit(d->hwirq, pctrl->skip_wake_irqs);
+}
+
 static int msm_gpio_irq_set_type(struct irq_data *d, unsigned int type)
 {
        struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
@@ -868,11 +928,21 @@ static int msm_gpio_irq_set_type(struct irq_data *d, unsigned int type)
        unsigned long flags;
        u32 val;
 
+       if (msm_gpio_needs_dual_edge_parent_workaround(d, type)) {
+               set_bit(d->hwirq, pctrl->dual_edge_irqs);
+               irq_set_handler_locked(d, handle_fasteoi_ack_irq);
+               msm_gpio_update_dual_edge_parent(d);
+               return 0;
+       }
+
        if (d->parent_data)
                irq_chip_set_type_parent(d, type);
 
-       if (test_bit(d->hwirq, pctrl->skip_wake_irqs))
+       if (test_bit(d->hwirq, pctrl->skip_wake_irqs)) {
+               clear_bit(d->hwirq, pctrl->dual_edge_irqs);
+               irq_set_handler_locked(d, handle_fasteoi_irq);
                return 0;
+       }
 
        g = &pctrl->soc->groups[d->hwirq];