Introduce a new function gpiod_set_raw_array to the consumer interface which
allows setting multiple outputs with just one function call. Also add an
optional set_multiple function to the driver interface. Without an
implementation of that function in the chip driver outputs are set
sequentially.

Implementing the set_multiple function in a chip driver allows for:
- Improved performance for certain use cases. The original motivation for this
  was the task of configuring an FPGA. In that specific case, where 9 GPIO
  lines have to be set many times, configuration time goes down from 48 s to
  19 s when using the new function.
- Simultaneous glitch-free setting of multiple pins on any kind of parallel
  bus attached to GPIOs provided they all reside on the same chip and bank.

Limitations:
  - Performance is only improved for normal high-low outputs. Open drain and
    open source outputs are always set separately from each other. Those kinds
    of outputs could probably be accelerated in a similar way if we could
    forgo the error checking when setting GPIO directions.
  - There is no gpiod_set_array function that regards the ACTIVE_LOW bits.
    The raw function should be sufficient for many use cases. So I avoided
    the code duplication the other functions would require.

Signed-off-by: Rojhalat Ibrahim <[email protected]>
---
Change log:
  v3: - add documentation
      - change commit message
  v2: - use descriptor interface
      - allow arbitrary groups of GPIOs spanning multiple chips

 Documentation/gpio/consumer.txt |  17 ++++++
 drivers/gpio/gpiolib.c          | 116 ++++++++++++++++++++++++++++++++++++++++
 include/linux/gpio/consumer.h   |  18 +++++++
 include/linux/gpio/driver.h     |   3 ++
 4 files changed, 154 insertions(+)

diff --git a/Documentation/gpio/consumer.txt b/Documentation/gpio/consumer.txt
index 09854fe..affcd9b 100644
--- a/Documentation/gpio/consumer.txt
+++ b/Documentation/gpio/consumer.txt
@@ -163,6 +163,23 @@ The active-low state of a GPIO can also be queried using 
the following call:
 Note that these functions should only be used with great moderation ; a driver
 should not have to care about the physical line level.
 
+Set multiple GPIO outputs with a single function call
+-----------------------------------------------------
+The following two functions set the raw output values of an array of GPIOs:
+
+       void gpiod_set_raw_array(unsigned int array_size,
+                                struct gpio_desc **desc_array,
+                                int *value_array)
+       void gpiod_set_raw_array_cansleep(unsigned int array_size,
+                                         struct gpio_desc **desc_array,
+                                         int *value_array)
+
+The array can be an arbitrary set of GPIOs. The functions will try to set
+GPIOs belonging to the same bank or chip simultaneously if supported by the
+corresponding chip driver. In that case a significantly improved performance
+can be expected. If simultaneous setting is not possible the GPIOs will be set
+sequentially.
+
 GPIOs mapped to IRQs
 --------------------
 GPIO lines can quite often be used as IRQs. You can get the IRQ number
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index f48817d..ee67ef1 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -2345,6 +2345,77 @@ static void _gpiod_set_raw_value(struct gpio_desc *desc, 
bool value)
                chip->set(chip, gpio_chip_hwgpio(desc), value);
 }
 
