Re: [PATCH 1/2][v3] gpiolib: allow simultaneous setting of multiple GPIO outputs

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Sunday 01 June 2014 18:36:32 Alexandre Courbot wrote:
> On Tue, May 27, 2014 at 8:14 PM, Rojhalat Ibrahim <imr@xxxxxxxxxxx> wrote:
> > 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.
> 
> How much effort would it be to also implement this function? IIUC it
> should just require one extra pass of the values array prior to
> calling gpiod_set_raw_array().
> 

I did not want to do that because that would change the array values
passed by the user. Or would that be acceptable?

> >
> > Signed-off-by: Rojhalat Ibrahim <imr@xxxxxxxxxxx>
> > ---
> > 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.
> 
> Nice and clean interface, taking advantage of the descriptor interface.
> 
> > +
> >  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++) {
> 
> As Linus already said, please avoid using the deprecated
> ARCH_NR_GPIOS. chip->ngpio should be just as usable in your case...
> 
> > +                       if (i > chip->ngpio - 1)
> > +                               break;
> 
> ... and it would allow you to remove that test.
> 

Ok.

> > +                       if (mask[i/32] == 0) {
> > +                               /* skip ahead */
> > +                               i = (i/32 + 1) * 32 - 1;
> > +                               continue;
> > +                       }
> > +                       if (mask[i/32] & (1 << (i % 32))) {
> 
> BIT(i % 32)? (and everywhere else where it applies)
> 

Will do.

> > +                               chip->set(chip, i, (bits[i/32] >> (i % 32)) & 1);
> 
> Why are you putting a space around the operator in "i % 32" but not in "i/32" ?
> 

I will do it consistently in the next revision.

> > +                               mask[i/32] &= ~(1 << (i % 32));
> 
> There are lots of mask[i/32] and bits[i/32] here. I'm sure the
> compiler will optimize them away, but for code clarity it might be
> worth to have each set of u32 be processed by another (inline)
> function.
> 

I'll look into that.

> > +                       }
> > +               }
> > +       }
> > +}
> > +
> > +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];
> 
> If ARCH_NR_GPIOS is not a multiple of 32, your array will miss one
> element. You should use DIV_ROUND_UP(ARCH_NR_GPIOS, 32), but even
> better, let's not use ARCH_NR_GPIOS at all.
> 
> Maybe someone will kill me for suggesting this, but I wonder if you
> could not use C99's VLA-on-stack feature here? Please rap my knuckles
> if this is taboo in the kernel, but something like this may work
> (untested though):
> 
> i = 0;
> 
> while (i < array_size) {
>        struct gpio_chip *chip = desc_array[i]->chip;
>        u32 mask[DIV_ROUND_UP(chip->ngpios, 32)];
>         ....
> 
>         do {
>              /* Process values for GPIOs of the same chip */
>         } while (++i < array_size && desc_array[i]->chip != chip);
> 
>         _gpio_chip_set_multiple(...);
>     }
> }
> 
> Something like this. It should also avoid some of the repetitions in
> your code. That is, *if* the stack VLAs are usable at all and someone
> does not fall on us shouting how evil this is.
> 

I'll look into that, too.

> > +       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));
> 
> __set_bit(&mask[hwgpio / 32], BIT(hwgpio % 32))
> 

Ok.

> > +                       if (value) {
> > +                               bits[hwgpio/32] |= (1 << (hwgpio % 32));
> 
> __set_bit(&bits[hwgpio / 32], BIT(hwgpio % 32))
> 

Ok.

> > +                       } else {
> > +                               bits[hwgpio/32] &= ~(1 << (hwgpio % 32));
> 
> __clear_bit(&bits[hwgpio / 32], BIT(hwgpio % 32));
> 

Ok.

> > +                       }
> > +                       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
> 
> Miss documentation for array_size here.
> 

Will fix that in the next revision.

> > + * @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;
> 
> What if array_size == 0? This seems to be more important to check that
> the value of desc_array[0].
> 

Will add a check in the next revision.
desc_array[0] is checked because it is used here:
            WARN_ON(desc_array[0]->chip->can_sleep)

> > +       /* 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
> 
> array_size documentation missing here as well.
> 

Will fix that in the next revision.

> > + * @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;
> 
> What happened to the other test here?
> 

desc_array[0] is not checked here because it is not used in that function.
I will add a check for array_size == 0 in the next revision.

> > +       _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);
> >
> 
> All in all I think this has good chances to be merged, once the issues
> are addressed. The issue of setting multiple GPIOs simulatenously
> comes regularly, and the performance improvement is undeniable here.
> 
> Alex.
> 

Thanks for the review. I'll send a new revision of the patch soon.

   Rojhalat


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




[Index of Archives]     [Linux SPI]     [Linux Kernel]     [Linux ARM (vger)]     [Linux ARM MSM]     [Linux Omap]     [Linux Arm]     [Linux Tegra]     [Fedora ARM]     [Linux for Samsung SOC]     [eCos]     [Linux Fastboot]     [Gcc Help]     [Git]     [DCCP]     [IETF Announce]     [Security]     [Linux MIPS]     [Yosemite Campsites]

  Powered by Linux