pinctrl: samsung: Protect bank registers with a spinlock

Certain pin control registers can be accessed from different contexts,
i.e. pinctrl, gpio and irq functions. This makes the locking provided by
pin control core insufficient.

This patch adds necessary locking using a per bank spinlock as it was
done in the old Samsung GPIO driver.

Signed-off-by: Tomasz Figa <tomasz.figa@gmail.com>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
diff --git a/drivers/pinctrl/pinctrl-samsung.c b/drivers/pinctrl/pinctrl-samsung.c
index 3475b92..b1d4ac8 100644
--- a/drivers/pinctrl/pinctrl-samsung.c
+++ b/drivers/pinctrl/pinctrl-samsung.c
@@ -27,6 +27,7 @@
 #include <linux/err.h>
 #include <linux/gpio.h>
 #include <linux/irqdomain.h>
+#include <linux/spinlock.h>
 
 #include "core.h"
 #include "pinctrl-samsung.h"
@@ -289,6 +290,7 @@
 	struct samsung_pin_bank *bank;
 	void __iomem *reg;
 	u32 mask, shift, data, pin_offset, cnt;
+	unsigned long flags;
 
 	drvdata = pinctrl_dev_get_drvdata(pctldev);
 	pins = drvdata->pin_groups[group].pins;
@@ -303,11 +305,15 @@
 		mask = (1 << bank->func_width) - 1;
 		shift = pin_offset * bank->func_width;
 
+		spin_lock_irqsave(&bank->slock, flags);
+
 		data = readl(reg);
 		data &= ~(mask << shift);
 		if (enable)
 			data |= drvdata->pin_groups[group].func << shift;
 		writel(data, reg);
+
+		spin_unlock_irqrestore(&bank->slock, flags);
 	}
 }
 
@@ -338,6 +344,7 @@
 	struct samsung_pinctrl_drv_data *drvdata;
 	void __iomem *reg;
 	u32 data, pin_offset, mask, shift;
+	unsigned long flags;
 
 	bank = gc_to_pin_bank(range->gc);
 	drvdata = pinctrl_dev_get_drvdata(pctldev);
@@ -348,11 +355,16 @@
 	mask = (1 << bank->func_width) - 1;
 	shift = pin_offset * bank->func_width;
 
+	spin_lock_irqsave(&bank->slock, flags);
+
 	data = readl(reg);
 	data &= ~(mask << shift);
 	if (!input)
 		data |= FUNC_OUTPUT << shift;
 	writel(data, reg);
+
+	spin_unlock_irqrestore(&bank->slock, flags);
+
 	return 0;
 }
 
@@ -376,6 +388,7 @@
 	enum pincfg_type cfg_type = PINCFG_UNPACK_TYPE(*config);
 	u32 data, width, pin_offset, mask, shift;
 	u32 cfg_value, cfg_reg;
+	unsigned long flags;
 
 	drvdata = pinctrl_dev_get_drvdata(pctldev);
 	pin_to_reg_bank(drvdata, pin - drvdata->ctrl->base, &reg_base,
@@ -406,6 +419,8 @@
 	if (!width)
 		return -EINVAL;
 
+	spin_lock_irqsave(&bank->slock, flags);
+
 	mask = (1 << width) - 1;
 	shift = pin_offset * width;
 	data = readl(reg_base + cfg_reg);
@@ -420,6 +435,9 @@
 		data &= mask;
 		*config = PINCFG_PACK(cfg_type, data);
 	}
+
+	spin_unlock_irqrestore(&bank->slock, flags);
+
 	return 0;
 }
 
@@ -479,16 +497,21 @@
 static void samsung_gpio_set(struct gpio_chip *gc, unsigned offset, int value)
 {
 	struct samsung_pin_bank *bank = gc_to_pin_bank(gc);
+	unsigned long flags;
 	void __iomem *reg;
 	u32 data;
 
 	reg = bank->drvdata->virt_base + bank->pctl_offset;
 
+	spin_lock_irqsave(&bank->slock, flags);
+
 	data = readl(reg + DAT_REG);
 	data &= ~(1 << offset);
 	if (value)
 		data |= 1 << offset;
 	writel(data, reg + DAT_REG);
+
+	spin_unlock_irqrestore(&bank->slock, flags);
 }
 
 /* gpiolib gpio_get callback function */
@@ -859,6 +882,7 @@
 
 	bank = ctrl->pin_banks;
 	for (i = 0; i < ctrl->nr_banks; ++i, ++bank) {
+		spin_lock_init(&bank->slock);
 		bank->drvdata = d;
 		bank->pin_base = ctrl->nr_pins;
 		ctrl->nr_pins += bank->nr_pins;