This patch adds basic support for handling shared GPIO lines in the core. The line must be configured with a child node in DT. Based on the configuration the core will use different strategy to manage the shared line: refcounted low: Keep the line low as long as there is at least one low request is registered refcounted high: Keep the line high as long as there is at least one high request is registered pass through: all requests are allowed to go through without refcounting. The pass through mode is equivalent to how currently the GPIOD_FLAGS_BIT_NONEXCLUSIVE is handled. Signed-off-by: Peter Ujfalusi <peter.ujfalusi@xxxxxx> --- drivers/gpio/gpiolib-of.c | 28 ++++++-- drivers/gpio/gpiolib.c | 132 +++++++++++++++++++++++++++++++++++--- drivers/gpio/gpiolib.h | 10 +++ 3 files changed, 157 insertions(+), 13 deletions(-) diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c index bd06743a5d7c..fbb628e6d8bc 100644 --- a/drivers/gpio/gpiolib-of.c +++ b/drivers/gpio/gpiolib-of.c @@ -531,6 +531,7 @@ struct gpio_desc *of_find_gpio(struct device *dev, const char *con_id, * @lflags: bitmask of gpio_lookup_flags GPIO_* values - returned from * of_find_gpio() or of_parse_own_gpio() * @dflags: gpiod_flags - optional GPIO initialization flags + * @sflags: Extra flags for the shared GPIO usage * * Returns GPIO descriptor to use with Linux GPIO API, or one of the errno * value on the error condition. @@ -539,7 +540,8 @@ static struct gpio_desc *of_parse_own_gpio(struct device_node *np, struct gpio_chip *chip, unsigned int idx, const char **name, unsigned long *lflags, - enum gpiod_flags *dflags) + enum gpiod_flags *dflags, + unsigned long *sflags) { struct device_node *chip_np; enum of_gpio_flags xlate_flags; @@ -592,6 +594,15 @@ static struct gpio_desc *of_parse_own_gpio(struct device_node *np, return ERR_PTR(-EINVAL); } + if (sflags) { + *sflags = 0; + + if (of_property_read_bool(np, "refcounted-low")) + set_bit(FLAG_REFCOUNT_LOW, sflags); + else if (of_property_read_bool(np, "refcounted-high")) + set_bit(FLAG_REFCOUNT_HIGH, sflags); + } + if (name && of_property_read_string(np, "line-name", name)) *name = np->name; @@ -611,22 +622,29 @@ static int of_gpiochip_scan_gpios(struct gpio_chip *chip) struct gpio_desc *desc = NULL; struct device_node *np; const char *name; - unsigned long lflags; + unsigned long lflags, sflags; enum gpiod_flags dflags; unsigned int i; int ret; for_each_available_child_of_node(chip->of_node, np) { - if (!of_property_read_bool(np, "gpio-hog")) + bool is_hog = of_property_read_bool(np, "gpio-hog"); + bool is_shared = of_property_read_bool(np, "gpio-shared"); + if (!is_hog && !is_shared) continue; for (i = 0;; i++) { desc = of_parse_own_gpio(np, chip, i, &name, &lflags, - &dflags); + &dflags, + is_shared ? &sflags : NULL); if (IS_ERR(desc)) break; - ret = gpiod_hog(desc, name, lflags, dflags); + if (is_hog) + ret = gpiod_hog(desc, name, lflags, dflags); + if (is_shared) + ret = gpiod_share(desc, name, lflags, dflags, + sflags); if (ret < 0) { of_node_put(np); return ret; diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index dba5f08f308c..b01836cd9e58 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -81,7 +81,7 @@ LIST_HEAD(gpio_devices); static DEFINE_MUTEX(gpio_machine_hogs_mutex); static LIST_HEAD(gpio_machine_hogs); -static void gpiochip_free_hogs(struct gpio_chip *chip); +static void gpiochip_free_owns(struct gpio_chip *chip); static int gpiochip_add_irqchip(struct gpio_chip *gpiochip, struct lock_class_key *lock_key, struct lock_class_key *request_key); @@ -1558,7 +1558,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *chip, void *data, err_remove_acpi_chip: acpi_gpiochip_remove(chip); err_remove_of_chip: - gpiochip_free_hogs(chip); + gpiochip_free_owns(chip); of_gpiochip_remove(chip); err_free_gpiochip_mask: gpiochip_remove_pin_ranges(chip); @@ -1612,7 +1612,7 @@ void gpiochip_remove(struct gpio_chip *chip) /* FIXME: should the legacy sysfs handling be moved to gpio_device? */ gpiochip_sysfs_unregister(gdev); - gpiochip_free_hogs(chip); + gpiochip_free_owns(chip); /* Numb the device, cancelling all outstanding operations */ gdev->chip = NULL; gpiochip_irqchip_remove(chip); @@ -2788,6 +2788,13 @@ static int gpiod_request_commit(struct gpio_desc *desc, const char *label) if (test_and_set_bit(FLAG_REQUESTED, &desc->flags) == 0) { desc_set_label(desc, label ? : "?"); ret = 0; + } else if (test_bit(FLAG_IS_SHARED, &desc->flags)) { + desc->shared_users++; + pr_info("New user for shared GPIO line %d\n", + desc_to_gpio(desc)); + kfree_const(label); + ret = 0; + goto done; } else { kfree_const(label); ret = -EBUSY; @@ -2894,6 +2901,15 @@ static bool gpiod_free_commit(struct gpio_desc *desc) spin_lock_irqsave(&gpio_lock, flags); + if (test_bit(FLAG_IS_SHARED, &desc->flags) && desc->shared_users) { + if (--desc->shared_users) { + spin_unlock_irqrestore(&gpio_lock, flags); + pr_info("User dropped for shared GPIO line %d\n", + desc_to_gpio(desc)); + return true; + } + } + chip = desc->gdev->chip; if (chip && test_bit(FLAG_REQUESTED, &desc->flags)) { if (chip->free) { @@ -3126,10 +3142,44 @@ int gpiod_direction_input(struct gpio_desc *desc) } EXPORT_SYMBOL_GPL(gpiod_direction_input); +static int gpiod_get_refcounted_value(struct gpio_desc *desc, int value) +{ + value = !!value; + + if (!test_bit(FLAG_IS_SHARED, &desc->flags)) + return value; + + if (test_bit(FLAG_REFCOUNT_LOW, &desc->flags)) { + if (!value) + desc->level_refcount++; + else if (desc->level_refcount) + desc->level_refcount--; + + if (desc->level_refcount) + value = 0; + } else if (test_bit(FLAG_REFCOUNT_HIGH, &desc->flags)) { + if (value) + desc->level_refcount++; + else if (desc->level_refcount) + desc->level_refcount--; + + if (desc->level_refcount) + value = 1; + } + + pr_debug("Shared %s GPIO line %d: counter: %d: value: %d\n", + test_bit(FLAG_REFCOUNT_LOW, &desc->flags) ? "refcounted low" : + test_bit(FLAG_REFCOUNT_HIGH, &desc->flags) ? "refcounted high" : + "pass through", desc_to_gpio(desc), desc->level_refcount, + value); + + return value; +} + static int gpiod_direction_output_raw_commit(struct gpio_desc *desc, int value) { struct gpio_chip *gc = desc->gdev->chip; - int val = !!value; + int val; int ret = 0; /* @@ -3144,6 +3194,8 @@ static int gpiod_direction_output_raw_commit(struct gpio_desc *desc, int value) return -EIO; } + val = gpiod_get_refcounted_value(desc, value); + if (gc->direction_output) { ret = gc->direction_output(gc, gpio_chip_hwgpio(desc), val); } else { @@ -3665,6 +3717,7 @@ static void gpiod_set_raw_value_commit(struct gpio_desc *desc, bool value) struct gpio_chip *chip; chip = desc->gdev->chip; + value = gpiod_get_refcounted_value(desc, value); trace_gpio_value(desc_to_gpio(desc), 0, value); chip->set(chip, gpio_chip_hwgpio(desc), value); } @@ -4618,6 +4671,9 @@ int gpiod_configure_flags(struct gpio_desc *desc, const char *con_id, { int ret; + if (test_bit(FLAG_IS_SHARED, &desc->flags)) + goto out; + if (lflags & GPIO_ACTIVE_LOW) set_bit(FLAG_ACTIVE_LOW, &desc->flags); @@ -4659,6 +4715,7 @@ int gpiod_configure_flags(struct gpio_desc *desc, const char *con_id, return 0; } +out: /* Process flags */ if (dflags & GPIOD_FLAGS_BIT_DIR_OUT) ret = gpiod_direction_output(desc, @@ -4890,16 +4947,72 @@ int gpiod_hog(struct gpio_desc *desc, const char *name, } /** - * gpiochip_free_hogs - Scan gpio-controller chip and release GPIO hog + * gpiod_share - Share the specified GPIO desc given the provided flags + * @desc: gpio whose value will be assigned + * @name: gpio line name + * @lflags: bitmask of gpio_lookup_flags GPIO_* values - returned from + * of_find_gpio() or of_get_gpio_hog() + * @dflags: gpiod_flags - optional GPIO initialization flags + * @sflags: Extra flags for the shared GPIO usage + */ +int gpiod_share(struct gpio_desc *desc, const char *name, + unsigned long lflags, enum gpiod_flags dflags, + unsigned long sflags) +{ + struct gpio_chip *chip; + struct gpio_desc *local_desc; + int hwnum; + int ret; + + chip = gpiod_to_chip(desc); + hwnum = gpio_chip_hwgpio(desc); + + if (!(dflags & GPIOD_FLAGS_BIT_DIR_OUT)) { + pr_err("shared GPIO %s (chip %s, offset %d) must be output\n", + name, chip->label, hwnum); + return -EINVAL; + } + + local_desc = gpiochip_request_own_desc(chip, hwnum, name, + lflags, dflags); + if (IS_ERR(local_desc)) { + ret = PTR_ERR(local_desc); + pr_err("requesting shared GPIO %s (chip %s, offset %d) failed, %d\n", + name, chip->label, hwnum, ret); + return ret; + } + + /* Mark GPIO as shared and set refcounting level if not pass through */ + set_bit(FLAG_IS_SHARED, &desc->flags); + if (test_bit(FLAG_REFCOUNT_LOW, &sflags)) + set_bit(FLAG_REFCOUNT_LOW, &desc->flags); + else if (test_bit(FLAG_REFCOUNT_HIGH, &sflags)) + set_bit(FLAG_REFCOUNT_HIGH, &desc->flags); + + pr_info("GPIO line %d (%s) shared as %s\n", + desc_to_gpio(desc), name, + test_bit(FLAG_REFCOUNT_LOW, &desc->flags) ? "refcounted low" : + test_bit(FLAG_REFCOUNT_HIGH, &desc->flags) ? "refcounted high" : + "pass through"); + + return 0; +} + +/** + * gpiochip_free_owns - Scan gpio-controller chip and release hogged or shared + * GPIOs * @chip: gpio chip to act on */ -static void gpiochip_free_hogs(struct gpio_chip *chip) +static void gpiochip_free_owns(struct gpio_chip *chip) { int id; for (id = 0; id < chip->ngpio; id++) { if (test_bit(FLAG_IS_HOGGED, &chip->gpiodev->descs[id].flags)) gpiochip_free_own_desc(&chip->gpiodev->descs[id]); + + if (test_bit(FLAG_IS_SHARED, &chip->gpiodev->descs[id].flags)) + gpiochip_free_own_desc(&chip->gpiodev->descs[id]); } } @@ -5115,6 +5228,7 @@ static void gpiolib_dbg_show(struct seq_file *s, struct gpio_device *gdev) bool is_out; bool is_irq; bool active_low; + bool shared; for (i = 0; i < gdev->ngpio; i++, gpio++, gdesc++) { if (!test_bit(FLAG_REQUESTED, &gdesc->flags)) { @@ -5129,12 +5243,14 @@ static void gpiolib_dbg_show(struct seq_file *s, struct gpio_device *gdev) is_out = test_bit(FLAG_IS_OUT, &gdesc->flags); is_irq = test_bit(FLAG_USED_AS_IRQ, &gdesc->flags); active_low = test_bit(FLAG_ACTIVE_LOW, &gdesc->flags); - seq_printf(s, " gpio-%-3d (%-20.20s|%-20.20s) %s %s %s%s", + shared = test_bit(FLAG_IS_SHARED, &gdesc->flags); + seq_printf(s, " gpio-%-3d (%-20.20s|%-20.20s) %s %s %s%s%s", gpio, gdesc->name ? gdesc->name : "", gdesc->label, is_out ? "out" : "in ", chip->get ? (chip->get(chip, i) ? "hi" : "lo") : "? ", is_irq ? "IRQ " : "", - active_low ? "ACTIVE LOW" : ""); + active_low ? "ACTIVE LOW " : "", + shared ? "SHARED" : ""); seq_printf(s, "\n"); } } diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index ca9bc1e4803c..0eec0857e3a8 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -111,11 +111,18 @@ struct gpio_desc { #define FLAG_PULL_UP 13 /* GPIO has pull up enabled */ #define FLAG_PULL_DOWN 14 /* GPIO has pull down enabled */ #define FLAG_BIAS_DISABLE 15 /* GPIO has pull disabled */ +#define FLAG_IS_SHARED 16 /* GPIO is shared */ +#define FLAG_REFCOUNT_LOW 17 /* Shared GPIO is refcounted for raw low */ +#define FLAG_REFCOUNT_HIGH 18 /* Shared GPIO is refcounted for raw high */ /* Connection label */ const char *label; /* Name of the GPIO */ const char *name; + /* Number of users of a shared GPIO */ + int shared_users; + /* Reference counter for shared GPIO (low or high level) */ + int level_refcount; }; int gpiod_request(struct gpio_desc *desc, const char *label); @@ -124,6 +131,9 @@ int gpiod_configure_flags(struct gpio_desc *desc, const char *con_id, unsigned long lflags, enum gpiod_flags dflags); int gpiod_hog(struct gpio_desc *desc, const char *name, unsigned long lflags, enum gpiod_flags dflags); +int gpiod_share(struct gpio_desc *desc, const char *name, + unsigned long lflags, enum gpiod_flags dflags, + unsigned long sflags); /* * Return the GPIO number of the passed descriptor relative to its chip -- Peter Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki. Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki