pinctrl: qcom: handle reserved ranges

Some Qualcomm boards feature reserved ranges of pins which are protected
by firmware. Attempting to read or write any registers associated with
these pins results the board resetting.

Add support for parsing these ranges from devicetree and ensure that the
pinctrl and GPIO drivers don't try to interact with these pins.

Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
Reviewed-by: Sumit Garg <sumit.garg@oss.qualcomm.com>
Link: https://lore.kernel.org/r/20250410-topic-sm8x50-pinctrl-reserved-ranges-v2-1-654488392b9a@linaro.org
Signed-off-by: Caleb Connolly <caleb.connolly@linaro.org>
diff --git a/arch/arm/mach-snapdragon/include/mach/gpio.h b/arch/arm/mach-snapdragon/include/mach/gpio.h
index cc8f405..11e8104 100644
--- a/arch/arm/mach-snapdragon/include/mach/gpio.h
+++ b/arch/arm/mach-snapdragon/include/mach/gpio.h
@@ -46,4 +46,19 @@
 	return pindata->special_pins_start && pin >= pindata->special_pins_start;
 }
 
+struct udevice;
+
+/**
+ * msm_pinctrl_is_reserved() - Check if a pin lies in a reserved range
+ *
+ * @dev: pinctrl device
+ * @pin: Pin number
+ *
+ * Returns: true if pin is reserved, otherwise false
+ *
+ * Call using dev_get_parent() from the GPIO device, it is a child of
+ * the pinctrl device.
+ */
+bool msm_pinctrl_is_reserved(struct udevice *dev, unsigned int pin);
+
 #endif /* _QCOM_GPIO_H_ */
diff --git a/drivers/gpio/msm_gpio.c b/drivers/gpio/msm_gpio.c
index 54afd44..6783fc7 100644
--- a/drivers/gpio/msm_gpio.c
+++ b/drivers/gpio/msm_gpio.c
@@ -151,6 +151,9 @@
 
 static int msm_gpio_set_flags(struct udevice *dev, unsigned int gpio, ulong flags)
 {
+	if (msm_pinctrl_is_reserved(dev_get_parent(dev), gpio))
+		return -EPERM;
+
 	if (flags & GPIOD_IS_OUT_ACTIVE) {
 		return msm_gpio_direction_output(dev, gpio, 1);
 	} else if (flags & GPIOD_IS_OUT) {
@@ -193,6 +196,9 @@
 {
 	struct msm_gpio_bank *priv = dev_get_priv(dev);
 
+	if (msm_pinctrl_is_reserved(dev_get_parent(dev), gpio))
+		return -EPERM;
+
 	if (qcom_is_special_pin(priv->pin_data, gpio))
 		return msm_gpio_get_value_special(priv, gpio);
 
@@ -232,6 +238,10 @@
 {
 	struct msm_gpio_bank *priv = dev_get_priv(dev);
 
+	if (msm_pinctrl_is_reserved(dev_get_parent(dev), gpio))
+		return GPIOF_UNKNOWN;
+
+	/* Always NOP for special pins, assume they're in the correct state */
 	if (qcom_is_special_pin(priv->pin_data, gpio))
 		return msm_gpio_get_function_special(priv, gpio);
 
diff --git a/drivers/pinctrl/qcom/pinctrl-qcom.c b/drivers/pinctrl/qcom/pinctrl-qcom.c
index 24d0319..c95db56 100644
--- a/drivers/pinctrl/qcom/pinctrl-qcom.c
+++ b/drivers/pinctrl/qcom/pinctrl-qcom.c
@@ -15,14 +15,18 @@
 #include <asm/gpio.h>
 #include <dm/pinctrl.h>
 #include <linux/bitops.h>
+#include <linux/bitmap.h>
 #include <linux/bug.h>
 #include <mach/gpio.h>
 
 #include "pinctrl-qcom.h"
 
+#define MSM_PINCTRL_MAX_PINS 256
+
 struct msm_pinctrl_priv {
 	phys_addr_t base;
 	struct msm_pinctrl_data *data;
+	DECLARE_BITMAP(reserved_map, MSM_PINCTRL_MAX_PINS);
 };
 
 #define GPIO_CONFIG_REG(priv, x) \
@@ -71,13 +75,60 @@
 	return priv->data->get_function_name(dev, selector);
 }
 
+static int msm_pinctrl_parse_ranges(struct udevice *dev)
+{
+	struct msm_pinctrl_priv *priv = dev_get_priv(dev);
+	ofnode node = dev_ofnode(dev);
+	int ret, count, i;
+	u32 *ranges;
+
+	if (ofnode_read_prop(node, "gpio-reserved-ranges", &count)) {
+		if (count % 2 == 1) {
+			dev_err(dev, "gpio-reserved-ranges must be a multiple of 2\n");
+			return -EINVAL;
+		}
+
+		ranges = malloc(count);
+		if (!ranges)
+			return -ENOMEM;
+
+		ret = ofnode_read_u32_array(node, "gpio-reserved-ranges", ranges, count / 4);
+		if (ret) {
+			dev_err(dev, "failed to read gpio-reserved-ranges array (%d)\n", ret);
+			return ret;
+		}
+
+		for (i = 0; i < count / 4; i += 2) {
+			if (ranges[i] >= MSM_PINCTRL_MAX_PINS ||
+			    (ranges[i] + ranges[i + 1]) >= MSM_PINCTRL_MAX_PINS) {
+				dev_err(dev, "invalid reserved-range (%d;%d)\n",
+					ranges[i], ranges[i + 1]);
+				return -EINVAL;
+			}
+
+			bitmap_set(priv->reserved_map, ranges[i], ranges[i + 1]);
+		}
+
+		free(ranges);
+	}
+
+	return 0;
+}
+
 static int msm_pinctrl_probe(struct udevice *dev)
 {
 	struct msm_pinctrl_priv *priv = dev_get_priv(dev);
+	int ret;
 
 	priv->base = dev_read_addr(dev);
 	priv->data = (struct msm_pinctrl_data *)dev_get_driver_data(dev);
 
+	ret = msm_pinctrl_parse_ranges(dev);
+	if (ret) {
+		printf("Couldn't parse reserved GPIO ranges!\n");
+		return ret;
+	}
+
 	return priv->base == FDT_ADDR_T_NONE ? -EINVAL : 0;
 }
 
@@ -97,6 +148,9 @@
 	if (func < 0)
 		return func;
 
+	if (msm_pinctrl_is_reserved(dev, pin_selector))
+		return -EPERM;
+
 	/* Always NOP for special pins, assume they're in the correct state */
 	if (qcom_is_special_pin(&priv->data->pin_data, pin_selector))
 		return 0;
@@ -145,6 +199,9 @@
 {
 	struct msm_pinctrl_priv *priv = dev_get_priv(dev);
 
+	if (msm_pinctrl_is_reserved(dev, pin_selector))
+		return -EPERM;
+
 	if (qcom_is_special_pin(&priv->data->pin_data, pin_selector))
 		return msm_pinconf_set_special(priv, pin_selector, param, argument);
 
@@ -241,3 +298,13 @@
 	.ops		= &msm_pinctrl_ops,
 	.probe		= msm_pinctrl_probe,
 };
+
+bool msm_pinctrl_is_reserved(struct udevice *dev, unsigned int pin)
+{
+	struct msm_pinctrl_priv *priv = dev_get_priv(dev);
+
+	if (pin >= MSM_PINCTRL_MAX_PINS)
+		return false;
+
+	return test_bit(pin, priv->reserved_map);
+}