diff options
Diffstat (limited to 'drivers/irqchip/irq-stm32-exti.c')
-rw-r--r-- | drivers/irqchip/irq-stm32-exti.c | 122 |
1 files changed, 103 insertions, 19 deletions
diff --git a/drivers/irqchip/irq-stm32-exti.c b/drivers/irqchip/irq-stm32-exti.c index 0a2088e12d96..6edfd4bfa169 100644 --- a/drivers/irqchip/irq-stm32-exti.c +++ b/drivers/irqchip/irq-stm32-exti.c @@ -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 @@ free_host_data: 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; } @@ -678,8 +762,8 @@ static int __init stm32_exti_init(const struct stm32_exti_drv_data *drv_data, domain = irq_domain_add_linear(node, drv_data->bank_nr * IRQS_PER_BANK, &irq_exti_domain_ops, NULL); if (!domain) { - pr_err("%s: Could not register interrupt domain.\n", - node->name); + pr_err("%pOFn: Could not register interrupt domain.\n", + node); ret = -ENOMEM; goto out_unmap; } @@ -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, @@ -768,7 +852,7 @@ __init stm32_exti_hierarchy_init(const struct stm32_exti_drv_data *drv_data, host_data); if (!domain) { - pr_err("%s: Could not register exti domain.\n", node->name); + pr_err("%pOFn: Could not register exti domain.\n", node); ret = -ENOMEM; goto out_unmap; } |