gpio: msm: add support for special pins

Leverage the data introduced in the struct msm_special_pin_data to allow
setting the gpio direction and value if supported by the pin data.

Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
Reviewed-by: Caleb Connolly <caleb.connolly@linaro.org>
diff --git a/drivers/gpio/msm_gpio.c b/drivers/gpio/msm_gpio.c
index 2fb266f..cea073b 100644
--- a/drivers/gpio/msm_gpio.c
+++ b/drivers/gpio/msm_gpio.c
@@ -34,13 +34,31 @@
 #define GPIO_IN_OUT_REG(dev, x) \
 	(GPIO_CONFIG_REG(dev, x) + 0x4)
 
+static void msm_gpio_direction_input_special(struct msm_gpio_bank *priv,
+					     unsigned int gpio)
+{
+	unsigned int offset = gpio - priv->pin_data->special_pins_start;
+	const struct msm_special_pin_data *data;
+
+	if (!priv->pin_data->special_pins_data)
+		return;
+
+	data = &priv->pin_data->special_pins_data[offset];
+
+	if (!data->ctl_reg || data->oe_bit >= 31)
+		return;
+
+	/* switch direction */
+	clrsetbits_le32(priv->base + data->ctl_reg,
+			BIT(data->oe_bit), 0);
+}
+
 static void msm_gpio_direction_input(struct udevice *dev, unsigned int gpio)
 {
 	struct msm_gpio_bank *priv = dev_get_priv(dev);
 
-	/* Always NOP for special pins, assume they're in the correct state */
 	if (qcom_is_special_pin(priv->pin_data, gpio))
-		return;
+		msm_gpio_direction_input_special(priv, gpio);
 
 	/* Disable OE bit */
 	clrsetbits_le32(priv->base + GPIO_CONFIG_REG(dev, gpio),
@@ -49,13 +67,33 @@
 	return;
 }
 
+static int msm_gpio_set_value_special(struct msm_gpio_bank *priv,
+				      unsigned int gpio, int value)
+{
+	unsigned int offset = gpio - priv->pin_data->special_pins_start;
+	const struct msm_special_pin_data *data;
+
+	if (!priv->pin_data->special_pins_data)
+		return 0;
+
+	data = &priv->pin_data->special_pins_data[offset];
+
+	if (!data->io_reg || data->out_bit >= 31)
+		return 0;
+
+	value = !!value;
+	/* set value */
+	writel(value << data->out_bit, priv->base + data->io_reg);
+
+	return 0;
+}
+
 static int msm_gpio_set_value(struct udevice *dev, unsigned int gpio, int value)
 {
 	struct msm_gpio_bank *priv = dev_get_priv(dev);
 
-	/* Always NOP for special pins, assume they're in the correct state */
 	if (qcom_is_special_pin(priv->pin_data, gpio))
-		return 0;
+		return msm_gpio_set_value_special(priv, gpio, value);
 
 	value = !!value;
 	/* set value */
@@ -64,14 +102,42 @@
 	return 0;
 }
 
+static int msm_gpio_direction_output_special(struct msm_gpio_bank *priv,
+					     unsigned int gpio,
+					     int value)
+{
+	unsigned int offset = gpio - priv->pin_data->special_pins_start;
+	const struct msm_special_pin_data *data;
+
+	if (!priv->pin_data->special_pins_data)
+		return 0;
+
+	data = &priv->pin_data->special_pins_data[offset];
+
+	if (!data->io_reg || data->out_bit >= 31)
+		return 0;
+
+	value = !!value;
+	/* set value */
+	writel(value << data->out_bit, priv->base + data->io_reg);
+
+	if (!data->ctl_reg || data->oe_bit >= 31)
+		return 0;
+
+	/* switch direction */
+	clrsetbits_le32(priv->base + data->ctl_reg,
+			BIT(data->oe_bit), BIT(data->oe_bit));
+
+	return 0;
+}
+
 static int msm_gpio_direction_output(struct udevice *dev, unsigned int gpio,
 				     int value)
 {
 	struct msm_gpio_bank *priv = dev_get_priv(dev);
 
-	/* Always NOP for special pins, assume they're in the correct state */
 	if (qcom_is_special_pin(priv->pin_data, gpio))
-		return 0;
+		return msm_gpio_direction_output_special(priv, gpio, value);
 
 	value = !!value;
 	/* set value */
@@ -100,13 +166,28 @@
 	return 0;
 }
 
+static int msm_gpio_get_value_special(struct msm_gpio_bank *priv, unsigned int gpio)
+{
+	unsigned int offset = gpio - priv->pin_data->special_pins_start;
+	const struct msm_special_pin_data *data;
+
+	if (!priv->pin_data->special_pins_data)
+		return 0;
+
+	data = &priv->pin_data->special_pins_data[offset];
+
+	if (!data->io_reg || data->in_bit >= 31)
+		return 0;
+
+	return !!(readl(priv->base + data->io_reg) >> data->in_bit);
+}
+
 static int msm_gpio_get_value(struct udevice *dev, unsigned int gpio)
 {
 	struct msm_gpio_bank *priv = dev_get_priv(dev);
 
-	/* Always NOP for special pins, assume they're in the correct state */
 	if (qcom_is_special_pin(priv->pin_data, gpio))
-		return 0;
+		return msm_gpio_get_value_special(priv, gpio);
 
 	return !!(readl(priv->base + GPIO_IN_OUT_REG(dev, gpio)) >> GPIO_IN);
 }