+static void _gpio_chip_set_multiple(struct gpio_chip *chip,
+                                   u32 mask[ARCH_NR_GPIOS/32],
+                                   u32 bits[ARCH_NR_GPIOS/32])
+{
+       if (chip->set_multiple) {
+               chip->set_multiple(chip, mask, bits);
+       } else {
+               int i;
+               for (i = 0; i < ARCH_NR_GPIOS; i++) {
+                       if (i > chip->ngpio - 1)
+                               break;
+                       if (mask[i/32] == 0) {
+                               /* skip ahead */
+                               i = (i/32 + 1) * 32 - 1;
+                               continue;
+                       }
+                       if (mask[i/32] & (1 << (i % 32))) {
+                               chip->set(chip, i, (bits[i/32] >> (i % 32)) & 
1);
+                               mask[i/32] &= ~(1 << (i % 32));
+                       }
+               }
+       }
+}
+
+static void _gpiod_set_raw_array(unsigned int array_size,
+                                struct gpio_desc **desc_array,
+                                int *value_array)
+{
+       struct gpio_chip *chip = desc_array[0]->chip;
+       u32 mask[ARCH_NR_GPIOS/32];
+       u32 bits[ARCH_NR_GPIOS/32];
+       int count = 0;
+       int i;
+
+       memset(mask, 0, sizeof(mask));
+       for (i = 0; i < array_size; i++) {
+               struct gpio_desc *desc = desc_array[i];
+               int hwgpio = gpio_chip_hwgpio(desc);
+               int value = value_array[i];
+
+               /* another chip; push collected bits to outputs */
+               if (desc->chip != chip) {
+                       if (count != 0) {
+                               _gpio_chip_set_multiple(chip, mask, bits);
+                               memset(mask, 0, sizeof(mask));
+                               count = 0;
+                       }
+                       chip = desc->chip;
+               }
+               /* collect all normal outputs belonging to the same chip */
+               /* open drain and open source outputs are set individually */
+               trace_gpio_value(desc_to_gpio(desc), 0, value);
+               if (test_bit(FLAG_OPEN_DRAIN, &desc->flags)) {
+                       _gpio_set_open_drain_value(desc, value);
+               } else if (test_bit(FLAG_OPEN_SOURCE, &desc->flags)) {
+                       _gpio_set_open_source_value(desc, value);
+               } else {
+                       mask[hwgpio/32] |= (1 << (hwgpio % 32));
+                       if (value) {
+                               bits[hwgpio/32] |= (1 << (hwgpio % 32));
+                       } else {
+                               bits[hwgpio/32] &= ~(1 << (hwgpio % 32));
+                       }
+                       count++;
+               }
+       }
+       if (count != 0) {
+               _gpio_chip_set_multiple(chip, mask, bits);
+       }
+}
+
 /**
  * gpiod_set_raw_value() - assign a gpio's raw value
  * @desc: gpio whose value will be assigned
@@ -2390,6 +2461,30 @@ void gpiod_set_value(struct gpio_desc *desc, int value)
 EXPORT_SYMBOL_GPL(gpiod_set_value);
 
 /**
+ * gpiod_set_raw_array() - assign values to an array of GPIOs
+ * @desc_array: array of GPIO descriptors whose values will be assigned
+ * @value_array: array of values to assign
+ *
+ * Set the raw values of the GPIOs, i.e. the values of the physical lines
+ * without regard for their ACTIVE_LOW status.
+ *
+ * This function should be called from contexts where we cannot sleep, and will
+ * complain if the GPIO chip functions potentially sleep.
+ */
+void gpiod_set_raw_array(unsigned int array_size,
+                        struct gpio_desc **desc_array, int *value_array)
+{
+       if (!desc_array)
+               return;
+       if (!desc_array[0])
+               return;
+       /* Should be using gpiod_set_raw_array_cansleep() */
+       WARN_ON(desc_array[0]->chip->can_sleep);
+       _gpiod_set_raw_array(array_size, desc_array, value_array);
+}
+EXPORT_SYMBOL_GPL(gpiod_set_raw_array);
+
+/**
  * gpiod_cansleep() - report whether gpio value access may sleep
  * @desc: gpio to check
  *
@@ -2559,6 +2654,27 @@ void gpiod_set_value_cansleep(struct gpio_desc *desc, 
int value)
 EXPORT_SYMBOL_GPL(gpiod_set_value_cansleep);
 
 /**
+ * gpiod_set_raw_array_cansleep() - assign values to an array of GPIOs
+ * @desc_array: array of GPIO descriptors whose values will be assigned
+ * @value_array: array of values to assign
+ *
+ * Set the raw values of the GPIOs, i.e. the values of the physical lines
+ * without regard for their ACTIVE_LOW status.
+ *
+ * This function is to be called from contexts that can sleep.
+ */
+void gpiod_set_raw_array_cansleep(unsigned int array_size,
+                                 struct gpio_desc **desc_array,
+                                 int *value_array)
+{
+       might_sleep_if(extra_checks);
+       if (!desc_array)
+               return;
+       _gpiod_set_raw_array(array_size, desc_array, value_array);
+}
+EXPORT_SYMBOL_GPL(gpiod_set_raw_array_cansleep);
+
+/**
  * gpiod_add_lookup_table() - register GPIO device consumers
  * @table: table of consumers to register
  */
