]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - drivers/irqchip/irq-stm32-exti.c
irqchip/stm32: protect configuration registers with hwspinlock
[linux.git] / drivers / irqchip / irq-stm32-exti.c
index 363385750fa741258f3036c00d7fffc5bf4b72f0..6edfd4bfa169ecbf488c897154d5e7000a9c5581 100644 (file)
@@ -6,6 +6,8 @@
  */
 
 #include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/hwspinlock.h>
 #include <linux/interrupt.h>
 #include <linux/io.h>
 #include <linux/irq.h>
@@ -20,6 +22,9 @@
 
 #define IRQS_PER_BANK 32
 
+#define HWSPNLCK_TIMEOUT       1000 /* usec */
+#define HWSPNLCK_RETRY_DELAY   100  /* usec */
+
 struct stm32_exti_bank {
        u32 imr_ofst;
        u32 emr_ofst;
@@ -32,6 +37,12 @@ struct stm32_exti_bank {
 
 #define UNDEF_REG ~0
 
+enum stm32_exti_hwspinlock {
+       HWSPINLOCK_UNKNOWN,
+       HWSPINLOCK_NONE,
+       HWSPINLOCK_READY,
+};
+
 struct stm32_desc_irq {
        u32 exti;
        u32 irq_parent;
@@ -58,6 +69,9 @@ struct stm32_exti_host_data {
        void __iomem *base;
        struct stm32_exti_chip_data *chips_data;
        const struct stm32_exti_drv_data *drv_data;
+       struct device_node *node;
+       enum stm32_exti_hwspinlock hwlock_state;
+       struct hwspinlock *hwlock;
 };
 
 static struct stm32_exti_host_data *stm32_host_data;
@@ -269,6 +283,64 @@ static int stm32_exti_set_type(struct irq_data *d,
        return 0;
 }
 
+static int stm32_exti_hwspin_lock(struct stm32_exti_chip_data *chip_data)
+{
+       struct stm32_exti_host_data *host_data = chip_data->host_data;
+       struct hwspinlock *hwlock;
+       int id, ret = 0, timeout = 0;
+
+       /* first time, check for hwspinlock availability */
+       if (unlikely(host_data->hwlock_state == HWSPINLOCK_UNKNOWN)) {
+               id = of_hwspin_lock_get_id(host_data->node, 0);
+               if (id >= 0) {
+                       hwlock = hwspin_lock_request_specific(id);
+                       if (hwlock) {
+                               /* found valid hwspinlock */
+                               host_data->hwlock_state = HWSPINLOCK_READY;
+                               host_data->hwlock = hwlock;
+                               pr_debug("%s hwspinlock = %d\n", __func__, id);
+                       } else {
+                               host_data->hwlock_state = HWSPINLOCK_NONE;
+                       }
+               } else if (id != -EPROBE_DEFER) {
+                       host_data->hwlock_state = HWSPINLOCK_NONE;
+               } else {
+                       /* hwspinlock driver shall be ready at that stage */
+                       ret = -EPROBE_DEFER;
+               }
+       }
+
+       if (likely(host_data->hwlock_state == HWSPINLOCK_READY)) {
+               /*
+                * Use the x_raw API since we are under spin_lock protection.
+                * Do not use the x_timeout API because we are under irq_disable
+                * mode (see __setup_irq())
+                */
+               do {
+                       ret = hwspin_trylock_raw(host_data->hwlock);
+                       if (!ret)
+                               return 0;
+
+                       udelay(HWSPNLCK_RETRY_DELAY);
+                       timeout += HWSPNLCK_RETRY_DELAY;
+               } while (timeout < HWSPNLCK_TIMEOUT);
+
+               if (ret == -EBUSY)
+                       ret = -ETIMEDOUT;
+       }
+
+       if (ret)
+               pr_err("%s can't get hwspinlock (%d)\n", __func__, ret);
+
+       return ret;
+}
+
+static void stm32_exti_hwspin_unlock(struct stm32_exti_chip_data *chip_data)
+{
+       if (likely(chip_data->host_data->hwlock_state == HWSPINLOCK_READY))
+               hwspin_unlock_raw(chip_data->host_data->hwlock);
+}
+
 static int stm32_irq_set_type(struct irq_data *d, unsigned int type)
 {
        struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
@@ -279,21 +351,26 @@ static int stm32_irq_set_type(struct irq_data *d, unsigned int type)
 
        irq_gc_lock(gc);
 
+       err = stm32_exti_hwspin_lock(chip_data);
+       if (err)
+               goto unlock;
+
        rtsr = irq_reg_readl(gc, stm32_bank->rtsr_ofst);
        ftsr = irq_reg_readl(gc, stm32_bank->ftsr_ofst);
 
        err = stm32_exti_set_type(d, type, &rtsr, &ftsr);
-       if (err) {
-               irq_gc_unlock(gc);
-               return err;
-       }
+       if (err)
+               goto unspinlock;
 
        irq_reg_writel(gc, rtsr, stm32_bank->rtsr_ofst);
        irq_reg_writel(gc, ftsr, stm32_bank->ftsr_ofst);
 
+unspinlock:
+       stm32_exti_hwspin_unlock(chip_data);
+unlock:
        irq_gc_unlock(gc);
 
-       return 0;
+       return err;
 }
 
 static void stm32_chip_suspend(struct stm32_exti_chip_data *chip_data,
@@ -460,20 +537,27 @@ static int stm32_exti_h_set_type(struct irq_data *d, unsigned int type)
        int err;
 
        raw_spin_lock(&chip_data->rlock);
+
+       err = stm32_exti_hwspin_lock(chip_data);
+       if (err)
+               goto unlock;
+
        rtsr = readl_relaxed(base + stm32_bank->rtsr_ofst);
        ftsr = readl_relaxed(base + stm32_bank->ftsr_ofst);
 
        err = stm32_exti_set_type(d, type, &rtsr, &ftsr);
-       if (err) {
-               raw_spin_unlock(&chip_data->rlock);
-               return err;
-       }
+       if (err)
+               goto unspinlock;
 
        writel_relaxed(rtsr, base + stm32_bank->rtsr_ofst);
        writel_relaxed(ftsr, base + stm32_bank->ftsr_ofst);
+
+unspinlock:
+       stm32_exti_hwspin_unlock(chip_data);
+unlock:
        raw_spin_unlock(&chip_data->rlock);
 
-       return 0;
+       return err;
 }
 
 static int stm32_exti_h_set_wake(struct irq_data *d, unsigned int on)
@@ -599,6 +683,8 @@ stm32_exti_host_data *stm32_exti_host_init(const struct stm32_exti_drv_data *dd,
                return NULL;
 
        host_data->drv_data = dd;
+       host_data->node = node;
+       host_data->hwlock_state = HWSPINLOCK_UNKNOWN;
        host_data->chips_data = kcalloc(dd->bank_nr,
                                        sizeof(struct stm32_exti_chip_data),
                                        GFP_KERNEL);
@@ -625,8 +711,7 @@ stm32_exti_host_data *stm32_exti_host_init(const struct stm32_exti_drv_data *dd,
 
 static struct
 stm32_exti_chip_data *stm32_exti_chip_init(struct stm32_exti_host_data *h_data,
-                                          u32 bank_idx,
-                                          struct device_node *node)
+                                          u32 bank_idx)
 {
        const struct stm32_exti_bank *stm32_bank;
        struct stm32_exti_chip_data *chip_data;
@@ -656,8 +741,7 @@ stm32_exti_chip_data *stm32_exti_chip_init(struct stm32_exti_host_data *h_data,
        if (stm32_bank->fpr_ofst != UNDEF_REG)
                writel_relaxed(~0UL, base + stm32_bank->fpr_ofst);
 
-       pr_info("%s: bank%d, External IRQs available:%#x\n",
-               node->full_name, bank_idx, irqs_mask);
+       pr_info("%pOF: bank%d\n", h_data->node, bank_idx);
 
        return chip_data;
 }
@@ -697,7 +781,7 @@ static int __init stm32_exti_init(const struct stm32_exti_drv_data *drv_data,
                struct stm32_exti_chip_data *chip_data;
 
                stm32_bank = drv_data->exti_banks[i];
-               chip_data = stm32_exti_chip_init(host_data, i, node);
+               chip_data = stm32_exti_chip_init(host_data, i);
 
                gc = irq_get_domain_generic_chip(domain, i * IRQS_PER_BANK);
 
@@ -760,7 +844,7 @@ __init stm32_exti_hierarchy_init(const struct stm32_exti_drv_data *drv_data,
                return -ENOMEM;
 
        for (i = 0; i < drv_data->bank_nr; i++)
-               stm32_exti_chip_init(host_data, i, node);
+               stm32_exti_chip_init(host_data, i);
 
        domain = irq_domain_add_hierarchy(parent_domain, 0,
                                          drv_data->bank_nr * IRQS_PER_BANK,