Expose a new 'maintain' sysfs attribute to control both suspend and
reset tolerance.

Signed-off-by: Andrew Jeffery <and...@aj.id.au>
---
 Documentation/gpio/sysfs.txt |  9 +++++
 drivers/gpio/gpiolib-sysfs.c | 88 ++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 93 insertions(+), 4 deletions(-)

diff --git a/Documentation/gpio/sysfs.txt b/Documentation/gpio/sysfs.txt
index aeab01aa4d00..f447f0746884 100644
--- a/Documentation/gpio/sysfs.txt
+++ b/Documentation/gpio/sysfs.txt
@@ -96,6 +96,15 @@ and have the following read/write attributes:
                for "rising" and "falling" edges will follow this
                setting.
 
+       "maintain" ... displays and controls whether the state of the GPIO is
+               maintained or lost on suspend or reset. The valid values take
+               the following meanings:
+
+               0: Do not maintain state on either suspend or reset
+               1: Maintain state for suspend only
+               2: Maintain state for reset only
+               3: Maintain state for both suspend and reset
+
 GPIO controllers have paths like /sys/class/gpio/gpiochip42/ (for the
 controller implementing GPIOs starting at #42) and have the following
 read-only attributes:
diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c
index 3f454eaf2101..bfa186e73e26 100644
--- a/drivers/gpio/gpiolib-sysfs.c
+++ b/drivers/gpio/gpiolib-sysfs.c
@@ -289,6 +289,74 @@ static ssize_t edge_store(struct device *dev,
 }
 static DEVICE_ATTR_RW(edge);
 
+#define GPIOLIB_SYSFS_MAINTAIN_SUSPEND BIT(0)
+#define GPIOLIB_SYSFS_MAINTAIN_RESET   BIT(1)
+#define GPIOLIB_SYSFS_MAINTAIN_ALL     GENMASK(1, 0)
+static ssize_t maintain_show(struct device *dev, struct device_attribute *attr,
+                            char *buf)
+{
+       struct gpiod_data *data = dev_get_drvdata(dev);
+       ssize_t status = 0;
+       int val = 0;
+
+       mutex_lock(&data->mutex);
+
+       if (!test_bit(FLAG_SLEEP_MAY_LOSE_VALUE, &data->desc->flags))
+               val |= GPIOLIB_SYSFS_MAINTAIN_SUSPEND;
+
+       if (test_bit(FLAG_RESET_TOLERANT, &data->desc->flags))
+               val |= GPIOLIB_SYSFS_MAINTAIN_RESET;
+
+       status = sprintf(buf, "%d\n", val);
+
+       mutex_unlock(&data->mutex);
+
+       return status;
+}
+
+static ssize_t maintain_store(struct device *dev,
+                             struct device_attribute *attr,
+                             const char *buf,
+                             size_t size)
+{
+       struct gpiod_data *data = dev_get_drvdata(dev);
+       struct gpio_chip *chip;
+       ssize_t status;
+       long provided;
+
+       mutex_lock(&data->mutex);
+
+       chip = data->desc->gdev->chip;
+
+       if (!chip->set_config)
+               return -ENOTSUPP;
+
+       status = kstrtol(buf, 0, &provided);
+       if (status < 0)
+               goto out;
+
+       if (provided & ~GPIOLIB_SYSFS_MAINTAIN_ALL) {
+               status = -EINVAL;
+               goto out;
+       }
+
+       if (!(provided & GPIOLIB_SYSFS_MAINTAIN_SUSPEND))
+               set_bit(FLAG_SLEEP_MAY_LOSE_VALUE, &data->desc->flags);
+       else
+               clear_bit(FLAG_SLEEP_MAY_LOSE_VALUE,
+                         &data->desc->flags);
+
+       /* Configure reset tolerance */
+       status = gpiod_set_reset_tolerant(data->desc,
+                       !!(provided & GPIOLIB_SYSFS_MAINTAIN_RESET));
+out:
+       mutex_unlock(&data->mutex);
+
+       return status ? : size;
+
+}
+static DEVICE_ATTR_RW(maintain);
+
 /* Caller holds gpiod-data mutex. */
 static int gpio_sysfs_set_active_low(struct device *dev, int value)
 {
@@ -378,6 +446,7 @@ static struct attribute *gpio_attrs[] = {
        &dev_attr_edge.attr,
        &dev_attr_value.attr,
        &dev_attr_active_low.attr,
+       &dev_attr_maintain.attr,
        NULL,
 };
 
@@ -474,11 +543,22 @@ static ssize_t export_store(struct class *class,
                        status = -ENODEV;
                goto done;
        }
-       status = gpiod_export(desc, true);
-       if (status < 0)
+
+       /*
+        * If userspace is requesting the GPIO via sysfs, make them explicitly
+        * configure reset tolerance each time by unconditionally disabling it
+        * here, as the export and configuration steps are not atomic.
+        */
+       status = gpiod_set_reset_tolerant(desc, false);
+       if (status < 0) {
                gpiod_free(desc);
-       else
-               set_bit(FLAG_SYSFS, &desc->flags);
+       } else {
+               status = gpiod_export(desc, true);
+               if (status < 0)
+                       gpiod_free(desc);
+               else
+                       set_bit(FLAG_SYSFS, &desc->flags);
+       }
 
 done:
        if (status)
-- 
2.11.0

Reply via email to