diff mbox series

[RFC,2/3] gpiolib: Add support for optional ramp-up delays

Message ID 20221212103525.231298-3-alexander.stein@ew.tq-group.com
State New
Headers show
Series gpiolib: ramp-up delay support | expand

Commit Message

Alexander Stein Dec. 12, 2022, 10:35 a.m. UTC
These delays are added when specified per GPIO line and GPIO is set
to high level, including any active low flag.

Signed-off-by: Alexander Stein <alexander.stein@ew.tq-group.com>
---
 drivers/gpio/gpiolib.c | 80 ++++++++++++++++++++++++++++++++++++++++++
 drivers/gpio/gpiolib.h |  3 ++
 2 files changed, 83 insertions(+)
diff mbox series

Patch

diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 5a66d9616d7cc..5848caf0b1e12 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -20,6 +20,7 @@ 
 #include <linux/pinctrl/consumer.h>
 #include <linux/fs.h>
 #include <linux/compat.h>
+#include <linux/delay.h>
 #include <linux/file.h>
 #include <uapi/linux/gpio.h>
 
@@ -432,6 +433,73 @@  static int devprop_gpiochip_set_names(struct gpio_chip *chip)
 	return 0;
 }
 
+/*
+ * devprop_gpiochip_set_delays - Set GPIO line delays using device properties
+ * @chip: GPIO chip whose delays should be set, if possible
+ *
+ * Looks for device property "gpio-ramp-up-delays-us" and if it exists assigns
+ * GPIO delays for the chip.
+ */
+static int devprop_gpiochip_set_delays(struct gpio_chip *chip)
+{
+	struct gpio_device *gdev = chip->gpiodev;
+	struct device *dev = &gdev->dev;
+	u32 *delays;
+	int ret, i;
+	int count;
+
+	count = device_property_count_u32(dev, "gpio-ramp-up-delays-us");
+	if (count < 0)
+		return 0;
+
+	/*
+	 * When offset is set in the driver side we assume the driver internally
+	 * is using more than one gpiochip per the same device. We have to stop
+	 * setting delays if the specified ones with 'gpio-ramp-up-delays-us'
+	 * are less than the offset in the device itself. This means all the
+	 * lines are not present for every single pin within all the internal
+	 * gpiochips.
+	 */
+	if (count <= chip->offset) {
+		dev_warn(dev, "gpio-ramp-up-delays-us too short (length %d), cannot map delays for the gpiochip at offset %u\n",
+			 count, chip->offset);
+		return 0;
+	}
+
+	delays = kcalloc(count, sizeof(*delays), GFP_KERNEL);
+	if (!delays)
+		return -ENOMEM;
+
+	ret = device_property_read_u32_array(dev, "gpio-ramp-up-delays-us",
+					     delays, count);
+	if (ret < 0) {
+		dev_warn(dev, "failed to read GPIO rampup delays: %d\n", ret);
+		kfree(delays);
+		return ret;
+	}
+
+	/*
+	 * When more than one gpiochip per device is used, 'count' can
+	 * contain at most number gpiochips x chip->ngpio. We have to
+	 * correctly distribute all defined lines taking into account
+	 * chip->offset as starting point from where we will assign
+	 * the delays to pins from the 'delays' array. Since property
+	 * 'gpio-ramp-up-delays-us' cannot contains gaps, we have to be sure
+	 * we only assign those pins that really exists since chip->ngpio
+	 * can be different of the chip->offset.
+	 */
+	count = (count > chip->offset) ? count - chip->offset : count;
+	if (count > chip->ngpio)
+		count = chip->ngpio;
+
+	for (i = 0; i < count; i++)
+		gdev->descs[i].ramp_up_delay_us = delays[chip->offset + i];
+
+	kfree(delays);
+
+	return 0;
+}
+
 static unsigned long *gpiochip_allocate_mask(struct gpio_chip *gc)
 {
 	unsigned long *p;
@@ -806,6 +874,9 @@  int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
 			goto err_remove_from_list;
 	}
 	ret = devprop_gpiochip_set_names(gc);
+	if (ret)
+		goto err_remove_from_list;
+	ret = devprop_gpiochip_set_delays(gc);
 	if (ret)
 		goto err_remove_from_list;
 
@@ -2962,6 +3033,9 @@  static void gpio_set_open_drain_value_commit(struct gpio_desc *desc, bool value)
 		gpiod_err(desc,
 			  "%s: Error in set_value for open drain err %d\n",
 			  __func__, ret);
+
+	if (desc->ramp_up_delay_us && value)
+		fsleep(desc->ramp_up_delay_us);
 }
 
 /*
@@ -2987,6 +3061,9 @@  static void gpio_set_open_source_value_commit(struct gpio_desc *desc, bool value
 		gpiod_err(desc,
 			  "%s: Error in set_value for open source err %d\n",
 			  __func__, ret);
+
+	if (desc->ramp_up_delay_us && value)
+		fsleep(desc->ramp_up_delay_us);
 }
 
 static void gpiod_set_raw_value_commit(struct gpio_desc *desc, bool value)
@@ -2996,6 +3073,9 @@  static void gpiod_set_raw_value_commit(struct gpio_desc *desc, bool value)
 	gc = desc->gdev->chip;
 	trace_gpio_value(desc_to_gpio(desc), 0, value);
 	gc->set(gc, gpio_chip_hwgpio(desc), value);
+
+	if (desc->ramp_up_delay_us && value)
+		fsleep(desc->ramp_up_delay_us);
 }
 
 /*
diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h
index b3c2db6eba80c..3d7e5781e00d2 100644
--- a/drivers/gpio/gpiolib.h
+++ b/drivers/gpio/gpiolib.h
@@ -143,6 +143,7 @@  extern struct list_head gpio_devices;
  * @name:		Line name
  * @hog:		Pointer to the device node that hogs this line (if any)
  * @debounce_period_us:	Debounce period in microseconds
+ * @ramp_up_delay_us:	Enable propagation delay in microseconds
  *
  * These are obtained using gpiod_get() and are preferable to the old
  * integer-based handles.
@@ -184,6 +185,8 @@  struct gpio_desc {
 	/* debounce period in microseconds */
 	unsigned int		debounce_period_us;
 #endif
+	/* enable propagation delay in microseconds */
+	unsigned int		ramp_up_delay_us;
 };
 
 #define gpiod_not_found(desc)		(IS_ERR(desc) && PTR_ERR(desc) == -ENOENT)