From: Rojhalat Ibrahim <imr@rtschenk.de>
To: "linux-gpio@vger.kernel.org" <linux-gpio@vger.kernel.org>
Cc: Alexandre Courbot <gnurou@gmail.com>,
Linus Walleij <linus.walleij@linaro.org>,
Grant Likely <grant.likely@linaro.org>,
Mark Brown <broonie@kernel.org>, Gerhard Sittig <gsi@denx.de>
Subject: [PATCH 1/2][v5] gpiolib: allow simultaneous setting of multiple GPIO outputs
Date: Mon, 16 Jun 2014 15:51:23 +0200 [thread overview]
Message-ID: <25983307.ZjJOfz2Kpg@pcimr> (raw)
Introduce new functions gpiod_set_array & gpiod_set_raw_array to the consumer
interface which allow 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
20 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.
Signed-off-by: Rojhalat Ibrahim <imr@rtschenk.de>
---
Change log:
v5: - check can_sleep property per chip
- remove superfluous checks
- supplement documentation
v4: - add gpiod_set_array function for setting logical values
- change interface of the set_multiple driver function to use
unsigned long as type for the bit fields
- use generic bitops (which also use unsigned long for bit fields)
- do not use ARCH_NR_GPIOS any more
v3: - add documentation
- change commit message
v2: - use descriptor interface
- allow arbitrary groups of GPIOs spanning multiple chips
Documentation/gpio/consumer.txt | 25 ++++++
drivers/gpio/gpiolib.c | 168 ++++++++++++++++++++++++++++++++++++++++
include/linux/gpio/consumer.h | 38 +++++++++
include/linux/gpio/driver.h | 4 +
4 files changed, 235 insertions(+)
diff --git a/Documentation/gpio/consumer.txt b/Documentation/gpio/consumer.txt
index d8abfc3..369e663 100644
--- a/Documentation/gpio/consumer.txt
+++ b/Documentation/gpio/consumer.txt
@@ -163,6 +163,31 @@ 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 functions set the output values of an array of GPIOs:
+
+ void gpiod_set_array(unsigned int array_size,
+ struct gpio_desc **desc_array,
+ int *value_array)
+ void gpiod_set_raw_array(unsigned int array_size,
+ struct gpio_desc **desc_array,
+ int *value_array)
+ void gpiod_set_array_cansleep(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.
+Note that for optimal performance GPIOs belonging to the same chip should be
+contiguous within the array of descriptors.
+
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 2ebc907..455e098 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -2370,6 +2370,88 @@ static void _gpiod_set_raw_value(struct gpio_desc *desc, bool value)
chip->set(chip, gpio_chip_hwgpio(desc), value);
}
+/*
+ * set multiple outputs on the same chip;
+ * use the chip's set_multiple function if available;
+ * otherwise set the outputs sequentially;
+ * @mask: bit mask array; one bit per output; BITS_PER_LONG bits per word
+ * defines which outputs are to be changed
+ * @bits: bit value array; one bit per output; BITS_PER_LONG bits per word
+ * defines the values the outputs specified by mask are to be set to
+ */
+static void gpio_chip_set_multiple(struct gpio_chip *chip,
+ unsigned long *mask, unsigned long *bits)
+{
+ if (chip->set_multiple) {
+ chip->set_multiple(chip, mask, bits);
+ } else {
+ int i;
+ for (i = 0; i < chip->ngpio; i++) {
+ if (mask[BIT_WORD(i)] == 0) {
+ /* no more set bits in this mask word;
+ * skip ahead to the next word */
+ i = (BIT_WORD(i) + 1) * BITS_PER_LONG - 1;
+ continue;
+ }
+ /* set outputs if the corresponding mask bit is set */
+ if (__test_and_clear_bit(i, mask)) {
+ chip->set(chip, i, test_bit(i, bits));
+ }
+ }
+ }
+}
+
+static void gpiod_set_array_priv(bool raw, bool can_sleep,
+ unsigned int array_size,
+ struct gpio_desc **desc_array,
+ int *value_array)
+{
+ int i = 0;
+
+ while (i < array_size) {
+ struct gpio_chip *chip = desc_array[i]->chip;
+ unsigned long mask[BITS_TO_LONGS(chip->ngpio)];
+ unsigned long bits[BITS_TO_LONGS(chip->ngpio)];
+ int count = 0;
+
+ if (!can_sleep) {
+ WARN_ON(chip->can_sleep);
+ }
+ memset(mask, 0, sizeof(mask));
+ do {
+ struct gpio_desc *desc = desc_array[i];
+ int hwgpio = gpio_chip_hwgpio(desc);
+ int value = value_array[i];
+
+ if (!raw && test_bit(FLAG_ACTIVE_LOW, &desc->flags))
+ value = !value;
+ trace_gpio_value(desc_to_gpio(desc), 0, value);
+ /*
+ * collect all normal outputs belonging to the same chip
+ * open drain and open source outputs are set individually
+ */
+ 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 {
+ __set_bit(hwgpio, mask);
+ if (value) {
+ __set_bit(hwgpio, bits);
+ } else {
+ __clear_bit(hwgpio, bits);
+ }
+ count++;
+ }
+ i++;
+ } while ((i < array_size) && (desc_array[i]->chip == chip));
+ /* push collected bits to outputs */
+ 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
@@ -2415,6 +2497,48 @@ 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
+ * @array_size: number of elements in the descriptor / value arrays
+ * @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;
+ gpiod_set_array_priv(true, false, array_size, desc_array, value_array);
+}
+EXPORT_SYMBOL_GPL(gpiod_set_raw_array);
+
+/**
+ * gpiod_set_array() - assign values to an array of GPIOs
+ * @array_size: number of elements in the descriptor / value arrays
+ * @desc_array: array of GPIO descriptors whose values will be assigned
+ * @value_array: array of values to assign
+ *
+ * Set the logical values of the GPIOs, i.e. taking their ACTIVE_LOW status
+ * into account.
+ *
+ * This function should be called from contexts where we cannot sleep, and will
+ * complain if the GPIO chip functions potentially sleep.
+ */
+void gpiod_set_array(unsigned int array_size,
+ struct gpio_desc **desc_array, int *value_array)
+{
+ if (!desc_array)
+ return;
+ gpiod_set_array_priv(false, false, array_size, desc_array, value_array);
+}
+EXPORT_SYMBOL_GPL(gpiod_set_array);
+
+/**
* gpiod_cansleep() - report whether gpio value access may sleep
* @desc: gpio to check
*
@@ -2584,6 +2708,50 @@ 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: number of elements in the descriptor / value arrays
+ * @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_array_priv(true, true, array_size, desc_array, value_array);
+}
+EXPORT_SYMBOL_GPL(gpiod_set_raw_array_cansleep);
+
+/**
+ * gpiod_set_array_cansleep() - assign values to an array of GPIOs
+ * @array_size: number of elements in the descriptor / value arrays
+ * @desc_array: array of GPIO descriptors whose values will be assigned
+ * @value_array: array of values to assign
+ *
+ * Set the logical values of the GPIOs, i.e. taking their ACTIVE_LOW status
+ * into account.
+ *
+ * This function is to be called from contexts that can sleep.
+ */
+void gpiod_set_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_array_priv(false, true, array_size, desc_array, value_array);
+}
+EXPORT_SYMBOL_GPL(gpiod_set_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 05e53cc..e51abe3 100644
--- a/include/linux/gpio/consumer.h
+++ b/include/linux/gpio/consumer.h
@@ -53,14 +53,24 @@ int gpiod_direction_output_raw(struct gpio_desc *desc, int value);
/* Value get/set from non-sleeping context */
int gpiod_get_value(const struct gpio_desc *desc);
void gpiod_set_value(struct gpio_desc *desc, int value);
+void gpiod_set_array(unsigned int array_size,
+ struct gpio_desc **desc_array, int *value_array);
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);
+void gpiod_set_array_cansleep(unsigned int array_size,
+ struct gpio_desc **desc_array,
+ int *value_array);
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);
@@ -180,6 +190,13 @@ static inline void gpiod_set_value(struct gpio_desc *desc, int value)
/* GPIO can never have been requested */
WARN_ON(1);
}
+static inline void gpiod_set_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_raw_value(const struct gpio_desc *desc)
{
/* GPIO can never have been requested */
@@ -191,6 +208,13 @@ static inline void gpiod_set_raw_value(struct gpio_desc *desc, int value)
/* GPIO can never have been requested */
WARN_ON(1);
}
+static inline 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)
{
@@ -203,6 +227,13 @@ static inline void gpiod_set_value_cansleep(struct gpio_desc *desc, int value)
/* GPIO can never have been requested */
WARN_ON(1);
}
+static inline void gpiod_set_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_get_raw_value_cansleep(const struct gpio_desc *desc)
{
/* GPIO can never have been requested */
@@ -215,6 +246,13 @@ static inline void gpiod_set_raw_value_cansleep(struct gpio_desc *desc,
/* GPIO can never have been requested */
WARN_ON(1);
}
+static inline 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 573e4f3..4814250 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;
@@ -87,6 +88,9 @@ struct gpio_chip {
unsigned offset);
void (*set)(struct gpio_chip *chip,
unsigned offset, int value);
+ void (*set_multiple)(struct gpio_chip *chip,
+ unsigned long *mask,
+ unsigned long *bits);
int (*set_debounce)(struct gpio_chip *chip,
unsigned offset,
unsigned debounce);
--
1.8.5.5
next reply other threads:[~2014-06-16 13:51 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-06-16 13:51 Rojhalat Ibrahim [this message]
2014-06-21 7:08 ` [PATCH 1/2][v5] gpiolib: allow simultaneous setting of multiple GPIO outputs Alexandre Courbot
2014-06-21 20:24 ` Mark Brown
2014-07-07 15:23 ` Linus Walleij
2014-07-07 16:12 ` Rojhalat Ibrahim
2014-07-09 10:17 ` Linus Walleij
2014-07-30 9:12 ` Rojhalat Ibrahim
2014-07-30 10:41 ` Mark Brown
2014-07-30 11:04 ` Rojhalat Ibrahim
2014-07-31 2:35 ` Alexandre Courbot
2014-07-31 7:12 ` Gerhard Sittig
2014-07-31 8:42 ` Rojhalat Ibrahim
2014-08-06 12:45 ` Gerhard Sittig
2014-08-06 19:39 ` Mark Brown
2014-08-07 9:52 ` Rojhalat Ibrahim
2014-08-29 5:21 ` Linus Walleij
2014-08-29 8:15 ` Wolfram Sang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=25983307.ZjJOfz2Kpg@pcimr \
--to=imr@rtschenk.de \
--cc=broonie@kernel.org \
--cc=gnurou@gmail.com \
--cc=grant.likely@linaro.org \
--cc=gsi@denx.de \
--cc=linus.walleij@linaro.org \
--cc=linux-gpio@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).