gpiolib: Implement fast processing path in get/set array
authorJanusz Krzysztofik <jmkrzyszt@gmail.com>
Wed, 5 Sep 2018 21:50:08 +0000 (23:50 +0200)
committerLinus Walleij <linus.walleij@linaro.org>
Thu, 13 Sep 2018 09:17:23 +0000 (11:17 +0200)
Certain GPIO descriptor arrays returned by gpio_get_array() may contain
information on direct mapping of array members to pins of a single GPIO
chip in hardware order.  In such cases, bitmaps of values can be passed
directly from/to the chip's .get/set_multiple() callbacks without
wasting time on iterations.

Add respective code to gpiod_get/set_array_bitmap_complex() functions.
Pins not applicable for fast path are processed as before, skipping
over the 'fast' ones.

Cc: Jonathan Corbet <corbet@lwn.net>
Signed-off-by: Janusz Krzysztofik <jmkrzyszt@gmail.com>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
Documentation/driver-api/gpio/board.rst
Documentation/driver-api/gpio/consumer.rst
drivers/gpio/gpiolib.c

index 2c112553df841444730beee24d30485857d99145..c66821e033c2d6ef61808b96d4d4e9d3d50f899a 100644 (file)
@@ -193,3 +193,18 @@ And the table can be added to the board code as follows::
 
 The line will be hogged as soon as the gpiochip is created or - in case the
 chip was created earlier - when the hog table is registered.
+
+Arrays of pins
+--------------
+In addition to requesting pins belonging to a function one by one, a device may
+also request an array of pins assigned to the function.  The way those pins are
+mapped to the device determines if the array qualifies for fast bitmap
+processing.  If yes, a bitmap is passed over get/set array functions directly
+between a caller and a respective .get/set_multiple() callback of a GPIO chip.
+
+In order to qualify for fast bitmap processing, the pin mapping must meet the
+following requirements:
+- it must belong to the same chip as other 'fast' pins of the function,
+- its index within the function must match its hardware number within the chip.
+
+Open drain and open source pins are excluded from fast bitmap output processing.
index 0afd95a12b104a78bc29c67fded0271be5e8e81b..cf992e5ab976e62a19d20026641096a1115dd195 100644 (file)
@@ -388,6 +388,14 @@ array_info should be set to NULL.
 Note that for optimal performance GPIOs belonging to the same chip should be
 contiguous within the array of descriptors.
 
+Still better performance may be achieved if array indexes of the descriptors
+match hardware pin numbers of a single chip.  If an array passed to a get/set
+array function matches the one obtained from gpiod_get_array() and array_info
+associated with the array is also passed, the function may take a fast bitmap
+processing path, passing the value_bitmap argument directly to the respective
+.get/set_multiple() callback of the chip.  That allows for utilization of GPIO
+banks as data I/O ports without much loss of performance.
+
 The return value of gpiod_get_array_value() and its variants is 0 on success
 or negative on error. Note the difference to gpiod_get_value(), which returns
 0 or 1 on success to convey the GPIO value. With the array functions, the GPIO
