]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/pinctrl/pinctrl-exynos.c
Merge tag 'pinctrl-for-v3.11-1' of git://git.kernel.org/pub/scm/linux/kernel/git...
[karo-tx-linux.git] / drivers / pinctrl / pinctrl-exynos.c
index 5f58cf0e96e2e1e55660677193bd0b142eecf771..a74b3cbd745163a8624930114eccc43f5dcdc90a 100644 (file)
@@ -50,37 +50,58 @@ static const struct of_device_id exynos_wkup_irq_ids[] = {
        { }
 };
 
-static void exynos_gpio_irq_unmask(struct irq_data *irqd)
+static void exynos_gpio_irq_mask(struct irq_data *irqd)
 {
        struct samsung_pin_bank *bank = irq_data_get_irq_chip_data(irqd);
        struct samsung_pinctrl_drv_data *d = bank->drvdata;
        unsigned long reg_mask = d->ctrl->geint_mask + bank->eint_offset;
        unsigned long mask;
+       unsigned long flags;
+
+       spin_lock_irqsave(&bank->slock, flags);
 
        mask = readl(d->virt_base + reg_mask);
-       mask &= ~(1 << irqd->hwirq);
+       mask |= 1 << irqd->hwirq;
        writel(mask, d->virt_base + reg_mask);
+
+       spin_unlock_irqrestore(&bank->slock, flags);
 }
 
-static void exynos_gpio_irq_mask(struct irq_data *irqd)
+static void exynos_gpio_irq_ack(struct irq_data *irqd)
 {
        struct samsung_pin_bank *bank = irq_data_get_irq_chip_data(irqd);
        struct samsung_pinctrl_drv_data *d = bank->drvdata;
-       unsigned long reg_mask = d->ctrl->geint_mask + bank->eint_offset;
-       unsigned long mask;
+       unsigned long reg_pend = d->ctrl->geint_pend + bank->eint_offset;
 
-       mask = readl(d->virt_base + reg_mask);
-       mask |= 1 << irqd->hwirq;
-       writel(mask, d->virt_base + reg_mask);
+       writel(1 << irqd->hwirq, d->virt_base + reg_pend);
 }
 
-static void exynos_gpio_irq_ack(struct irq_data *irqd)
+static void exynos_gpio_irq_unmask(struct irq_data *irqd)
 {
        struct samsung_pin_bank *bank = irq_data_get_irq_chip_data(irqd);
        struct samsung_pinctrl_drv_data *d = bank->drvdata;
-       unsigned long reg_pend = d->ctrl->geint_pend + bank->eint_offset;
+       unsigned long reg_mask = d->ctrl->geint_mask + bank->eint_offset;
+       unsigned long mask;
+       unsigned long flags;
 
-       writel(1 << irqd->hwirq, d->virt_base + reg_pend);
+       /*
+        * Ack level interrupts right before unmask
+        *
+        * If we don't do this we'll get a double-interrupt.  Level triggered
+        * interrupts must not fire an interrupt if the level is not
+        * _currently_ active, even if it was active while the interrupt was
+        * masked.
+        */
+       if (irqd_get_trigger_type(irqd) & IRQ_TYPE_LEVEL_MASK)
+               exynos_gpio_irq_ack(irqd);
+
+       spin_lock_irqsave(&bank->slock, flags);
+
+       mask = readl(d->virt_base + reg_mask);
+       mask &= ~(1 << irqd->hwirq);
+       writel(mask, d->virt_base + reg_mask);
+
+       spin_unlock_irqrestore(&bank->slock, flags);
 }
 
 static int exynos_gpio_irq_set_type(struct irq_data *irqd, unsigned int type)
@@ -258,37 +279,58 @@ err_domains:
        return ret;
 }
 
-static void exynos_wkup_irq_unmask(struct irq_data *irqd)
+static void exynos_wkup_irq_mask(struct irq_data *irqd)
 {
        struct samsung_pin_bank *b = irq_data_get_irq_chip_data(irqd);
        struct samsung_pinctrl_drv_data *d = b->drvdata;
        unsigned long reg_mask = d->ctrl->weint_mask + b->eint_offset;
        unsigned long mask;
+       unsigned long flags;
+
+       spin_lock_irqsave(&b->slock, flags);
 
        mask = readl(d->virt_base + reg_mask);
-       mask &= ~(1 << irqd->hwirq);
+       mask |= 1 << irqd->hwirq;
        writel(mask, d->virt_base + reg_mask);
+
+       spin_unlock_irqrestore(&b->slock, flags);
 }
 
-static void exynos_wkup_irq_mask(struct irq_data *irqd)
+static void exynos_wkup_irq_ack(struct irq_data *irqd)
 {
        struct samsung_pin_bank *b = irq_data_get_irq_chip_data(irqd);
        struct samsung_pinctrl_drv_data *d = b->drvdata;
-       unsigned long reg_mask = d->ctrl->weint_mask + b->eint_offset;
-       unsigned long mask;
+       unsigned long pend = d->ctrl->weint_pend + b->eint_offset;
 
-       mask = readl(d->virt_base + reg_mask);
-       mask |= 1 << irqd->hwirq;
-       writel(mask, d->virt_base + reg_mask);
+       writel(1 << irqd->hwirq, d->virt_base + pend);
 }
 
-static void exynos_wkup_irq_ack(struct irq_data *irqd)
+static void exynos_wkup_irq_unmask(struct irq_data *irqd)
 {
        struct samsung_pin_bank *b = irq_data_get_irq_chip_data(irqd);
        struct samsung_pinctrl_drv_data *d = b->drvdata;
-       unsigned long pend = d->ctrl->weint_pend + b->eint_offset;
+       unsigned long reg_mask = d->ctrl->weint_mask + b->eint_offset;
+       unsigned long mask;
+       unsigned long flags;
 
-       writel(1 << irqd->hwirq, d->virt_base + pend);
+       /*
+        * Ack level interrupts right before unmask
+        *
+        * If we don't do this we'll get a double-interrupt.  Level triggered
+        * interrupts must not fire an interrupt if the level is not
+        * _currently_ active, even if it was active while the interrupt was
+        * masked.
+        */
+       if (irqd_get_trigger_type(irqd) & IRQ_TYPE_LEVEL_MASK)
+               exynos_wkup_irq_ack(irqd);
+
+       spin_lock_irqsave(&b->slock, flags);
+
+       mask = readl(d->virt_base + reg_mask);
+       mask &= ~(1 << irqd->hwirq);
+       writel(mask, d->virt_base + reg_mask);
+
+       spin_unlock_irqrestore(&b->slock, flags);
 }
 
 static int exynos_wkup_irq_set_type(struct irq_data *irqd, unsigned int type)