Skip to content

Commit

Permalink
gpio: zynq: protect direction in/out with a spinlock
Browse files Browse the repository at this point in the history
Fix race condition when changing the direction (in/out) of the GPIO pin.
The read-modify-write sequence (as coded in the driver) isn't atomic and
requires synchronization (spinlock).

Signed-off-by: Glenn Langedock <Glenn.Langedock@barco.com>
Signed-off-by: Michal Simek <michal.simek@xilinx.com>
  • Loading branch information
Glenn Langedock authored and Michal Simek committed Apr 23, 2018
1 parent 92c1378 commit 3551688
Showing 1 changed file with 11 additions and 0 deletions.
11 changes: 11 additions & 0 deletions drivers/gpio/gpio-zynq.c
Expand Up @@ -14,6 +14,7 @@
#include <linux/gpio/driver.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
Expand Down Expand Up @@ -120,6 +121,7 @@ struct gpio_regs {
* @irq: interrupt for the GPIO device
* @p_data: pointer to platform data
* @context: context registers
* @dirlock: lock used for direction in/out synchronization
*/
struct zynq_gpio {
struct gpio_chip chip;
Expand All @@ -128,6 +130,7 @@ struct zynq_gpio {
int irq;
const struct zynq_platform_data *p_data;
struct gpio_regs context;
spinlock_t dirlock;
};

/**
Expand Down Expand Up @@ -301,6 +304,7 @@ static int zynq_gpio_dir_in(struct gpio_chip *chip, unsigned int pin)
{
u32 reg;
unsigned int bank_num, bank_pin_num;
unsigned long flags;
struct zynq_gpio *gpio = gpiochip_get_data(chip);

zynq_gpio_get_bank_pin(pin, &bank_num, &bank_pin_num, gpio);
Expand All @@ -314,9 +318,11 @@ static int zynq_gpio_dir_in(struct gpio_chip *chip, unsigned int pin)
return -EINVAL;

/* clear the bit in direction mode reg to set the pin as input */
spin_lock_irqsave(&gpio->dirlock, flags);
reg = readl_relaxed(gpio->base_addr + ZYNQ_GPIO_DIRM_OFFSET(bank_num));
reg &= ~BIT(bank_pin_num);
writel_relaxed(reg, gpio->base_addr + ZYNQ_GPIO_DIRM_OFFSET(bank_num));
spin_unlock_irqrestore(&gpio->dirlock, flags);

return 0;
}
Expand All @@ -338,11 +344,13 @@ static int zynq_gpio_dir_out(struct gpio_chip *chip, unsigned int pin,
{
u32 reg;
unsigned int bank_num, bank_pin_num;
unsigned long flags;
struct zynq_gpio *gpio = gpiochip_get_data(chip);

zynq_gpio_get_bank_pin(pin, &bank_num, &bank_pin_num, gpio);

/* set the GPIO pin as output */
spin_lock_irqsave(&gpio->dirlock, flags);
reg = readl_relaxed(gpio->base_addr + ZYNQ_GPIO_DIRM_OFFSET(bank_num));
reg |= BIT(bank_pin_num);
writel_relaxed(reg, gpio->base_addr + ZYNQ_GPIO_DIRM_OFFSET(bank_num));
Expand All @@ -351,6 +359,7 @@ static int zynq_gpio_dir_out(struct gpio_chip *chip, unsigned int pin,
reg = readl_relaxed(gpio->base_addr + ZYNQ_GPIO_OUTEN_OFFSET(bank_num));
reg |= BIT(bank_pin_num);
writel_relaxed(reg, gpio->base_addr + ZYNQ_GPIO_OUTEN_OFFSET(bank_num));
spin_unlock_irqrestore(&gpio->dirlock, flags);

/* set the state of the pin */
zynq_gpio_set_value(chip, pin, state);
Expand Down Expand Up @@ -848,6 +857,8 @@ static int zynq_gpio_probe(struct platform_device *pdev)
return ret;
}

spin_lock_init(&gpio->dirlock);

pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
ret = pm_runtime_get_sync(&pdev->dev);
Expand Down

0 comments on commit 3551688

Please sign in to comment.