index cd7c1deed132db7bc919559d289281c961d7ff4f..d7532aa6c0bcff608c2acb677ce026adee960c1c 100644 (file)
@@ -2789,7 +2789,36 @@ int gpiod_get_array_value_complex(bool raw, bool can_sleep,
                                  struct gpio_array *array_info,
                                  unsigned long *value_bitmap)
 {
-       int i = 0;
+       int err, i = 0;
+
+       /*
+        * Validate array_info against desc_array and its size.
+        * It should immediately follow desc_array if both
+        * have been obtained from the same gpiod_get_array() call.
+        */
+       if (array_info && array_info->desc == desc_array &&
+           array_size <= array_info->size &&
+           (void *)array_info == desc_array + array_info->size) {
+               if (!can_sleep)
+                       WARN_ON(array_info->chip->can_sleep);
+
+               err = gpio_chip_get_multiple(array_info->chip,
+                                            array_info->get_mask,
+                                            value_bitmap);
+               if (err)
+                       return err;
+
+               if (!raw && !bitmap_empty(array_info->invert_mask, array_size))
+                       bitmap_xor(value_bitmap, value_bitmap,
+                                  array_info->invert_mask, array_size);
+
+               if (bitmap_full(array_info->get_mask, array_size))
+                       return 0;
+
+               i = find_first_zero_bit(array_info->get_mask, array_size);
+       } else {
+               array_info = NULL;
+       }
 
        while (i < array_size) {
                struct gpio_chip *chip = desc_array[i]->gdev->chip;
@@ -2820,7 +2849,12 @@ int gpiod_get_array_value_complex(bool raw, bool can_sleep,
                        int hwgpio = gpio_chip_hwgpio(desc);
 
                        __set_bit(hwgpio, mask);
-                       i++;
+
+                       if (array_info)
+                               find_next_zero_bit(array_info->get_mask,
+                                                  array_size, i);
+                       else
+                               i++;
                } while ((i < array_size) &&
                         (desc_array[i]->gdev->chip == chip));
 
@@ -2831,7 +2865,7 @@ int gpiod_get_array_value_complex(bool raw, bool can_sleep,
                        return ret;
                }
 
-               for (j = first; j < i; j++) {
+               for (j = first; j < i; ) {
                        const struct gpio_desc *desc = desc_array[j];
                        int hwgpio = gpio_chip_hwgpio(desc);
                        int value = test_bit(hwgpio, bits);
@@ -2840,6 +2874,11 @@ int gpiod_get_array_value_complex(bool raw, bool can_sleep,
                                value = !value;
                        __assign_bit(j, value_bitmap, value);
                        trace_gpio_value(desc_to_gpio(desc), 1, value);
+
+                       if (array_info)
+                               find_next_zero_bit(array_info->get_mask, i, j);
+                       else
+                               j++;
                }
 
                if (mask != fastpath)
@@ -3043,6 +3082,32 @@ int gpiod_set_array_value_complex(bool raw, bool can_sleep,
 {
        int i = 0;
 
+       /*
+        * Validate array_info against desc_array and its size.
+        * It should immediately follow desc_array if both
+        * have been obtained from the same gpiod_get_array() call.
+        */
+       if (array_info && array_info->desc == desc_array &&
+           array_size <= array_info->size &&
+           (void *)array_info == desc_array + array_info->size) {
+               if (!can_sleep)
+                       WARN_ON(array_info->chip->can_sleep);
+
+               if (!raw && !bitmap_empty(array_info->invert_mask, array_size))
+                       bitmap_xor(value_bitmap, value_bitmap,
+                                  array_info->invert_mask, array_size);
+
+               gpio_chip_set_multiple(array_info->chip, array_info->set_mask,
+                                      value_bitmap);
+
+               if (bitmap_full(array_info->set_mask, array_size))
+                       return 0;
+
+               i = find_first_zero_bit(array_info->set_mask, array_size);
+       } else {
+               array_info = NULL;
+       }
+
        while (i < array_size) {
                struct gpio_chip *chip = desc_array[i]->gdev->chip;
                unsigned long fastpath[2 * BITS_TO_LONGS(FASTPATH_NGPIO)];
@@ -3070,7 +3135,14 @@ int gpiod_set_array_value_complex(bool raw, bool can_sleep,
                        int hwgpio = gpio_chip_hwgpio(desc);
                        int value = test_bit(i, value_bitmap);
 
-                       if (!raw && test_bit(FLAG_ACTIVE_LOW, &desc->flags))
+                       /*
+                        * Pins applicable for fast input but not for
+                        * fast output processing may have been already
+                        * inverted inside the fast path, skip them.
+                        */
+                       if (!raw && !(array_info &&
+                           test_bit(i, array_info->invert_mask)) &&
+                           test_bit(FLAG_ACTIVE_LOW, &desc->flags))
                                value = !value;
                        trace_gpio_value(desc_to_gpio(desc), 0, value);
                        /*
@@ -3089,7 +3161,12 @@ int gpiod_set_array_value_complex(bool raw, bool can_sleep,
                                        __clear_bit(hwgpio, bits);
                                count++;
                        }
-                       i++;
+
+                       if (array_info)
+                               find_next_zero_bit(array_info->set_mask,
+                                                  array_size, i);
+                       else
+                               i++;
                } while ((i < array_size) &&
                         (desc_array[i]->gdev->chip == chip));
                /* push collected bits to outputs */