diff --git a/include/linux/gpio/consumer.h b/include/linux/gpio/consumer.h
index bed128e..1d0bab3 100644
--- a/include/linux/gpio/consumer.h
+++ b/include/linux/gpio/consumer.h
@@ -42,12 +42,17 @@ int gpiod_get_value(const struct gpio_desc *desc);
 void gpiod_set_value(struct gpio_desc *desc, int value);
 int gpiod_get_raw_value(const struct gpio_desc *desc);
 void gpiod_set_raw_value(struct gpio_desc *desc, int value);
+void gpiod_set_raw_array(unsigned int array_size,
+                        struct gpio_desc **desc_array, int *value_array);
 
 /* Value get/set from sleeping context */
 int gpiod_get_value_cansleep(const struct gpio_desc *desc);
 void gpiod_set_value_cansleep(struct gpio_desc *desc, int value);
 int gpiod_get_raw_value_cansleep(const struct gpio_desc *desc);
 void gpiod_set_raw_value_cansleep(struct gpio_desc *desc, int value);
+void gpiod_set_raw_array_cansleep(unsigned int array_size,
+                                 struct gpio_desc **desc_array,
+                                 int *value_array);
 
 int gpiod_set_debounce(struct gpio_desc *desc, unsigned debounce);
 
@@ -150,6 +155,12 @@ static inline void gpiod_set_raw_value(struct gpio_desc 
*desc, int value)
        /* GPIO can never have been requested */
        WARN_ON(1);
 }
+void gpiod_set_raw_array(unsigned int array_size,
+                        struct gpio_desc **desc_array, int *value_array)
+{
+       /* GPIO can never have been requested */
+       WARN_ON(1);
+}
 
 static inline int gpiod_get_value_cansleep(const struct gpio_desc *desc)
 {
@@ -174,6 +185,13 @@ static inline void gpiod_set_raw_value_cansleep(struct 
gpio_desc *desc,
        /* GPIO can never have been requested */
        WARN_ON(1);
 }
+void gpiod_set_raw_array_cansleep(unsigned int array_size,
+                                 struct gpio_desc **desc_array,
+                                 int *value_array)
+{
+       /* GPIO can never have been requested */
+       WARN_ON(1);
+}
 
 static inline int gpiod_set_debounce(struct gpio_desc *desc, unsigned debounce)
 {
diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h
index 1827b43..d7968c8 100644
--- a/include/linux/gpio/driver.h
+++ b/include/linux/gpio/driver.h
@@ -32,6 +32,7 @@ struct seq_file;
  * @get: returns value for signal "offset"; for output signals this
  *     returns either the value actually sensed, or zero
  * @set: assigns output value for signal "offset"
+ * @set_multiple: assigns output values for multiple signals defined by "mask"
  * @set_debounce: optional hook for setting debounce time for specified gpio in
  *      interrupt triggered gpio chips
  * @to_irq: optional hook supporting non-static gpio_to_irq() mappings;
@@ -84,6 +85,8 @@ struct gpio_chip {
                                                unsigned offset);
        void                    (*set)(struct gpio_chip *chip,
                                                unsigned offset, int value);
+       void                    (*set_multiple)(struct gpio_chip *chip,
+                                               u32 *mask, u32 *bits);
        int                     (*set_debounce)(struct gpio_chip *chip,
                                                unsigned offset,
                                                unsigned debounce);

--
1.8.5.5
--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to