diff options
author | Tomasz Figa <t.figa@samsung.com> | 2014-08-08 19:48:05 -0400 |
---|---|---|
committer | Linus Walleij <linus.walleij@linaro.org> | 2014-08-21 08:24:29 -0400 |
commit | f6a8249f9e55d45a47777d2a3cc69defa23c87bb (patch) | |
tree | fe98f6b10bd5d6a0f6401dca9b098b6e87005b15 | |
parent | 4b6fe45a79a9ca2429eaf8713be7bdc936c211c3 (diff) |
pinctrl: exynos: Lock GPIOs as interrupts when used as EINTs
Currently after configuring a GPIO pin as an interrupt related pinmux
registers are changed, but there is no protection from calling
gpio_direction_*() in a badly written driver, which would cause the same
pinmux register to be reconfigured for regular input/output and this
disabling interrupt capability of the pin.
This patch addresses this issue by moving pinmux reconfiguration to
.irq_{request,release}_resources() callback of irq_chip and calling
gpio_lock_as_irq() helper to prevent reconfiguration of pin direction.
Setting up a GPIO interrupt on Samsung SoCs is a two-step operation -
in addition to trigger configuration in a dedicated register, the pinmux
must be also reconfigured to GPIO interrupt, which is a different function
than normal GPIO input, although I/O-wise they both behave in the same way
and gpio_get_value() can be used on a pin configured as IRQ as well.
Such design implies subtleties such as gpio_direction_input() not having
to fail if a pin is already configured as an interrupt nor change the
configuration to normal input. But the FLAG_USED_AS_IRQ set in gpiolib by
gpio_lock_as_irq() is only used to check that gpio_direction_output() is
not called, it's not used to prevent gpio_direction_input() to be called.
So this is not a complete solution for Samsung SoCs but it's definitely a
move in the right direction.
Signed-off-by: Tomasz Figa <t.figa@samsung.com>
[javier: use request resources instead of startup and expand commit message]
Signed-off-by: Javier Martinez Canillas <javier.martinez@collabora.co.uk>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
-rw-r--r-- | drivers/pinctrl/samsung/pinctrl-exynos.c | 69 | ||||
-rw-r--r-- | drivers/pinctrl/samsung/pinctrl-samsung.h | 1 |
2 files changed, 64 insertions, 6 deletions
diff --git a/drivers/pinctrl/samsung/pinctrl-exynos.c b/drivers/pinctrl/samsung/pinctrl-exynos.c index 003bfd874a61..d7154ed0b0eb 100644 --- a/drivers/pinctrl/samsung/pinctrl-exynos.c +++ b/drivers/pinctrl/samsung/pinctrl-exynos.c | |||
@@ -127,14 +127,10 @@ static int exynos_irq_set_type(struct irq_data *irqd, unsigned int type) | |||
127 | struct irq_chip *chip = irq_data_get_irq_chip(irqd); | 127 | struct irq_chip *chip = irq_data_get_irq_chip(irqd); |
128 | struct exynos_irq_chip *our_chip = to_exynos_irq_chip(chip); | 128 | struct exynos_irq_chip *our_chip = to_exynos_irq_chip(chip); |
129 | struct samsung_pin_bank *bank = irq_data_get_irq_chip_data(irqd); | 129 | struct samsung_pin_bank *bank = irq_data_get_irq_chip_data(irqd); |
130 | struct samsung_pin_bank_type *bank_type = bank->type; | ||
131 | struct samsung_pinctrl_drv_data *d = bank->drvdata; | 130 | struct samsung_pinctrl_drv_data *d = bank->drvdata; |
132 | unsigned int pin = irqd->hwirq; | 131 | unsigned int shift = EXYNOS_EINT_CON_LEN * irqd->hwirq; |
133 | unsigned int shift = EXYNOS_EINT_CON_LEN * pin; | ||
134 | unsigned int con, trig_type; | 132 | unsigned int con, trig_type; |
135 | unsigned long reg_con = our_chip->eint_con + bank->eint_offset; | 133 | unsigned long reg_con = our_chip->eint_con + bank->eint_offset; |
136 | unsigned long flags; | ||
137 | unsigned int mask; | ||
138 | 134 | ||
139 | switch (type) { | 135 | switch (type) { |
140 | case IRQ_TYPE_EDGE_RISING: | 136 | case IRQ_TYPE_EDGE_RISING: |
@@ -167,8 +163,32 @@ static int exynos_irq_set_type(struct irq_data *irqd, unsigned int type) | |||
167 | con |= trig_type << shift; | 163 | con |= trig_type << shift; |
168 | writel(con, d->virt_base + reg_con); | 164 | writel(con, d->virt_base + reg_con); |
169 | 165 | ||
166 | return 0; | ||
167 | } | ||
168 | |||
169 | static int exynos_irq_request_resources(struct irq_data *irqd) | ||
170 | { | ||
171 | struct irq_chip *chip = irq_data_get_irq_chip(irqd); | ||
172 | struct exynos_irq_chip *our_chip = to_exynos_irq_chip(chip); | ||
173 | struct samsung_pin_bank *bank = irq_data_get_irq_chip_data(irqd); | ||
174 | struct samsung_pin_bank_type *bank_type = bank->type; | ||
175 | struct samsung_pinctrl_drv_data *d = bank->drvdata; | ||
176 | unsigned int shift = EXYNOS_EINT_CON_LEN * irqd->hwirq; | ||
177 | unsigned long reg_con = our_chip->eint_con + bank->eint_offset; | ||
178 | unsigned long flags; | ||
179 | unsigned int mask; | ||
180 | unsigned int con; | ||
181 | int ret; | ||
182 | |||
183 | ret = gpio_lock_as_irq(&bank->gpio_chip, irqd->hwirq); | ||
184 | if (ret) { | ||
185 | dev_err(bank->gpio_chip.dev, "unable to lock pin %s-%lu IRQ\n", | ||
186 | bank->name, irqd->hwirq); | ||
187 | return ret; | ||
188 | } | ||
189 | |||
170 | reg_con = bank->pctl_offset + bank_type->reg_offset[PINCFG_TYPE_FUNC]; | 190 | reg_con = bank->pctl_offset + bank_type->reg_offset[PINCFG_TYPE_FUNC]; |
171 | shift = pin * bank_type->fld_width[PINCFG_TYPE_FUNC]; | 191 | shift = irqd->hwirq * bank_type->fld_width[PINCFG_TYPE_FUNC]; |
172 | mask = (1 << bank_type->fld_width[PINCFG_TYPE_FUNC]) - 1; | 192 | mask = (1 << bank_type->fld_width[PINCFG_TYPE_FUNC]) - 1; |
173 | 193 | ||
174 | spin_lock_irqsave(&bank->slock, flags); | 194 | spin_lock_irqsave(&bank->slock, flags); |
@@ -180,9 +200,42 @@ static int exynos_irq_set_type(struct irq_data *irqd, unsigned int type) | |||
180 | 200 | ||
181 | spin_unlock_irqrestore(&bank->slock, flags); | 201 | spin_unlock_irqrestore(&bank->slock, flags); |
182 | 202 | ||
203 | exynos_irq_unmask(irqd); | ||
204 | |||
183 | return 0; | 205 | return 0; |
184 | } | 206 | } |
185 | 207 | ||
208 | static void exynos_irq_release_resources(struct irq_data *irqd) | ||
209 | { | ||
210 | struct irq_chip *chip = irq_data_get_irq_chip(irqd); | ||
211 | struct exynos_irq_chip *our_chip = to_exynos_irq_chip(chip); | ||
212 | struct samsung_pin_bank *bank = irq_data_get_irq_chip_data(irqd); | ||
213 | struct samsung_pin_bank_type *bank_type = bank->type; | ||
214 | struct samsung_pinctrl_drv_data *d = bank->drvdata; | ||
215 | unsigned int shift = EXYNOS_EINT_CON_LEN * irqd->hwirq; | ||
216 | unsigned long reg_con = our_chip->eint_con + bank->eint_offset; | ||
217 | unsigned long flags; | ||
218 | unsigned int mask; | ||
219 | unsigned int con; | ||
220 | |||
221 | reg_con = bank->pctl_offset + bank_type->reg_offset[PINCFG_TYPE_FUNC]; | ||
222 | shift = irqd->hwirq * bank_type->fld_width[PINCFG_TYPE_FUNC]; | ||
223 | mask = (1 << bank_type->fld_width[PINCFG_TYPE_FUNC]) - 1; | ||
224 | |||
225 | exynos_irq_mask(irqd); | ||
226 | |||
227 | spin_lock_irqsave(&bank->slock, flags); | ||
228 | |||
229 | con = readl(d->virt_base + reg_con); | ||
230 | con &= ~(mask << shift); | ||
231 | con |= FUNC_INPUT << shift; | ||
232 | writel(con, d->virt_base + reg_con); | ||
233 | |||
234 | spin_unlock_irqrestore(&bank->slock, flags); | ||
235 | |||
236 | gpio_unlock_as_irq(&bank->gpio_chip, irqd->hwirq); | ||
237 | } | ||
238 | |||
186 | /* | 239 | /* |
187 | * irq_chip for gpio interrupts. | 240 | * irq_chip for gpio interrupts. |
188 | */ | 241 | */ |
@@ -193,6 +246,8 @@ static struct exynos_irq_chip exynos_gpio_irq_chip = { | |||
193 | .irq_mask = exynos_irq_mask, | 246 | .irq_mask = exynos_irq_mask, |
194 | .irq_ack = exynos_irq_ack, | 247 | .irq_ack = exynos_irq_ack, |
195 | .irq_set_type = exynos_irq_set_type, | 248 | .irq_set_type = exynos_irq_set_type, |
249 | .irq_request_resources = exynos_irq_request_resources, | ||
250 | .irq_release_resources = exynos_irq_release_resources, | ||
196 | }, | 251 | }, |
197 | .eint_con = EXYNOS_GPIO_ECON_OFFSET, | 252 | .eint_con = EXYNOS_GPIO_ECON_OFFSET, |
198 | .eint_mask = EXYNOS_GPIO_EMASK_OFFSET, | 253 | .eint_mask = EXYNOS_GPIO_EMASK_OFFSET, |
@@ -336,6 +391,8 @@ static struct exynos_irq_chip exynos_wkup_irq_chip = { | |||
336 | .irq_ack = exynos_irq_ack, | 391 | .irq_ack = exynos_irq_ack, |
337 | .irq_set_type = exynos_irq_set_type, | 392 | .irq_set_type = exynos_irq_set_type, |
338 | .irq_set_wake = exynos_wkup_irq_set_wake, | 393 | .irq_set_wake = exynos_wkup_irq_set_wake, |
394 | .irq_request_resources = exynos_irq_request_resources, | ||
395 | .irq_release_resources = exynos_irq_release_resources, | ||
339 | }, | 396 | }, |
340 | .eint_con = EXYNOS_WKUP_ECON_OFFSET, | 397 | .eint_con = EXYNOS_WKUP_ECON_OFFSET, |
341 | .eint_mask = EXYNOS_WKUP_EMASK_OFFSET, | 398 | .eint_mask = EXYNOS_WKUP_EMASK_OFFSET, |
diff --git a/drivers/pinctrl/samsung/pinctrl-samsung.h b/drivers/pinctrl/samsung/pinctrl-samsung.h index 2b882320e8e9..5cedc9d26390 100644 --- a/drivers/pinctrl/samsung/pinctrl-samsung.h +++ b/drivers/pinctrl/samsung/pinctrl-samsung.h | |||
@@ -26,6 +26,7 @@ | |||
26 | #include <linux/gpio.h> | 26 | #include <linux/gpio.h> |
27 | 27 | ||
28 | /* pinmux function number for pin as gpio output line */ | 28 | /* pinmux function number for pin as gpio output line */ |
29 | #define FUNC_INPUT 0x0 | ||
29 | #define FUNC_OUTPUT 0x1 | 30 | #define FUNC_OUTPUT 0x1 |
30 | 31 | ||
31 | /** | 32 | /** |