From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.5 required=3.0 tests=DKIM_INVALID,DKIM_SIGNED, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_PATCH,MAILING_LIST_MULTI,SIGNED_OFF_BY, SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 23131C432C0 for ; Thu, 28 Nov 2019 03:42:55 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id D0409215A5 for ; Thu, 28 Nov 2019 03:42:54 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=fpond.eu header.i=@fpond.eu header.b="D93PJ1GN" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org D0409215A5 Authentication-Results: mail.kernel.org; dmarc=none (p=none dis=none) header.from=fpond.eu Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Received: from localhost ([::1]:45036 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1iaAhh-00015e-UA for qemu-devel@archiver.kernel.org; Wed, 27 Nov 2019 22:42:54 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:37172) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1iaAfn-0007mZ-4O for qemu-devel@nongnu.org; Wed, 27 Nov 2019 22:40:57 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1iaAfk-0006Mp-E0 for qemu-devel@nongnu.org; Wed, 27 Nov 2019 22:40:54 -0500 Received: from mo6-p03-ob.smtp.rzone.de ([2a01:238:20a:202:5303::12]:14931) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1iaAfi-0006IP-Ck for qemu-devel@nongnu.org; Wed, 27 Nov 2019 22:40:51 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; t=1574912447; s=strato-dkim-0002; d=fpond.eu; h=Subject:References:In-Reply-To:Message-ID:Cc:To:From:Date: X-RZG-CLASS-ID:X-RZG-AUTH:From:Subject:Sender; bh=9VZbf0Xs2EGNp4/lQMwBxAcCex+Qmz2/ZTDWaOMFVWE=; b=D93PJ1GN4gD3r9JE7pw+HEe4CZ4VNb4Jd9o0XwQsQjcSoYRnp+aafmeSVuI7vqi2Bq jDzgKQ0CyU3vpCxAHnQ8+Gi6W2UHrC0iCq5FGDv6igdPwCIDdNBXz8v5XRKBf19yDycU 3RF8vzTLJ1Aw5lN1Pn5LU+NSpmddDp1PtBHawGTC2mf/iymvNFH2Y/vV17h+jJaPtNi2 kMAkoZD2KdR/rtEife/8ts+MKlsrNFU3/ehIHzzdmMNH1lkDNYjsHJddW/riokSaqqko y/8WqICZ8dQdLBjY6alQoARAfhtKGBdhUEbjxOiwo5H1U04d10mVmAmmUeL/iHjlifAX +r7w== X-RZG-AUTH: ":OWANVUa4dPFUgKR/3dpvnYP0Np73amq+g13rqGzmt2bYDnKIKaws6YXTsc4=" X-RZG-CLASS-ID: mo00 Received: from oxapp04-01.back.ox.d0m.de by smtp-ox.front (RZmta 46.0.0 AUTH) with ESMTPSA id 604beevAS3el2J0 (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (curve X9_62_prime256v1 with 256 ECDH bits, eq. 3072 bits RSA)) (Client did not present a certificate); Thu, 28 Nov 2019 04:40:47 +0100 (CET) Date: Thu, 28 Nov 2019 04:40:47 +0100 (CET) From: Ulrich Hecht To: Geert Uytterhoeven , Linus Walleij , Bartosz Golaszewski , Jonathan Corbet , Rob Herring , Mark Rutland , Harish Jenny K N , Eugeniu Rosca Message-ID: <1945494371.1467708.1574912447486@webmail.strato.com> In-Reply-To: <20191127084253.16356-6-geert+renesas@glider.be> References: <20191127084253.16356-1-geert+renesas@glider.be> <20191127084253.16356-6-geert+renesas@glider.be> Subject: Re: [PATCH v3 5/7] gpio: Add GPIO Aggregator/Repeater driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 7bit X-Priority: 3 Importance: Normal X-Mailer: Open-Xchange Mailer v7.10.1-Rev22 X-Originating-IP: 112.198.74.215 X-Originating-Client: open-xchange-appsuite X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 2a01:238:20a:202:5303::12 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Peter Maydell , qemu-devel@nongnu.org, linux-doc@vger.kernel.org, Marc Zyngier , Magnus Damm , Christoffer Dall , linux-kernel@vger.kernel.org, linux-renesas-soc@vger.kernel.org, linux-gpio@vger.kernel.org, devicetree@vger.kernel.org, Alexander Graf , Paolo Bonzini , Phil Reid Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" Thank you for this series! > On November 27, 2019 at 9:42 AM Geert Uytterhoeven wrote: > > > GPIO controllers are exported to userspace using /dev/gpiochip* > character devices. Access control to these devices is provided by > standard UNIX file system permissions, on an all-or-nothing basis: > either a GPIO controller is accessible for a user, or it is not. > Currently no mechanism exists to control access to individual GPIOs. > > Hence add a GPIO driver to aggregate existing GPIOs, and expose them as > a new gpiochip. > > This supports the following use cases: > 1. Aggregating GPIOs using Sysfs > This is useful for implementing access control, and assigning a set > of GPIOs to a specific user or virtual machine. > > 2. GPIO Repeater in Device Tree > This supports modelling e.g. GPIO inverters in DT. > > 3. Generic GPIO Driver > This provides userspace access to a simple GPIO-operated device > described in DT, cfr. e.g. spidev for SPI-operated devices. > > Signed-off-by: Geert Uytterhoeven > --- > v3: > - Absorb GPIO forwarder, > - Integrate GPIO Repeater and Generic GPIO driver functionality, > - Use the aggregator parameters to create a GPIO lookup table instead > of an array of GPIO descriptors, which allows to simplify the code: > 1. This removes the need for calling gpio_name_to_desc(), > gpiochip_find(), gpiochip_get_desc(), and gpiod_request(), > 2. This allows the platform device to always use > devm_gpiod_get_index(), regardless of the origin of the GPIOs, > - Move parameter parsing from platform device probe to sysfs attribute > store, removing the need for platform data passing, > - Use more devm_*() functions to simplify cleanup, > - Add pr_fmt(), > - General refactoring. > > v2: > - Add missing initialization of i in gpio_virt_agg_probe(), > - Update for removed .need_valid_mask field and changed > .init_valid_mask() signature, > - Drop "virtual", rename to gpio-aggregator, > - Drop bogus FIXME related to gpiod_set_transitory() expectations, > - Use new GPIO Forwarder Helper, > - Lift limit on the maximum number of GPIOs, > - Improve parsing: > - add support for specifying GPIOs by line name, > - add support for specifying GPIO chips by ID, > - add support for GPIO offset ranges, > - names and offset specifiers must be separated by whitespace, > - GPIO offsets must separated by spaces, > - Use str_has_prefix() and kstrtouint(). > --- > drivers/gpio/Kconfig | 13 + > drivers/gpio/Makefile | 1 + > drivers/gpio/gpio-aggregator.c | 587 +++++++++++++++++++++++++++++++++ > 3 files changed, 601 insertions(+) > create mode 100644 drivers/gpio/gpio-aggregator.c > > diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig > index 8adffd42f8cb0559..36b6b57a6b05e906 100644 > --- a/drivers/gpio/Kconfig > +++ b/drivers/gpio/Kconfig > @@ -1507,6 +1507,19 @@ config GPIO_VIPERBOARD > > endmenu > > +config GPIO_AGGREGATOR > + tristate "GPIO Aggregator/Repeater" > + help > + Say yes here to enable the GPIO Aggregator and repeater, which Inconsistent capitalization (Aggregator vs. repeater). > + provides a way to aggregate and/or repeat existing GPIOs into a new > + GPIO device. > + This can serve the following purposes: > + 1. Assign a collection of GPIOs to a user, or export them to a > + virtual machine, > + 2. Support GPIOs that are connected to a physical inverter, > + 3. Provide a generic driver for a GPIO-operated device, to be > + controlled from userspace using the GPIO chardev interface. > + > config GPIO_MOCKUP > tristate "GPIO Testing Driver" > select IRQ_SIM > diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile > index 34eb8b2b12dd656c..f9971eeb1f32335f 100644 > --- a/drivers/gpio/Makefile > +++ b/drivers/gpio/Makefile > @@ -25,6 +25,7 @@ obj-$(CONFIG_GPIO_74XX_MMIO) += gpio-74xx-mmio.o > obj-$(CONFIG_GPIO_ADNP) += gpio-adnp.o > obj-$(CONFIG_GPIO_ADP5520) += gpio-adp5520.o > obj-$(CONFIG_GPIO_ADP5588) += gpio-adp5588.o > +obj-$(CONFIG_GPIO_AGGREGATOR) += gpio-aggregator.o > obj-$(CONFIG_GPIO_ALTERA_A10SR) += gpio-altera-a10sr.o > obj-$(CONFIG_GPIO_ALTERA) += gpio-altera.o > obj-$(CONFIG_GPIO_AMD8111) += gpio-amd8111.o > diff --git a/drivers/gpio/gpio-aggregator.c b/drivers/gpio/gpio-aggregator.c > new file mode 100644 > index 0000000000000000..873578c6f9683db8 > --- /dev/null > +++ b/drivers/gpio/gpio-aggregator.c > @@ -0,0 +1,587 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +// > +// GPIO Aggregator and Repeater > +// > +// Copyright (C) 2019 Glider bvba > + > +#define DRV_NAME "gpio-aggregator" > +#define pr_fmt(fmt) DRV_NAME ": " fmt > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include "gpiolib.h" > + > + > + /* > + * GPIO Aggregator sysfs interface > + */ Should this comment really be indented? > + > +struct gpio_aggregator { > + struct gpiod_lookup_table *lookups; > + struct platform_device *pdev; > + char args[]; > +}; > + > +static DEFINE_MUTEX(gpio_aggregator_lock); /* protects idr */ > +static DEFINE_IDR(gpio_aggregator_idr); > + > +static char *get_arg(char **args) > +{ > + char *start = *args, *end; > + > + while (isspace(*start)) > + start++; "start = skip_spaces(start);", perhaps. (Looking at a grep through drivers/, I would say usage of while(isspace(...))... vs. skip_spaces() is about 60/40.) > + > + if (!*start) > + return NULL; > + > + if (*start == '"') { > + /* Quoted arg */ > + end = strchr(++start, '"'); > + if (!end) > + return ERR_PTR(-EINVAL); > + } else { > + /* Unquoted arg */ > + for (end = start; *end && !isspace(*end); end++) ; > + } > + > + if (*end) > + *end++ = '\0'; > + > + *args = end; > + return start; > +} > + > +static bool isrange(const char *s) > +{ > + size_t n = strlen(s); > + > + if (IS_ERR_OR_NULL(s)) > + return false; > + > + while (1) { > + n = strspn(s, "0123456789"); > + if (!n) > + return false; > + > + s += n; > + > + switch (*s++) { > + case '\0': > + return true; > + > + case '-': > + case ',': > + break; > + > + default: > + return false; > + } > + } > +} > + > +static int aggr_add_gpio(struct gpio_aggregator *aggr, const char *label, > + int hwnum, unsigned int *n) > +{ > + struct gpiod_lookup_table *lookups; > + > + lookups = krealloc(aggr->lookups, struct_size(lookups, table, *n + 2), > + GFP_KERNEL); > + if (!lookups) > + return -ENOMEM; > + > + lookups->table[*n].chip_label = label; > + lookups->table[*n].chip_hwnum = hwnum; > + lookups->table[*n].idx = *n; > + > + (*n)++; > + memset(&lookups->table[*n], 0, sizeof(lookups->table[*n])); > + > + aggr->lookups = lookups; > + return 0; > +} > + > +static int aggr_parse(struct gpio_aggregator *aggr) > +{ > + char *name, *offsets, *first, *last, *next; > + unsigned int a, b, i, n = 0; Too many single-letter variables. "first_index" and "last_index" instead of "a" and "b" would make it easier to understand. > + char *args = aggr->args; > + int error; > + > + for (name = get_arg(&args), offsets = get_arg(&args); name; > + offsets = get_arg(&args)) { > + if (IS_ERR(name)) { > + pr_err("Cannot get GPIO specifier: %ld\n", > + PTR_ERR(name)); > + return PTR_ERR(name); > + } > + > + if (!isrange(offsets)) { > + /* Named GPIO line */ > + error = aggr_add_gpio(aggr, name, -1, &n); The "-1" magic could use a #define, IMO. > + if (error) > + return error; > + > + name = offsets; > + continue; > + } > + > + /* GPIO chip + offset(s) */ > + for (first = offsets; *first; first = next) { > + next = strchrnul(first, ','); > + if (*next) > + *next++ = '\0'; > + > + last = strchr(first, '-'); > + if (last) > + *last++ = '\0'; > + > + if (kstrtouint(first, 10, &a)) { > + pr_err("Cannot parse GPIO index %s\n", first); > + return -EINVAL; > + } > + > + if (!last) { > + b = a; > + } else if (kstrtouint(last, 10, &b)) { > + pr_err("Cannot parse GPIO index %s\n", last); > + return -EINVAL; > + } > + > + for (i = a; i <= b; i++) { > + error = aggr_add_gpio(aggr, name, i, &n); > + if (error) > + return error; > + } > + } > + > + name = get_arg(&args); > + } > + > + if (!n) { > + pr_err("No GPIOs specified\n"); > + return -EINVAL; > + } > + > + return 0; > +} > + > +static ssize_t new_device_store(struct device_driver *driver, const char *buf, > + size_t count) > +{ > + struct gpio_aggregator *aggr; > + struct platform_device *pdev; > + int res, id; > + > + /* kernfs guarantees string termination, so count + 1 is safe */ > + aggr = kzalloc(sizeof(*aggr) + count + 1, GFP_KERNEL); > + if (!aggr) > + return -ENOMEM; > + > + memcpy(aggr->args, buf, count + 1); > + > + aggr->lookups = kzalloc(struct_size(aggr->lookups, table, 1), > + GFP_KERNEL); > + if (!aggr->lookups) { > + res = -ENOMEM; > + goto free_ga; > + } > + > + mutex_lock(&gpio_aggregator_lock); > + id = idr_alloc(&gpio_aggregator_idr, aggr, 0, 0, GFP_KERNEL); > + mutex_unlock(&gpio_aggregator_lock); > + > + if (id < 0) { > + res = id; > + goto free_table; > + } > + > + aggr->lookups->dev_id = kasprintf(GFP_KERNEL, "%s.%d", DRV_NAME, id); > + if (!aggr->lookups) { > + res = -ENOMEM; > + goto remove_idr; > + } > + > + res = aggr_parse(aggr); > + if (res) > + goto free_dev_id; > + > + gpiod_add_lookup_table(aggr->lookups); > + > + pdev = platform_device_register_simple(DRV_NAME, id, NULL, 0); > + if (IS_ERR(pdev)) { > + res = PTR_ERR(pdev); > + goto remove_table; > + } > + > + aggr->pdev = pdev; > + return count; > + > +remove_table: > + gpiod_remove_lookup_table(aggr->lookups); > +free_dev_id: > + kfree(aggr->lookups->dev_id); > +remove_idr: > + mutex_lock(&gpio_aggregator_lock); > + idr_remove(&gpio_aggregator_idr, id); > + mutex_unlock(&gpio_aggregator_lock); > +free_table: > + kfree(aggr->lookups); > +free_ga: > + kfree(aggr); > + return res; > +} > + > +static DRIVER_ATTR_WO(new_device); > + > +static void gpio_aggregator_free(struct gpio_aggregator *aggr) > +{ > + platform_device_unregister(aggr->pdev); > + gpiod_remove_lookup_table(aggr->lookups); > + kfree(aggr->lookups->dev_id); > + kfree(aggr->lookups); > + kfree(aggr); > +} > + > +static ssize_t delete_device_store(struct device_driver *driver, > + const char *buf, size_t count) > +{ > + struct gpio_aggregator *aggr; > + unsigned int id; > + int error; > + > + if (!str_has_prefix(buf, DRV_NAME ".")) > + return -EINVAL; > + > + error = kstrtouint(buf + strlen(DRV_NAME "."), 10, &id); > + if (error) > + return error; > + > + mutex_lock(&gpio_aggregator_lock); > + aggr = idr_remove(&gpio_aggregator_idr, id); > + mutex_unlock(&gpio_aggregator_lock); > + if (!aggr) > + return -ENOENT; > + > + gpio_aggregator_free(aggr); > + return count; > +} > +static DRIVER_ATTR_WO(delete_device); > + > +static struct attribute *gpio_aggregator_attrs[] = { > + &driver_attr_new_device.attr, > + &driver_attr_delete_device.attr, > + NULL, > +}; > +ATTRIBUTE_GROUPS(gpio_aggregator); > + > +static int __exit gpio_aggregator_idr_remove(int id, void *p, void *data) > +{ > + gpio_aggregator_free(p); > + return 0; > +} > + > +static void __exit gpio_aggregator_remove_all(void) > +{ > + mutex_lock(&gpio_aggregator_lock); > + idr_for_each(&gpio_aggregator_idr, gpio_aggregator_idr_remove, NULL); > + idr_destroy(&gpio_aggregator_idr); > + mutex_unlock(&gpio_aggregator_lock); > +} > + > + > + /* > + * Common GPIO Forwarder > + */ > + > +struct gpiochip_fwd { > + struct gpio_chip chip; > + struct gpio_desc **descs; > + union { > + struct mutex mlock; /* protects tmp[] if can_sleep */ > + spinlock_t slock; /* protects tmp[] if !can_sleep */ > + }; > + unsigned long tmp[]; /* values and descs for multiple ops */ > +}; > + > +static int gpio_fwd_get_direction(struct gpio_chip *chip, unsigned int offset) > +{ > + struct gpiochip_fwd *fwd = gpiochip_get_data(chip); > + > + return gpiod_get_direction(fwd->descs[offset]); > +} > + > +static int gpio_fwd_direction_input(struct gpio_chip *chip, unsigned int offset) > +{ > + struct gpiochip_fwd *fwd = gpiochip_get_data(chip); > + > + return gpiod_direction_input(fwd->descs[offset]); > +} > + > +static int gpio_fwd_direction_output(struct gpio_chip *chip, > + unsigned int offset, int value) > +{ > + struct gpiochip_fwd *fwd = gpiochip_get_data(chip); > + > + return gpiod_direction_output(fwd->descs[offset], value); > +} > + > +static int gpio_fwd_get(struct gpio_chip *chip, unsigned int offset) > +{ > + struct gpiochip_fwd *fwd = gpiochip_get_data(chip); > + > + return gpiod_get_value(fwd->descs[offset]); > +} > + > +static int gpio_fwd_get_multiple(struct gpio_chip *chip, unsigned long *mask, > + unsigned long *bits) > +{ > + struct gpiochip_fwd *fwd = gpiochip_get_data(chip); > + unsigned long *values, flags; > + struct gpio_desc **descs; > + unsigned int i, j = 0; > + int error; > + > + if (chip->can_sleep) > + mutex_lock(&fwd->mlock); > + else > + spin_lock_irqsave(&fwd->slock, flags); > + > + values = &fwd->tmp[0]; > + bitmap_clear(values, 0, fwd->chip.ngpio); > + descs = (void *)&fwd->tmp[BITS_TO_LONGS(fwd->chip.ngpio)]; This use of the tmp array, which I presume serves to avoid extra memory allocations at the time of device creation, is odd enough to deserve a comment. > + > + for_each_set_bit(i, mask, fwd->chip.ngpio) > + descs[j++] = fwd->descs[i]; > + > + error = gpiod_get_array_value(j, descs, NULL, values); > + if (!error) { > + j = 0; > + for_each_set_bit(i, mask, fwd->chip.ngpio) > + __assign_bit(i, bits, test_bit(j++, values)); > + } > + > + if (chip->can_sleep) > + mutex_unlock(&fwd->mlock); > + else > + spin_unlock_irqrestore(&fwd->slock, flags); > + > + return error; > +} > + > +static void gpio_fwd_set(struct gpio_chip *chip, unsigned int offset, int value) > +{ > + struct gpiochip_fwd *fwd = gpiochip_get_data(chip); > + > + gpiod_set_value(fwd->descs[offset], value); > +} > + > +static void gpio_fwd_set_multiple(struct gpio_chip *chip, unsigned long *mask, > + unsigned long *bits) > +{ > + struct gpiochip_fwd *fwd = gpiochip_get_data(chip); > + unsigned long *values, flags; > + struct gpio_desc **descs; > + unsigned int i, j = 0; > + > + if (chip->can_sleep) > + mutex_lock(&fwd->mlock); > + else > + spin_lock_irqsave(&fwd->slock, flags); > + > + values = &fwd->tmp[0]; > + descs = (void *)&fwd->tmp[BITS_TO_LONGS(fwd->chip.ngpio)]; > + > + for_each_set_bit(i, mask, fwd->chip.ngpio) { > + __assign_bit(j, values, test_bit(i, bits)); > + descs[j++] = fwd->descs[i]; > + } > + > + gpiod_set_array_value(j, descs, NULL, values); > + > + if (chip->can_sleep) > + mutex_unlock(&fwd->mlock); > + else > + spin_unlock_irqrestore(&fwd->slock, flags); > +} > + > +static int gpio_fwd_set_config(struct gpio_chip *chip, unsigned int offset, > + unsigned long config) > +{ > + struct gpiochip_fwd *fwd = gpiochip_get_data(chip); > + > + chip = fwd->descs[offset]->gdev->chip; > + if (chip->set_config) > + return chip->set_config(chip, offset, config); > + > + return -ENOTSUPP; > +} > + > +static int gpio_fwd_init_valid_mask(struct gpio_chip *chip, > + unsigned long *valid_mask, > + unsigned int ngpios) > +{ > + struct gpiochip_fwd *fwd = gpiochip_get_data(chip); > + unsigned int i; > + > + for (i = 0; i < ngpios; i++) { > + if (!gpiochip_line_is_valid(fwd->descs[i]->gdev->chip, > + gpio_chip_hwgpio(fwd->descs[i]))) > + clear_bit(i, valid_mask); > + } > + > + return 0; > +} > + > +/** > + * gpiochip_fwd_create() - Create a new GPIO forwarder > + * @dev: Parent device pointer > + * @ngpios: Number of GPIOs in the forwarder. > + * @descs: Array containing the GPIO descriptors to forward to. > + * This array must contain @ngpios entries, and must not be deallocated > + * before the forwarder has been destroyed again. > + * > + * This function creates a new gpiochip, which forwards all GPIO operations to > + * the passed GPIO descriptors. > + * > + * Return: An opaque object pointer, or an ERR_PTR()-encoded negative error > + * code on failure. > + */ > +static struct gpiochip_fwd *gpiochip_fwd_create(struct device *dev, > + unsigned int ngpios, > + struct gpio_desc *descs[]) > +{ > + const char *label = dev_name(dev); > + struct gpiochip_fwd *fwd; > + struct gpio_chip *chip; > + unsigned int i; > + int error; > + > + fwd = devm_kzalloc(dev, struct_size(fwd, tmp, > + BITS_TO_LONGS(ngpios) + ngpios), GFP_KERNEL); > + if (!fwd) > + return ERR_PTR(-ENOMEM); > + > + chip = &fwd->chip; > + > + for (i = 0; i < ngpios; i++) { > + dev_dbg(dev, "gpio %u => gpio-%d (%s)\n", i, > + desc_to_gpio(descs[i]), descs[i]->label ? : "?"); > + > + if (gpiod_cansleep(descs[i])) > + chip->can_sleep = true; > + if (descs[i]->gdev->chip->set_config) > + chip->set_config = gpio_fwd_set_config; > + if (descs[i]->gdev->chip->init_valid_mask) > + chip->init_valid_mask = gpio_fwd_init_valid_mask; > + } > + > + chip->label = label; > + chip->parent = dev; > + chip->owner = THIS_MODULE; > + chip->get_direction = gpio_fwd_get_direction; > + chip->direction_input = gpio_fwd_direction_input; > + chip->direction_output = gpio_fwd_direction_output; > + chip->get = gpio_fwd_get; > + chip->get_multiple = gpio_fwd_get_multiple; > + chip->set = gpio_fwd_set; > + chip->set_multiple = gpio_fwd_set_multiple; > + chip->base = -1; > + chip->ngpio = ngpios; > + fwd->descs = descs; > + > + if (chip->can_sleep) > + mutex_init(&fwd->mlock); > + else > + spin_lock_init(&fwd->slock); > + > + error = devm_gpiochip_add_data(dev, chip, fwd); > + if (error) > + return ERR_PTR(error); > + > + return fwd; > +} > + > + > + /* > + * Common GPIO Aggregator/Repeater platform device > + */ > + > +static int gpio_aggregator_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct gpio_desc **descs; > + struct gpiochip_fwd *fwd; > + int i, n; > + > + n = gpiod_count(dev, NULL); > + if (n < 0) > + return n; > + > + descs = devm_kmalloc_array(dev, n, sizeof(*descs), GFP_KERNEL); > + if (!descs) > + return -ENOMEM; > + > + for (i = 0; i < n; i++) { > + descs[i] = devm_gpiod_get_index(dev, NULL, i, GPIOD_ASIS); > + if (IS_ERR(descs[i])) > + return PTR_ERR(descs[i]); > + } > + > + fwd = gpiochip_fwd_create(dev, n, descs); > + if (IS_ERR(fwd)) > + return PTR_ERR(fwd); > + > + platform_set_drvdata(pdev, fwd); > + return 0; > +} > + > +#ifdef CONFIG_OF > +static const struct of_device_id gpio_aggregator_dt_ids[] = { > + { .compatible = "gpio-repeater" }, > + /* > + * Add GPIO-operated devices controlled from userspace below, > + * or use "driver_override" in sysfs > + */ > + {}, > +}; > +MODULE_DEVICE_TABLE(of, gpio_aggregator_dt_ids); > +#endif > + > +static struct platform_driver gpio_aggregator_driver = { > + .probe = gpio_aggregator_probe, > + .driver = { > + .name = DRV_NAME, > + .groups = gpio_aggregator_groups, > + .of_match_table = of_match_ptr(gpio_aggregator_dt_ids), > + }, > +}; > + > +static int __init gpio_aggregator_init(void) > +{ > + return platform_driver_register(&gpio_aggregator_driver); > +} > +module_init(gpio_aggregator_init); > + > +static void __exit gpio_aggregator_exit(void) > +{ > + gpio_aggregator_remove_all(); > + platform_driver_unregister(&gpio_aggregator_driver); > +} > +module_exit(gpio_aggregator_exit); > + > +MODULE_AUTHOR("Geert Uytterhoeven "); > +MODULE_DESCRIPTION("GPIO Aggregator/Repeater"); > +MODULE_LICENSE("GPL v2"); > -- > 2.17.1 > CU Uli