gpio: zynq: protect direction in/out with a spinlock
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> Signed-off-by: Srinivas Neeli <srinivas.neeli@xilinx.com> Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
This commit is contained in:

committed by
Bartosz Golaszewski

parent
8346b92ecd
commit
fdcfec11b2
@@ -10,6 +10,7 @@
|
|||||||
#include <linux/gpio/driver.h>
|
#include <linux/gpio/driver.h>
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/interrupt.h>
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/spinlock.h>
|
||||||
#include <linux/io.h>
|
#include <linux/io.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
@@ -116,6 +117,7 @@ struct gpio_regs {
|
|||||||
* @irq: interrupt for the GPIO device
|
* @irq: interrupt for the GPIO device
|
||||||
* @p_data: pointer to platform data
|
* @p_data: pointer to platform data
|
||||||
* @context: context registers
|
* @context: context registers
|
||||||
|
* @dirlock: lock used for direction in/out synchronization
|
||||||
*/
|
*/
|
||||||
struct zynq_gpio {
|
struct zynq_gpio {
|
||||||
struct gpio_chip chip;
|
struct gpio_chip chip;
|
||||||
@@ -124,6 +126,7 @@ struct zynq_gpio {
|
|||||||
int irq;
|
int irq;
|
||||||
const struct zynq_platform_data *p_data;
|
const struct zynq_platform_data *p_data;
|
||||||
struct gpio_regs context;
|
struct gpio_regs context;
|
||||||
|
spinlock_t dirlock; /* lock */
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -297,6 +300,7 @@ static int zynq_gpio_dir_in(struct gpio_chip *chip, unsigned int pin)
|
|||||||
{
|
{
|
||||||
u32 reg;
|
u32 reg;
|
||||||
unsigned int bank_num, bank_pin_num;
|
unsigned int bank_num, bank_pin_num;
|
||||||
|
unsigned long flags;
|
||||||
struct zynq_gpio *gpio = gpiochip_get_data(chip);
|
struct zynq_gpio *gpio = gpiochip_get_data(chip);
|
||||||
|
|
||||||
zynq_gpio_get_bank_pin(pin, &bank_num, &bank_pin_num, gpio);
|
zynq_gpio_get_bank_pin(pin, &bank_num, &bank_pin_num, gpio);
|
||||||
@@ -310,9 +314,11 @@ static int zynq_gpio_dir_in(struct gpio_chip *chip, unsigned int pin)
|
|||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
/* clear the bit in direction mode reg to set the pin as input */
|
/* 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 = readl_relaxed(gpio->base_addr + ZYNQ_GPIO_DIRM_OFFSET(bank_num));
|
||||||
reg &= ~BIT(bank_pin_num);
|
reg &= ~BIT(bank_pin_num);
|
||||||
writel_relaxed(reg, gpio->base_addr + ZYNQ_GPIO_DIRM_OFFSET(bank_num));
|
writel_relaxed(reg, gpio->base_addr + ZYNQ_GPIO_DIRM_OFFSET(bank_num));
|
||||||
|
spin_unlock_irqrestore(&gpio->dirlock, flags);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -334,11 +340,13 @@ static int zynq_gpio_dir_out(struct gpio_chip *chip, unsigned int pin,
|
|||||||
{
|
{
|
||||||
u32 reg;
|
u32 reg;
|
||||||
unsigned int bank_num, bank_pin_num;
|
unsigned int bank_num, bank_pin_num;
|
||||||
|
unsigned long flags;
|
||||||
struct zynq_gpio *gpio = gpiochip_get_data(chip);
|
struct zynq_gpio *gpio = gpiochip_get_data(chip);
|
||||||
|
|
||||||
zynq_gpio_get_bank_pin(pin, &bank_num, &bank_pin_num, gpio);
|
zynq_gpio_get_bank_pin(pin, &bank_num, &bank_pin_num, gpio);
|
||||||
|
|
||||||
/* set the GPIO pin as output */
|
/* 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 = readl_relaxed(gpio->base_addr + ZYNQ_GPIO_DIRM_OFFSET(bank_num));
|
||||||
reg |= BIT(bank_pin_num);
|
reg |= BIT(bank_pin_num);
|
||||||
writel_relaxed(reg, gpio->base_addr + ZYNQ_GPIO_DIRM_OFFSET(bank_num));
|
writel_relaxed(reg, gpio->base_addr + ZYNQ_GPIO_DIRM_OFFSET(bank_num));
|
||||||
@@ -347,6 +355,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 = readl_relaxed(gpio->base_addr + ZYNQ_GPIO_OUTEN_OFFSET(bank_num));
|
||||||
reg |= BIT(bank_pin_num);
|
reg |= BIT(bank_pin_num);
|
||||||
writel_relaxed(reg, gpio->base_addr + ZYNQ_GPIO_OUTEN_OFFSET(bank_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 */
|
/* set the state of the pin */
|
||||||
zynq_gpio_set_value(chip, pin, state);
|
zynq_gpio_set_value(chip, pin, state);
|
||||||
@@ -885,6 +894,8 @@ static int zynq_gpio_probe(struct platform_device *pdev)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spin_lock_init(&gpio->dirlock);
|
||||||
|
|
||||||
pm_runtime_set_active(&pdev->dev);
|
pm_runtime_set_active(&pdev->dev);
|
||||||
pm_runtime_enable(&pdev->dev);
|
pm_runtime_enable(&pdev->dev);
|
||||||
ret = pm_runtime_get_sync(&pdev->dev);
|
ret = pm_runtime_get_sync(&pdev->dev);
|
||||||
|
Reference in New Issue
Block a user