From 2c6c34e0cabe1351c6b1894ba8093cac11882e71 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 3 Apr 2025 18:59:12 +0300 Subject: [PATCH 001/123] gpiolib: acpi: Improve struct acpi_gpio_info memory footprint The struct acpi_gpio_info has two boolean members that are located not close to each other making two gaps due to alignment requirements. Group them to improve memory footprint. `pahole` difference before and after (on 32-bit): - /* size: 36, cachelines: 1, members: 9 */ - /* sum members: 30, holes: 2, sum holes: 6 */ + /* size: 32, cachelines: 1, members: 9 */ + /* sum members: 30, holes: 1, sum holes: 2 */ Acked-by: Mika Westerberg Link: https://lore.kernel.org/r/20250403160034.2680485-2-andriy.shevchenko@linux.intel.com Signed-off-by: Andy Shevchenko --- drivers/gpio/gpiolib-acpi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c index 69caa35c58df..878b11c81c7b 100644 --- a/drivers/gpio/gpiolib-acpi.c +++ b/drivers/gpio/gpiolib-acpi.c @@ -96,10 +96,10 @@ struct acpi_gpio_chip { * @adev: reference to ACPI device which consumes GPIO resource * @flags: GPIO initialization flags * @gpioint: if %true this GPIO is of type GpioInt otherwise type is GpioIo + * @wake_capable: wake capability as provided by ACPI * @pin_config: pin bias as provided by ACPI * @polarity: interrupt polarity as provided by ACPI * @triggering: triggering type as provided by ACPI - * @wake_capable: wake capability as provided by ACPI * @debounce: debounce timeout as provided by ACPI * @quirks: Linux specific quirks as provided by struct acpi_gpio_mapping */ @@ -107,10 +107,10 @@ struct acpi_gpio_info { struct acpi_device *adev; enum gpiod_flags flags; bool gpioint; + bool wake_capable; int pin_config; int polarity; int triggering; - bool wake_capable; unsigned int debounce; unsigned int quirks; }; From d5ab616eb89659ef8f5c3a94eaf444992e34a2cd Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 3 Apr 2025 18:59:13 +0300 Subject: [PATCH 002/123] gpiolib: acpi: Remove index parameter from acpi_gpio_property_lookup() In all cases the supplied index is the same as the passed one in the struct acpi_gpio_lookup. Remove index parameter and reuse one from the struct acpi_gpio_lookup instead. add/remove: 0/0 grow/shrink: 0/2 up/down: 0/-14 (-14) Function old new delta acpi_get_gpiod_by_index 462 456 -6 acpi_gpio_property_lookup 425 417 -8 Total: Before=15375, After=15361, chg -0.09% Acked-by: Mika Westerberg Link: https://lore.kernel.org/r/20250403160034.2680485-3-andriy.shevchenko@linux.intel.com Signed-off-by: Andy Shevchenko --- drivers/gpio/gpiolib-acpi.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c index 878b11c81c7b..afeb8d1c7102 100644 --- a/drivers/gpio/gpiolib-acpi.c +++ b/drivers/gpio/gpiolib-acpi.c @@ -830,17 +830,17 @@ static int acpi_gpio_resource_lookup(struct acpi_gpio_lookup *lookup, return 0; } -static int acpi_gpio_property_lookup(struct fwnode_handle *fwnode, - const char *propname, int index, +static int acpi_gpio_property_lookup(struct fwnode_handle *fwnode, const char *propname, struct acpi_gpio_lookup *lookup) { struct fwnode_reference_args args; + unsigned int index = lookup->index; unsigned int quirks = 0; int ret; memset(&args, 0, sizeof(args)); - ret = __acpi_node_get_property_reference(fwnode, propname, index, 3, - &args); + + ret = __acpi_node_get_property_reference(fwnode, propname, index, 3, &args); if (ret) { struct acpi_device *adev; @@ -905,8 +905,7 @@ static struct gpio_desc *acpi_get_gpiod_by_index(struct acpi_device *adev, if (propname) { dev_dbg(&adev->dev, "GPIO: looking up %s\n", propname); - ret = acpi_gpio_property_lookup(acpi_fwnode_handle(adev), - propname, index, &lookup); + ret = acpi_gpio_property_lookup(acpi_fwnode_handle(adev), propname, &lookup); if (ret) return ERR_PTR(ret); @@ -955,7 +954,7 @@ static struct gpio_desc *acpi_get_gpiod_from_data(struct fwnode_handle *fwnode, memset(&lookup, 0, sizeof(lookup)); lookup.index = index; - ret = acpi_gpio_property_lookup(fwnode, propname, index, &lookup); + ret = acpi_gpio_property_lookup(fwnode, propname, &lookup); if (ret) return ERR_PTR(ret); From 1be1cd03a93339f14c8f4fe300bca321fddc6478 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 3 Apr 2025 18:59:14 +0300 Subject: [PATCH 003/123] gpiolib: acpi: Reduce memory footprint for struct acpi_gpio_params The line_index member in the struct acpi_gpio_params replicates what is covered in the ACPI GpioIo() or GpioInt() resource. The value there is limited to 16-bit one, so we don't really need to have a full 32-bit storage for it. Together with followed boolean the structure will be smaller. add/remove: 0/0 grow/shrink: 0/1 up/down: 0/-3 (-3) Function old new delta acpi_gpio_property_lookup 417 414 -3 Total: Before=15361, After=15358, chg -0.02% `pahole` difference before and after: - /* size: 12, cachelines: 1, members: 3 */ - /* padding: 3 */ + /* size: 8, cachelines: 1, members: 3 */ + /* padding: 1 */ Acked-by: Mika Westerberg Link: https://lore.kernel.org/r/20250403160034.2680485-4-andriy.shevchenko@linux.intel.com Signed-off-by: Andy Shevchenko --- include/linux/gpio/consumer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/gpio/consumer.h b/include/linux/gpio/consumer.h index 45b651c05b9c..899179972bec 100644 --- a/include/linux/gpio/consumer.h +++ b/include/linux/gpio/consumer.h @@ -587,7 +587,7 @@ struct gpio_desc *devm_fwnode_gpiod_get(struct device *dev, struct acpi_gpio_params { unsigned int crs_entry_index; - unsigned int line_index; + unsigned short line_index; bool active_low; }; From 8de54265eba0e7d7d6b0f46c0cff7a7b862a0df9 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 3 Apr 2025 18:59:15 +0300 Subject: [PATCH 004/123] gpiolib: acpi: Rename par to params for better readability Rename par to params for better readability in acpi_get_driver_gpio_data(). No functional changes intended. Requested-by: Bartosz Golaszewski Acked-by: Mika Westerberg Link: https://lore.kernel.org/r/20250403160034.2680485-5-andriy.shevchenko@linux.intel.com Signed-off-by: Andy Shevchenko --- drivers/gpio/gpiolib-acpi.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c index afeb8d1c7102..d6e9563d9364 100644 --- a/drivers/gpio/gpiolib-acpi.c +++ b/drivers/gpio/gpiolib-acpi.c @@ -653,12 +653,12 @@ static bool acpi_get_driver_gpio_data(struct acpi_device *adev, for (gm = adev->driver_gpios; gm->name; gm++) if (!strcmp(name, gm->name) && gm->data && index < gm->size) { - const struct acpi_gpio_params *par = gm->data + index; + const struct acpi_gpio_params *params = gm->data + index; args->fwnode = acpi_fwnode_handle(adev); - args->args[0] = par->crs_entry_index; - args->args[1] = par->line_index; - args->args[2] = par->active_low; + args->args[0] = params->crs_entry_index; + args->args[1] = params->line_index; + args->args[2] = params->active_low; args->nargs = 3; *quirks = gm->quirks; From bd0df44795053e53a25c521016d083311a05fae0 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 3 Apr 2025 18:59:16 +0300 Subject: [PATCH 005/123] gpiolib: acpi: Reuse struct acpi_gpio_params in struct acpi_gpio_lookup Some of the contents of struct acpi_gpio_lookup repeats what we have in the struct acpi_gpio_params. Reuse the latter in the former. Acked-by: Mika Westerberg Link: https://lore.kernel.org/r/20250403160034.2680485-6-andriy.shevchenko@linux.intel.com Signed-off-by: Andy Shevchenko --- drivers/gpio/gpiolib-acpi.c | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c index d6e9563d9364..f44d25df15cb 100644 --- a/drivers/gpio/gpiolib-acpi.c +++ b/drivers/gpio/gpiolib-acpi.c @@ -744,9 +744,7 @@ static int acpi_gpio_update_gpiod_lookup_flags(unsigned long *lookupflags, struct acpi_gpio_lookup { struct acpi_gpio_info info; - int index; - u16 pin_index; - bool active_low; + struct acpi_gpio_params params; struct gpio_desc *desc; int n; }; @@ -754,6 +752,7 @@ struct acpi_gpio_lookup { static int acpi_populate_gpio_lookup(struct acpi_resource *ares, void *data) { struct acpi_gpio_lookup *lookup = data; + struct acpi_gpio_params *params = &lookup->params; if (ares->type != ACPI_RESOURCE_TYPE_GPIO) return 1; @@ -765,12 +764,12 @@ static int acpi_populate_gpio_lookup(struct acpi_resource *ares, void *data) u16 pin_index; if (lookup->info.quirks & ACPI_GPIO_QUIRK_ONLY_GPIOIO && gpioint) - lookup->index++; + params->crs_entry_index++; - if (lookup->n++ != lookup->index) + if (lookup->n++ != params->crs_entry_index) return 1; - pin_index = lookup->pin_index; + pin_index = params->line_index; if (pin_index >= agpio->pin_table_length) return 1; @@ -796,7 +795,7 @@ static int acpi_populate_gpio_lookup(struct acpi_resource *ares, void *data) lookup->info.polarity = agpio->polarity; lookup->info.triggering = agpio->triggering; } else { - lookup->info.polarity = lookup->active_low; + lookup->info.polarity = params->active_low; } lookup->info.flags = acpi_gpio_to_gpiod_flags(agpio, lookup->info.polarity); @@ -834,7 +833,8 @@ static int acpi_gpio_property_lookup(struct fwnode_handle *fwnode, const char *p struct acpi_gpio_lookup *lookup) { struct fwnode_reference_args args; - unsigned int index = lookup->index; + struct acpi_gpio_params *params = &lookup->params; + unsigned int index = params->crs_entry_index; unsigned int quirks = 0; int ret; @@ -857,9 +857,9 @@ static int acpi_gpio_property_lookup(struct fwnode_handle *fwnode, const char *p if (args.nargs != 3) return -EPROTO; - lookup->index = args.args[0]; - lookup->pin_index = args.args[1]; - lookup->active_low = !!args.args[2]; + params->crs_entry_index = args.args[0]; + params->line_index = args.args[1]; + params->active_low = !!args.args[2]; lookup->info.adev = to_acpi_device_node(args.fwnode); lookup->info.quirks = quirks; @@ -897,10 +897,11 @@ static struct gpio_desc *acpi_get_gpiod_by_index(struct acpi_device *adev, struct acpi_gpio_info *info) { struct acpi_gpio_lookup lookup; + struct acpi_gpio_params *params = &lookup.params; int ret; memset(&lookup, 0, sizeof(lookup)); - lookup.index = index; + params->crs_entry_index = index; if (propname) { dev_dbg(&adev->dev, "GPIO: looking up %s\n", propname); @@ -909,11 +910,11 @@ static struct gpio_desc *acpi_get_gpiod_by_index(struct acpi_device *adev, if (ret) return ERR_PTR(ret); - dev_dbg(&adev->dev, "GPIO: _DSD returned %s %d %u %u\n", - dev_name(&lookup.info.adev->dev), lookup.index, - lookup.pin_index, lookup.active_low); + dev_dbg(&adev->dev, "GPIO: _DSD returned %s %u %u %u\n", + dev_name(&lookup.info.adev->dev), + params->crs_entry_index, params->line_index, params->active_low); } else { - dev_dbg(&adev->dev, "GPIO: looking up %d in _CRS\n", index); + dev_dbg(&adev->dev, "GPIO: looking up %u in _CRS\n", params->crs_entry_index); lookup.info.adev = adev; } @@ -943,6 +944,7 @@ static struct gpio_desc *acpi_get_gpiod_from_data(struct fwnode_handle *fwnode, struct acpi_gpio_info *info) { struct acpi_gpio_lookup lookup; + struct acpi_gpio_params *params = &lookup.params; int ret; if (!is_acpi_data_node(fwnode)) @@ -952,7 +954,7 @@ static struct gpio_desc *acpi_get_gpiod_from_data(struct fwnode_handle *fwnode, return ERR_PTR(-EINVAL); memset(&lookup, 0, sizeof(lookup)); - lookup.index = index; + params->crs_entry_index = index; ret = acpi_gpio_property_lookup(fwnode, propname, &lookup); if (ret) From 8b4f52ef7a412dd8e0b390ad4815bd74323f6f99 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 3 Apr 2025 18:59:17 +0300 Subject: [PATCH 006/123] gpiolib: acpi: Deduplicate some code in __acpi_find_gpio() __acpi_find_gpio() calls two functions depending on the supplied con_id and possibility to fallback to _CRS lookup. Those functions have the same pieces of code that can be done only in one place. Do it so. This gives an impressive shrink of the generated code for x86_64: add/remove: 0/2 grow/shrink: 0/4 up/down: 0/-1204 (-1204) Function old new delta acpi_find_gpio.__UNIQUE_ID_ddebug478 56 - -56 acpi_dev_gpio_irq_wake_get_by.__UNIQUE_ID_ddebug480 56 - -56 acpi_find_gpio 354 216 -138 acpi_get_gpiod_by_index 456 307 -149 __acpi_find_gpio 877 638 -239 acpi_dev_gpio_irq_wake_get_by 695 129 -566 Total: Before=15358, After=14154, chg -7.84% Acked-by: Mika Westerberg Link: https://lore.kernel.org/r/20250403160034.2680485-7-andriy.shevchenko@linux.intel.com Signed-off-by: Andy Shevchenko --- drivers/gpio/gpiolib-acpi.c | 101 +++++++++++++++++------------------- 1 file changed, 48 insertions(+), 53 deletions(-) diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c index f44d25df15cb..b3fcb9d5a39f 100644 --- a/drivers/gpio/gpiolib-acpi.c +++ b/drivers/gpio/gpiolib-acpi.c @@ -804,8 +804,7 @@ static int acpi_populate_gpio_lookup(struct acpi_resource *ares, void *data) return 1; } -static int acpi_gpio_resource_lookup(struct acpi_gpio_lookup *lookup, - struct acpi_gpio_info *info) +static int acpi_gpio_resource_lookup(struct acpi_gpio_lookup *lookup) { struct acpi_device *adev = lookup->info.adev; struct list_head res_list; @@ -824,8 +823,6 @@ static int acpi_gpio_resource_lookup(struct acpi_gpio_lookup *lookup, if (!lookup->desc) return -ENOENT; - if (info) - *info = lookup->info; return 0; } @@ -871,97 +868,83 @@ static int acpi_gpio_property_lookup(struct fwnode_handle *fwnode, const char *p * acpi_get_gpiod_by_index() - get a GPIO descriptor from device resources * @adev: pointer to a ACPI device to get GPIO from * @propname: Property name of the GPIO (optional) - * @index: index of GpioIo/GpioInt resource (starting from %0) - * @info: info pointer to fill in (optional) + * @lookup: pointer to struct acpi_gpio_lookup to fill in * - * Function goes through ACPI resources for @adev and based on @index looks + * Function goes through ACPI resources for @adev and based on @lookup.index looks * up a GpioIo/GpioInt resource, translates it to the Linux GPIO descriptor, - * and returns it. @index matches GpioIo/GpioInt resources only so if there - * are total %3 GPIO resources, the index goes from %0 to %2. + * and returns it. @lookup.index matches GpioIo/GpioInt resources only so if there + * are total 3 GPIO resources, the index goes from 0 to 2. * * If @propname is specified the GPIO is looked using device property. In * that case @index is used to select the GPIO entry in the property value * (in case of multiple). * * Returns: - * GPIO descriptor to use with Linux generic GPIO API. - * If the GPIO cannot be translated or there is an error an ERR_PTR is - * returned. + * 0 on success, negative errno on failure. + * + * The @lookup is filled with GPIO descriptor to use with Linux generic GPIO API. + * If the GPIO cannot be translated an error will be returned. * * Note: if the GPIO resource has multiple entries in the pin list, this * function only returns the first. */ -static struct gpio_desc *acpi_get_gpiod_by_index(struct acpi_device *adev, - const char *propname, - int index, - struct acpi_gpio_info *info) +static int acpi_get_gpiod_by_index(struct acpi_device *adev, const char *propname, + struct acpi_gpio_lookup *lookup) { - struct acpi_gpio_lookup lookup; - struct acpi_gpio_params *params = &lookup.params; + struct acpi_gpio_info *info = &lookup->info; + struct acpi_gpio_params *params = &lookup->params; int ret; - memset(&lookup, 0, sizeof(lookup)); - params->crs_entry_index = index; - if (propname) { dev_dbg(&adev->dev, "GPIO: looking up %s\n", propname); - ret = acpi_gpio_property_lookup(acpi_fwnode_handle(adev), propname, &lookup); + ret = acpi_gpio_property_lookup(acpi_fwnode_handle(adev), propname, lookup); if (ret) - return ERR_PTR(ret); + return ret; dev_dbg(&adev->dev, "GPIO: _DSD returned %s %u %u %u\n", - dev_name(&lookup.info.adev->dev), + dev_name(&info->adev->dev), params->crs_entry_index, params->line_index, params->active_low); } else { dev_dbg(&adev->dev, "GPIO: looking up %u in _CRS\n", params->crs_entry_index); - lookup.info.adev = adev; + info->adev = adev; } - ret = acpi_gpio_resource_lookup(&lookup, info); - return ret ? ERR_PTR(ret) : lookup.desc; + return acpi_gpio_resource_lookup(lookup); } /** * acpi_get_gpiod_from_data() - get a GPIO descriptor from ACPI data node * @fwnode: pointer to an ACPI firmware node to get the GPIO information from * @propname: Property name of the GPIO - * @index: index of GpioIo/GpioInt resource (starting from %0) - * @info: info pointer to fill in (optional) + * @lookup: pointer to struct acpi_gpio_lookup to fill in * * This function uses the property-based GPIO lookup to get to the GPIO * resource with the relevant information from a data-only ACPI firmware node * and uses that to obtain the GPIO descriptor to return. * * Returns: - * GPIO descriptor to use with Linux generic GPIO API. - * If the GPIO cannot be translated or there is an error an ERR_PTR is - * returned. + * 0 on success, negative errno on failure. + * + * The @lookup is filled with GPIO descriptor to use with Linux generic GPIO API. + * If the GPIO cannot be translated an error will be returned. */ -static struct gpio_desc *acpi_get_gpiod_from_data(struct fwnode_handle *fwnode, - const char *propname, - int index, - struct acpi_gpio_info *info) +static int acpi_get_gpiod_from_data(struct fwnode_handle *fwnode, const char *propname, + struct acpi_gpio_lookup *lookup) { - struct acpi_gpio_lookup lookup; - struct acpi_gpio_params *params = &lookup.params; int ret; if (!is_acpi_data_node(fwnode)) - return ERR_PTR(-ENODEV); + return -ENODEV; if (!propname) - return ERR_PTR(-EINVAL); + return -EINVAL; - memset(&lookup, 0, sizeof(lookup)); - params->crs_entry_index = index; - - ret = acpi_gpio_property_lookup(fwnode, propname, &lookup); + ret = acpi_gpio_property_lookup(fwnode, propname, lookup); if (ret) - return ERR_PTR(ret); + return ret; - ret = acpi_gpio_resource_lookup(&lookup, info); - return ret ? ERR_PTR(ret) : lookup.desc; + return acpi_gpio_resource_lookup(lookup); } static bool acpi_can_fallback_to_crs(struct acpi_device *adev, @@ -983,17 +966,24 @@ __acpi_find_gpio(struct fwnode_handle *fwnode, const char *con_id, unsigned int bool can_fallback, struct acpi_gpio_info *info) { struct acpi_device *adev = to_acpi_device_node(fwnode); + struct acpi_gpio_lookup lookup; struct gpio_desc *desc; char propname[32]; + int ret; + + memset(&lookup, 0, sizeof(lookup)); + lookup.params.crs_entry_index = idx; /* Try first from _DSD */ for_each_gpio_property_name(propname, con_id) { if (adev) - desc = acpi_get_gpiod_by_index(adev, - propname, idx, info); + ret = acpi_get_gpiod_by_index(adev, propname, &lookup); else - desc = acpi_get_gpiod_from_data(fwnode, - propname, idx, info); + ret = acpi_get_gpiod_from_data(fwnode, propname, &lookup); + if (ret) + continue; + + desc = lookup.desc; if (PTR_ERR(desc) == -EPROBE_DEFER) return desc; @@ -1002,8 +992,13 @@ __acpi_find_gpio(struct fwnode_handle *fwnode, const char *con_id, unsigned int } /* Then from plain _CRS GPIOs */ - if (can_fallback) - return acpi_get_gpiod_by_index(adev, NULL, idx, info); + if (can_fallback) { + ret = acpi_get_gpiod_by_index(adev, NULL, &lookup); + if (ret) + return ERR_PTR(ret); + + return lookup.desc; + } return ERR_PTR(-ENOENT); } From 92ac7de3175e3c17cbb76ae752b598cfb9dadf49 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 11 Mar 2025 15:19:51 +0100 Subject: [PATCH 007/123] gpiolib: don't allow setting values on input lines Some drivers as well as the character device and sysfs code check whether the line actually is in output mode before allowing the user to set a value. However, GPIO value setters now return integer values and can indicate failures. This allows us to move these checks into the core code. Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250311-gpio-set-check-output-v1-1-d971bca9e6fa@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-cdev.c | 3 --- drivers/gpio/gpiolib-sysfs.c | 12 +++++------- drivers/gpio/gpiolib.c | 12 ++++++++++++ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index 107d75558b5a..e6a289fa0f8f 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -1366,9 +1366,6 @@ static long linereq_set_values(struct linereq *lr, void __user *ip) /* scan requested lines to determine the subset to be set */ for (num_set = 0, i = 0; i < lr->num_lines; i++) { if (lv.mask & BIT_ULL(i)) { - /* setting inputs is not allowed */ - if (!test_bit(FLAG_IS_OUT, &lr->lines[i].desc->flags)) - return -EPERM; /* add to compacted values */ if (lv.bits & BIT_ULL(i)) __set_bit(num_set, vals); diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c index 1acfa43bf1ab..4a3aa09dad9d 100644 --- a/drivers/gpio/gpiolib-sysfs.c +++ b/drivers/gpio/gpiolib-sysfs.c @@ -134,16 +134,14 @@ static ssize_t value_store(struct device *dev, long value; status = kstrtol(buf, 0, &value); - - guard(mutex)(&data->mutex); - - if (!test_bit(FLAG_IS_OUT, &desc->flags)) - return -EPERM; - if (status) return status; - gpiod_set_value_cansleep(desc, value); + guard(mutex)(&data->mutex); + + status = gpiod_set_value_cansleep(desc, value); + if (status) + return status; return size; } diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index b8197502a5ac..8252a671d720 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -3593,6 +3593,9 @@ static int gpio_set_open_source_value_commit(struct gpio_desc *desc, bool value) static int gpiod_set_raw_value_commit(struct gpio_desc *desc, bool value) { + if (unlikely(!test_bit(FLAG_IS_OUT, &desc->flags))) + return -EPERM; + CLASS(gpio_chip_guard, guard)(desc); if (!guard.gc) return -ENODEV; @@ -3664,6 +3667,12 @@ int gpiod_set_array_value_complex(bool raw, bool can_sleep, if (!can_sleep) WARN_ON(array_info->gdev->can_sleep); + for (i = 0; i < array_size; i++) { + if (unlikely(!test_bit(FLAG_IS_OUT, + &desc_array[i]->flags))) + return -EPERM; + } + guard(srcu)(&array_info->gdev->srcu); gc = srcu_dereference(array_info->gdev->chip, &array_info->gdev->srcu); @@ -3723,6 +3732,9 @@ 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 (unlikely(!test_bit(FLAG_IS_OUT, &desc->flags))) + return -EPERM; + /* * Pins applicable for fast input but not for * fast output processing may have been already From 4e310626eb4df52a31a142c1360fead0fcbd3793 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 27 Mar 2025 08:49:44 +0800 Subject: [PATCH 008/123] gpiolib: of: Add polarity quirk for s5m8767 This is prepare patch for switching s5m8767 regulator driver to use GPIO descriptor. DTS for exynos5250 spring incorrectly specifies "active low" polarity for the DVS and DS line. But per datasheet, they are actually active high. So add polarity quirk for it. Signed-off-by: Peng Fan Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250327004945.563765-1-peng.fan@oss.nxp.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-of.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c index eb667f8f1ead..f29143c71e9d 100644 --- a/drivers/gpio/gpiolib-of.c +++ b/drivers/gpio/gpiolib-of.c @@ -222,6 +222,15 @@ static void of_gpio_try_fixup_polarity(const struct device_node *np, */ { "lantiq,pci-xway", "gpio-reset", false }, #endif +#if IS_ENABLED(CONFIG_REGULATOR_S5M8767) + /* + * According to S5M8767, the DVS and DS pin are + * active-high signals. However, exynos5250-spring.dts use + * active-low setting. + */ + { "samsung,s5m8767-pmic", "s5m8767,pmic-buck-dvs-gpios", true }, + { "samsung,s5m8767-pmic", "s5m8767,pmic-buck-ds-gpios", true }, +#endif #if IS_ENABLED(CONFIG_TOUCHSCREEN_TSC2005) /* * DTS for Nokia N900 incorrectly specified "active high" From f4271a891dca46ecf6813273ed63e502862f45da Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 24 Mar 2025 13:53:26 +0100 Subject: [PATCH 009/123] dt-bindings: gpio: Correct indentation and style in DTS example DTS example in the bindings should be indented with 2- or 4-spaces and aligned with opening '- |', so correct any differences like 3-spaces or mixtures 2- and 4-spaces in one binding. While re-indenting, drop unused labels. No functional changes here, but saves some comments during reviews of new patches built on existing code. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Sander Vanheule Reviewed-by: Manikandan Muralidharan Reviewed-by: Rob Herring (Arm) Reviewed-by: Geert Uytterhoeven Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250324125326.82270-1-krzysztof.kozlowski@linaro.org Signed-off-by: Bartosz Golaszewski --- .../bindings/gpio/atmel,at91rm9200-gpio.yaml | 16 ++--- .../bindings/gpio/fairchild,74hc595.yaml | 20 +++--- .../devicetree/bindings/gpio/gpio-mxs.yaml | 70 +++++++++---------- .../devicetree/bindings/gpio/nxp,pcf8575.yaml | 24 +++---- .../bindings/gpio/realtek,otto-gpio.yaml | 8 +-- .../bindings/gpio/renesas,em-gio.yaml | 20 +++--- .../bindings/gpio/renesas,rcar-gpio.yaml | 24 +++---- .../devicetree/bindings/gpio/sifive,gpio.yaml | 6 +- .../bindings/gpio/toshiba,gpio-visconti.yaml | 24 +++---- .../bindings/gpio/xlnx,gpio-xilinx.yaml | 48 ++++++------- 10 files changed, 130 insertions(+), 130 deletions(-) diff --git a/Documentation/devicetree/bindings/gpio/atmel,at91rm9200-gpio.yaml b/Documentation/devicetree/bindings/gpio/atmel,at91rm9200-gpio.yaml index 3dd70933ed8e..d810043b56b6 100644 --- a/Documentation/devicetree/bindings/gpio/atmel,at91rm9200-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/atmel,at91rm9200-gpio.yaml @@ -69,13 +69,13 @@ examples: #include gpio@fffff400 { - compatible = "atmel,at91rm9200-gpio"; - reg = <0xfffff400 0x200>; - interrupts = <2 IRQ_TYPE_LEVEL_HIGH 1>; - #gpio-cells = <2>; - gpio-controller; - interrupt-controller; - #interrupt-cells = <2>; - clocks = <&pmc PMC_TYPE_PERIPHERAL 2>; + compatible = "atmel,at91rm9200-gpio"; + reg = <0xfffff400 0x200>; + interrupts = <2 IRQ_TYPE_LEVEL_HIGH 1>; + #gpio-cells = <2>; + gpio-controller; + interrupt-controller; + #interrupt-cells = <2>; + clocks = <&pmc PMC_TYPE_PERIPHERAL 2>; }; ... diff --git a/Documentation/devicetree/bindings/gpio/fairchild,74hc595.yaml b/Documentation/devicetree/bindings/gpio/fairchild,74hc595.yaml index 0e5c22929bde..ab35bcf98101 100644 --- a/Documentation/devicetree/bindings/gpio/fairchild,74hc595.yaml +++ b/Documentation/devicetree/bindings/gpio/fairchild,74hc595.yaml @@ -71,15 +71,15 @@ unevaluatedProperties: false examples: - | spi { - #address-cells = <1>; - #size-cells = <0>; + #address-cells = <1>; + #size-cells = <0>; - gpio5: gpio5@0 { - compatible = "fairchild,74hc595"; - reg = <0>; - gpio-controller; - #gpio-cells = <2>; - registers-number = <4>; - spi-max-frequency = <100000>; - }; + gpio5@0 { + compatible = "fairchild,74hc595"; + reg = <0>; + gpio-controller; + #gpio-cells = <2>; + registers-number = <4>; + spi-max-frequency = <100000>; + }; }; diff --git a/Documentation/devicetree/bindings/gpio/gpio-mxs.yaml b/Documentation/devicetree/bindings/gpio/gpio-mxs.yaml index 8ff54369d16c..b58e08c8ecd8 100644 --- a/Documentation/devicetree/bindings/gpio/gpio-mxs.yaml +++ b/Documentation/devicetree/bindings/gpio/gpio-mxs.yaml @@ -84,52 +84,52 @@ examples: reg = <0x80018000 0x2000>; gpio@0 { - compatible = "fsl,imx28-gpio"; - reg = <0>; - interrupts = <127>; - gpio-controller; - #gpio-cells = <2>; - interrupt-controller; - #interrupt-cells = <2>; + compatible = "fsl,imx28-gpio"; + reg = <0>; + interrupts = <127>; + gpio-controller; + #gpio-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; }; gpio@1 { - compatible = "fsl,imx28-gpio"; - reg = <1>; - interrupts = <126>; - gpio-controller; - #gpio-cells = <2>; - interrupt-controller; - #interrupt-cells = <2>; + compatible = "fsl,imx28-gpio"; + reg = <1>; + interrupts = <126>; + gpio-controller; + #gpio-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; }; gpio@2 { - compatible = "fsl,imx28-gpio"; - reg = <2>; - interrupts = <125>; - gpio-controller; - #gpio-cells = <2>; - interrupt-controller; - #interrupt-cells = <2>; + compatible = "fsl,imx28-gpio"; + reg = <2>; + interrupts = <125>; + gpio-controller; + #gpio-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; }; gpio@3 { - compatible = "fsl,imx28-gpio"; - reg = <3>; - interrupts = <124>; - gpio-controller; - #gpio-cells = <2>; - interrupt-controller; - #interrupt-cells = <2>; + compatible = "fsl,imx28-gpio"; + reg = <3>; + interrupts = <124>; + gpio-controller; + #gpio-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; }; gpio@4 { - compatible = "fsl,imx28-gpio"; - reg = <4>; - interrupts = <123>; - gpio-controller; - #gpio-cells = <2>; - interrupt-controller; - #interrupt-cells = <2>; + compatible = "fsl,imx28-gpio"; + reg = <4>; + interrupts = <123>; + gpio-controller; + #gpio-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; }; }; diff --git a/Documentation/devicetree/bindings/gpio/nxp,pcf8575.yaml b/Documentation/devicetree/bindings/gpio/nxp,pcf8575.yaml index 8bca574bb66d..5a6ecaa7b44b 100644 --- a/Documentation/devicetree/bindings/gpio/nxp,pcf8575.yaml +++ b/Documentation/devicetree/bindings/gpio/nxp,pcf8575.yaml @@ -128,17 +128,17 @@ additionalProperties: false examples: - | i2c { - #address-cells = <1>; - #size-cells = <0>; + #address-cells = <1>; + #size-cells = <0>; - pcf8575: gpio@20 { - compatible = "nxp,pcf8575"; - reg = <0x20>; - interrupt-parent = <&irqpin2>; - interrupts = <3 0>; - gpio-controller; - #gpio-cells = <2>; - interrupt-controller; - #interrupt-cells = <2>; - }; + gpio@20 { + compatible = "nxp,pcf8575"; + reg = <0x20>; + interrupt-parent = <&irqpin2>; + interrupts = <3 0>; + gpio-controller; + #gpio-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + }; }; diff --git a/Documentation/devicetree/bindings/gpio/realtek,otto-gpio.yaml b/Documentation/devicetree/bindings/gpio/realtek,otto-gpio.yaml index 39fd959c45d2..728099c65824 100644 --- a/Documentation/devicetree/bindings/gpio/realtek,otto-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/realtek,otto-gpio.yaml @@ -81,7 +81,7 @@ dependencies: examples: - | - gpio@3500 { + gpio@3500 { compatible = "realtek,rtl8380-gpio", "realtek,otto-gpio"; reg = <0x3500 0x1c>; gpio-controller; @@ -91,9 +91,9 @@ examples: #interrupt-cells = <2>; interrupt-parent = <&rtlintc>; interrupts = <23>; - }; + }; - | - gpio@3300 { + gpio@3300 { compatible = "realtek,rtl9300-gpio", "realtek,otto-gpio"; reg = <0x3300 0x1c>, <0x3338 0x8>; gpio-controller; @@ -103,6 +103,6 @@ examples: #interrupt-cells = <2>; interrupt-parent = <&rtlintc>; interrupts = <13>; - }; + }; ... diff --git a/Documentation/devicetree/bindings/gpio/renesas,em-gio.yaml b/Documentation/devicetree/bindings/gpio/renesas,em-gio.yaml index 8bdef812c87c..49fb8f613ead 100644 --- a/Documentation/devicetree/bindings/gpio/renesas,em-gio.yaml +++ b/Documentation/devicetree/bindings/gpio/renesas,em-gio.yaml @@ -57,14 +57,14 @@ examples: - | #include gpio0: gpio@e0050000 { - compatible = "renesas,em-gio"; - reg = <0xe0050000 0x2c>, <0xe0050040 0x20>; - interrupts = , - ; - gpio-controller; - #gpio-cells = <2>; - gpio-ranges = <&pfc 0 0 32>; - ngpios = <32>; - interrupt-controller; - #interrupt-cells = <2>; + compatible = "renesas,em-gio"; + reg = <0xe0050000 0x2c>, <0xe0050040 0x20>; + interrupts = , + ; + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&pfc 0 0 32>; + ngpios = <32>; + interrupt-controller; + #interrupt-cells = <2>; }; diff --git a/Documentation/devicetree/bindings/gpio/renesas,rcar-gpio.yaml b/Documentation/devicetree/bindings/gpio/renesas,rcar-gpio.yaml index cc7a950a6030..d32e103a64aa 100644 --- a/Documentation/devicetree/bindings/gpio/renesas,rcar-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/renesas,rcar-gpio.yaml @@ -138,16 +138,16 @@ examples: #include #include gpio3: gpio@e6053000 { - compatible = "renesas,gpio-r8a77470", "renesas,rcar-gen2-gpio"; - reg = <0xe6053000 0x50>; - interrupts = ; - clocks = <&cpg CPG_MOD 909>; - power-domains = <&sysc R8A77470_PD_ALWAYS_ON>; - resets = <&cpg 909>; - gpio-controller; - #gpio-cells = <2>; - gpio-ranges = <&pfc 0 96 30>; - gpio-reserved-ranges = <17 10>; - interrupt-controller; - #interrupt-cells = <2>; + compatible = "renesas,gpio-r8a77470", "renesas,rcar-gen2-gpio"; + reg = <0xe6053000 0x50>; + interrupts = ; + clocks = <&cpg CPG_MOD 909>; + power-domains = <&sysc R8A77470_PD_ALWAYS_ON>; + resets = <&cpg 909>; + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&pfc 0 96 30>; + gpio-reserved-ranges = <17 10>; + interrupt-controller; + #interrupt-cells = <2>; }; diff --git a/Documentation/devicetree/bindings/gpio/sifive,gpio.yaml b/Documentation/devicetree/bindings/gpio/sifive,gpio.yaml index fc095646adea..4bdc201b719e 100644 --- a/Documentation/devicetree/bindings/gpio/sifive,gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/sifive,gpio.yaml @@ -76,8 +76,8 @@ additionalProperties: false examples: - | - #include - gpio@10060000 { + #include + gpio@10060000 { compatible = "sifive,fu540-c000-gpio", "sifive,gpio0"; interrupt-parent = <&plic>; interrupts = <7>, <8>, <9>, <10>, <11>, <12>, <13>, <14>, <15>, <16>, @@ -88,6 +88,6 @@ examples: #gpio-cells = <2>; interrupt-controller; #interrupt-cells = <2>; - }; + }; ... diff --git a/Documentation/devicetree/bindings/gpio/toshiba,gpio-visconti.yaml b/Documentation/devicetree/bindings/gpio/toshiba,gpio-visconti.yaml index b085450b527f..712063417bc8 100644 --- a/Documentation/devicetree/bindings/gpio/toshiba,gpio-visconti.yaml +++ b/Documentation/devicetree/bindings/gpio/toshiba,gpio-visconti.yaml @@ -48,22 +48,22 @@ additionalProperties: false examples: - | - #include - #include + #include + #include - soc { + soc { #address-cells = <2>; #size-cells = <2>; gpio: gpio@28020000 { - compatible = "toshiba,gpio-tmpv7708"; - reg = <0 0x28020000 0 0x1000>; - #gpio-cells = <0x2>; - gpio-ranges = <&pmux 0 0 32>; - gpio-controller; - interrupt-controller; - #interrupt-cells = <2>; - interrupt-parent = <&gic>; + compatible = "toshiba,gpio-tmpv7708"; + reg = <0 0x28020000 0 0x1000>; + #gpio-cells = <0x2>; + gpio-ranges = <&pmux 0 0 32>; + gpio-controller; + interrupt-controller; + #interrupt-cells = <2>; + interrupt-parent = <&gic>; }; - }; + }; ... diff --git a/Documentation/devicetree/bindings/gpio/xlnx,gpio-xilinx.yaml b/Documentation/devicetree/bindings/gpio/xlnx,gpio-xilinx.yaml index d3d8a2e143ed..8fbf12ca067e 100644 --- a/Documentation/devicetree/bindings/gpio/xlnx,gpio-xilinx.yaml +++ b/Documentation/devicetree/bindings/gpio/xlnx,gpio-xilinx.yaml @@ -126,29 +126,29 @@ examples: - | #include - gpio@a0020000 { - compatible = "xlnx,xps-gpio-1.00.a"; - reg = <0xa0020000 0x10000>; - #gpio-cells = <2>; - #interrupt-cells = <0x2>; - clocks = <&zynqmp_clk 71>; - gpio-controller; - interrupt-controller; - interrupt-names = "ip2intc_irpt"; - interrupt-parent = <&gic>; - interrupts = <0 89 4>; - xlnx,all-inputs = <0x0>; - xlnx,all-inputs-2 = <0x0>; - xlnx,all-outputs = <0x0>; - xlnx,all-outputs-2 = <0x0>; - xlnx,dout-default = <0x0>; - xlnx,dout-default-2 = <0x0>; - xlnx,gpio-width = <0x20>; - xlnx,gpio2-width = <0x20>; - xlnx,interrupt-present = <0x1>; - xlnx,is-dual = <0x1>; - xlnx,tri-default = <0xFFFFFFFF>; - xlnx,tri-default-2 = <0xFFFFFFFF>; - }; + gpio@a0020000 { + compatible = "xlnx,xps-gpio-1.00.a"; + reg = <0xa0020000 0x10000>; + #gpio-cells = <2>; + #interrupt-cells = <0x2>; + clocks = <&zynqmp_clk 71>; + gpio-controller; + interrupt-controller; + interrupt-names = "ip2intc_irpt"; + interrupt-parent = <&gic>; + interrupts = <0 89 4>; + xlnx,all-inputs = <0x0>; + xlnx,all-inputs-2 = <0x0>; + xlnx,all-outputs = <0x0>; + xlnx,all-outputs-2 = <0x0>; + xlnx,dout-default = <0x0>; + xlnx,dout-default-2 = <0x0>; + xlnx,gpio-width = <0x20>; + xlnx,gpio2-width = <0x20>; + xlnx,interrupt-present = <0x1>; + xlnx,is-dual = <0x1>; + xlnx,tri-default = <0xFFFFFFFF>; + xlnx,tri-default-2 = <0xFFFFFFFF>; + }; ... From ac7d33f3899762b5d0c90a51a96ea11b6d7660ba Mon Sep 17 00:00:00 2001 From: Emanuele Ghidoli Date: Mon, 31 Mar 2025 09:26:44 +0200 Subject: [PATCH 010/123] dt-bindings: gpio: pca95xx: add Toradex ecgpiol16 The Toradex ecgpiol16 is a 16-bit I2C I/O expander implemented using a small MCU. Its register interface and behavior are compatible with the PCAL6416. Signed-off-by: Emanuele Ghidoli Signed-off-by: Francesco Dolcini Acked-by: Conor Dooley Link: https://lore.kernel.org/r/20250331072644.17921-1-francesco@dolcini.it Signed-off-by: Bartosz Golaszewski --- Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml b/Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml index 7b1eb08fa055..4d3f52f8d1b8 100644 --- a/Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml +++ b/Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml @@ -16,6 +16,9 @@ description: |+ properties: compatible: oneOf: + - items: + - const: toradex,ecgpiol16 + - const: nxp,pcal6416 - items: - const: diodes,pi4ioe5v6534q - const: nxp,pcal6534 @@ -132,6 +135,7 @@ allOf: - maxim,max7325 - maxim,max7326 - maxim,max7327 + - toradex,ecgpiol16 then: properties: reset-gpios: false From 338893af8e0d442abcd9f3f56a17fd37d60142c3 Mon Sep 17 00:00:00 2001 From: Nikolaos Pasaloukos Date: Thu, 27 Mar 2025 11:27:04 +0000 Subject: [PATCH 011/123] dt-bindings: Document Blaize BLZP1600 GPIO driver This is a custom silicon GPIO driver provided by VeriSilicon Microelectronics. It has 32 input/output ports which can be configured as edge or level triggered interrupts. It also provides a de-bounce feature. This controller is used on the Blaize BLZP1600 SoC. Signed-off-by: Nikolaos Pasaloukos Reviewed-by: Conor Dooley Link: https://lore.kernel.org/r/20250327-kernel-upstreaming-add_gpio_support-v2-1-bbe51f8d66da@blaize.com Signed-off-by: Bartosz Golaszewski --- .../bindings/gpio/blaize,blzp1600-gpio.yaml | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 Documentation/devicetree/bindings/gpio/blaize,blzp1600-gpio.yaml diff --git a/Documentation/devicetree/bindings/gpio/blaize,blzp1600-gpio.yaml b/Documentation/devicetree/bindings/gpio/blaize,blzp1600-gpio.yaml new file mode 100644 index 000000000000..a05f6ea619c3 --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/blaize,blzp1600-gpio.yaml @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/gpio/blaize,blzp1600-gpio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Blaize BLZP1600 GPIO controller + +description: + Blaize BLZP1600 GPIO controller is an implementation of the VeriSilicon + APB GPIO v0.2 IP block. It has 32 ports each of which are intended to be + represented as child nodes with the generic GPIO-controller properties + as described in this binding's file. + +maintainers: + - Nikolaos Pasaloukos + - James Cowgill + - Matt Redfearn + - Neil Jones + +properties: + $nodename: + pattern: "^gpio@[0-9a-f]+$" + + compatible: + enum: + - blaize,blzp1600-gpio + + reg: + maxItems: 1 + + gpio-controller: true + + '#gpio-cells': + const: 2 + + ngpios: + default: 32 + minimum: 1 + maximum: 32 + + interrupts: + maxItems: 1 + + gpio-line-names: true + + interrupt-controller: true + + '#interrupt-cells': + const: 2 + +required: + - compatible + - reg + - gpio-controller + - '#gpio-cells' + +dependencies: + interrupt-controller: [ interrupts ] + +additionalProperties: false + +examples: + - | + #include + + gpio: gpio@4c0000 { + compatible = "blaize,blzp1600-gpio"; + reg = <0x004c0000 0x1000>; + gpio-controller; + #gpio-cells = <2>; + ngpios = <32>; + interrupt-controller; + #interrupt-cells = <2>; + interrupts = ; + }; +... From 602ae04f56542d8ed76eb5f9cdf688850e71a5f6 Mon Sep 17 00:00:00 2001 From: Nikolaos Pasaloukos Date: Thu, 27 Mar 2025 11:27:05 +0000 Subject: [PATCH 012/123] gpio: Enable Blaize BLZP1600 GPIO support Blaize BLZP1600 GPIO controller is provided by VeriSilicon Microelectronics based on the GPIO APB v0.2 design. It has 32 input/output ports which can be configured as edge or level triggered interrupts. It also provides a de-bounce feature. This controller is used on the Blaize BLZP1600 SoC. Reviewed-by: Linus Walleij Signed-off-by: Nikolaos Pasaloukos Link: https://lore.kernel.org/r/20250327-kernel-upstreaming-add_gpio_support-v2-2-bbe51f8d66da@blaize.com Signed-off-by: Bartosz Golaszewski --- MAINTAINERS | 10 ++ drivers/gpio/Kconfig | 11 ++ drivers/gpio/Makefile | 1 + drivers/gpio/gpio-blzp1600.c | 283 +++++++++++++++++++++++++++++++++++ 4 files changed, 305 insertions(+) create mode 100644 drivers/gpio/gpio-blzp1600.c diff --git a/MAINTAINERS b/MAINTAINERS index 96b827049501..e0a00b6ede3b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4195,6 +4195,16 @@ F: Documentation/ABI/stable/sysfs-class-bluetooth F: include/net/bluetooth/ F: net/bluetooth/ +BLZP1600 GPIO DRIVER +M: James Cowgill +M: Matt Redfearn +M: Neil Jones +M: Nikolaos Pasaloukos +L: linux-gpio@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/gpio/blaize,blzp1600-gpio.yaml +F: drivers/gpio/gpio-blzp1600.c + BONDING DRIVER M: Jay Vosburgh L: netdev@vger.kernel.org diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index f2c39bbff83a..3244b478d078 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -213,6 +213,17 @@ config GPIO_BCM_XGS_IPROC help Say yes here to enable GPIO support for Broadcom XGS iProc SoCs. +config GPIO_BLZP1600 + tristate "Blaize BLZP1600 GPIO support" + default y if ARCH_BLAIZE + depends on OF_GPIO + select GPIO_GENERIC + select GPIOLIB_IRQCHIP + help + Say Y or M here to add support for the Blaize BLZP1600 GPIO device. + The controller is based on the Verisilicon Microelectronics GPIO APB v0.2 + IP block. + config GPIO_BRCMSTB tristate "BRCMSTB GPIO support" default y if (ARCH_BRCMSTB || BMIPS_GENERIC) diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index af130882ffee..8661cfd8fd8c 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -45,6 +45,7 @@ obj-$(CONFIG_GPIO_BCM_XGS_IPROC) += gpio-xgs-iproc.o obj-$(CONFIG_GPIO_BD71815) += gpio-bd71815.o obj-$(CONFIG_GPIO_BD71828) += gpio-bd71828.o obj-$(CONFIG_GPIO_BD9571MWV) += gpio-bd9571mwv.o +obj-$(CONFIG_GPIO_BLZP1600) += gpio-blzp1600.o obj-$(CONFIG_GPIO_BRCMSTB) += gpio-brcmstb.o obj-$(CONFIG_GPIO_BT8XX) += gpio-bt8xx.o obj-$(CONFIG_GPIO_CADENCE) += gpio-cadence.o diff --git a/drivers/gpio/gpio-blzp1600.c b/drivers/gpio/gpio-blzp1600.c new file mode 100644 index 000000000000..77ad0e596f3e --- /dev/null +++ b/drivers/gpio/gpio-blzp1600.c @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2019 VeriSilicon Limited. + * Copyright (C) 2025 Blaize, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GPIO_DIR_REG 0x00 +#define GPIO_CTRL_REG 0x04 +#define GPIO_SET_REG 0x08 +#define GPIO_CLR_REG 0x0C +#define GPIO_ODATA_REG 0x10 +#define GPIO_IDATA_REG 0x14 +#define GPIO_IEN_REG 0x18 +#define GPIO_IS_REG 0x1C +#define GPIO_IBE_REG 0x20 +#define GPIO_IEV_REG 0x24 +#define GPIO_RIS_REG 0x28 +#define GPIO_IM_REG 0x2C +#define GPIO_MIS_REG 0x30 +#define GPIO_IC_REG 0x34 +#define GPIO_DB_REG 0x38 +#define GPIO_DFG_REG 0x3C + +#define DRIVER_NAME "blzp1600-gpio" + +struct blzp1600_gpio { + void __iomem *base; + struct gpio_chip gc; + int irq; +}; + +static inline struct blzp1600_gpio *get_blzp1600_gpio_from_irq_data(struct irq_data *d) +{ + return gpiochip_get_data(irq_data_get_irq_chip_data(d)); +} + +static inline struct blzp1600_gpio *get_blzp1600_gpio_from_irq_desc(struct irq_desc *d) +{ + return gpiochip_get_data(irq_desc_get_handler_data(d)); +} + +static inline u32 blzp1600_gpio_read(struct blzp1600_gpio *chip, unsigned int offset) +{ + return readl_relaxed(chip->base + offset); +} + +static inline void blzp1600_gpio_write(struct blzp1600_gpio *chip, unsigned int offset, u32 val) +{ + writel_relaxed(val, chip->base + offset); +} + +static inline void blzp1600_gpio_rmw(void __iomem *reg, u32 mask, bool set) +{ + u32 val = readl_relaxed(reg); + + if (set) + val |= mask; + else + val &= ~mask; + + writel_relaxed(val, reg); +} + +static void blzp1600_gpio_irq_mask(struct irq_data *d) +{ + struct blzp1600_gpio *chip = get_blzp1600_gpio_from_irq_data(d); + + guard(raw_spinlock_irqsave)(&chip->gc.bgpio_lock); + blzp1600_gpio_rmw(chip->base + GPIO_IM_REG, BIT(d->hwirq), 1); +} + +static void blzp1600_gpio_irq_unmask(struct irq_data *d) +{ + struct blzp1600_gpio *chip = get_blzp1600_gpio_from_irq_data(d); + + guard(raw_spinlock_irqsave)(&chip->gc.bgpio_lock); + blzp1600_gpio_rmw(chip->base + GPIO_IM_REG, BIT(d->hwirq), 0); +} + +static void blzp1600_gpio_irq_ack(struct irq_data *d) +{ + struct blzp1600_gpio *chip = get_blzp1600_gpio_from_irq_data(d); + + blzp1600_gpio_write(chip, GPIO_IC_REG, BIT(d->hwirq)); +} + +static void blzp1600_gpio_irq_enable(struct irq_data *d) +{ + struct blzp1600_gpio *chip = get_blzp1600_gpio_from_irq_data(d); + + gpiochip_enable_irq(&chip->gc, irqd_to_hwirq(d)); + + guard(raw_spinlock_irqsave)(&chip->gc.bgpio_lock); + blzp1600_gpio_rmw(chip->base + GPIO_DIR_REG, BIT(d->hwirq), 0); + blzp1600_gpio_rmw(chip->base + GPIO_IEN_REG, BIT(d->hwirq), 1); +} + +static void blzp1600_gpio_irq_disable(struct irq_data *d) +{ + struct blzp1600_gpio *chip = get_blzp1600_gpio_from_irq_data(d); + + guard(raw_spinlock_irqsave)(&chip->gc.bgpio_lock); + blzp1600_gpio_rmw(chip->base + GPIO_IEN_REG, BIT(d->hwirq), 0); + gpiochip_disable_irq(&chip->gc, irqd_to_hwirq(d)); +} + +static int blzp1600_gpio_irq_set_type(struct irq_data *d, u32 type) +{ + struct blzp1600_gpio *chip = get_blzp1600_gpio_from_irq_data(d); + u32 edge_level, single_both, fall_rise; + int mask = BIT(d->hwirq); + + guard(raw_spinlock_irqsave)(&chip->gc.bgpio_lock); + edge_level = blzp1600_gpio_read(chip, GPIO_IS_REG); + single_both = blzp1600_gpio_read(chip, GPIO_IBE_REG); + fall_rise = blzp1600_gpio_read(chip, GPIO_IEV_REG); + + switch (type) { + case IRQ_TYPE_EDGE_BOTH: + edge_level &= ~mask; + single_both |= mask; + break; + case IRQ_TYPE_EDGE_RISING: + edge_level &= ~mask; + single_both &= ~mask; + fall_rise |= mask; + break; + case IRQ_TYPE_EDGE_FALLING: + edge_level &= ~mask; + single_both &= ~mask; + fall_rise &= ~mask; + break; + case IRQ_TYPE_LEVEL_HIGH: + edge_level |= mask; + fall_rise |= mask; + break; + case IRQ_TYPE_LEVEL_LOW: + edge_level |= mask; + fall_rise &= ~mask; + break; + default: + return -EINVAL; + } + + blzp1600_gpio_write(chip, GPIO_IS_REG, edge_level); + blzp1600_gpio_write(chip, GPIO_IBE_REG, single_both); + blzp1600_gpio_write(chip, GPIO_IEV_REG, fall_rise); + + if (type & IRQ_TYPE_LEVEL_MASK) + irq_set_handler_locked(d, handle_level_irq); + else + irq_set_handler_locked(d, handle_edge_irq); + + return 0; +} + +static const struct irq_chip blzp1600_gpio_irqchip = { + .name = DRIVER_NAME, + .irq_ack = blzp1600_gpio_irq_ack, + .irq_mask = blzp1600_gpio_irq_mask, + .irq_unmask = blzp1600_gpio_irq_unmask, + .irq_set_type = blzp1600_gpio_irq_set_type, + .irq_enable = blzp1600_gpio_irq_enable, + .irq_disable = blzp1600_gpio_irq_disable, + .flags = IRQCHIP_IMMUTABLE | IRQCHIP_MASK_ON_SUSPEND, + GPIOCHIP_IRQ_RESOURCE_HELPERS, +}; + +static void blzp1600_gpio_irqhandler(struct irq_desc *desc) +{ + struct blzp1600_gpio *gpio = get_blzp1600_gpio_from_irq_desc(desc); + struct irq_chip *irqchip = irq_desc_get_chip(desc); + unsigned long irq_status; + int hwirq = 0; + + chained_irq_enter(irqchip, desc); + irq_status = blzp1600_gpio_read(gpio, GPIO_RIS_REG); + for_each_set_bit(hwirq, &irq_status, gpio->gc.ngpio) + generic_handle_domain_irq(gpio->gc.irq.domain, hwirq); + + chained_irq_exit(irqchip, desc); +} + +static int blzp1600_gpio_set_debounce(struct gpio_chip *gc, unsigned int offset, + unsigned int debounce) +{ + struct blzp1600_gpio *chip = gpiochip_get_data(gc); + + guard(raw_spinlock_irqsave)(&chip->gc.bgpio_lock); + blzp1600_gpio_rmw(chip->base + GPIO_DB_REG, BIT(offset), debounce); + + return 0; +} + +static int blzp1600_gpio_set_config(struct gpio_chip *gc, unsigned int offset, unsigned long config) +{ + u32 debounce; + + if (pinconf_to_config_param(config) != PIN_CONFIG_INPUT_DEBOUNCE) + return -ENOTSUPP; + + debounce = pinconf_to_config_argument(config); + return blzp1600_gpio_set_debounce(gc, offset, debounce); +} + +static int blzp1600_gpio_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct blzp1600_gpio *chip; + struct gpio_chip *gc; + int ret; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(chip->base)) + return PTR_ERR(chip->base); + + ret = bgpio_init(&chip->gc, &pdev->dev, 4, chip->base + GPIO_IDATA_REG, + chip->base + GPIO_SET_REG, chip->base + GPIO_CLR_REG, + chip->base + GPIO_DIR_REG, NULL, 0); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to register generic gpio\n"); + + /* configure the gpio chip */ + gc = &chip->gc; + gc->set_config = blzp1600_gpio_set_config; + + if (of_property_read_bool(node, "interrupt-controller")) { + struct gpio_irq_chip *girq; + + chip->irq = platform_get_irq(pdev, 0); + if (chip->irq < 0) + return chip->irq; + + girq = &gc->irq; + gpio_irq_chip_set_chip(girq, &blzp1600_gpio_irqchip); + girq->parent_handler = blzp1600_gpio_irqhandler; + girq->num_parents = 1; + girq->parents = devm_kcalloc(&pdev->dev, 1, sizeof(*girq->parents), GFP_KERNEL); + if (!girq->parents) + return -ENOMEM; + + girq->parents[0] = chip->irq; + girq->default_type = IRQ_TYPE_NONE; + } + + return devm_gpiochip_add_data(&pdev->dev, gc, chip); +} + +static const struct of_device_id blzp1600_gpio_of_match[] = { + { .compatible = "blaize,blzp1600-gpio", }, + { /* Sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, blzp1600_gpio_of_match); + +static struct platform_driver blzp1600_gpio_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(blzp1600_gpio_of_match), + }, + .probe = blzp1600_gpio_probe, +}; + +module_platform_driver(blzp1600_gpio_driver); + +MODULE_AUTHOR("Nikolaos Pasaloukos "); +MODULE_DESCRIPTION("Blaize BLZP1600 GPIO driver"); +MODULE_LICENSE("GPL"); From 27986833e8e675b6c17654d13623590a46f9033e Mon Sep 17 00:00:00 2001 From: Yixun Lan Date: Tue, 8 Apr 2025 23:11:20 +0800 Subject: [PATCH 013/123] gpiolib: support parsing gpio three-cell interrupts scheme gpio irq which using three-cell scheme should always call instance_match() function to find the correct irqdomain. The select() function will be called with !DOMAIN_BUS_ANY, so for specific gpio irq driver, it need to set bus token explicitly, something like: irq_domain_update_bus_token(girq->domain, DOMAIN_BUS_WIRED); Signed-off-by: Yixun Lan Link: https://lore.kernel.org/r/20250408-04-gpio-irq-threecell-v4-1-fd170d5e2d2b@gentoo.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-of.c | 8 ++++++++ drivers/gpio/gpiolib-of.h | 6 ++++++ drivers/gpio/gpiolib.c | 22 ++++++++++++++++++---- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c index f29143c71e9d..3651c4178b81 100644 --- a/drivers/gpio/gpiolib-of.c +++ b/drivers/gpio/gpiolib-of.c @@ -1285,3 +1285,11 @@ void of_gpiochip_remove(struct gpio_chip *chip) { of_node_put(dev_of_node(&chip->gpiodev->dev)); } + +bool of_gpiochip_instance_match(struct gpio_chip *gc, unsigned int index) +{ + if (gc->of_node_instance_match) + return gc->of_node_instance_match(gc, index); + + return false; +} diff --git a/drivers/gpio/gpiolib-of.h b/drivers/gpio/gpiolib-of.h index 16d6ac8cb156..3eebfac290c5 100644 --- a/drivers/gpio/gpiolib-of.h +++ b/drivers/gpio/gpiolib-of.h @@ -22,6 +22,7 @@ struct gpio_desc *of_find_gpio(struct device_node *np, unsigned long *lookupflags); int of_gpiochip_add(struct gpio_chip *gc); void of_gpiochip_remove(struct gpio_chip *gc); +bool of_gpiochip_instance_match(struct gpio_chip *gc, unsigned int index); int of_gpio_count(const struct fwnode_handle *fwnode, const char *con_id); #else static inline struct gpio_desc *of_find_gpio(struct device_node *np, @@ -33,6 +34,11 @@ static inline struct gpio_desc *of_find_gpio(struct device_node *np, } static inline int of_gpiochip_add(struct gpio_chip *gc) { return 0; } static inline void of_gpiochip_remove(struct gpio_chip *gc) { } +static inline bool of_gpiochip_instance_match(struct gpio_chip *gc, + unsigned int index) +{ + return false; +} static inline int of_gpio_count(const struct fwnode_handle *fwnode, const char *con_id) { diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 8252a671d720..ed8397a88dea 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -1507,9 +1507,8 @@ static int gpiochip_hierarchy_irq_domain_translate(struct irq_domain *d, unsigned int *type) { /* We support standard DT translation */ - if (is_of_node(fwspec->fwnode) && fwspec->param_count == 2) { - return irq_domain_translate_twocell(d, fwspec, hwirq, type); - } + if (is_of_node(fwspec->fwnode)) + return irq_domain_translate_twothreecell(d, fwspec, hwirq, type); /* This is for board files and others not using DT */ if (is_fwnode_irqchip(fwspec->fwnode)) { @@ -1811,11 +1810,26 @@ static void gpiochip_irq_unmap(struct irq_domain *d, unsigned int irq) irq_set_chip_data(irq, NULL); } +static int gpiochip_irq_select(struct irq_domain *d, struct irq_fwspec *fwspec, + enum irq_domain_bus_token bus_token) +{ + struct fwnode_handle *fwnode = fwspec->fwnode; + struct gpio_chip *gc = d->host_data; + unsigned int index = fwspec->param[0]; + + if (fwspec->param_count == 3 && is_of_node(fwnode)) + return of_gpiochip_instance_match(gc, index); + + /* Fallback for twocells */ + return (fwnode && (d->fwnode == fwnode) && (d->bus_token == bus_token)); +} + static const struct irq_domain_ops gpiochip_domain_ops = { .map = gpiochip_irq_map, .unmap = gpiochip_irq_unmap, + .select = gpiochip_irq_select, /* Virtually all GPIO irqchips are twocell:ed */ - .xlate = irq_domain_xlate_twocell, + .xlate = irq_domain_xlate_twothreecell, }; static struct irq_domain *gpiochip_simple_create_domain(struct gpio_chip *gc) From 265daffe788aa1cc5925d0afcde4fe6e99c66638 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 7 Apr 2025 09:08:14 +0200 Subject: [PATCH 014/123] gpio: provide gpiod_is_equal() There are users in the kernel that directly compare raw GPIO descriptor pointers in order to determine whether they refer to the same physical GPIO pin. This accidentally works like this but is not guaranteed by any API contract. Let's provide a comparator function that hides the actual logic. Reviewed-by: Mark Brown Link: https://lore.kernel.org/r/20250407-gpiod-is-equal-v1-1-7d85f568ae6e@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 14 ++++++++++++++ include/linux/gpio/consumer.h | 9 +++++++++ 2 files changed, 23 insertions(+) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index b8197502a5ac..2e5b6982e76d 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -265,6 +265,20 @@ struct gpio_device *gpiod_to_gpio_device(struct gpio_desc *desc) } EXPORT_SYMBOL_GPL(gpiod_to_gpio_device); +/** + * gpiod_is_equal() - Check if two GPIO descriptors refer to the same pin. + * @desc: Descriptor to compare. + * @other: The second descriptor to compare against. + * + * Returns: + * True if the descriptors refer to the same physical pin. False otherwise. + */ +bool gpiod_is_equal(struct gpio_desc *desc, struct gpio_desc *other) +{ + return desc == other; +} +EXPORT_SYMBOL_GPL(gpiod_is_equal); + /** * gpio_device_get_base() - Get the base GPIO number allocated by this device * @gdev: GPIO device diff --git a/include/linux/gpio/consumer.h b/include/linux/gpio/consumer.h index 45b651c05b9c..7355abadaef4 100644 --- a/include/linux/gpio/consumer.h +++ b/include/linux/gpio/consumer.h @@ -180,6 +180,8 @@ struct gpio_desc *devm_fwnode_gpiod_get_index(struct device *dev, enum gpiod_flags flags, const char *label); +bool gpiod_is_equal(struct gpio_desc *desc, struct gpio_desc *other); + #else /* CONFIG_GPIOLIB */ #include @@ -547,6 +549,13 @@ struct gpio_desc *devm_fwnode_gpiod_get_index(struct device *dev, return ERR_PTR(-ENOSYS); } +static inline bool +gpiod_is_equal(struct gpio_desc *desc, struct gpio_desc *other) +{ + WARN_ON(desc || other); + return false; +} + #endif /* CONFIG_GPIOLIB */ #if IS_ENABLED(CONFIG_GPIOLIB) && IS_ENABLED(CONFIG_HTE) From 7a56efeabffd13a162073068b8e29113c65f9e64 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Mon, 7 Apr 2025 13:30:11 +0900 Subject: [PATCH 015/123] gpio: aggregator: reorder functions to prepare for configfs introduction Reorder functions in drivers/gpio/gpio-aggregator.c to prepare for the configfs-based interface additions in subsequent commits. Arrange the code so that the configfs implementations will appear above the existing sysfs-specific code, since the latter will partly depend on the configfs interface implementations when it starts to expose the settings to configfs. The order in drivers/gpio/gpio-aggregator.c will be as follows: * Basic gpio_aggregator/gpio_aggregator_line representations * Common utility functions * GPIO Forwarder implementations * Configfs interface implementations * Sysfs interface implementations * Platform device implementations * Module init/exit implementations This separate commit ensures a clean diff for the subsequent commits. No functional change. Reviewed-by: Geert Uytterhoeven Signed-off-by: Koichiro Den Link: https://lore.kernel.org/r/20250407043019.4105613-2-koichiro.den@canonical.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-aggregator.c | 380 +++++++++++++++++---------------- 1 file changed, 192 insertions(+), 188 deletions(-) diff --git a/drivers/gpio/gpio-aggregator.c b/drivers/gpio/gpio-aggregator.c index d232ea865356..e026deb4ac64 100644 --- a/drivers/gpio/gpio-aggregator.c +++ b/drivers/gpio/gpio-aggregator.c @@ -61,194 +61,6 @@ static int aggr_add_gpio(struct gpio_aggregator *aggr, const char *key, return 0; } -static int aggr_parse(struct gpio_aggregator *aggr) -{ - char *args = skip_spaces(aggr->args); - char *name, *offsets, *p; - unsigned int i, n = 0; - int error = 0; - - unsigned long *bitmap __free(bitmap) = - bitmap_alloc(AGGREGATOR_MAX_GPIOS, GFP_KERNEL); - if (!bitmap) - return -ENOMEM; - - args = next_arg(args, &name, &p); - while (*args) { - args = next_arg(args, &offsets, &p); - - p = get_options(offsets, 0, &error); - if (error == 0 || *p) { - /* Named GPIO line */ - error = aggr_add_gpio(aggr, name, U16_MAX, &n); - if (error) - return error; - - name = offsets; - continue; - } - - /* GPIO chip + offset(s) */ - error = bitmap_parselist(offsets, bitmap, AGGREGATOR_MAX_GPIOS); - if (error) { - pr_err("Cannot parse %s: %d\n", offsets, error); - return error; - } - - for_each_set_bit(i, bitmap, AGGREGATOR_MAX_GPIOS) { - error = aggr_add_gpio(aggr, name, i, &n); - if (error) - return error; - } - - args = next_arg(args, &name, &p); - } - - 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; - - if (!try_module_get(THIS_MODULE)) - return -ENOENT; - - /* kernfs guarantees string termination, so count + 1 is safe */ - aggr = kzalloc(sizeof(*aggr) + count + 1, GFP_KERNEL); - if (!aggr) { - res = -ENOMEM; - goto put_module; - } - - 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->dev_id) { - 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; - module_put(THIS_MODULE); - 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); -put_module: - module_put(THIS_MODULE); - 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; - - if (!try_module_get(THIS_MODULE)) - return -ENOENT; - - mutex_lock(&gpio_aggregator_lock); - aggr = idr_remove(&gpio_aggregator_idr, id); - mutex_unlock(&gpio_aggregator_lock); - if (!aggr) { - module_put(THIS_MODULE); - return -ENOENT; - } - - gpio_aggregator_free(aggr); - module_put(THIS_MODULE); - 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); -} - /* * GPIO Forwarder @@ -583,6 +395,184 @@ static struct gpiochip_fwd *gpiochip_fwd_create(struct device *dev, } +/* + * Sysfs interface + */ +static int aggr_parse(struct gpio_aggregator *aggr) +{ + char *args = skip_spaces(aggr->args); + char *name, *offsets, *p; + unsigned int i, n = 0; + int error = 0; + + unsigned long *bitmap __free(bitmap) = + bitmap_alloc(AGGREGATOR_MAX_GPIOS, GFP_KERNEL); + if (!bitmap) + return -ENOMEM; + + args = next_arg(args, &name, &p); + while (*args) { + args = next_arg(args, &offsets, &p); + + p = get_options(offsets, 0, &error); + if (error == 0 || *p) { + /* Named GPIO line */ + error = aggr_add_gpio(aggr, name, U16_MAX, &n); + if (error) + return error; + + name = offsets; + continue; + } + + /* GPIO chip + offset(s) */ + error = bitmap_parselist(offsets, bitmap, AGGREGATOR_MAX_GPIOS); + if (error) { + pr_err("Cannot parse %s: %d\n", offsets, error); + return error; + } + + for_each_set_bit(i, bitmap, AGGREGATOR_MAX_GPIOS) { + error = aggr_add_gpio(aggr, name, i, &n); + if (error) + return error; + } + + args = next_arg(args, &name, &p); + } + + 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; + + if (!try_module_get(THIS_MODULE)) + return -ENOENT; + + /* kernfs guarantees string termination, so count + 1 is safe */ + aggr = kzalloc(sizeof(*aggr) + count + 1, GFP_KERNEL); + if (!aggr) { + res = -ENOMEM; + goto put_module; + } + + 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->dev_id) { + 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; + module_put(THIS_MODULE); + 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); +put_module: + module_put(THIS_MODULE); + 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; + + if (!try_module_get(THIS_MODULE)) + return -ENOENT; + + mutex_lock(&gpio_aggregator_lock); + aggr = idr_remove(&gpio_aggregator_idr, id); + mutex_unlock(&gpio_aggregator_lock); + if (!aggr) { + module_put(THIS_MODULE); + return -ENOENT; + } + + gpio_aggregator_free(aggr); + module_put(THIS_MODULE); + 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); + + /* * GPIO Aggregator platform device */ @@ -640,6 +630,20 @@ static struct platform_driver gpio_aggregator_driver = { }, }; +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); +} + static int __init gpio_aggregator_init(void) { return platform_driver_register(&gpio_aggregator_driver); From 7616dd97ae22e5f69b24774455673d183d4191c9 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Mon, 7 Apr 2025 13:30:12 +0900 Subject: [PATCH 016/123] gpio: aggregator: unify function naming Unify function names to use gpio_aggregator_ prefix (except GPIO forwarder implementations, which remain unchanged in subsequent commits). While at it, rename the pre-existing gpio_aggregator_free() to gpio_aggregator_destory(), since that name will be used by new alloc/free functions introduced in the next commit, for which the name is more appropriate. No functional change. Signed-off-by: Koichiro Den Link: https://lore.kernel.org/r/20250407043019.4105613-3-koichiro.den@canonical.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-aggregator.c | 37 ++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/drivers/gpio/gpio-aggregator.c b/drivers/gpio/gpio-aggregator.c index e026deb4ac64..ff5cd5eaa550 100644 --- a/drivers/gpio/gpio-aggregator.c +++ b/drivers/gpio/gpio-aggregator.c @@ -42,8 +42,8 @@ struct gpio_aggregator { static DEFINE_MUTEX(gpio_aggregator_lock); /* protects idr */ static DEFINE_IDR(gpio_aggregator_idr); -static int aggr_add_gpio(struct gpio_aggregator *aggr, const char *key, - int hwnum, unsigned int *n) +static int gpio_aggregator_add_gpio(struct gpio_aggregator *aggr, + const char *key, int hwnum, unsigned int *n) { struct gpiod_lookup_table *lookups; @@ -398,7 +398,7 @@ static struct gpiochip_fwd *gpiochip_fwd_create(struct device *dev, /* * Sysfs interface */ -static int aggr_parse(struct gpio_aggregator *aggr) +static int gpio_aggregator_parse(struct gpio_aggregator *aggr) { char *args = skip_spaces(aggr->args); char *name, *offsets, *p; @@ -417,7 +417,7 @@ static int aggr_parse(struct gpio_aggregator *aggr) p = get_options(offsets, 0, &error); if (error == 0 || *p) { /* Named GPIO line */ - error = aggr_add_gpio(aggr, name, U16_MAX, &n); + error = gpio_aggregator_add_gpio(aggr, name, U16_MAX, &n); if (error) return error; @@ -433,7 +433,7 @@ static int aggr_parse(struct gpio_aggregator *aggr) } for_each_set_bit(i, bitmap, AGGREGATOR_MAX_GPIOS) { - error = aggr_add_gpio(aggr, name, i, &n); + error = gpio_aggregator_add_gpio(aggr, name, i, &n); if (error) return error; } @@ -449,8 +449,8 @@ static int aggr_parse(struct gpio_aggregator *aggr) return 0; } -static ssize_t new_device_store(struct device_driver *driver, const char *buf, - size_t count) +static ssize_t gpio_aggregator_new_device_store(struct device_driver *driver, + const char *buf, size_t count) { struct gpio_aggregator *aggr; struct platform_device *pdev; @@ -490,7 +490,7 @@ static ssize_t new_device_store(struct device_driver *driver, const char *buf, goto remove_idr; } - res = aggr_parse(aggr); + res = gpio_aggregator_parse(aggr); if (res) goto free_dev_id; @@ -523,9 +523,10 @@ static ssize_t new_device_store(struct device_driver *driver, const char *buf, return res; } -static DRIVER_ATTR_WO(new_device); +static struct driver_attribute driver_attr_gpio_aggregator_new_device = + __ATTR(new_device, 0200, NULL, gpio_aggregator_new_device_store); -static void gpio_aggregator_free(struct gpio_aggregator *aggr) +static void gpio_aggregator_destroy(struct gpio_aggregator *aggr) { platform_device_unregister(aggr->pdev); gpiod_remove_lookup_table(aggr->lookups); @@ -534,8 +535,8 @@ static void gpio_aggregator_free(struct gpio_aggregator *aggr) kfree(aggr); } -static ssize_t delete_device_store(struct device_driver *driver, - const char *buf, size_t count) +static ssize_t gpio_aggregator_delete_device_store(struct device_driver *driver, + const char *buf, size_t count) { struct gpio_aggregator *aggr; unsigned int id; @@ -559,15 +560,17 @@ static ssize_t delete_device_store(struct device_driver *driver, return -ENOENT; } - gpio_aggregator_free(aggr); + gpio_aggregator_destroy(aggr); module_put(THIS_MODULE); return count; } -static DRIVER_ATTR_WO(delete_device); + +static struct driver_attribute driver_attr_gpio_aggregator_delete_device = + __ATTR(delete_device, 0200, NULL, gpio_aggregator_delete_device_store); static struct attribute *gpio_aggregator_attrs[] = { - &driver_attr_new_device.attr, - &driver_attr_delete_device.attr, + &driver_attr_gpio_aggregator_new_device.attr, + &driver_attr_gpio_aggregator_delete_device.attr, NULL }; ATTRIBUTE_GROUPS(gpio_aggregator); @@ -632,7 +635,7 @@ static struct platform_driver gpio_aggregator_driver = { static int __exit gpio_aggregator_idr_remove(int id, void *p, void *data) { - gpio_aggregator_free(p); + gpio_aggregator_destroy(p); return 0; } From 88fe1d1a646b3b01dcc335c44e7b33ea510f620e Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Mon, 7 Apr 2025 13:30:13 +0900 Subject: [PATCH 017/123] gpio: aggregator: add gpio_aggregator_{alloc,free}() Prepare for the upcoming configfs interface. These functions will be used by both the existing sysfs interface and the new configfs interface, reducing code duplication. No functional change. Signed-off-by: Koichiro Den Link: https://lore.kernel.org/r/20250407043019.4105613-4-koichiro.den@canonical.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-aggregator.c | 58 +++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/drivers/gpio/gpio-aggregator.c b/drivers/gpio/gpio-aggregator.c index ff5cd5eaa550..6f933a76b2b9 100644 --- a/drivers/gpio/gpio-aggregator.c +++ b/drivers/gpio/gpio-aggregator.c @@ -36,12 +36,41 @@ struct gpio_aggregator { struct gpiod_lookup_table *lookups; struct platform_device *pdev; + int id; char args[]; }; static DEFINE_MUTEX(gpio_aggregator_lock); /* protects idr */ static DEFINE_IDR(gpio_aggregator_idr); +static int gpio_aggregator_alloc(struct gpio_aggregator **aggr, size_t arg_size) +{ + int ret; + + struct gpio_aggregator *new __free(kfree) = kzalloc( + sizeof(*new) + arg_size, GFP_KERNEL); + if (!new) + return -ENOMEM; + + scoped_guard(mutex, &gpio_aggregator_lock) + ret = idr_alloc(&gpio_aggregator_idr, new, 0, 0, GFP_KERNEL); + + if (ret < 0) + return ret; + + new->id = ret; + *aggr = no_free_ptr(new); + return 0; +} + +static void gpio_aggregator_free(struct gpio_aggregator *aggr) +{ + scoped_guard(mutex, &gpio_aggregator_lock) + idr_remove(&gpio_aggregator_idr, aggr->id); + + kfree(aggr); +} + static int gpio_aggregator_add_gpio(struct gpio_aggregator *aggr, const char *key, int hwnum, unsigned int *n) { @@ -454,17 +483,15 @@ static ssize_t gpio_aggregator_new_device_store(struct device_driver *driver, { struct gpio_aggregator *aggr; struct platform_device *pdev; - int res, id; + int res; if (!try_module_get(THIS_MODULE)) return -ENOENT; /* kernfs guarantees string termination, so count + 1 is safe */ - aggr = kzalloc(sizeof(*aggr) + count + 1, GFP_KERNEL); - if (!aggr) { - res = -ENOMEM; + res = gpio_aggregator_alloc(&aggr, count + 1); + if (res) goto put_module; - } memcpy(aggr->args, buf, count + 1); @@ -475,19 +502,10 @@ static ssize_t gpio_aggregator_new_device_store(struct device_driver *driver, 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); + aggr->lookups->dev_id = kasprintf(GFP_KERNEL, "%s.%d", DRV_NAME, aggr->id); if (!aggr->lookups->dev_id) { res = -ENOMEM; - goto remove_idr; + goto free_table; } res = gpio_aggregator_parse(aggr); @@ -496,7 +514,7 @@ static ssize_t gpio_aggregator_new_device_store(struct device_driver *driver, gpiod_add_lookup_table(aggr->lookups); - pdev = platform_device_register_simple(DRV_NAME, id, NULL, 0); + pdev = platform_device_register_simple(DRV_NAME, aggr->id, NULL, 0); if (IS_ERR(pdev)) { res = PTR_ERR(pdev); goto remove_table; @@ -510,14 +528,10 @@ static ssize_t gpio_aggregator_new_device_store(struct device_driver *driver, 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); + gpio_aggregator_free(aggr); put_module: module_put(THIS_MODULE); return res; From 86f162e73d2d81ef6d819c06a3b6c2fda77a79b8 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Mon, 7 Apr 2025 13:30:14 +0900 Subject: [PATCH 018/123] gpio: aggregator: introduce basic configfs interface The existing sysfs 'new_device' interface has several limitations: * No way to determine when GPIO aggregator creation is complete. * No way to retrieve errors when creating a GPIO aggregator. * No way to trace a GPIO line of an aggregator back to its corresponding physical device. * The 'new_device' echo does not indicate which virtual gpiochip was created. * No way to assign names to GPIO lines exported through an aggregator. Introduce the new configfs interface for gpio-aggregator to address these limitations. It provides a more streamlined, modern, and extensible configuration method. For backward compatibility, the 'new_device' interface and its behavior is retained for now. This commit implements basic functionalities: /config/gpio-aggregator// /config/gpio-aggregator//live /config/gpio-aggregator//dev_name /config/gpio-aggregator/// /config/gpio-aggregator///key /config/gpio-aggregator///offset /config/gpio-aggregator///name Basic setup flow is: 1. Create a directory for a GPIO aggregator. 2. Create subdirectories for each line you want to instantiate. 3. In each line directory, configure the key and offset. The key/offset semantics are as follows: * If offset is >= 0: - key specifies the name of the chip this GPIO belongs to - offset specifies the line offset within that chip. * If offset is <0: - key needs to specify the GPIO line name. 4. Return to the aggregator's root directory and write '1' to the live attribute. For example, the command in the existing kernel doc: echo 'e6052000.gpio 19 e6050000.gpio 20-21' > new_device is equivalent to: mkdir /sys/kernel/config/gpio-aggregator/ # Change to name of your choice (e.g. "aggr0") cd /sys/kernel/config/gpio-aggregator/ mkdir line0 line1 line2 # Only "line" naming allowed. echo e6052000.gpio > line0/key echo 19 > line0/offset echo e6050000.gpio > line1/key echo 20 > line1/offset echo e6050000.gpio > line2/key echo 21 > line2/offset echo 1 > live The corresponding gpio_device id can be identified as follows: cd /sys/kernel/config/gpio-aggregator/ ls -d /sys/devices/platform/`cat dev_name`/gpiochip* Also, via configfs, custom GPIO line name can be set like this: cd /sys/kernel/config/gpio-aggregator/ echo "abc" > line1/name Signed-off-by: Koichiro Den Link: https://lore.kernel.org/r/20250407043019.4105613-5-koichiro.den@canonical.com [Bartosz: remove stray newlines] Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 2 + drivers/gpio/gpio-aggregator.c | 637 ++++++++++++++++++++++++++++++++- 2 files changed, 626 insertions(+), 13 deletions(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 3244b478d078..688a01ad4d69 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1880,6 +1880,8 @@ menu "Virtual GPIO drivers" config GPIO_AGGREGATOR tristate "GPIO Aggregator" + select CONFIGFS_FS + select DEV_SYNC_PROBE help Say yes here to enable the GPIO Aggregator, which provides a way to aggregate existing GPIO lines into a new virtual GPIO chip. diff --git a/drivers/gpio/gpio-aggregator.c b/drivers/gpio/gpio-aggregator.c index 6f933a76b2b9..e5199de2e815 100644 --- a/drivers/gpio/gpio-aggregator.c +++ b/drivers/gpio/gpio-aggregator.c @@ -9,10 +9,13 @@ #include #include +#include #include #include #include #include +#include +#include #include #include #include @@ -27,6 +30,8 @@ #include #include +#include "dev-sync-probe.h" + #define AGGREGATOR_MAX_GPIOS 512 /* @@ -34,12 +39,38 @@ */ struct gpio_aggregator { + struct dev_sync_probe_data probe_data; + struct config_group group; struct gpiod_lookup_table *lookups; - struct platform_device *pdev; + struct mutex lock; int id; + + /* List of gpio_aggregator_line. Always added in order */ + struct list_head list_head; + + /* used by legacy sysfs interface only */ + bool init_via_sysfs; char args[]; }; +struct gpio_aggregator_line { + struct config_group group; + struct gpio_aggregator *parent; + struct list_head entry; + + /* Line index within the aggregator device */ + unsigned int idx; + + /* Custom name for the virtual line */ + const char *name; + /* GPIO chip label or line name */ + const char *key; + /* Can be negative to indicate lookup by line name */ + int offset; + + enum gpio_lookup_flags flags; +}; + static DEFINE_MUTEX(gpio_aggregator_lock); /* protects idr */ static DEFINE_IDR(gpio_aggregator_idr); @@ -59,6 +90,8 @@ static int gpio_aggregator_alloc(struct gpio_aggregator **aggr, size_t arg_size) return ret; new->id = ret; + INIT_LIST_HEAD(&new->list_head); + mutex_init(&new->lock); *aggr = no_free_ptr(new); return 0; } @@ -68,6 +101,7 @@ static void gpio_aggregator_free(struct gpio_aggregator *aggr) scoped_guard(mutex, &gpio_aggregator_lock) idr_remove(&gpio_aggregator_idr, aggr->id); + mutex_destroy(&aggr->lock); kfree(aggr); } @@ -90,6 +124,71 @@ static int gpio_aggregator_add_gpio(struct gpio_aggregator *aggr, return 0; } +static bool gpio_aggregator_is_active(struct gpio_aggregator *aggr) +{ + lockdep_assert_held(&aggr->lock); + + return aggr->probe_data.pdev && platform_get_drvdata(aggr->probe_data.pdev); +} + +static size_t gpio_aggregator_count_lines(struct gpio_aggregator *aggr) +{ + lockdep_assert_held(&aggr->lock); + + return list_count_nodes(&aggr->list_head); +} + +static struct gpio_aggregator_line * +gpio_aggregator_line_alloc(struct gpio_aggregator *parent, unsigned int idx, + char *key, int offset) +{ + struct gpio_aggregator_line *line; + + line = kzalloc(sizeof(*line), GFP_KERNEL); + if (!line) + return ERR_PTR(-ENOMEM); + + if (key) { + line->key = kstrdup(key, GFP_KERNEL); + if (!line->key) { + kfree(line); + return ERR_PTR(-ENOMEM); + } + } + + line->flags = GPIO_LOOKUP_FLAGS_DEFAULT; + line->parent = parent; + line->idx = idx; + line->offset = offset; + INIT_LIST_HEAD(&line->entry); + + return line; +} + +static void gpio_aggregator_line_add(struct gpio_aggregator *aggr, + struct gpio_aggregator_line *line) +{ + struct gpio_aggregator_line *tmp; + + lockdep_assert_held(&aggr->lock); + + list_for_each_entry(tmp, &aggr->list_head, entry) { + if (tmp->idx > line->idx) { + list_add_tail(&line->entry, &tmp->entry); + return; + } + } + list_add_tail(&line->entry, &aggr->list_head); +} + +static void gpio_aggregator_line_del(struct gpio_aggregator *aggr, + struct gpio_aggregator_line *line) +{ + lockdep_assert_held(&aggr->lock); + + list_del(&line->entry); +} + /* * GPIO Forwarder @@ -423,6 +522,473 @@ static struct gpiochip_fwd *gpiochip_fwd_create(struct device *dev, return fwd; } +/* + * Configfs interface + */ + +static struct gpio_aggregator * +to_gpio_aggregator(struct config_item *item) +{ + struct config_group *group = to_config_group(item); + + return container_of(group, struct gpio_aggregator, group); +} + +static struct gpio_aggregator_line * +to_gpio_aggregator_line(struct config_item *item) +{ + struct config_group *group = to_config_group(item); + + return container_of(group, struct gpio_aggregator_line, group); +} + +static struct fwnode_handle * +gpio_aggregator_make_device_sw_node(struct gpio_aggregator *aggr) +{ + struct property_entry properties[2]; + struct gpio_aggregator_line *line; + size_t num_lines; + int n = 0; + + memset(properties, 0, sizeof(properties)); + + num_lines = gpio_aggregator_count_lines(aggr); + if (num_lines == 0) + return NULL; + + const char **line_names __free(kfree) = kcalloc( + num_lines, sizeof(*line_names), GFP_KERNEL); + if (!line_names) + return ERR_PTR(-ENOMEM); + + /* The list is always sorted as new elements are inserted in order. */ + list_for_each_entry(line, &aggr->list_head, entry) + line_names[n++] = line->name ?: ""; + + properties[0] = PROPERTY_ENTRY_STRING_ARRAY_LEN( + "gpio-line-names", + line_names, num_lines); + + return fwnode_create_software_node(properties, NULL); +} + +static int gpio_aggregator_activate(struct gpio_aggregator *aggr) +{ + struct platform_device_info pdevinfo; + struct gpio_aggregator_line *line; + struct fwnode_handle *swnode; + unsigned int n = 0; + int ret = 0; + + if (gpio_aggregator_count_lines(aggr) == 0) + return -EINVAL; + + aggr->lookups = kzalloc(struct_size(aggr->lookups, table, 1), + GFP_KERNEL); + if (!aggr->lookups) + return -ENOMEM; + + swnode = gpio_aggregator_make_device_sw_node(aggr); + if (IS_ERR(swnode)) + goto err_remove_lookups; + + memset(&pdevinfo, 0, sizeof(pdevinfo)); + pdevinfo.name = DRV_NAME; + pdevinfo.id = aggr->id; + pdevinfo.fwnode = swnode; + + /* The list is always sorted as new elements are inserted in order. */ + list_for_each_entry(line, &aggr->list_head, entry) { + /* + * - Either GPIO chip label or line name must be configured + * (i.e. line->key must be non-NULL) + * - Line directories must be named with sequential numeric + * suffixes starting from 0. (i.e. ./line0, ./line1, ...) + */ + if (!line->key || line->idx != n) { + ret = -EINVAL; + goto err_remove_swnode; + } + + if (line->offset < 0) + ret = gpio_aggregator_add_gpio(aggr, line->key, + U16_MAX, &n); + else + ret = gpio_aggregator_add_gpio(aggr, line->key, + line->offset, &n); + if (ret) + goto err_remove_swnode; + } + + aggr->lookups->dev_id = kasprintf(GFP_KERNEL, "%s.%d", DRV_NAME, aggr->id); + if (!aggr->lookups->dev_id) { + ret = -ENOMEM; + goto err_remove_swnode; + } + + gpiod_add_lookup_table(aggr->lookups); + + ret = dev_sync_probe_register(&aggr->probe_data, &pdevinfo); + if (ret) + goto err_remove_lookup_table; + + return 0; + +err_remove_lookup_table: + kfree(aggr->lookups->dev_id); + gpiod_remove_lookup_table(aggr->lookups); +err_remove_swnode: + fwnode_remove_software_node(swnode); +err_remove_lookups: + kfree(aggr->lookups); + + return ret; +} + +static void gpio_aggregator_deactivate(struct gpio_aggregator *aggr) +{ + dev_sync_probe_unregister(&aggr->probe_data); + gpiod_remove_lookup_table(aggr->lookups); + kfree(aggr->lookups->dev_id); + kfree(aggr->lookups); +} + +static void gpio_aggregator_lockup_configfs(struct gpio_aggregator *aggr, + bool lock) +{ + struct configfs_subsystem *subsys = aggr->group.cg_subsys; + struct gpio_aggregator_line *line; + + /* + * The device only needs to depend on leaf lines. This is + * sufficient to lock up all the configfs entries that the + * instantiated, alive device depends on. + */ + list_for_each_entry(line, &aggr->list_head, entry) { + if (lock) + configfs_depend_item_unlocked( + subsys, &line->group.cg_item); + else + configfs_undepend_item_unlocked( + &line->group.cg_item); + } +} + +static ssize_t +gpio_aggregator_line_key_show(struct config_item *item, char *page) +{ + struct gpio_aggregator_line *line = to_gpio_aggregator_line(item); + struct gpio_aggregator *aggr = line->parent; + + guard(mutex)(&aggr->lock); + + return sysfs_emit(page, "%s\n", line->key ?: ""); +} + +static ssize_t +gpio_aggregator_line_key_store(struct config_item *item, const char *page, + size_t count) +{ + struct gpio_aggregator_line *line = to_gpio_aggregator_line(item); + struct gpio_aggregator *aggr = line->parent; + + char *key __free(kfree) = kstrndup(skip_spaces(page), count, + GFP_KERNEL); + if (!key) + return -ENOMEM; + + strim(key); + + guard(mutex)(&aggr->lock); + + if (gpio_aggregator_is_active(aggr)) + return -EBUSY; + + kfree(line->key); + line->key = no_free_ptr(key); + + return count; +} +CONFIGFS_ATTR(gpio_aggregator_line_, key); + +static ssize_t +gpio_aggregator_line_name_show(struct config_item *item, char *page) +{ + struct gpio_aggregator_line *line = to_gpio_aggregator_line(item); + struct gpio_aggregator *aggr = line->parent; + + guard(mutex)(&aggr->lock); + + return sysfs_emit(page, "%s\n", line->name ?: ""); +} + +static ssize_t +gpio_aggregator_line_name_store(struct config_item *item, const char *page, + size_t count) +{ + struct gpio_aggregator_line *line = to_gpio_aggregator_line(item); + struct gpio_aggregator *aggr = line->parent; + + char *name __free(kfree) = kstrndup(skip_spaces(page), count, + GFP_KERNEL); + if (!name) + return -ENOMEM; + + strim(name); + + guard(mutex)(&aggr->lock); + + if (gpio_aggregator_is_active(aggr)) + return -EBUSY; + + kfree(line->name); + line->name = no_free_ptr(name); + + return count; +} +CONFIGFS_ATTR(gpio_aggregator_line_, name); + +static ssize_t +gpio_aggregator_line_offset_show(struct config_item *item, char *page) +{ + struct gpio_aggregator_line *line = to_gpio_aggregator_line(item); + struct gpio_aggregator *aggr = line->parent; + + guard(mutex)(&aggr->lock); + + return sysfs_emit(page, "%d\n", line->offset); +} + +static ssize_t +gpio_aggregator_line_offset_store(struct config_item *item, const char *page, + size_t count) +{ + struct gpio_aggregator_line *line = to_gpio_aggregator_line(item); + struct gpio_aggregator *aggr = line->parent; + int offset, ret; + + ret = kstrtoint(page, 0, &offset); + if (ret) + return ret; + + /* + * When offset == -1, 'key' represents a line name to lookup. + * When 0 <= offset < 65535, 'key' represents the label of the chip with + * the 'offset' value representing the line within that chip. + * + * GPIOLIB uses the U16_MAX value to indicate lookup by line name so + * the greatest offset we can accept is (U16_MAX - 1). + */ + if (offset > (U16_MAX - 1) || offset < -1) + return -EINVAL; + + guard(mutex)(&aggr->lock); + + if (gpio_aggregator_is_active(aggr)) + return -EBUSY; + + line->offset = offset; + + return count; +} +CONFIGFS_ATTR(gpio_aggregator_line_, offset); + +static struct configfs_attribute *gpio_aggregator_line_attrs[] = { + &gpio_aggregator_line_attr_key, + &gpio_aggregator_line_attr_name, + &gpio_aggregator_line_attr_offset, + NULL +}; + +static ssize_t +gpio_aggregator_device_dev_name_show(struct config_item *item, char *page) +{ + struct gpio_aggregator *aggr = to_gpio_aggregator(item); + struct platform_device *pdev; + + guard(mutex)(&aggr->lock); + + pdev = aggr->probe_data.pdev; + if (pdev) + return sysfs_emit(page, "%s\n", dev_name(&pdev->dev)); + + return sysfs_emit(page, "%s.%d\n", DRV_NAME, aggr->id); +} +CONFIGFS_ATTR_RO(gpio_aggregator_device_, dev_name); + +static ssize_t +gpio_aggregator_device_live_show(struct config_item *item, char *page) +{ + struct gpio_aggregator *aggr = to_gpio_aggregator(item); + + guard(mutex)(&aggr->lock); + + return sysfs_emit(page, "%c\n", + gpio_aggregator_is_active(aggr) ? '1' : '0'); +} + +static ssize_t +gpio_aggregator_device_live_store(struct config_item *item, const char *page, + size_t count) +{ + struct gpio_aggregator *aggr = to_gpio_aggregator(item); + int ret = 0; + bool live; + + ret = kstrtobool(page, &live); + if (ret) + return ret; + + if (!try_module_get(THIS_MODULE)) + return -ENOENT; + + if (live) + gpio_aggregator_lockup_configfs(aggr, true); + + scoped_guard(mutex, &aggr->lock) { + if (live == gpio_aggregator_is_active(aggr)) + ret = -EPERM; + else if (live) + ret = gpio_aggregator_activate(aggr); + else + gpio_aggregator_deactivate(aggr); + } + + /* + * Undepend is required only if device disablement (live == 0) + * succeeds or if device enablement (live == 1) fails. + */ + if (live == !!ret) + gpio_aggregator_lockup_configfs(aggr, false); + + module_put(THIS_MODULE); + + return ret ?: count; +} +CONFIGFS_ATTR(gpio_aggregator_device_, live); + +static struct configfs_attribute *gpio_aggregator_device_attrs[] = { + &gpio_aggregator_device_attr_dev_name, + &gpio_aggregator_device_attr_live, + NULL +}; + +static void +gpio_aggregator_line_release(struct config_item *item) +{ + struct gpio_aggregator_line *line = to_gpio_aggregator_line(item); + struct gpio_aggregator *aggr = line->parent; + + guard(mutex)(&aggr->lock); + + gpio_aggregator_line_del(aggr, line); + kfree(line->key); + kfree(line->name); + kfree(line); +} + +static struct configfs_item_operations gpio_aggregator_line_item_ops = { + .release = gpio_aggregator_line_release, +}; + +static const struct config_item_type gpio_aggregator_line_type = { + .ct_item_ops = &gpio_aggregator_line_item_ops, + .ct_attrs = gpio_aggregator_line_attrs, + .ct_owner = THIS_MODULE, +}; + +static void gpio_aggregator_device_release(struct config_item *item) +{ + struct gpio_aggregator *aggr = to_gpio_aggregator(item); + + /* + * If the aggregator is active, this code wouldn't be reached, + * so calling gpio_aggregator_deactivate() is always unnecessary. + */ + gpio_aggregator_free(aggr); +} + +static struct configfs_item_operations gpio_aggregator_device_item_ops = { + .release = gpio_aggregator_device_release, +}; + +static struct config_group * +gpio_aggregator_device_make_group(struct config_group *group, const char *name) +{ + struct gpio_aggregator *aggr = to_gpio_aggregator(&group->cg_item); + struct gpio_aggregator_line *line; + unsigned int idx; + int ret, nchar; + + ret = sscanf(name, "line%u%n", &idx, &nchar); + if (ret != 1 || nchar != strlen(name)) + return ERR_PTR(-EINVAL); + + guard(mutex)(&aggr->lock); + + if (gpio_aggregator_is_active(aggr)) + return ERR_PTR(-EBUSY); + + list_for_each_entry(line, &aggr->list_head, entry) + if (line->idx == idx) + return ERR_PTR(-EINVAL); + + line = gpio_aggregator_line_alloc(aggr, idx, NULL, -1); + if (!line) + return ERR_PTR(-ENOMEM); + + config_group_init_type_name(&line->group, name, &gpio_aggregator_line_type); + + gpio_aggregator_line_add(aggr, line); + + return &line->group; +} + +static struct configfs_group_operations gpio_aggregator_device_group_ops = { + .make_group = gpio_aggregator_device_make_group, +}; + +static const struct config_item_type gpio_aggregator_device_type = { + .ct_group_ops = &gpio_aggregator_device_group_ops, + .ct_item_ops = &gpio_aggregator_device_item_ops, + .ct_attrs = gpio_aggregator_device_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_group * +gpio_aggregator_make_group(struct config_group *group, const char *name) +{ + struct gpio_aggregator *aggr; + int ret; + + /* arg space is unneeded */ + ret = gpio_aggregator_alloc(&aggr, 0); + if (ret) + return ERR_PTR(ret); + + config_group_init_type_name(&aggr->group, name, &gpio_aggregator_device_type); + dev_sync_probe_init(&aggr->probe_data); + + return &aggr->group; +} + +static struct configfs_group_operations gpio_aggregator_group_ops = { + .make_group = gpio_aggregator_make_group, +}; + +static const struct config_item_type gpio_aggregator_type = { + .ct_group_ops = &gpio_aggregator_group_ops, + .ct_owner = THIS_MODULE, +}; + +static struct configfs_subsystem gpio_aggregator_subsys = { + .su_group = { + .cg_item = { + .ci_namebuf = DRV_NAME, + .ci_type = &gpio_aggregator_type, + }, + }, +}; /* * Sysfs interface @@ -495,6 +1061,7 @@ static ssize_t gpio_aggregator_new_device_store(struct device_driver *driver, memcpy(aggr->args, buf, count + 1); + aggr->init_via_sysfs = true; aggr->lookups = kzalloc(struct_size(aggr->lookups, table, 1), GFP_KERNEL); if (!aggr->lookups) { @@ -520,7 +1087,7 @@ static ssize_t gpio_aggregator_new_device_store(struct device_driver *driver, goto remove_table; } - aggr->pdev = pdev; + aggr->probe_data.pdev = pdev; module_put(THIS_MODULE); return count; @@ -542,10 +1109,7 @@ static struct driver_attribute driver_attr_gpio_aggregator_new_device = static void gpio_aggregator_destroy(struct gpio_aggregator *aggr) { - platform_device_unregister(aggr->pdev); - gpiod_remove_lookup_table(aggr->lookups); - kfree(aggr->lookups->dev_id); - kfree(aggr->lookups); + gpio_aggregator_deactivate(aggr); kfree(aggr); } @@ -567,12 +1131,19 @@ static ssize_t gpio_aggregator_delete_device_store(struct device_driver *driver, return -ENOENT; mutex_lock(&gpio_aggregator_lock); - aggr = idr_remove(&gpio_aggregator_idr, id); - mutex_unlock(&gpio_aggregator_lock); - if (!aggr) { + aggr = idr_find(&gpio_aggregator_idr, id); + /* + * For simplicity, devices created via configfs cannot be deleted + * via sysfs. + */ + if (aggr && aggr->init_via_sysfs) + idr_remove(&gpio_aggregator_idr, id); + else { + mutex_unlock(&gpio_aggregator_lock); module_put(THIS_MODULE); return -ENOENT; } + mutex_unlock(&gpio_aggregator_lock); gpio_aggregator_destroy(aggr); module_put(THIS_MODULE); @@ -589,7 +1160,6 @@ static struct attribute *gpio_aggregator_attrs[] = { }; ATTRIBUTE_GROUPS(gpio_aggregator); - /* * GPIO Aggregator platform device */ @@ -649,21 +1219,61 @@ static struct platform_driver gpio_aggregator_driver = { static int __exit gpio_aggregator_idr_remove(int id, void *p, void *data) { + /* + * There should be no aggregator created via configfs, as their + * presence would prevent module unloading. + */ gpio_aggregator_destroy(p); return 0; } static void __exit gpio_aggregator_remove_all(void) { - mutex_lock(&gpio_aggregator_lock); + /* + * Configfs callbacks acquire gpio_aggregator_lock when accessing + * gpio_aggregator_idr, so to prevent lock inversion deadlock, we + * cannot protect idr_for_each invocation here with + * gpio_aggregator_lock, as gpio_aggregator_idr_remove() accesses + * configfs groups. Fortunately, the new_device/delete_device path + * and the module unload path are mutually exclusive, thanks to an + * explicit try_module_get inside of those driver attr handlers. + * Also, when we reach here, no configfs entries present or being + * created. Therefore, no need to protect with gpio_aggregator_lock + * below. + */ idr_for_each(&gpio_aggregator_idr, gpio_aggregator_idr_remove, NULL); idr_destroy(&gpio_aggregator_idr); - mutex_unlock(&gpio_aggregator_lock); } static int __init gpio_aggregator_init(void) { - return platform_driver_register(&gpio_aggregator_driver); + int ret = 0; + + config_group_init(&gpio_aggregator_subsys.su_group); + mutex_init(&gpio_aggregator_subsys.su_mutex); + ret = configfs_register_subsystem(&gpio_aggregator_subsys); + if (ret) { + pr_err("Failed to register the '%s' configfs subsystem: %d\n", + gpio_aggregator_subsys.su_group.cg_item.ci_namebuf, ret); + mutex_destroy(&gpio_aggregator_subsys.su_mutex); + return ret; + } + + /* + * CAVEAT: This must occur after configfs registration. Otherwise, + * a race condition could arise: driver attribute groups might be + * exposed and accessed by users before configfs registration + * completes. new_device_store() does not expect a partially + * initialized configfs state. + */ + ret = platform_driver_register(&gpio_aggregator_driver); + if (ret) { + pr_err("Failed to register the platform driver: %d\n", ret); + mutex_destroy(&gpio_aggregator_subsys.su_mutex); + configfs_unregister_subsystem(&gpio_aggregator_subsys); + } + + return ret; } module_init(gpio_aggregator_init); @@ -671,6 +1281,7 @@ static void __exit gpio_aggregator_exit(void) { gpio_aggregator_remove_all(); platform_driver_unregister(&gpio_aggregator_driver); + configfs_unregister_subsystem(&gpio_aggregator_subsys); } module_exit(gpio_aggregator_exit); From 4ec2315d7fabeb08e9ad7995bd16f34118e4633b Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Mon, 7 Apr 2025 13:30:15 +0900 Subject: [PATCH 019/123] gpio: aggregator: rename 'name' to 'key' in gpio_aggregator_parse() Rename the local variable 'name' in gpio_aggregator_parse() to 'key' because struct gpio_aggregator_line now uses the 'name' field for the custom line name and the local variable actually represents a 'key'. This change prepares for the next but one commit. No functional change. Reviewed-by: Geert Uytterhoeven Signed-off-by: Koichiro Den Link: https://lore.kernel.org/r/20250407043019.4105613-6-koichiro.den@canonical.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-aggregator.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpio/gpio-aggregator.c b/drivers/gpio/gpio-aggregator.c index e5199de2e815..e8c21ea9a5ea 100644 --- a/drivers/gpio/gpio-aggregator.c +++ b/drivers/gpio/gpio-aggregator.c @@ -996,7 +996,7 @@ static struct configfs_subsystem gpio_aggregator_subsys = { static int gpio_aggregator_parse(struct gpio_aggregator *aggr) { char *args = skip_spaces(aggr->args); - char *name, *offsets, *p; + char *key, *offsets, *p; unsigned int i, n = 0; int error = 0; @@ -1005,18 +1005,18 @@ static int gpio_aggregator_parse(struct gpio_aggregator *aggr) if (!bitmap) return -ENOMEM; - args = next_arg(args, &name, &p); + args = next_arg(args, &key, &p); while (*args) { args = next_arg(args, &offsets, &p); p = get_options(offsets, 0, &error); if (error == 0 || *p) { /* Named GPIO line */ - error = gpio_aggregator_add_gpio(aggr, name, U16_MAX, &n); + error = gpio_aggregator_add_gpio(aggr, key, U16_MAX, &n); if (error) return error; - name = offsets; + key = offsets; continue; } @@ -1028,12 +1028,12 @@ static int gpio_aggregator_parse(struct gpio_aggregator *aggr) } for_each_set_bit(i, bitmap, AGGREGATOR_MAX_GPIOS) { - error = gpio_aggregator_add_gpio(aggr, name, i, &n); + error = gpio_aggregator_add_gpio(aggr, key, i, &n); if (error) return error; } - args = next_arg(args, &name, &p); + args = next_arg(args, &key, &p); } if (!n) { From 83c8e3df642f5fde320278a5e6ce9e46f9689d1a Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Mon, 7 Apr 2025 13:30:16 +0900 Subject: [PATCH 020/123] gpio: aggregator: expose aggregator created via legacy sysfs to configfs Expose settings for aggregators created using the sysfs 'new_device' interface to configfs. Once written to 'new_device', an "_sysfs." path appears in the configfs regardless of whether the probe succeeds. Consequently, users can no longer use that prefix for custom GPIO aggregator names. The 'live' attribute changes to 1 when the probe succeeds and the GPIO forwarder is instantiated. Note that the aggregator device created via sysfs is asynchronous, i.e. writing into 'new_device' returns without waiting for probe completion, and the probe may succeed, fail, or eventually succeed via deferred probe. Thus, the 'live' attribute may change from 0 to 1 asynchronously without notice. So, editing key/offset/name while it's waiting for deferred probe is prohibited. The configfs auto-generation relies on create_default_group(), which inherently prohibits rmdir(2). To align with the limitation, this commit also prohibits mkdir(2) for them. When users want to change the number of lines for an aggregator initialized via 'new_device', they need to tear down the device using 'delete_device' and reconfigure it from scratch. This does not break previous behavior; users of legacy sysfs interface simply gain additional almost read-only configfs exposure. Still, users can write to the 'live' attribute to toggle the device unless it's waiting for deferred probe. So once probe succeeds, they can deactivate it in the same manner as the devices initialized via configfs. Signed-off-by: Koichiro Den Link: https://lore.kernel.org/r/20250407043019.4105613-7-koichiro.den@canonical.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-aggregator.c | 138 ++++++++++++++++++++++++++++++--- 1 file changed, 126 insertions(+), 12 deletions(-) diff --git a/drivers/gpio/gpio-aggregator.c b/drivers/gpio/gpio-aggregator.c index e8c21ea9a5ea..74e0f9faeaba 100644 --- a/drivers/gpio/gpio-aggregator.c +++ b/drivers/gpio/gpio-aggregator.c @@ -33,6 +33,7 @@ #include "dev-sync-probe.h" #define AGGREGATOR_MAX_GPIOS 512 +#define AGGREGATOR_LEGACY_PREFIX "_sysfs" /* * GPIO Aggregator sysfs interface @@ -131,6 +132,14 @@ static bool gpio_aggregator_is_active(struct gpio_aggregator *aggr) return aggr->probe_data.pdev && platform_get_drvdata(aggr->probe_data.pdev); } +/* Only aggregators created via legacy sysfs can be "activating". */ +static bool gpio_aggregator_is_activating(struct gpio_aggregator *aggr) +{ + lockdep_assert_held(&aggr->lock); + + return aggr->probe_data.pdev && !platform_get_drvdata(aggr->probe_data.pdev); +} + static size_t gpio_aggregator_count_lines(struct gpio_aggregator *aggr) { lockdep_assert_held(&aggr->lock); @@ -189,6 +198,30 @@ static void gpio_aggregator_line_del(struct gpio_aggregator *aggr, list_del(&line->entry); } +static void gpio_aggregator_free_lines(struct gpio_aggregator *aggr) +{ + struct gpio_aggregator_line *line, *tmp; + + list_for_each_entry_safe(line, tmp, &aggr->list_head, entry) { + configfs_unregister_group(&line->group); + /* + * Normally, we acquire aggr->lock within the configfs + * callback. However, in the legacy sysfs interface case, + * calling configfs_(un)register_group while holding + * aggr->lock could cause a deadlock. Fortunately, this is + * unnecessary because the new_device/delete_device path + * and the module unload path are mutually exclusive, + * thanks to an explicit try_module_get. That's why this + * minimal scoped_guard suffices. + */ + scoped_guard(mutex, &aggr->lock) + gpio_aggregator_line_del(aggr, line); + kfree(line->key); + kfree(line->name); + kfree(line); + } +} + /* * GPIO Forwarder @@ -701,7 +734,8 @@ gpio_aggregator_line_key_store(struct config_item *item, const char *page, guard(mutex)(&aggr->lock); - if (gpio_aggregator_is_active(aggr)) + if (gpio_aggregator_is_activating(aggr) || + gpio_aggregator_is_active(aggr)) return -EBUSY; kfree(line->key); @@ -738,7 +772,8 @@ gpio_aggregator_line_name_store(struct config_item *item, const char *page, guard(mutex)(&aggr->lock); - if (gpio_aggregator_is_active(aggr)) + if (gpio_aggregator_is_activating(aggr) || + gpio_aggregator_is_active(aggr)) return -EBUSY; kfree(line->name); @@ -784,7 +819,8 @@ gpio_aggregator_line_offset_store(struct config_item *item, const char *page, guard(mutex)(&aggr->lock); - if (gpio_aggregator_is_active(aggr)) + if (gpio_aggregator_is_activating(aggr) || + gpio_aggregator_is_active(aggr)) return -EBUSY; line->offset = offset; @@ -842,11 +878,12 @@ gpio_aggregator_device_live_store(struct config_item *item, const char *page, if (!try_module_get(THIS_MODULE)) return -ENOENT; - if (live) + if (live && !aggr->init_via_sysfs) gpio_aggregator_lockup_configfs(aggr, true); scoped_guard(mutex, &aggr->lock) { - if (live == gpio_aggregator_is_active(aggr)) + if (gpio_aggregator_is_activating(aggr) || + (live == gpio_aggregator_is_active(aggr))) ret = -EPERM; else if (live) ret = gpio_aggregator_activate(aggr); @@ -858,7 +895,7 @@ gpio_aggregator_device_live_store(struct config_item *item, const char *page, * Undepend is required only if device disablement (live == 0) * succeeds or if device enablement (live == 1) fails. */ - if (live == !!ret) + if (live == !!ret && !aggr->init_via_sysfs) gpio_aggregator_lockup_configfs(aggr, false); module_put(THIS_MODULE); @@ -902,7 +939,7 @@ static void gpio_aggregator_device_release(struct config_item *item) struct gpio_aggregator *aggr = to_gpio_aggregator(item); /* - * If the aggregator is active, this code wouldn't be reached, + * At this point, aggr is neither active nor activating, * so calling gpio_aggregator_deactivate() is always unnecessary. */ gpio_aggregator_free(aggr); @@ -924,6 +961,15 @@ gpio_aggregator_device_make_group(struct config_group *group, const char *name) if (ret != 1 || nchar != strlen(name)) return ERR_PTR(-EINVAL); + if (aggr->init_via_sysfs) + /* + * Aggregators created via legacy sysfs interface are exposed as + * default groups, which means rmdir(2) is prohibited for them. + * For simplicity, and to avoid confusion, we also prohibit + * mkdir(2). + */ + return ERR_PTR(-EPERM); + guard(mutex)(&aggr->lock); if (gpio_aggregator_is_active(aggr)) @@ -961,6 +1007,14 @@ gpio_aggregator_make_group(struct config_group *group, const char *name) struct gpio_aggregator *aggr; int ret; + /* + * "_sysfs" prefix is reserved for auto-generated config group + * for devices create via legacy sysfs interface. + */ + if (strncmp(name, AGGREGATOR_LEGACY_PREFIX, + sizeof(AGGREGATOR_LEGACY_PREFIX)) == 0) + return ERR_PTR(-EINVAL); + /* arg space is unneeded */ ret = gpio_aggregator_alloc(&aggr, 0); if (ret) @@ -996,6 +1050,8 @@ static struct configfs_subsystem gpio_aggregator_subsys = { static int gpio_aggregator_parse(struct gpio_aggregator *aggr) { char *args = skip_spaces(aggr->args); + struct gpio_aggregator_line *line; + char name[CONFIGFS_ITEM_NAME_LEN]; char *key, *offsets, *p; unsigned int i, n = 0; int error = 0; @@ -1012,9 +1068,24 @@ static int gpio_aggregator_parse(struct gpio_aggregator *aggr) p = get_options(offsets, 0, &error); if (error == 0 || *p) { /* Named GPIO line */ + scnprintf(name, sizeof(name), "line%u", n); + line = gpio_aggregator_line_alloc(aggr, n, key, -1); + if (!line) { + error = -ENOMEM; + goto err; + } + config_group_init_type_name(&line->group, name, + &gpio_aggregator_line_type); + error = configfs_register_group(&aggr->group, + &line->group); + if (error) + goto err; + scoped_guard(mutex, &aggr->lock) + gpio_aggregator_line_add(aggr, line); + error = gpio_aggregator_add_gpio(aggr, key, U16_MAX, &n); if (error) - return error; + goto err; key = offsets; continue; @@ -1028,9 +1099,24 @@ static int gpio_aggregator_parse(struct gpio_aggregator *aggr) } for_each_set_bit(i, bitmap, AGGREGATOR_MAX_GPIOS) { + scnprintf(name, sizeof(name), "line%u", n); + line = gpio_aggregator_line_alloc(aggr, n, key, i); + if (!line) { + error = -ENOMEM; + goto err; + } + config_group_init_type_name(&line->group, name, + &gpio_aggregator_line_type); + error = configfs_register_group(&aggr->group, + &line->group); + if (error) + goto err; + scoped_guard(mutex, &aggr->lock) + gpio_aggregator_line_add(aggr, line); + error = gpio_aggregator_add_gpio(aggr, key, i, &n); if (error) - return error; + goto err; } args = next_arg(args, &key, &p); @@ -1038,15 +1124,20 @@ static int gpio_aggregator_parse(struct gpio_aggregator *aggr) if (!n) { pr_err("No GPIOs specified\n"); - return -EINVAL; + goto err; } return 0; + +err: + gpio_aggregator_free_lines(aggr); + return error; } static ssize_t gpio_aggregator_new_device_store(struct device_driver *driver, const char *buf, size_t count) { + char name[CONFIGFS_ITEM_NAME_LEN]; struct gpio_aggregator *aggr; struct platform_device *pdev; int res; @@ -1075,10 +1166,25 @@ static ssize_t gpio_aggregator_new_device_store(struct device_driver *driver, goto free_table; } - res = gpio_aggregator_parse(aggr); + scnprintf(name, sizeof(name), "%s.%d", AGGREGATOR_LEGACY_PREFIX, aggr->id); + config_group_init_type_name(&aggr->group, name, &gpio_aggregator_device_type); + + /* + * Since the device created by sysfs might be toggled via configfs + * 'live' attribute later, this initialization is needed. + */ + dev_sync_probe_init(&aggr->probe_data); + + /* Expose to configfs */ + res = configfs_register_group(&gpio_aggregator_subsys.su_group, + &aggr->group); if (res) goto free_dev_id; + res = gpio_aggregator_parse(aggr); + if (res) + goto unregister_group; + gpiod_add_lookup_table(aggr->lookups); pdev = platform_device_register_simple(DRV_NAME, aggr->id, NULL, 0); @@ -1093,6 +1199,8 @@ static ssize_t gpio_aggregator_new_device_store(struct device_driver *driver, remove_table: gpiod_remove_lookup_table(aggr->lookups); +unregister_group: + configfs_unregister_group(&aggr->group); free_dev_id: kfree(aggr->lookups->dev_id); free_table: @@ -1109,7 +1217,13 @@ static struct driver_attribute driver_attr_gpio_aggregator_new_device = static void gpio_aggregator_destroy(struct gpio_aggregator *aggr) { - gpio_aggregator_deactivate(aggr); + scoped_guard(mutex, &aggr->lock) { + if (gpio_aggregator_is_activating(aggr) || + gpio_aggregator_is_active(aggr)) + gpio_aggregator_deactivate(aggr); + } + gpio_aggregator_free_lines(aggr); + configfs_unregister_group(&aggr->group); kfree(aggr); } From 0269c768de1b5e430c3f9a6869261606c82abe90 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Mon, 7 Apr 2025 13:30:17 +0900 Subject: [PATCH 021/123] gpio: aggregator: cancel deferred probe for devices created via configfs For aggregators initialized via configfs, write 1 to 'live' waits for probe completion and returns an error if the probe fails, unlike the legacy sysfs interface, which is asynchronous. Since users control the liveness of the aggregator device and might be editing configurations while 'live' is 0, deferred probing is both unnatural and unsafe. Cancel deferred probe for purely configfs-based aggregators when probe fails. Signed-off-by: Koichiro Den Link: https://lore.kernel.org/r/20250407043019.4105613-8-koichiro.den@canonical.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-aggregator.c | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-aggregator.c b/drivers/gpio/gpio-aggregator.c index 74e0f9faeaba..dde969f29ee2 100644 --- a/drivers/gpio/gpio-aggregator.c +++ b/drivers/gpio/gpio-aggregator.c @@ -72,6 +72,10 @@ struct gpio_aggregator_line { enum gpio_lookup_flags flags; }; +struct gpio_aggregator_pdev_meta { + bool init_via_sysfs; +}; + static DEFINE_MUTEX(gpio_aggregator_lock); /* protects idr */ static DEFINE_IDR(gpio_aggregator_idr); @@ -1137,6 +1141,7 @@ static int gpio_aggregator_parse(struct gpio_aggregator *aggr) static ssize_t gpio_aggregator_new_device_store(struct device_driver *driver, const char *buf, size_t count) { + struct gpio_aggregator_pdev_meta meta = { .init_via_sysfs = true }; char name[CONFIGFS_ITEM_NAME_LEN]; struct gpio_aggregator *aggr; struct platform_device *pdev; @@ -1187,7 +1192,7 @@ static ssize_t gpio_aggregator_new_device_store(struct device_driver *driver, gpiod_add_lookup_table(aggr->lookups); - pdev = platform_device_register_simple(DRV_NAME, aggr->id, NULL, 0); + pdev = platform_device_register_data(NULL, DRV_NAME, aggr->id, &meta, sizeof(meta)); if (IS_ERR(pdev)) { res = PTR_ERR(pdev); goto remove_table; @@ -1280,7 +1285,9 @@ ATTRIBUTE_GROUPS(gpio_aggregator); static int gpio_aggregator_probe(struct platform_device *pdev) { + struct gpio_aggregator_pdev_meta *meta; struct device *dev = &pdev->dev; + bool init_via_sysfs = false; struct gpio_desc **descs; struct gpiochip_fwd *fwd; unsigned long features; @@ -1294,10 +1301,28 @@ static int gpio_aggregator_probe(struct platform_device *pdev) if (!descs) return -ENOMEM; + meta = dev_get_platdata(&pdev->dev); + if (meta && meta->init_via_sysfs) + init_via_sysfs = true; + for (i = 0; i < n; i++) { descs[i] = devm_gpiod_get_index(dev, NULL, i, GPIOD_ASIS); - if (IS_ERR(descs[i])) + if (IS_ERR(descs[i])) { + /* + * Deferred probing is not suitable when the aggregator + * is created via configfs. They should just retry later + * whenever they like. For device creation via sysfs, + * error is propagated without overriding for backward + * compatibility. .prevent_deferred_probe is kept unset + * for other cases. + */ + if (!init_via_sysfs && !dev_of_node(dev) && + descs[i] == ERR_PTR(-EPROBE_DEFER)) { + pr_warn("Deferred probe canceled for creation via configfs.\n"); + return -ENODEV; + } return PTR_ERR(descs[i]); + } } features = (uintptr_t)device_get_match_data(dev); From 10f94d092bba9ab08004c624c604e976e6e3cd22 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Mon, 7 Apr 2025 13:30:18 +0900 Subject: [PATCH 022/123] Documentation: gpio: document configfs interface for gpio-aggregator Add documentation for the newly added configfs-based interface for GPIO aggregator. Signed-off-by: Koichiro Den Link: https://lore.kernel.org/r/20250407043019.4105613-9-koichiro.den@canonical.com Signed-off-by: Bartosz Golaszewski --- .../admin-guide/gpio/gpio-aggregator.rst | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/Documentation/admin-guide/gpio/gpio-aggregator.rst b/Documentation/admin-guide/gpio/gpio-aggregator.rst index 5cd1e7221756..8374a9df9105 100644 --- a/Documentation/admin-guide/gpio/gpio-aggregator.rst +++ b/Documentation/admin-guide/gpio/gpio-aggregator.rst @@ -69,6 +69,113 @@ write-only attribute files in sysfs. $ echo gpio-aggregator.0 > delete_device +Aggregating GPIOs using Configfs +-------------------------------- + +**Group:** ``/config/gpio-aggregator`` + + This is the root directory of the gpio-aggregator configfs tree. + +**Group:** ``/config/gpio-aggregator/`` + + This directory represents a GPIO aggregator device. You can assign any + name to ```` (e.g. ``agg0``), except names starting with + ``_sysfs`` prefix, which are reserved for auto-generated configfs + entries corresponding to devices created via Sysfs. + +**Attribute:** ``/config/gpio-aggregator//live`` + + The ``live`` attribute allows to trigger the actual creation of the device + once it's fully configured. Accepted values are: + + * ``1``, ``yes``, ``true`` : enable the virtual device + * ``0``, ``no``, ``false`` : disable the virtual device + +**Attribute:** ``/config/gpio-aggregator//dev_name`` + + The read-only ``dev_name`` attribute exposes the name of the device as it + will appear in the system on the platform bus (e.g. ``gpio-aggregator.0``). + This is useful for identifying a character device for the newly created + aggregator. If it's ``gpio-aggregator.0``, + ``/sys/devices/platform/gpio-aggregator.0/gpiochipX`` path tells you that the + GPIO device id is ``X``. + +You must create subdirectories for each virtual line you want to +instantiate, named exactly as ``line0``, ``line1``, ..., ``lineY``, when +you want to instantiate ``Y+1`` (Y >= 0) lines. Configure all lines before +activating the device by setting ``live`` to 1. + +**Group:** ``/config/gpio-aggregator///`` + + This directory represents a GPIO line to include in the aggregator. + +**Attribute:** ``/config/gpio-aggregator///key`` + +**Attribute:** ``/config/gpio-aggregator///offset`` + + The default values after creating the ```` directory are: + + * ``key`` : + * ``offset`` : -1 + + ``key`` must always be explicitly configured, while ``offset`` depends. + Two configuration patterns exist for each ````: + + (a). For lookup by GPIO line name: + + * Set ``key`` to the line name. + * Ensure ``offset`` remains -1 (the default). + + (b). For lookup by GPIO chip name and the line offset within the chip: + + * Set ``key`` to the chip name. + * Set ``offset`` to the line offset (0 <= ``offset`` < 65535). + +**Attribute:** ``/config/gpio-aggregator///name`` + + The ``name`` attribute sets a custom name for lineY. If left unset, the + line will remain unnamed. + +Once the configuration is done, the ``'live'`` attribute must be set to 1 +in order to instantiate the aggregator device. It can be set back to 0 to +destroy the virtual device. The module will synchronously wait for the new +aggregator device to be successfully probed and if this doesn't happen, writing +to ``'live'`` will result in an error. This is a different behaviour from the +case when you create it using sysfs ``new_device`` interface. + +.. note:: + + For aggregators created via Sysfs, the configfs entries are + auto-generated and appear as ``/config/gpio-aggregator/_sysfs./``. You + cannot add or remove line directories with mkdir(2)/rmdir(2). To modify + lines, you must use the "delete_device" interface to tear down the + existing device and reconfigure it from scratch. However, you can still + toggle the aggregator with the ``live`` attribute and adjust the + ``key``, ``offset``, and ``name`` attributes for each line when ``live`` + is set to 0 by hand (i.e. it's not waiting for deferred probe). + +Sample configuration commands +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: sh + + # Create a directory for an aggregator device + $ mkdir /sys/kernel/config/gpio-aggregator/agg0 + + # Configure each line + $ mkdir /sys/kernel/config/gpio-aggregator/agg0/line0 + $ echo gpiochip0 > /sys/kernel/config/gpio-aggregator/agg0/line0/key + $ echo 6 > /sys/kernel/config/gpio-aggregator/agg0/line0/offset + $ echo test0 > /sys/kernel/config/gpio-aggregator/agg0/line0/name + $ mkdir /sys/kernel/config/gpio-aggregator/agg0/line1 + $ echo gpiochip0 > /sys/kernel/config/gpio-aggregator/agg0/line1/key + $ echo 7 > /sys/kernel/config/gpio-aggregator/agg0/line1/offset + $ echo test1 > /sys/kernel/config/gpio-aggregator/agg0/line1/name + + # Activate the aggregator device + $ echo 1 > /sys/kernel/config/gpio-aggregator/agg0/live + + Generic GPIO Driver ------------------- From 6d7f0c1103ef3935eb3f302d09af4ef9e85212a3 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Mon, 7 Apr 2025 13:30:19 +0900 Subject: [PATCH 023/123] selftests: gpio: add test cases for gpio-aggregator Add a set of tests for gpio-aggregator module. This test covers both pre-existing new_device/delete_device interface and new configfs-based interface. Signed-off-by: Koichiro Den Link: https://lore.kernel.org/r/20250407043019.4105613-10-koichiro.den@canonical.com Signed-off-by: Bartosz Golaszewski --- tools/testing/selftests/gpio/Makefile | 2 +- tools/testing/selftests/gpio/config | 1 + .../testing/selftests/gpio/gpio-aggregator.sh | 723 ++++++++++++++++++ 3 files changed, 725 insertions(+), 1 deletion(-) create mode 100755 tools/testing/selftests/gpio/gpio-aggregator.sh diff --git a/tools/testing/selftests/gpio/Makefile b/tools/testing/selftests/gpio/Makefile index e0884390447d..7bfe315f7001 100644 --- a/tools/testing/selftests/gpio/Makefile +++ b/tools/testing/selftests/gpio/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 -TEST_PROGS := gpio-mockup.sh gpio-sim.sh +TEST_PROGS := gpio-mockup.sh gpio-sim.sh gpio-aggregator.sh TEST_FILES := gpio-mockup-sysfs.sh TEST_GEN_PROGS_EXTENDED := gpio-mockup-cdev gpio-chip-info gpio-line-name CFLAGS += -O2 -g -Wall $(KHDR_INCLUDES) diff --git a/tools/testing/selftests/gpio/config b/tools/testing/selftests/gpio/config index 409a8532facc..1287abeaac7e 100644 --- a/tools/testing/selftests/gpio/config +++ b/tools/testing/selftests/gpio/config @@ -2,3 +2,4 @@ CONFIG_GPIOLIB=y CONFIG_GPIO_CDEV=y CONFIG_GPIO_MOCKUP=m CONFIG_GPIO_SIM=m +CONFIG_GPIO_AGGREGATOR=m diff --git a/tools/testing/selftests/gpio/gpio-aggregator.sh b/tools/testing/selftests/gpio/gpio-aggregator.sh new file mode 100755 index 000000000000..f1bab62c4cd2 --- /dev/null +++ b/tools/testing/selftests/gpio/gpio-aggregator.sh @@ -0,0 +1,723 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2025 Bartosz Golaszewski +# Copyright (C) 2025 Koichiro Den + +BASE_DIR=$(dirname "$0") +CONFIGFS_SIM_DIR="/sys/kernel/config/gpio-sim" +CONFIGFS_AGG_DIR="/sys/kernel/config/gpio-aggregator" +SYSFS_AGG_DIR="/sys/bus/platform/drivers/gpio-aggregator" +MODULE="gpio-aggregator" + +fail() { + echo "$*" >&2 + echo "GPIO $MODULE test FAIL" + exit 1 +} + +skip() { + echo "$*" >&2 + echo "GPIO $MODULE test SKIP" + exit 4 +} + +# gpio-sim +sim_enable_chip() { + local CHIP=$1 + + echo 1 > "$CONFIGFS_SIM_DIR/$CHIP/live" || fail "Unable to enable the chip" +} + +sim_disable_chip() { + local CHIP=$1 + + echo 0 > "$CONFIGFS_SIM_DIR/$CHIP/live" || fail "Unable to disable the chip" +} + +sim_configfs_cleanup() { + local NOCHECK=${1:-0} + + for CHIP_DIR in "$CONFIGFS_SIM_DIR"/*; do + [ -d "$CHIP_DIR" ] || continue + echo 0 > "$CHIP_DIR/live" + find "$CHIP_DIR" -depth -type d -exec rmdir {} \; + done + [ "$NOCHECK" -eq 1 ] && return; + remaining=$(find "$CONFIGFS_SIM_DIR" -mindepth 1 -type d 2> /dev/null) + if [ -n "$remaining" ]; then + fail "Directories remain in $CONFIGFS_SIM_DIR: $remaining" + fi +} + +sim_get_chip_label() { + local CHIP=$1 + local BANK=$2 + local CHIP_NAME=$(cat "$CONFIGFS_SIM_DIR/$CHIP/$BANK/chip_name" 2> /dev/null) || \ + fail "Unable to read the chip name from configfs" + + $BASE_DIR/gpio-chip-info "/dev/$CHIP_NAME" label || \ + fail "Unable to read the chip label from the character device" +} + +# gpio-aggregator +agg_create_chip() { + local CHIP=$1 + + mkdir "$CONFIGFS_AGG_DIR/$CHIP" +} + +agg_remove_chip() { + local CHIP=$1 + + find "$CONFIGFS_AGG_DIR/$CHIP/" -depth -type d -exec rmdir {} \; || \ + fail "Unable to remove $CONFIGFS_AGG_DIR/$CHIP" +} + +agg_create_line() { + local CHIP=$1 + local LINE=$2 + + mkdir "$CONFIGFS_AGG_DIR/$CHIP/$LINE" +} + +agg_remove_line() { + local CHIP=$1 + local LINE=$2 + + rmdir "$CONFIGFS_AGG_DIR/$CHIP/$LINE" +} + +agg_set_key() { + local CHIP=$1 + local LINE=$2 + local KEY=$3 + + echo "$KEY" > "$CONFIGFS_AGG_DIR/$CHIP/$LINE/key" || fail "Unable to set the lookup key" +} + +agg_set_offset() { + local CHIP=$1 + local LINE=$2 + local OFFSET=$3 + + echo "$OFFSET" > "$CONFIGFS_AGG_DIR/$CHIP/$LINE/offset" || \ + fail "Unable to set the lookup offset" +} + +agg_set_line_name() { + local CHIP=$1 + local LINE=$2 + local NAME=$3 + + echo "$NAME" > "$CONFIGFS_AGG_DIR/$CHIP/$LINE/name" || fail "Unable to set the line name" +} + +agg_enable_chip() { + local CHIP=$1 + + echo 1 > "$CONFIGFS_AGG_DIR/$CHIP/live" || fail "Unable to enable the chip" +} + +agg_disable_chip() { + local CHIP=$1 + + echo 0 > "$CONFIGFS_AGG_DIR/$CHIP/live" || fail "Unable to disable the chip" +} + +agg_configfs_cleanup() { + local NOCHECK=${1:-0} + + for CHIP_DIR in "$CONFIGFS_AGG_DIR"/*; do + [ -d "$CHIP_DIR" ] || continue + echo 0 > "$CHIP_DIR/live" 2> /dev/null + find "$CHIP_DIR" -depth -type d -exec rmdir {} \; + done + [ "$NOCHECK" -eq 1 ] && return; + remaining=$(find "$CONFIGFS_AGG_DIR" -mindepth 1 -type d 2> /dev/null) + if [ -n "$remaining" ]; then + fail "Directories remain in $CONFIGFS_AGG_DIR: $remaining" + fi +} + +agg_configfs_dev_name() { + local CHIP=$1 + + cat "$CONFIGFS_AGG_DIR/$CHIP/dev_name" 2> /dev/null || \ + fail "Unable to read the device name from configfs" +} + +agg_configfs_chip_name() { + local CHIP=$1 + local DEV_NAME=$(agg_configfs_dev_name "$CHIP") + local CHIP_LIST=$(find "/sys/devices/platform/$DEV_NAME" \ + -maxdepth 1 -type d -name "gpiochip[0-9]*" 2> /dev/null) + local CHIP_COUNT=$(echo "$CHIP_LIST" | wc -l) + + if [ -z "$CHIP_LIST" ]; then + fail "No gpiochip in /sys/devices/platform/$DEV_NAME/" + elif [ "$CHIP_COUNT" -ne 1 ]; then + fail "Multiple gpiochips unexpectedly found: $CHIP_LIST" + fi + basename "$CHIP_LIST" +} + +agg_get_chip_num_lines() { + local CHIP=$1 + local N_DIR=$(ls -d $CONFIGFS_AGG_DIR/$CHIP/line[0-9]* 2> /dev/null | wc -l) + local N_LINES + + if [ "$(cat $CONFIGFS_AGG_DIR/$CHIP/live)" = 0 ]; then + echo "$N_DIR" + else + N_LINES=$( + $BASE_DIR/gpio-chip-info \ + "/dev/$(agg_configfs_chip_name "$CHIP")" num-lines + ) || fail "Unable to read the number of lines from the character device" + if [ $N_DIR != $N_LINES ]; then + fail "Discrepancy between two sources for the number of lines" + fi + echo "$N_LINES" + fi +} + +agg_get_chip_label() { + local CHIP=$1 + + $BASE_DIR/gpio-chip-info "/dev/$(agg_configfs_chip_name "$CHIP")" label || \ + fail "Unable to read the chip label from the character device" +} + +agg_get_line_name() { + local CHIP=$1 + local OFFSET=$2 + local NAME_CONFIGFS=$(cat "$CONFIGFS_AGG_DIR/$CHIP/line${OFFSET}/name") + local NAME_CDEV + + if [ "$(cat "$CONFIGFS_AGG_DIR/$CHIP/live")" = 0 ]; then + echo "$NAME_CONFIGFS" + else + NAME_CDEV=$( + $BASE_DIR/gpio-line-name \ + "/dev/$(agg_configfs_chip_name "$CHIP")" "$OFFSET" + ) || fail "Unable to read the line name from the character device" + if [ "$NAME_CONFIGFS" != "$NAME_CDEV" ]; then + fail "Discrepancy between two sources for the name of line" + fi + echo "$NAME_CDEV" + fi +} + + +# Load the modules. This will pull in configfs if needed too. +modprobe gpio-sim || skip "unable to load the gpio-sim module" +modprobe gpio-aggregator || skip "unable to load the gpio-aggregator module" + +# Make sure configfs is mounted at /sys/kernel/config. Wait a bit if needed. +for IDX in $(seq 5); do + if [ "$IDX" -eq "5" ]; then + skip "configfs not mounted at /sys/kernel/config" + fi + + mountpoint -q /sys/kernel/config && break + sleep 0.1 +done + +# If the module was already loaded: remove all previous chips +agg_configfs_cleanup +sim_configfs_cleanup + +trap "exit 1" SIGTERM SIGINT +trap "agg_configfs_cleanup 1; sim_configfs_cleanup 1" EXIT + +# Use gpio-sim chips as the test backend +for CHIP in $(seq -f "chip%g" 0 1); do + mkdir $CONFIGFS_SIM_DIR/$CHIP + for BANK in $(seq -f "bank%g" 0 1); do + mkdir -p "$CONFIGFS_SIM_DIR/$CHIP/$BANK" + echo "${CHIP}_${BANK}" > "$CONFIGFS_SIM_DIR/$CHIP/$BANK/label" || \ + fail "unable to set the chip label" + echo 16 > "$CONFIGFS_SIM_DIR/$CHIP/$BANK/num_lines" || \ + fail "unable to set the number of lines" + for IDX in $(seq 0 15); do + LINE_NAME="${CHIP}${BANK}_${IDX}" + LINE_DIR="$CONFIGFS_SIM_DIR/$CHIP/$BANK/line$IDX" + mkdir -p $LINE_DIR + echo "$LINE_NAME" > "$LINE_DIR/name" || fail "unable to set the line name" + done + done + sim_enable_chip "$CHIP" +done + +echo "1. GPIO aggregator creation/deletion" + +echo "1.1. Creation/deletion via configfs" + +echo "1.1.1. Minimum creation/deletion" +agg_create_chip agg0 +agg_create_line agg0 line0 +agg_set_key agg0 line0 "$(sim_get_chip_label chip0 bank0)" +agg_set_offset agg0 line0 5 +agg_set_line_name agg0 line0 test0 +agg_enable_chip agg0 +test "$(cat "$CONFIGFS_AGG_DIR/agg0/live")" = 1 || fail "chip unexpectedly dead" +test "$(agg_get_chip_label agg0)" = "$(agg_configfs_dev_name agg0)" || \ + fail "label is inconsistent" +test "$(agg_get_chip_num_lines agg0)" = "1" || fail "number of lines is not 1" +test "$(agg_get_line_name agg0 0)" = "test0" || fail "line name is unset" +agg_disable_chip agg0 +agg_remove_line agg0 line0 +agg_remove_chip agg0 + +echo "1.1.2. Complex creation/deletion" +agg_create_chip agg0 +agg_create_line agg0 line0 +agg_create_line agg0 line1 +agg_create_line agg0 line2 +agg_create_line agg0 line3 +agg_set_key agg0 line0 "$(sim_get_chip_label chip0 bank0)" +agg_set_key agg0 line1 "$(sim_get_chip_label chip0 bank1)" +agg_set_key agg0 line2 "$(sim_get_chip_label chip1 bank0)" +agg_set_key agg0 line3 "$(sim_get_chip_label chip1 bank1)" +agg_set_offset agg0 line0 1 +agg_set_offset agg0 line1 3 +agg_set_offset agg0 line2 5 +agg_set_offset agg0 line3 7 +agg_set_line_name agg0 line0 test0 +agg_set_line_name agg0 line1 test1 +agg_set_line_name agg0 line2 test2 +agg_set_line_name agg0 line3 test3 +agg_enable_chip agg0 +test "$(cat "$CONFIGFS_AGG_DIR/agg0/live")" = 1 || fail "chip unexpectedly dead" +test "$(agg_get_chip_label agg0)" = "$(agg_configfs_dev_name agg0)" || \ + fail "label is inconsistent" +test "$(agg_get_chip_num_lines agg0)" = "4" || fail "number of lines is not 1" +test "$(agg_get_line_name agg0 0)" = "test0" || fail "line name is unset" +test "$(agg_get_line_name agg0 1)" = "test1" || fail "line name is unset" +test "$(agg_get_line_name agg0 2)" = "test2" || fail "line name is unset" +test "$(agg_get_line_name agg0 3)" = "test3" || fail "line name is unset" +agg_disable_chip agg0 +agg_remove_line agg0 line0 +agg_remove_line agg0 line1 +agg_remove_line agg0 line2 +agg_remove_line agg0 line3 +agg_remove_chip agg0 + +echo "1.1.3. Can't instantiate a chip without any line" +agg_create_chip agg0 +echo 1 > "$CONFIGFS_AGG_DIR/agg0/live" 2> /dev/null && fail "chip unexpectedly enabled" +test "$(cat "$CONFIGFS_AGG_DIR/agg0/live")" = 0 || fail "chip unexpectedly alive" +agg_remove_chip agg0 + +echo "1.1.4. Can't instantiate a chip with invalid configuration" +agg_create_chip agg0 +agg_create_line agg0 line0 +agg_set_key agg0 line0 "chipX_bankX" +agg_set_offset agg0 line0 99 +agg_set_line_name agg0 line0 test0 +echo 1 > "$CONFIGFS_AGG_DIR/agg0/live" 2> /dev/null && fail "chip unexpectedly enabled" +test "$(cat "$CONFIGFS_AGG_DIR/agg0/live")" = 0 || fail "chip unexpectedly alive" +agg_remove_line agg0 line0 +agg_remove_chip agg0 + +echo "1.1.5. Can't instantiate a chip asynchronously via deferred probe" +agg_create_chip agg0 +agg_create_line agg0 line0 +agg_set_key agg0 line0 "chip0_bank0" +agg_set_offset agg0 line0 5 +agg_set_line_name agg0 line0 test0 +sim_disable_chip chip0 +echo 1 > "$CONFIGFS_AGG_DIR/agg0/live" 2> /dev/null && fail "chip unexpectedly enabled" +test "$(cat "$CONFIGFS_AGG_DIR/agg0/live")" = 0 || fail "chip unexpectedly alive" +sim_enable_chip chip0 +sleep 1 +test "$(cat "$CONFIGFS_AGG_DIR/agg0/live")" = 0 || \ + fail "chip unexpectedly transitioned to 'live' state" +agg_remove_line agg0 line0 +agg_remove_chip agg0 + +echo "1.2. Creation/deletion via sysfs" + +echo "1.2.1. Minimum creation/deletion" +echo "chip0_bank0 0" > "$SYSFS_AGG_DIR/new_device" +CHIPNAME=$(agg_configfs_chip_name _sysfs.0) +test "$(cat "$CONFIGFS_AGG_DIR/_sysfs.0/live")" = 1 || fail "chip unexpectedly dead" +test "$(agg_get_chip_label _sysfs.0)" = "$(agg_configfs_dev_name _sysfs.0)" || \ + fail "label is inconsistent" +test "$(agg_get_chip_num_lines _sysfs.0)" = "1" || fail "number of lines is not 1" +test "$(agg_get_line_name _sysfs.0 0)" = "" || fail "line name is unset" +echo "$(agg_configfs_dev_name _sysfs.0)" > "$SYSFS_AGG_DIR/delete_device" +test -d $CONFIGFS_AGG_DIR/_sysfs.0 && fail "_sysfs.0 unexpectedly remains" +test -d /dev/${CHIPNAME} && fail "/dev/${CHIPNAME} unexpectedly remains" + +echo "1.2.2. Complex creation/deletion" +echo "chip0bank0_0 chip1_bank1 10-11" > "$SYSFS_AGG_DIR/new_device" +CHIPNAME=$(agg_configfs_chip_name _sysfs.0) +test "$(cat "$CONFIGFS_AGG_DIR/_sysfs.0/live")" = 1 || fail "chip unexpectedly dead" +test "$(agg_get_chip_label _sysfs.0)" = "$(agg_configfs_dev_name _sysfs.0)" || \ + fail "label is inconsistent" +test "$(agg_get_chip_num_lines _sysfs.0)" = "3" || fail "number of lines is not 3" +test "$(agg_get_line_name _sysfs.0 0)" = "" || fail "line name is unset" +test "$(agg_get_line_name _sysfs.0 1)" = "" || fail "line name is unset" +test "$(agg_get_line_name _sysfs.0 2)" = "" || fail "line name is unset" +echo "$(agg_configfs_dev_name _sysfs.0)" > "$SYSFS_AGG_DIR/delete_device" +test -d $CONFIGFS_AGG_DIR/_sysfs.0 && fail "_sysfs.0 unexpectedly remains" +test -d /dev/${CHIPNAME} && fail "/dev/${CHIPNAME} unexpectedly remains" + +echo "1.2.3. Asynchronous creation with deferred probe" +sim_disable_chip chip0 +echo 'chip0_bank0 0' > $SYSFS_AGG_DIR/new_device +sleep 1 +test "$(cat "$CONFIGFS_AGG_DIR/_sysfs.0/live")" = 0 || fail "chip unexpectedly alive" +sim_enable_chip chip0 +sleep 1 +CHIPNAME=$(agg_configfs_chip_name _sysfs.0) +test "$(cat "$CONFIGFS_AGG_DIR/_sysfs.0/live")" = 1 || fail "chip unexpectedly remains dead" +test "$(agg_get_chip_label _sysfs.0)" = "$(agg_configfs_dev_name _sysfs.0)" || \ + fail "label is inconsistent" +test "$(agg_get_chip_num_lines _sysfs.0)" = "1" || fail "number of lines is not 1" +test "$(agg_get_line_name _sysfs.0 0)" = "" || fail "line name unexpectedly set" +echo "$(agg_configfs_dev_name _sysfs.0)" > "$SYSFS_AGG_DIR/delete_device" +test -d $CONFIGFS_AGG_DIR/_sysfs.0 && fail "_sysfs.0 unexpectedly remains" +test -d /dev/${CHIPNAME} && fail "/dev/${CHIPNAME} unexpectedly remains" + +echo "1.2.4. Can't instantiate a chip with invalid configuration" +echo "xyz 0" > "$SYSFS_AGG_DIR/new_device" +test "$(cat $CONFIGFS_AGG_DIR/_sysfs.0/live)" = 0 || fail "chip unexpectedly alive" +echo "$(agg_configfs_dev_name _sysfs.0)" > "$SYSFS_AGG_DIR/delete_device" + +echo "2. GPIO aggregator configuration" + +echo "2.1. Configuring aggregators instantiated via configfs" +setup_2_1() { + agg_create_chip agg0 + agg_create_line agg0 line0 + agg_create_line agg0 line1 + agg_set_key agg0 line0 "$(sim_get_chip_label chip0 bank0)" + agg_set_key agg0 line1 "$(sim_get_chip_label chip1 bank0)" + agg_set_offset agg0 line0 1 + agg_set_offset agg0 line1 3 + agg_set_line_name agg0 line0 test0 + agg_set_line_name agg0 line1 test1 + agg_enable_chip agg0 +} +teardown_2_1() { + agg_configfs_cleanup +} + +echo "2.1.1. While offline" + +echo "2.1.1.1. Line can be added/removed" +setup_2_1 +agg_disable_chip agg0 +agg_create_line agg0 line2 +agg_set_key agg0 line2 "$(sim_get_chip_label chip0 bank1)" +agg_set_offset agg0 line2 5 +agg_enable_chip agg0 +test "$(agg_get_chip_num_lines agg0)" = "3" || fail "number of lines is not 1" +teardown_2_1 + +echo "2.1.1.2. Line key can be modified" +setup_2_1 +agg_disable_chip agg0 +agg_set_key agg0 line0 "$(sim_get_chip_label chip0 bank1)" +agg_set_key agg0 line1 "$(sim_get_chip_label chip1 bank1)" +agg_enable_chip agg0 +teardown_2_1 + +echo "2.1.1.3. Line name can be modified" +setup_2_1 +agg_disable_chip agg0 +agg_set_line_name agg0 line0 new0 +agg_set_line_name agg0 line1 new1 +agg_enable_chip agg0 +test "$(agg_get_line_name agg0 0)" = "new0" || fail "line name is unset" +test "$(agg_get_line_name agg0 1)" = "new1" || fail "line name is unset" +teardown_2_1 + +echo "2.1.1.4. Line offset can be modified" +setup_2_1 +agg_disable_chip agg0 +agg_set_offset agg0 line0 5 +agg_set_offset agg0 line1 7 +agg_enable_chip agg0 +teardown_2_1 + +echo "2.1.1.5. Can re-enable a chip after valid reconfiguration" +setup_2_1 +agg_disable_chip agg0 +agg_set_key agg0 line0 "$(sim_get_chip_label chip1 bank1)" +agg_set_offset agg0 line0 15 +agg_set_key agg0 line1 "$(sim_get_chip_label chip0 bank1)" +agg_set_offset agg0 line0 14 +agg_create_line agg0 line2 +agg_set_key agg0 line2 "$(sim_get_chip_label chip0 bank1)" +agg_set_offset agg0 line2 13 +agg_enable_chip agg0 +test "$(agg_get_chip_num_lines agg0)" = "3" || fail "number of lines is not 1" +teardown_2_1 + +echo "2.1.1.7. Can't re-enable a chip with invalid reconfiguration" +setup_2_1 +agg_disable_chip agg0 +agg_set_key agg0 line0 invalidkey +echo 1 > "$CONFIGFS_AGG_DIR/agg0/live" 2> /dev/null && fail "chip unexpectedly enabled" +teardown_2_1 +setup_2_1 +agg_disable_chip agg0 +agg_set_offset agg0 line0 99 +echo 1 > "$CONFIGFS_AGG_DIR/agg0/live" 2> /dev/null && fail "chip unexpectedly enabled" +teardown_2_1 + +echo "2.1.2. While online" + +echo "2.1.2.1. Can't add/remove line" +setup_2_1 +mkdir "$CONFIGFS_AGG_DIR/agg0/line2" 2> /dev/null && fail "line unexpectedly added" +rmdir "$CONFIGFS_AGG_DIR/agg0/line1" 2> /dev/null && fail "line unexpectedly removed" +teardown_2_1 + +echo "2.1.2.2. Can't modify line key" +setup_2_1 +echo "chip1_bank1" > "$CONFIGFS_AGG_DIR/agg0/line0/key" 2> /dev/null && \ + fail "lookup key unexpectedly updated" +teardown_2_1 + +echo "2.1.2.3. Can't modify line name" +setup_2_1 +echo "new0" > "$CONFIGFS_AGG_DIR/agg0/line0/name" 2> /dev/null && \ + fail "name unexpectedly updated" +teardown_2_1 + +echo "2.1.2.4. Can't modify line offset" +setup_2_1 +echo "5" > "$CONFIGFS_AGG_DIR/agg0/line0/offset" 2> /dev/null && \ + fail "offset unexpectedly updated" +teardown_2_1 + +echo "2.2. Configuring aggregators instantiated via sysfs" +setup_2_2() { + echo "chip0_bank0 1 chip1_bank0 3" > "$SYSFS_AGG_DIR/new_device" +} +teardown_2_2() { + echo "$(agg_configfs_dev_name _sysfs.0)" > "$SYSFS_AGG_DIR/delete_device" +} + +echo "2.2.1. While online" + +echo "2.2.1.1. Can toggle live" +setup_2_2 +agg_disable_chip _sysfs.0 +agg_enable_chip _sysfs.0 +teardown_2_2 + +echo "2.2.1.2. Can't add/remove line" +setup_2_2 +mkdir "$CONFIGFS_AGG_DIR/_sysfs.0/line2" 2> /dev/null && fail "line unexpectedly added" +rmdir "$CONFIGFS_AGG_DIR/_sysfs.0/line1" 2> /dev/null && fail "line unexpectedly removed" +teardown_2_2 + +echo "2.2.1.3. Can't modify line key" +setup_2_2 +echo "chip1_bank1" > "$CONFIGFS_AGG_DIR/_sysfs.0/line0/key" 2> /dev/null && \ + fail "lookup key unexpectedly updated" +teardown_2_2 + +echo "2.2.1.4. Can't modify line name" +setup_2_2 +echo "new0" > "$CONFIGFS_AGG_DIR/_sysfs.0/line0/name" 2> /dev/null && \ + fail "name unexpectedly updated" +teardown_2_2 + +echo "2.2.1.5. Can't modify line offset" +setup_2_2 +echo "5" > "$CONFIGFS_AGG_DIR/_sysfs.0/line0/offset" 2> /dev/null && \ + fail "offset unexpectedly updated" +teardown_2_2 + +echo "2.2.2. While waiting for deferred probe" + +echo "2.2.2.1. Can't add/remove line despite live = 0" +sim_disable_chip chip0 +setup_2_2 +mkdir "$CONFIGFS_AGG_DIR/_sysfs.0/line2" 2> /dev/null && fail "line unexpectedly added" +rmdir "$CONFIGFS_AGG_DIR/_sysfs.0/line1" 2> /dev/null && fail "line unexpectedly removed" +teardown_2_2 +sim_enable_chip chip0 + +echo "2.2.2.2. Can't modify line key" +sim_disable_chip chip0 +setup_2_2 +echo "chip1_bank1" > "$CONFIGFS_AGG_DIR/_sysfs.0/line0/key" 2> /dev/null && \ + fail "lookup key unexpectedly updated" +teardown_2_2 +sim_enable_chip chip0 + +echo "2.2.2.3. Can't modify line name" +sim_disable_chip chip0 +setup_2_2 +echo "new0" > "$CONFIGFS_AGG_DIR/_sysfs.0/line0/name" 2> /dev/null && \ + fail "name unexpectedly updated" +teardown_2_2 +sim_enable_chip chip0 + +echo "2.2.2.4. Can't modify line offset" +sim_disable_chip chip0 +setup_2_2 +echo 5 > "$CONFIGFS_AGG_DIR/_sysfs.0/line0/offset" 2> /dev/null && \ + fail "offset unexpectedly updated" +teardown_2_2 +sim_enable_chip chip0 + +echo "2.2.2.5. Can't toggle live" +sim_disable_chip chip0 +setup_2_2 +test "$(cat "$CONFIGFS_AGG_DIR/_sysfs.0/live")" = 0 || fail "chip unexpectedly alive" +echo 1 > "$CONFIGFS_AGG_DIR/_sysfs.0/live" 2> /dev/null && fail "chip unexpectedly enabled" +teardown_2_2 +sim_enable_chip chip0 + +echo "2.2.3. While offline" + +echo "2.2.3.1. Can't add/remove line despite live = 0" +setup_2_2 +agg_disable_chip _sysfs.0 +mkdir "$CONFIGFS_AGG_DIR/_sysfs.0/line2" 2> /dev/null && fail "line unexpectedly added" +rmdir "$CONFIGFS_AGG_DIR/_sysfs.0/line1" 2> /dev/null && fail "line unexpectedly removed" +teardown_2_2 + +echo "2.2.3.2. Line key can be modified" +setup_2_2 +agg_disable_chip _sysfs.0 +agg_set_key _sysfs.0 line0 "$(sim_get_chip_label chip0 bank1)" +agg_set_key _sysfs.0 line1 "$(sim_get_chip_label chip1 bank1)" +agg_enable_chip _sysfs.0 +teardown_2_2 + +echo "2.2.3.3. Line name can be modified" +setup_2_2 +agg_disable_chip _sysfs.0 +agg_set_line_name _sysfs.0 line0 new0 +agg_set_line_name _sysfs.0 line1 new1 +agg_enable_chip _sysfs.0 +test "$(agg_get_line_name _sysfs.0 0)" = "new0" || fail "line name is unset" +test "$(agg_get_line_name _sysfs.0 1)" = "new1" || fail "line name is unset" +teardown_2_2 + +echo "2.2.3.4. Line offset can be modified" +setup_2_2 +agg_disable_chip _sysfs.0 +agg_set_offset _sysfs.0 line0 5 +agg_set_offset _sysfs.0 line1 7 +agg_enable_chip _sysfs.0 +teardown_2_2 + +echo "2.2.3.5. Can re-enable a chip with valid reconfiguration" +setup_2_2 +agg_disable_chip _sysfs.0 +agg_set_key _sysfs.0 line0 "$(sim_get_chip_label chip1 bank1)" +agg_set_offset _sysfs.0 line0 15 +agg_set_key _sysfs.0 line1 "$(sim_get_chip_label chip0 bank1)" +agg_set_offset _sysfs.0 line0 14 +agg_enable_chip _sysfs.0 +teardown_2_2 + +echo "2.2.3.6. Can't re-enable a chip with invalid reconfiguration" +setup_2_2 +agg_disable_chip _sysfs.0 +agg_set_key _sysfs.0 line0 invalidkey +echo 1 > "$CONFIGFS_AGG_DIR/_sysfs.0/live" 2> /dev/null && fail "chip unexpectedly enabled" +teardown_2_2 +setup_2_2 +agg_disable_chip _sysfs.0 +agg_set_offset _sysfs.0 line0 99 +echo 1 > "$CONFIGFS_AGG_DIR/_sysfs.0/live" 2> /dev/null && fail "chip unexpectedly enabled" +teardown_2_2 + +echo "3. Module unload" + +echo "3.1. Can't unload module if there is at least one device created via configfs" +agg_create_chip agg0 +modprobe -r gpio-aggregator 2> /dev/null +test -d /sys/module/gpio_aggregator || fail "module unexpectedly unloaded" +agg_remove_chip agg0 + +echo "3.2. Can unload module if there is no device created via configfs" +echo "chip0_bank0 1 chip1_bank0 3" > "$SYSFS_AGG_DIR/new_device" +modprobe -r gpio-aggregator 2> /dev/null +test -d /sys/module/gpio_aggregator && fail "module unexpectedly remains to be loaded" +modprobe gpio-aggregator 2> /dev/null + +echo "4. GPIO forwarder functional" +SETTINGS="chip0:bank0:2 chip0:bank1:4 chip1:bank0:6 chip1:bank1:8" +setup_4() { + local OFFSET=0 + agg_create_chip agg0 + for SETTING in $SETTINGS; do + CHIP=$(echo "$SETTING" | cut -d: -f1) + BANK=$(echo "$SETTING" | cut -d: -f2) + LINE=$(echo "$SETTING" | cut -d: -f3) + agg_create_line agg0 "line${OFFSET}" + agg_set_key agg0 "line${OFFSET}" "$(sim_get_chip_label "$CHIP" "$BANK")" + agg_set_offset agg0 "line${OFFSET}" "$LINE" + OFFSET=$(expr $OFFSET + 1) + done + agg_enable_chip agg0 +} +teardown_4() { + agg_configfs_cleanup +} + +echo "4.1. Forwarding set values" +setup_4 +OFFSET=0 +for SETTING in $SETTINGS; do + CHIP=$(echo "$SETTING" | cut -d: -f1) + BANK=$(echo "$SETTING" | cut -d: -f2) + LINE=$(echo "$SETTING" | cut -d: -f3) + DEVNAME=$(cat "$CONFIGFS_SIM_DIR/$CHIP/dev_name") + CHIPNAME=$(cat "$CONFIGFS_SIM_DIR/$CHIP/$BANK/chip_name") + VAL_PATH="/sys/devices/platform/$DEVNAME/$CHIPNAME/sim_gpio${LINE}/value" + test $(cat $VAL_PATH) = "0" || fail "incorrect value read from sysfs" + $BASE_DIR/gpio-mockup-cdev -s 1 "/dev/$(agg_configfs_chip_name agg0)" "$OFFSET" & + mock_pid=$! + sleep 0.1 # FIXME Any better way? + test "$(cat $VAL_PATH)" = "1" || fail "incorrect value read from sysfs" + kill "$mock_pid" + OFFSET=$(expr $OFFSET + 1) +done +teardown_4 + +echo "4.2. Forwarding set config" +setup_4 +OFFSET=0 +for SETTING in $SETTINGS; do + CHIP=$(echo "$SETTING" | cut -d: -f1) + BANK=$(echo "$SETTING" | cut -d: -f2) + LINE=$(echo "$SETTING" | cut -d: -f3) + DEVNAME=$(cat "$CONFIGFS_SIM_DIR/$CHIP/dev_name") + CHIPNAME=$(cat "$CONFIGFS_SIM_DIR/$CHIP/$BANK/chip_name") + VAL_PATH="/sys/devices/platform/$DEVNAME/$CHIPNAME/sim_gpio${LINE}/value" + $BASE_DIR/gpio-mockup-cdev -b pull-up "/dev/$(agg_configfs_chip_name agg0)" "$OFFSET" + test $(cat "$VAL_PATH") = "1" || fail "incorrect value read from sysfs" + OFFSET=$(expr $OFFSET + 1) +done +teardown_4 + +echo "5. Race condition verification" + +echo "5.1. Stress test of new_device/delete_device and module load/unload" +for _ in $(seq 1000); do + { + echo "dummy 0" > "$SYSFS_AGG_DIR/new_device" + cat "$CONFIGFS_AGG_DIR/_sysfs.0/dev_name" > "$SYSFS_AGG_DIR/delete_device" + } 2> /dev/null +done & +writer_pid=$! +while kill -0 "$writer_pid" 2> /dev/null; do + { + modprobe gpio-aggregator + modprobe -r gpio-aggregator + } 2> /dev/null +done + +echo "GPIO $MODULE test PASS" From eebfcb98cdc0228f5e1b7407f9db1c602bd8e545 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Sat, 12 Apr 2025 13:15:00 +0300 Subject: [PATCH 024/123] gpio: aggregator: fix "_sysfs" prefix check in gpio_aggregator_make_group() This code is intended to reject strings that start with "_sysfs" but the strcmp() limit is wrong so checks the whole string instead of the prefix. Fixes: 83c8e3df642f ("gpio: aggregator: expose aggregator created via legacy sysfs to configfs") Signed-off-by: Dan Carpenter Reviewed-by: Geert Uytterhoeven Acked-by: Koichiro Den Link: https://lore.kernel.org/r/30210ed77b40b4b6629de659cb56b9ec7832c447.1744452787.git.dan.carpenter@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-aggregator.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-aggregator.c b/drivers/gpio/gpio-aggregator.c index dde969f29ee2..b4c9e373a6ec 100644 --- a/drivers/gpio/gpio-aggregator.c +++ b/drivers/gpio/gpio-aggregator.c @@ -1016,7 +1016,7 @@ gpio_aggregator_make_group(struct config_group *group, const char *name) * for devices create via legacy sysfs interface. */ if (strncmp(name, AGGREGATOR_LEGACY_PREFIX, - sizeof(AGGREGATOR_LEGACY_PREFIX)) == 0) + sizeof(AGGREGATOR_LEGACY_PREFIX) - 1) == 0) return ERR_PTR(-EINVAL); /* arg space is unneeded */ From 2e8636ca340002f3ac31383622911a1aa75fb086 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Sat, 12 Apr 2025 13:15:08 +0300 Subject: [PATCH 025/123] gpio: aggregator: Fix gpio_aggregator_line_alloc() checking The gpio_aggregator_line_alloc() function returns error pointers, but the callers check for NULL. Update the error checking in the callers. Fixes: 83c8e3df642f ("gpio: aggregator: expose aggregator created via legacy sysfs to configfs") Signed-off-by: Dan Carpenter Acked-by: Koichiro Den Link: https://lore.kernel.org/r/cc71d8cf6e9bb4bb8cd9ae5050100081891d9345.1744452787.git.dan.carpenter@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-aggregator.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpio/gpio-aggregator.c b/drivers/gpio/gpio-aggregator.c index b4c9e373a6ec..e1b2efc0df99 100644 --- a/drivers/gpio/gpio-aggregator.c +++ b/drivers/gpio/gpio-aggregator.c @@ -984,8 +984,8 @@ gpio_aggregator_device_make_group(struct config_group *group, const char *name) return ERR_PTR(-EINVAL); line = gpio_aggregator_line_alloc(aggr, idx, NULL, -1); - if (!line) - return ERR_PTR(-ENOMEM); + if (IS_ERR(line)) + return ERR_CAST(line); config_group_init_type_name(&line->group, name, &gpio_aggregator_line_type); @@ -1074,8 +1074,8 @@ static int gpio_aggregator_parse(struct gpio_aggregator *aggr) /* Named GPIO line */ scnprintf(name, sizeof(name), "line%u", n); line = gpio_aggregator_line_alloc(aggr, n, key, -1); - if (!line) { - error = -ENOMEM; + if (IS_ERR(line)) { + error = PTR_ERR(line); goto err; } config_group_init_type_name(&line->group, name, @@ -1105,8 +1105,8 @@ static int gpio_aggregator_parse(struct gpio_aggregator *aggr) for_each_set_bit(i, bitmap, AGGREGATOR_MAX_GPIOS) { scnprintf(name, sizeof(name), "line%u", n); line = gpio_aggregator_line_alloc(aggr, n, key, i); - if (!line) { - error = -ENOMEM; + if (IS_ERR(line)) { + error = PTR_ERR(line); goto err; } config_group_init_type_name(&line->group, name, From db1baf69e563fc222a75c0add5c76f437c717ac0 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Sat, 12 Apr 2025 13:15:16 +0300 Subject: [PATCH 026/123] gpio: aggregator: Return an error if there are no GPIOs in gpio_aggregator_parse() The error handling in gpio_aggregator_parse() was re-written. It now returns success if there are no GPIOs. Restore the previous behavior and return -EINVAL instead. Fixes: 83c8e3df642f ("gpio: aggregator: expose aggregator created via legacy sysfs to configfs") Signed-off-by: Dan Carpenter Acked-by: Koichiro Den Link: https://lore.kernel.org/r/9dcd5fda7a3819e896d9eee4156e7c46c9a64595.1744452787.git.dan.carpenter@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-aggregator.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpio/gpio-aggregator.c b/drivers/gpio/gpio-aggregator.c index e1b2efc0df99..62bb50af7cda 100644 --- a/drivers/gpio/gpio-aggregator.c +++ b/drivers/gpio/gpio-aggregator.c @@ -1128,6 +1128,7 @@ static int gpio_aggregator_parse(struct gpio_aggregator *aggr) if (!n) { pr_err("No GPIOs specified\n"); + error = -EINVAL; goto err; } From 05b43de95add3d787a7a88378086bf01c10b3f40 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Sat, 12 Apr 2025 13:15:25 +0300 Subject: [PATCH 027/123] gpio: aggregator: Fix error code in gpio_aggregator_activate() Propagate the error code if gpio_aggregator_make_device_sw_node() fails. Don't return success. Fixes: 86f162e73d2d ("gpio: aggregator: introduce basic configfs interface") Signed-off-by: Dan Carpenter Acked-by: Koichiro Den Link: https://lore.kernel.org/r/79b804a0769a434698616bebedacc0e5d5605fdc.1744452787.git.dan.carpenter@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-aggregator.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-aggregator.c b/drivers/gpio/gpio-aggregator.c index 62bb50af7cda..071d76dbfcec 100644 --- a/drivers/gpio/gpio-aggregator.c +++ b/drivers/gpio/gpio-aggregator.c @@ -626,8 +626,10 @@ static int gpio_aggregator_activate(struct gpio_aggregator *aggr) return -ENOMEM; swnode = gpio_aggregator_make_device_sw_node(aggr); - if (IS_ERR(swnode)) + if (IS_ERR(swnode)) { + ret = PTR_ERR(swnode); goto err_remove_lookups; + } memset(&pdevinfo, 0, sizeof(pdevinfo)); pdevinfo.name = DRV_NAME; From d945ff52642d98eb6fa191f88a9cfde729129395 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Sat, 12 Apr 2025 13:15:31 +0300 Subject: [PATCH 028/123] gpio: aggregator: Fix leak in gpio_aggregator_parse() Call gpio_aggregator_free_lines() before returning on this error path. Fixes: 83c8e3df642f ("gpio: aggregator: expose aggregator created via legacy sysfs to configfs") Signed-off-by: Dan Carpenter Acked-by: Koichiro Den Link: https://lore.kernel.org/r/e023bfe52509ce1bef6209ec7c47e99279c551dd.1744452787.git.dan.carpenter@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-aggregator.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-aggregator.c b/drivers/gpio/gpio-aggregator.c index 071d76dbfcec..6f941db02c04 100644 --- a/drivers/gpio/gpio-aggregator.c +++ b/drivers/gpio/gpio-aggregator.c @@ -1101,7 +1101,7 @@ static int gpio_aggregator_parse(struct gpio_aggregator *aggr) error = bitmap_parselist(offsets, bitmap, AGGREGATOR_MAX_GPIOS); if (error) { pr_err("Cannot parse %s: %d\n", offsets, error); - return error; + goto err; } for_each_set_bit(i, bitmap, AGGREGATOR_MAX_GPIOS) { From 290ffcfe3042dcd5b701a98edd48d7962d35556b Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Sun, 13 Apr 2025 00:01:19 +0900 Subject: [PATCH 029/123] selftests: gpio: gpio-aggregator: add a test case for _sysfs prefix reservation The kernel doc for gpio-aggregator configfs interface, which was recently added, states that users should not be able to create an aggregator with a name prefixed by "_sysfs" via configfs. However, it was found that this guard does not function as expected (thanks to Dan Carpenter for identifying and fixing the issue). Add a test case to verify the guard. Signed-off-by: Koichiro Den Link: https://lore.kernel.org/r/20250412150119.1461023-1-koichiro.den@canonical.com Signed-off-by: Bartosz Golaszewski --- tools/testing/selftests/gpio/gpio-aggregator.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/testing/selftests/gpio/gpio-aggregator.sh b/tools/testing/selftests/gpio/gpio-aggregator.sh index f1bab62c4cd2..9b6f80ad9f8a 100755 --- a/tools/testing/selftests/gpio/gpio-aggregator.sh +++ b/tools/testing/selftests/gpio/gpio-aggregator.sh @@ -335,6 +335,10 @@ test "$(cat "$CONFIGFS_AGG_DIR/agg0/live")" = 0 || \ agg_remove_line agg0 line0 agg_remove_chip agg0 +echo "1.1.6. Can't instantiate a chip with _sysfs prefix" +mkdir "$CONFIGFS_AGG_DIR/_sysfs" 2> /dev/null && fail "chip _sysfs unexpectedly created" +mkdir "$CONFIGFS_AGG_DIR/_sysfs.foo" 2> /dev/null && fail "chip _sysfs.foo unexpectedly created" + echo "1.2. Creation/deletion via sysfs" echo "1.2.1. Minimum creation/deletion" From df9b3454a00ed9e4c85295611b28b7041fc2a1d9 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Wed, 9 Apr 2025 11:22:35 +0200 Subject: [PATCH 030/123] gpio: GPIO_BLZP1600 should depend on ARCH_BLAIZE The Blaize BLZP1600 GPIO controller is only present on BLaize BLZP1600 SoCs. Hence add a dependency on ARCH_BLAIZE, to prevent asking the user about this driver when configuring a kernel without Blaize platform support. Fixes: 602ae04f56542d8e ("gpio: Enable Blaize BLZP1600 GPIO support") Signed-off-by: Geert Uytterhoeven Reviewed-by: Nikolaos Pasaloukos Link: https://lore.kernel.org/r/a75af4b623579a193b4a59350c063969aabb5b71.1744190524.git.geert+renesas@glider.be Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 688a01ad4d69..975cc95be542 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -216,6 +216,7 @@ config GPIO_BCM_XGS_IPROC config GPIO_BLZP1600 tristate "Blaize BLZP1600 GPIO support" default y if ARCH_BLAIZE + depends on ARCH_BLAIZE || COMPILE_TEST depends on OF_GPIO select GPIO_GENERIC select GPIOLIB_IRQCHIP From afb4aed832f9000a5d15ecd28a7b2faa5789d28d Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 7 Apr 2025 09:13:10 +0200 Subject: [PATCH 031/123] gpio: dln2: use new line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Signed-off-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20250407-gpiochip-set-rv-gpio-part1-v1-1-78399683ca38@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-dln2.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpio-dln2.c b/drivers/gpio/gpio-dln2.c index 596da59d4b13..4bd3c47eaf93 100644 --- a/drivers/gpio/gpio-dln2.c +++ b/drivers/gpio/gpio-dln2.c @@ -220,11 +220,12 @@ static int dln2_gpio_get(struct gpio_chip *chip, unsigned int offset) return dln2_gpio_pin_get_out_val(dln2, offset); } -static void dln2_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +static int dln2_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) { struct dln2_gpio *dln2 = gpiochip_get_data(chip); - dln2_gpio_pin_set_out_val(dln2, offset, value); + return dln2_gpio_pin_set_out_val(dln2, offset, value); } static int dln2_gpio_set_direction(struct gpio_chip *chip, unsigned offset, @@ -468,7 +469,7 @@ static int dln2_gpio_probe(struct platform_device *pdev) dln2->gpio.base = -1; dln2->gpio.ngpio = pins; dln2->gpio.can_sleep = true; - dln2->gpio.set = dln2_gpio_set; + dln2->gpio.set_rv = dln2_gpio_set; dln2->gpio.get = dln2_gpio_get; dln2->gpio.request = dln2_gpio_request; dln2->gpio.free = dln2_gpio_free; From 21d3c6531b113b1aaecd99ae90dddd55c20f372b Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 7 Apr 2025 09:13:11 +0200 Subject: [PATCH 032/123] gpio: eic-sprd: use new line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Signed-off-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20250407-gpiochip-set-rv-gpio-part1-v1-2-78399683ca38@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-eic-sprd.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-eic-sprd.c b/drivers/gpio/gpio-eic-sprd.c index d4bf8d187e16..f2973d0b7138 100644 --- a/drivers/gpio/gpio-eic-sprd.c +++ b/drivers/gpio/gpio-eic-sprd.c @@ -203,9 +203,10 @@ static int sprd_eic_direction_input(struct gpio_chip *chip, unsigned int offset) return 0; } -static void sprd_eic_set(struct gpio_chip *chip, unsigned int offset, int value) +static int sprd_eic_set(struct gpio_chip *chip, unsigned int offset, int value) { /* EICs are always input, nothing need to do here. */ + return 0; } static int sprd_eic_set_debounce(struct gpio_chip *chip, unsigned int offset, @@ -662,7 +663,7 @@ static int sprd_eic_probe(struct platform_device *pdev) sprd_eic->chip.request = sprd_eic_request; sprd_eic->chip.free = sprd_eic_free; sprd_eic->chip.set_config = sprd_eic_set_config; - sprd_eic->chip.set = sprd_eic_set; + sprd_eic->chip.set_rv = sprd_eic_set; fallthrough; case SPRD_EIC_ASYNC: case SPRD_EIC_SYNC: From 097cf61ba5bd69328a69c4920f3454089fae0a43 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 7 Apr 2025 09:13:12 +0200 Subject: [PATCH 033/123] gpio: em: use new line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Signed-off-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20250407-gpiochip-set-rv-gpio-part1-v1-3-78399683ca38@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-em.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-em.c b/drivers/gpio/gpio-em.c index 6c862c572322..857c28895131 100644 --- a/drivers/gpio/gpio-em.c +++ b/drivers/gpio/gpio-em.c @@ -204,13 +204,15 @@ static void __em_gio_set(struct gpio_chip *chip, unsigned int reg, (BIT(shift + 16)) | (value << shift)); } -static void em_gio_set(struct gpio_chip *chip, unsigned offset, int value) +static int em_gio_set(struct gpio_chip *chip, unsigned int offset, int value) { /* output is split into two registers */ if (offset < 16) __em_gio_set(chip, GIO_OL, offset, value); else __em_gio_set(chip, GIO_OH, offset - 16, value); + + return 0; } static int em_gio_direction_output(struct gpio_chip *chip, unsigned offset, @@ -304,7 +306,7 @@ static int em_gio_probe(struct platform_device *pdev) gpio_chip->direction_input = em_gio_direction_input; gpio_chip->get = em_gio_get; gpio_chip->direction_output = em_gio_direction_output; - gpio_chip->set = em_gio_set; + gpio_chip->set_rv = em_gio_set; gpio_chip->to_irq = em_gio_to_irq; gpio_chip->request = pinctrl_gpio_request; gpio_chip->free = em_gio_free; From bcdd5b37640cc577cd93d20f00519e70d7add8a6 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 7 Apr 2025 09:13:13 +0200 Subject: [PATCH 034/123] gpio: exar: use new line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Signed-off-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20250407-gpiochip-set-rv-gpio-part1-v1-4-78399683ca38@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-exar.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/gpio/gpio-exar.c b/drivers/gpio/gpio-exar.c index d5909a4f0433..beb98286d13e 100644 --- a/drivers/gpio/gpio-exar.c +++ b/drivers/gpio/gpio-exar.c @@ -93,8 +93,8 @@ static int exar_get_value(struct gpio_chip *chip, unsigned int offset) return !!(regmap_test_bits(exar_gpio->regmap, addr, BIT(bit))); } -static void exar_set_value(struct gpio_chip *chip, unsigned int offset, - int value) +static int exar_set_value(struct gpio_chip *chip, unsigned int offset, + int value) { struct exar_gpio_chip *exar_gpio = gpiochip_get_data(chip); unsigned int addr = exar_offset_to_lvl_addr(exar_gpio, offset); @@ -105,7 +105,7 @@ static void exar_set_value(struct gpio_chip *chip, unsigned int offset, * regmap_write_bits() forces value to be written when an external * pull up/down might otherwise indicate value was already set. */ - regmap_write_bits(exar_gpio->regmap, addr, BIT(bit), bit_value); + return regmap_write_bits(exar_gpio->regmap, addr, BIT(bit), bit_value); } static int exar_direction_output(struct gpio_chip *chip, unsigned int offset, @@ -114,11 +114,13 @@ static int exar_direction_output(struct gpio_chip *chip, unsigned int offset, struct exar_gpio_chip *exar_gpio = gpiochip_get_data(chip); unsigned int addr = exar_offset_to_sel_addr(exar_gpio, offset); unsigned int bit = exar_offset_to_bit(exar_gpio, offset); + int ret; - exar_set_value(chip, offset, value); - regmap_clear_bits(exar_gpio->regmap, addr, BIT(bit)); + ret = exar_set_value(chip, offset, value); + if (ret) + return ret; - return 0; + return regmap_clear_bits(exar_gpio->regmap, addr, BIT(bit)); } static int exar_direction_input(struct gpio_chip *chip, unsigned int offset) @@ -209,7 +211,7 @@ static int gpio_exar_probe(struct platform_device *pdev) exar_gpio->gpio_chip.direction_input = exar_direction_input; exar_gpio->gpio_chip.get_direction = exar_get_direction; exar_gpio->gpio_chip.get = exar_get_value; - exar_gpio->gpio_chip.set = exar_set_value; + exar_gpio->gpio_chip.set_rv = exar_set_value; exar_gpio->gpio_chip.base = -1; exar_gpio->gpio_chip.ngpio = ngpios; exar_gpio->index = index; From 8bf15cf9f9fca825fc03fb13d166f0ebdc2a0232 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 7 Apr 2025 09:13:15 +0200 Subject: [PATCH 035/123] gpio: f7188: use new line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Signed-off-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20250407-gpiochip-set-rv-gpio-part1-v1-6-78399683ca38@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-f7188x.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpio-f7188x.c b/drivers/gpio/gpio-f7188x.c index 3875fd940ccb..dfcd3634f279 100644 --- a/drivers/gpio/gpio-f7188x.c +++ b/drivers/gpio/gpio-f7188x.c @@ -159,7 +159,8 @@ static int f7188x_gpio_direction_in(struct gpio_chip *chip, unsigned offset); static int f7188x_gpio_get(struct gpio_chip *chip, unsigned offset); static int f7188x_gpio_direction_out(struct gpio_chip *chip, unsigned offset, int value); -static void f7188x_gpio_set(struct gpio_chip *chip, unsigned offset, int value); +static int f7188x_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value); static int f7188x_gpio_set_config(struct gpio_chip *chip, unsigned offset, unsigned long config); @@ -172,7 +173,7 @@ static int f7188x_gpio_set_config(struct gpio_chip *chip, unsigned offset, .direction_input = f7188x_gpio_direction_in, \ .get = f7188x_gpio_get, \ .direction_output = f7188x_gpio_direction_out, \ - .set = f7188x_gpio_set, \ + .set_rv = f7188x_gpio_set, \ .set_config = f7188x_gpio_set_config, \ .base = -1, \ .ngpio = _ngpio, \ @@ -391,7 +392,8 @@ static int f7188x_gpio_direction_out(struct gpio_chip *chip, return 0; } -static void f7188x_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +static int f7188x_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) { int err; struct f7188x_gpio_bank *bank = gpiochip_get_data(chip); @@ -400,7 +402,8 @@ static void f7188x_gpio_set(struct gpio_chip *chip, unsigned offset, int value) err = superio_enter(sio->addr); if (err) - return; + return err; + superio_select(sio->addr, sio->device); data_out = superio_inb(sio->addr, f7188x_gpio_data_out(bank->regbase)); @@ -411,6 +414,8 @@ static void f7188x_gpio_set(struct gpio_chip *chip, unsigned offset, int value) superio_outb(sio->addr, f7188x_gpio_data_out(bank->regbase), data_out); superio_exit(sio->addr); + + return 0; } static int f7188x_gpio_set_config(struct gpio_chip *chip, unsigned offset, From 96af71338208bdb2c53de0876cf50001e20afc84 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 7 Apr 2025 09:13:16 +0200 Subject: [PATCH 036/123] gpio: graniterapids: use new line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Signed-off-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20250407-gpiochip-set-rv-gpio-part1-v1-7-78399683ca38@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-graniterapids.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpio-graniterapids.c b/drivers/gpio/gpio-graniterapids.c index ad6a045fd3d2..f25283e5239d 100644 --- a/drivers/gpio/gpio-graniterapids.c +++ b/drivers/gpio/gpio-graniterapids.c @@ -116,7 +116,7 @@ static int gnr_gpio_get(struct gpio_chip *gc, unsigned int gpio) return !!(dw & GNR_CFG_DW_RXSTATE); } -static void gnr_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value) +static int gnr_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value) { u32 clear = 0; u32 set = 0; @@ -126,7 +126,7 @@ static void gnr_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value) else clear = GNR_CFG_DW_TXSTATE; - gnr_gpio_configure_line(gc, gpio, clear, set); + return gnr_gpio_configure_line(gc, gpio, clear, set); } static int gnr_gpio_get_direction(struct gpio_chip *gc, unsigned int gpio) @@ -159,7 +159,7 @@ static const struct gpio_chip gnr_gpio_chip = { .owner = THIS_MODULE, .request = gnr_gpio_request, .get = gnr_gpio_get, - .set = gnr_gpio_set, + .set_rv = gnr_gpio_set, .get_direction = gnr_gpio_get_direction, .direction_input = gnr_gpio_direction_input, .direction_output = gnr_gpio_direction_output, From d170eef8d4b07f653fd5158f5b35b7bc0cbfbd07 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 7 Apr 2025 09:13:17 +0200 Subject: [PATCH 037/123] gpio: gw-pld: use new line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Signed-off-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20250407-gpiochip-set-rv-gpio-part1-v1-8-78399683ca38@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-gw-pld.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpio-gw-pld.c b/drivers/gpio/gpio-gw-pld.c index 7e29a2d8de1a..a40ba99a3aea 100644 --- a/drivers/gpio/gpio-gw-pld.c +++ b/drivers/gpio/gpio-gw-pld.c @@ -62,9 +62,9 @@ static int gw_pld_output8(struct gpio_chip *gc, unsigned offset, int value) return i2c_smbus_write_byte(gw->client, gw->out); } -static void gw_pld_set8(struct gpio_chip *gc, unsigned offset, int value) +static int gw_pld_set8(struct gpio_chip *gc, unsigned int offset, int value) { - gw_pld_output8(gc, offset, value); + return gw_pld_output8(gc, offset, value); } static int gw_pld_probe(struct i2c_client *client) @@ -86,7 +86,7 @@ static int gw_pld_probe(struct i2c_client *client) gw->chip.direction_input = gw_pld_input8; gw->chip.get = gw_pld_get8; gw->chip.direction_output = gw_pld_output8; - gw->chip.set = gw_pld_set8; + gw->chip.set_rv = gw_pld_set8; gw->client = client; /* From 1791226b712841b028a69bc0365e66f46717373a Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 7 Apr 2025 09:13:18 +0200 Subject: [PATCH 038/123] gpio: htc-egpio: enable building with COMPILE_TEST=y Extend the build coverage by allowing to build the module with COMPILE_TEST enabled. Signed-off-by: Bartosz Golaszewski Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250407-gpiochip-set-rv-gpio-part1-v1-9-78399683ca38@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 975cc95be542..12baf7805d7e 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1377,7 +1377,7 @@ config GPIO_DLN2 config HTC_EGPIO bool "HTC EGPIO support" - depends on ARM + depends on ARM || COMPILE_TEST help This driver supports the CPLD egpio chip present on several HTC phones. It provides basic support for input From 8416e1c9717691812e5b7c8ac5936491ae8a0d7a Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 7 Apr 2025 09:13:19 +0200 Subject: [PATCH 039/123] gpio: htc-egpio: use new line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Signed-off-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20250407-gpiochip-set-rv-gpio-part1-v1-10-78399683ca38@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-htc-egpio.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/gpio/gpio-htc-egpio.c b/drivers/gpio/gpio-htc-egpio.c index a40bd56673fe..b1844a676c7c 100644 --- a/drivers/gpio/gpio-htc-egpio.c +++ b/drivers/gpio/gpio-htc-egpio.c @@ -170,7 +170,7 @@ static int egpio_direction_input(struct gpio_chip *chip, unsigned offset) * Output pins */ -static void egpio_set(struct gpio_chip *chip, unsigned offset, int value) +static int egpio_set(struct gpio_chip *chip, unsigned int offset, int value) { unsigned long flag; struct egpio_chip *egpio; @@ -198,6 +198,8 @@ static void egpio_set(struct gpio_chip *chip, unsigned offset, int value) egpio->cached_values &= ~(1 << offset); egpio_writew((egpio->cached_values >> shift) & ei->reg_mask, ei, reg); spin_unlock_irqrestore(&ei->lock, flag); + + return 0; } static int egpio_direction_output(struct gpio_chip *chip, @@ -206,12 +208,10 @@ static int egpio_direction_output(struct gpio_chip *chip, struct egpio_chip *egpio; egpio = gpiochip_get_data(chip); - if (test_bit(offset, &egpio->is_out)) { - egpio_set(chip, offset, value); - return 0; - } else { - return -EINVAL; - } + if (test_bit(offset, &egpio->is_out)) + return egpio_set(chip, offset, value); + + return -EINVAL; } static int egpio_get_direction(struct gpio_chip *chip, unsigned offset) @@ -324,7 +324,7 @@ static int __init egpio_probe(struct platform_device *pdev) chip->parent = &pdev->dev; chip->owner = THIS_MODULE; chip->get = egpio_get; - chip->set = egpio_set; + chip->set_rv = egpio_set; chip->direction_input = egpio_direction_input; chip->direction_output = egpio_direction_output; chip->get_direction = egpio_get_direction; From f3592d252f77e50c737f91389c3a6b6926f82fc3 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 7 Apr 2025 09:13:20 +0200 Subject: [PATCH 040/123] gpio: ich: enable building with COMPILE_TEST=y Extend the build coverage by allowing to build the module with COMPILE_TEST enabled. Signed-off-by: Bartosz Golaszewski Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250407-gpiochip-set-rv-gpio-part1-v1-11-78399683ca38@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 12baf7805d7e..c4f7cc024efb 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -380,8 +380,7 @@ config GPIO_HLWD config GPIO_ICH tristate "Intel ICH GPIO" - depends on X86 - depends on LPC_ICH + depends on (X86 && LPC_ICH) || COMPILE_TEST help Say yes here to support the GPIO functionality of a number of Intel ICH-based chipsets. Currently supported devices: ICH6, ICH7, ICH8 From f74195d4b245a188c424237a0375993c8e2865c7 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 7 Apr 2025 09:13:21 +0200 Subject: [PATCH 041/123] gpio: ich: use new line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Signed-off-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20250407-gpiochip-set-rv-gpio-part1-v1-12-78399683ca38@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-ich.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpio-ich.c b/drivers/gpio/gpio-ich.c index 0be9285efebc..67089b2423d8 100644 --- a/drivers/gpio/gpio-ich.c +++ b/drivers/gpio/gpio-ich.c @@ -175,12 +175,16 @@ static int ichx_gpio_direction_input(struct gpio_chip *gpio, unsigned int nr) static int ichx_gpio_direction_output(struct gpio_chip *gpio, unsigned int nr, int val) { + int ret; + /* Disable blink hardware which is available for GPIOs from 0 to 31. */ if (nr < 32 && ichx_priv.desc->have_blink) ichx_write_bit(GPO_BLINK, nr, 0, 0); /* Set GPIO output value. */ - ichx_write_bit(GPIO_LVL, nr, val, 0); + ret = ichx_write_bit(GPIO_LVL, nr, val, 0); + if (ret) + return ret; /* * Try setting pin as an output and verify it worked since many pins @@ -252,9 +256,9 @@ static int ich6_gpio_request(struct gpio_chip *chip, unsigned int nr) return ichx_gpio_request(chip, nr); } -static void ichx_gpio_set(struct gpio_chip *chip, unsigned int nr, int val) +static int ichx_gpio_set(struct gpio_chip *chip, unsigned int nr, int val) { - ichx_write_bit(GPIO_LVL, nr, val, 0); + return ichx_write_bit(GPIO_LVL, nr, val, 0); } static void ichx_gpiolib_setup(struct gpio_chip *chip) @@ -269,7 +273,7 @@ static void ichx_gpiolib_setup(struct gpio_chip *chip) chip->get = ichx_priv.desc->get ? ichx_priv.desc->get : ichx_gpio_get; - chip->set = ichx_gpio_set; + chip->set_rv = ichx_gpio_set; chip->get_direction = ichx_gpio_get_direction; chip->direction_input = ichx_gpio_direction_input; chip->direction_output = ichx_gpio_direction_output; From df1540f8f311a8b2ef0e450e7e40710784acc8c7 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 8 Apr 2025 10:40:57 +0200 Subject: [PATCH 042/123] gpio: ds4520: don't check the 'ngpios' property in the driver This is already done by the gpio-regmap abstraction by calling gpiochip_get_ngpios(). We don't need to do this at the driver level. Signed-off-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20250408-gpio-regmap-ngpios-v2-1-26f254014f57@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-ds4520.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/gpio/gpio-ds4520.c b/drivers/gpio/gpio-ds4520.c index 1903deaef3e9..f52ecae382a4 100644 --- a/drivers/gpio/gpio-ds4520.c +++ b/drivers/gpio/gpio-ds4520.c @@ -25,7 +25,6 @@ static int ds4520_gpio_probe(struct i2c_client *client) struct gpio_regmap_config config = { }; struct device *dev = &client->dev; struct regmap *regmap; - u32 ngpio; u32 base; int ret; @@ -33,10 +32,6 @@ static int ds4520_gpio_probe(struct i2c_client *client) if (ret) return dev_err_probe(dev, ret, "Missing 'reg' property.\n"); - ret = device_property_read_u32(dev, "ngpios", &ngpio); - if (ret) - return dev_err_probe(dev, ret, "Missing 'ngpios' property.\n"); - regmap = devm_regmap_init_i2c(client, &ds4520_regmap_config); if (IS_ERR(regmap)) return dev_err_probe(dev, PTR_ERR(regmap), @@ -44,7 +39,6 @@ static int ds4520_gpio_probe(struct i2c_client *client) config.regmap = regmap; config.parent = dev; - config.ngpio = ngpio; config.reg_dat_base = base + DS4520_IO_STATUS0; config.reg_set_base = base + DS4520_PULLUP0; From d024e482630acfb513027b5e4549a7b792ea8abb Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 8 Apr 2025 16:36:29 +0200 Subject: [PATCH 043/123] gpio: blzp1600: drop dependency on OF headers Use the generic boolean device property getter instead of the OF-specific variant. This allows us to stop pulling in linux/of.h. While at it: drop the of_irq.h inclusion as none of its symbols are used in this driver. Signed-off-by: Bartosz Golaszewski Reviewed-by: Nikolaos Pasaloukos Link: https://lore.kernel.org/r/20250408143629.125576-1-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-blzp1600.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpio-blzp1600.c b/drivers/gpio/gpio-blzp1600.c index 77ad0e596f3e..2760a13c0801 100644 --- a/drivers/gpio/gpio-blzp1600.c +++ b/drivers/gpio/gpio-blzp1600.c @@ -10,9 +10,8 @@ #include #include #include -#include -#include #include +#include #include #include @@ -217,7 +216,6 @@ static int blzp1600_gpio_set_config(struct gpio_chip *gc, unsigned int offset, u static int blzp1600_gpio_probe(struct platform_device *pdev) { - struct device_node *node = pdev->dev.of_node; struct blzp1600_gpio *chip; struct gpio_chip *gc; int ret; @@ -240,7 +238,7 @@ static int blzp1600_gpio_probe(struct platform_device *pdev) gc = &chip->gc; gc->set_config = blzp1600_gpio_set_config; - if (of_property_read_bool(node, "interrupt-controller")) { + if (device_property_present(&pdev->dev, "interrupt-controller")) { struct gpio_irq_chip *girq; chip->irq = platform_get_irq(pdev, 0); From 6273ef3560728f111c480e580970ba25cdee85b1 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 8 Apr 2025 14:59:01 +0200 Subject: [PATCH 044/123] gpio: zynq: enable building the modules with COMPILE_TEST=y Extend the build coverage by allowing to build the modules with COMPILE_TEST enabled. Signed-off-by: Bartosz Golaszewski Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250408-gpio-compile-test-v1-1-140e108e9392@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index c4f7cc024efb..592bb1f624c0 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -841,14 +841,14 @@ config GPIO_ZEVIO config GPIO_ZYNQ tristate "Xilinx Zynq GPIO support" - depends on ARCH_ZYNQ || ARCH_ZYNQMP + depends on ARCH_ZYNQ || ARCH_ZYNQMP || COMPILE_TEST select GPIOLIB_IRQCHIP help Say yes here to support Xilinx Zynq GPIO controller. config GPIO_ZYNQMP_MODEPIN tristate "ZynqMP ps-mode pin GPIO configuration driver" - depends on ZYNQMP_FIRMWARE + depends on ZYNQMP_FIRMWARE || COMPILE_TEST default ZYNQMP_FIRMWARE help Say yes here to support the ZynqMP ps-mode pin GPIO configuration From 4675b78c88a4397adf20eb17e8dcf216d29cb09b Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 8 Apr 2025 14:59:02 +0200 Subject: [PATCH 045/123] gpio: msc313: enable building the module with COMPILE_TEST=y Extend the build coverage by allowing to build the module with COMPILE_TEST enabled. Signed-off-by: Bartosz Golaszewski Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250408-gpio-compile-test-v1-2-140e108e9392@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 592bb1f624c0..dcb8128d06c0 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -877,7 +877,7 @@ config GPIO_AMD_FCH config GPIO_MSC313 bool "MStar MSC313 GPIO support" - depends on ARCH_MSTARV7 + depends on ARCH_MSTARV7 || COMPILE_TEST default ARCH_MSTARV7 select GPIOLIB_IRQCHIP select IRQ_DOMAIN_HIERARCHY From bcb4be090783f0ce1acdff86242e6f55a2137b64 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 8 Apr 2025 14:59:03 +0200 Subject: [PATCH 046/123] gpio: pl061: enable building the module with COMPILE_TEST=y Extend the build coverage by allowing to build the module with COMPILE_TEST enabled. Signed-off-by: Bartosz Golaszewski Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250408-gpio-compile-test-v1-3-140e108e9392@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index dcb8128d06c0..f0de782c04cc 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -551,7 +551,7 @@ config GPIO_OMAP config GPIO_PL061 tristate "PrimeCell PL061 GPIO support" - depends on ARM_AMBA + depends on ARM_AMBA || COMPILE_TEST select IRQ_DOMAIN select GPIOLIB_IRQCHIP help From 3d1c5b622141418c739291d945686137fd92cd3a Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 8 Apr 2025 14:59:04 +0200 Subject: [PATCH 047/123] gpio: rtd: enable building the module with COMPILE_TEST=y Extend the build coverage by allowing to build the module with COMPILE_TEST enabled. Signed-off-by: Bartosz Golaszewski Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250408-gpio-compile-test-v1-4-140e108e9392@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index f0de782c04cc..169158089608 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -615,7 +615,7 @@ config GPIO_ROCKCHIP config GPIO_RTD tristate "Realtek DHC GPIO support" - depends on ARCH_REALTEK + depends on ARCH_REALTEK || COMPILE_TEST default y select GPIOLIB_IRQCHIP help From 4e8a72f9b7120c5775e051b3c41fd7cc46e1ae8b Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 8 Apr 2025 14:59:06 +0200 Subject: [PATCH 048/123] gpio: tn48m: enable building the module with COMPILE_TEST=y Extend the build coverage by allowing to build the module with COMPILE_TEST enabled. Signed-off-by: Bartosz Golaszewski Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250408-gpio-compile-test-v1-6-140e108e9392@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 169158089608..e08e2161c1eb 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1536,7 +1536,7 @@ config GPIO_TIMBERDALE config GPIO_TN48M_CPLD tristate "Delta Networks TN48M switch CPLD GPIO driver" - depends on MFD_TN48M_CPLD + depends on MFD_TN48M_CPLD || COMPILE_TEST select GPIO_REGMAP help This enables support for the GPIOs found on the Delta From 26bb7614fa04278abbf0902d516b884677c12e86 Mon Sep 17 00:00:00 2001 From: Yixun Lan Date: Sat, 12 Apr 2025 07:31:28 +0800 Subject: [PATCH 049/123] dt-bindings: gpio: spacemit: add support for K1 SoC The GPIO controller of K1 support basic functions as input/output, all pins can be used as interrupt which route to one IRQ line, trigger type can be select between rising edge, falling edge, or both. There are four GPIO banks, each consisting of 32 pins. Reviewed-by: Linus Walleij Reviewed-by: Rob Herring (Arm) Signed-off-by: Yixun Lan Link: https://lore.kernel.org/r/20250412-03-k1-gpio-v8-1-1c6862d272ec@gentoo.org Signed-off-by: Bartosz Golaszewski --- .../bindings/gpio/spacemit,k1-gpio.yaml | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 Documentation/devicetree/bindings/gpio/spacemit,k1-gpio.yaml diff --git a/Documentation/devicetree/bindings/gpio/spacemit,k1-gpio.yaml b/Documentation/devicetree/bindings/gpio/spacemit,k1-gpio.yaml new file mode 100644 index 000000000000..ec0232e72c71 --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/spacemit,k1-gpio.yaml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/gpio/spacemit,k1-gpio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: SpacemiT K1 GPIO controller + +maintainers: + - Yixun Lan + +description: + The controller's registers are organized as sets of eight 32-bit + registers with each set of port controlling 32 pins. A single + interrupt line is shared for all of the pins by the controller. + +properties: + $nodename: + pattern: "^gpio@[0-9a-f]+$" + + compatible: + const: spacemit,k1-gpio + + reg: + maxItems: 1 + + clocks: + items: + - description: GPIO Core Clock + - description: GPIO Bus Clock + + clock-names: + items: + - const: core + - const: bus + + resets: + maxItems: 1 + + gpio-controller: true + + "#gpio-cells": + const: 3 + description: + The first two cells are the GPIO bank index and offset inside the bank, + the third cell should specify GPIO flag. + + gpio-ranges: true + + interrupts: + maxItems: 1 + + interrupt-controller: true + + "#interrupt-cells": + const: 3 + description: + The first two cells are the GPIO bank index and offset inside the bank, + the third cell should specify interrupt flag. The controller does not + support level interrupts, so flags of IRQ_TYPE_LEVEL_HIGH, + IRQ_TYPE_LEVEL_LOW should not be used. + Refer for valid flags. + +required: + - compatible + - reg + - clocks + - clock-names + - gpio-controller + - "#gpio-cells" + - interrupts + - interrupt-controller + - "#interrupt-cells" + - gpio-ranges + +additionalProperties: false + +examples: + - | + gpio@d4019000 { + compatible = "spacemit,k1-gpio"; + reg = <0xd4019000 0x800>; + clocks =<&ccu 9>, <&ccu 61>; + clock-names = "core", "bus"; + gpio-controller; + #gpio-cells = <3>; + interrupts = <58>; + interrupt-controller; + interrupt-parent = <&plic>; + #interrupt-cells = <3>; + gpio-ranges = <&pinctrl 0 0 0 32>, + <&pinctrl 1 0 32 32>, + <&pinctrl 2 0 64 32>, + <&pinctrl 3 0 96 32>; + }; +... From d00553240ef89542661b9088dc50c8ebf480a23e Mon Sep 17 00:00:00 2001 From: Yixun Lan Date: Sat, 12 Apr 2025 07:31:29 +0800 Subject: [PATCH 050/123] gpio: spacemit: add support for K1 SoC Implement GPIO functionality which capable of setting pin as input, output. Also, each pin can be used as interrupt which support rising, falling, or both edge type trigger. Reviewed-by: Alex Elder Reviewed-by: Linus Walleij Signed-off-by: Yixun Lan Link: https://lore.kernel.org/r/20250412-03-k1-gpio-v8-2-1c6862d272ec@gentoo.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 9 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-spacemit-k1.c | 293 ++++++++++++++++++++++++++++++++ 3 files changed, 303 insertions(+) create mode 100644 drivers/gpio/gpio-spacemit-k1.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index e08e2161c1eb..bbbb550cac93 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -667,6 +667,15 @@ config GPIO_SNPS_CREG where only several fields in register belong to GPIO lines and each GPIO line owns a field with different length and on/off value. +config GPIO_SPACEMIT_K1 + tristate "SPACEMIT K1 GPIO support" + depends on ARCH_SPACEMIT || COMPILE_TEST + depends on OF_GPIO + select GPIO_GENERIC + select GPIOLIB_IRQCHIP + help + Say yes here to support the SpacemiT's K1 GPIO device. + config GPIO_SPEAR_SPICS bool "ST SPEAr13xx SPI Chip Select as GPIO support" depends on PLAT_SPEAR diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 8661cfd8fd8c..9aabbb9cb4c6 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -160,6 +160,7 @@ obj-$(CONFIG_GPIO_SIOX) += gpio-siox.o obj-$(CONFIG_GPIO_SL28CPLD) += gpio-sl28cpld.o obj-$(CONFIG_GPIO_SLOPPY_LOGIC_ANALYZER) += gpio-sloppy-logic-analyzer.o obj-$(CONFIG_GPIO_SODAVILLE) += gpio-sodaville.o +obj-$(CONFIG_GPIO_SPACEMIT_K1) += gpio-spacemit-k1.o obj-$(CONFIG_GPIO_SPEAR_SPICS) += gpio-spear-spics.o obj-$(CONFIG_GPIO_SPRD) += gpio-sprd.o obj-$(CONFIG_GPIO_STMPE) += gpio-stmpe.o diff --git a/drivers/gpio/gpio-spacemit-k1.c b/drivers/gpio/gpio-spacemit-k1.c new file mode 100644 index 000000000000..f027066365ff --- /dev/null +++ b/drivers/gpio/gpio-spacemit-k1.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * Copyright (C) 2023-2025 SpacemiT (Hangzhou) Technology Co. Ltd + * Copyright (C) 2025 Yixun Lan + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* register offset */ +#define SPACEMIT_GPLR 0x00 /* port level - R */ +#define SPACEMIT_GPDR 0x0c /* port direction - R/W */ +#define SPACEMIT_GPSR 0x18 /* port set - W */ +#define SPACEMIT_GPCR 0x24 /* port clear - W */ +#define SPACEMIT_GRER 0x30 /* port rising edge R/W */ +#define SPACEMIT_GFER 0x3c /* port falling edge R/W */ +#define SPACEMIT_GEDR 0x48 /* edge detect status - R/W1C */ +#define SPACEMIT_GSDR 0x54 /* (set) direction - W */ +#define SPACEMIT_GCDR 0x60 /* (clear) direction - W */ +#define SPACEMIT_GSRER 0x6c /* (set) rising edge detect enable - W */ +#define SPACEMIT_GCRER 0x78 /* (clear) rising edge detect enable - W */ +#define SPACEMIT_GSFER 0x84 /* (set) falling edge detect enable - W */ +#define SPACEMIT_GCFER 0x90 /* (clear) falling edge detect enable - W */ +#define SPACEMIT_GAPMASK 0x9c /* interrupt mask , 0 disable, 1 enable - R/W */ + +#define SPACEMIT_NR_BANKS 4 +#define SPACEMIT_NR_GPIOS_PER_BANK 32 + +#define to_spacemit_gpio_bank(x) container_of((x), struct spacemit_gpio_bank, gc) + +struct spacemit_gpio; + +struct spacemit_gpio_bank { + struct gpio_chip gc; + struct spacemit_gpio *sg; + void __iomem *base; + u32 irq_mask; + u32 irq_rising_edge; + u32 irq_falling_edge; +}; + +struct spacemit_gpio { + struct device *dev; + struct spacemit_gpio_bank sgb[SPACEMIT_NR_BANKS]; +}; + +static u32 spacemit_gpio_bank_index(struct spacemit_gpio_bank *gb) +{ + return (u32)(gb - gb->sg->sgb); +} + +static irqreturn_t spacemit_gpio_irq_handler(int irq, void *dev_id) +{ + struct spacemit_gpio_bank *gb = dev_id; + unsigned long pending; + u32 n, gedr; + + gedr = readl(gb->base + SPACEMIT_GEDR); + if (!gedr) + return IRQ_NONE; + writel(gedr, gb->base + SPACEMIT_GEDR); + + pending = gedr & gb->irq_mask; + if (!pending) + return IRQ_NONE; + + for_each_set_bit(n, &pending, BITS_PER_LONG) + handle_nested_irq(irq_find_mapping(gb->gc.irq.domain, n)); + + return IRQ_HANDLED; +} + +static void spacemit_gpio_irq_ack(struct irq_data *d) +{ + struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(d); + + writel(BIT(irqd_to_hwirq(d)), gb->base + SPACEMIT_GEDR); +} + +static void spacemit_gpio_irq_mask(struct irq_data *d) +{ + struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(d); + u32 bit = BIT(irqd_to_hwirq(d)); + + gb->irq_mask &= ~bit; + writel(gb->irq_mask, gb->base + SPACEMIT_GAPMASK); + + if (bit & gb->irq_rising_edge) + writel(bit, gb->base + SPACEMIT_GCRER); + + if (bit & gb->irq_falling_edge) + writel(bit, gb->base + SPACEMIT_GCFER); +} + +static void spacemit_gpio_irq_unmask(struct irq_data *d) +{ + struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(d); + u32 bit = BIT(irqd_to_hwirq(d)); + + gb->irq_mask |= bit; + + if (bit & gb->irq_rising_edge) + writel(bit, gb->base + SPACEMIT_GSRER); + + if (bit & gb->irq_falling_edge) + writel(bit, gb->base + SPACEMIT_GSFER); + + writel(gb->irq_mask, gb->base + SPACEMIT_GAPMASK); +} + +static int spacemit_gpio_irq_set_type(struct irq_data *d, unsigned int type) +{ + struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(d); + u32 bit = BIT(irqd_to_hwirq(d)); + + if (type & IRQ_TYPE_EDGE_RISING) { + gb->irq_rising_edge |= bit; + writel(bit, gb->base + SPACEMIT_GSRER); + } else { + gb->irq_rising_edge &= ~bit; + writel(bit, gb->base + SPACEMIT_GCRER); + } + + if (type & IRQ_TYPE_EDGE_FALLING) { + gb->irq_falling_edge |= bit; + writel(bit, gb->base + SPACEMIT_GSFER); + } else { + gb->irq_falling_edge &= ~bit; + writel(bit, gb->base + SPACEMIT_GCFER); + } + + return 0; +} + +static void spacemit_gpio_irq_print_chip(struct irq_data *data, struct seq_file *p) +{ + struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(data); + + seq_printf(p, "%s-%d", dev_name(gb->gc.parent), spacemit_gpio_bank_index(gb)); +} + +static struct irq_chip spacemit_gpio_chip = { + .name = "k1-gpio-irqchip", + .irq_ack = spacemit_gpio_irq_ack, + .irq_mask = spacemit_gpio_irq_mask, + .irq_unmask = spacemit_gpio_irq_unmask, + .irq_set_type = spacemit_gpio_irq_set_type, + .irq_print_chip = spacemit_gpio_irq_print_chip, + .flags = IRQCHIP_IMMUTABLE | IRQCHIP_SKIP_SET_WAKE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, +}; + +static bool spacemit_of_node_instance_match(struct gpio_chip *gc, unsigned int i) +{ + struct spacemit_gpio_bank *gb = gpiochip_get_data(gc); + struct spacemit_gpio *sg = gb->sg; + + if (i >= SPACEMIT_NR_BANKS) + return false; + + return (gc == &sg->sgb[i].gc); +} + +static int spacemit_gpio_add_bank(struct spacemit_gpio *sg, + void __iomem *regs, + int index, int irq) +{ + struct spacemit_gpio_bank *gb = &sg->sgb[index]; + struct gpio_chip *gc = &gb->gc; + struct device *dev = sg->dev; + struct gpio_irq_chip *girq; + void __iomem *dat, *set, *clr, *dirin, *dirout; + int ret, bank_base[] = { 0x0, 0x4, 0x8, 0x100 }; + + gb->base = regs + bank_base[index]; + + dat = gb->base + SPACEMIT_GPLR; + set = gb->base + SPACEMIT_GPSR; + clr = gb->base + SPACEMIT_GPCR; + dirin = gb->base + SPACEMIT_GCDR; + dirout = gb->base + SPACEMIT_GSDR; + + /* This registers 32 GPIO lines per bank */ + ret = bgpio_init(gc, dev, 4, dat, set, clr, dirout, dirin, + BGPIOF_UNREADABLE_REG_SET | BGPIOF_UNREADABLE_REG_DIR); + if (ret) + return dev_err_probe(dev, ret, "failed to init gpio chip\n"); + + gb->sg = sg; + + gc->label = dev_name(dev); + gc->request = gpiochip_generic_request; + gc->free = gpiochip_generic_free; + gc->ngpio = SPACEMIT_NR_GPIOS_PER_BANK; + gc->base = -1; + gc->of_gpio_n_cells = 3; + gc->of_node_instance_match = spacemit_of_node_instance_match; + + girq = &gc->irq; + girq->threaded = true; + girq->handler = handle_simple_irq; + + gpio_irq_chip_set_chip(girq, &spacemit_gpio_chip); + + /* Disable Interrupt */ + writel(0, gb->base + SPACEMIT_GAPMASK); + /* Disable Edge Detection Settings */ + writel(0x0, gb->base + SPACEMIT_GRER); + writel(0x0, gb->base + SPACEMIT_GFER); + /* Clear Interrupt */ + writel(0xffffffff, gb->base + SPACEMIT_GCRER); + writel(0xffffffff, gb->base + SPACEMIT_GCFER); + + ret = devm_request_threaded_irq(dev, irq, NULL, + spacemit_gpio_irq_handler, + IRQF_ONESHOT | IRQF_SHARED, + gb->gc.label, gb); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to register IRQ\n"); + + ret = devm_gpiochip_add_data(dev, gc, gb); + if (ret) + return ret; + + /* Distuingish IRQ domain, for selecting threecells mode */ + irq_domain_update_bus_token(girq->domain, DOMAIN_BUS_WIRED); + + return 0; +} + +static int spacemit_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct spacemit_gpio *sg; + struct clk *core_clk, *bus_clk; + void __iomem *regs; + int i, irq, ret; + + sg = devm_kzalloc(dev, sizeof(*sg), GFP_KERNEL); + if (!sg) + return -ENOMEM; + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + sg->dev = dev; + + core_clk = devm_clk_get_enabled(dev, "core"); + if (IS_ERR(core_clk)) + return dev_err_probe(dev, PTR_ERR(core_clk), "failed to get clock\n"); + + bus_clk = devm_clk_get_enabled(dev, "bus"); + if (IS_ERR(bus_clk)) + return dev_err_probe(dev, PTR_ERR(bus_clk), "failed to get bus clock\n"); + + for (i = 0; i < SPACEMIT_NR_BANKS; i++) { + ret = spacemit_gpio_add_bank(sg, regs, i, irq); + if (ret) + return ret; + } + + return 0; +} + +static const struct of_device_id spacemit_gpio_dt_ids[] = { + { .compatible = "spacemit,k1-gpio" }, + { /* sentinel */ } +}; + +static struct platform_driver spacemit_gpio_driver = { + .probe = spacemit_gpio_probe, + .driver = { + .name = "k1-gpio", + .of_match_table = spacemit_gpio_dt_ids, + }, +}; +module_platform_driver(spacemit_gpio_driver); + +MODULE_AUTHOR("Yixun Lan "); +MODULE_DESCRIPTION("GPIO driver for SpacemiT K1 SoC"); +MODULE_LICENSE("GPL"); From f1d6cd0774bc211429ebf24187dc3d271feb5ed6 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 16 Apr 2025 12:55:09 +0300 Subject: [PATCH 051/123] gpiolib: Make taking gpio_lookup_lock consistent There are two ways to take a lock: plain call to the mutex_lock() or using guard()() / scoped_guard(). The driver inconsistently uses both. Make taking gpio_lookup_lock consistent. Reviewed-by: Linus Walleij Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250416095645.2027695-2-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index f208901ba478..40ae0e09fb9f 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -4363,12 +4363,10 @@ void gpiod_add_lookup_tables(struct gpiod_lookup_table **tables, size_t n) { unsigned int i; - mutex_lock(&gpio_lookup_lock); + guard(mutex)(&gpio_lookup_lock); for (i = 0; i < n; i++) list_add_tail(&tables[i]->list, &gpio_lookup_list); - - mutex_unlock(&gpio_lookup_lock); } /** @@ -4427,11 +4425,9 @@ void gpiod_remove_lookup_table(struct gpiod_lookup_table *table) if (!table) return; - mutex_lock(&gpio_lookup_lock); + guard(mutex)(&gpio_lookup_lock); list_del(&table->list); - - mutex_unlock(&gpio_lookup_lock); } EXPORT_SYMBOL_GPL(gpiod_remove_lookup_table); From d4fe58c8ea17db3e76cf80ad0fcdc655bba0153c Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 16 Apr 2025 12:55:10 +0300 Subject: [PATCH 052/123] gpiolib: Convert to use guard()() for gpio_machine_hogs_mutex The driver uses guard()()/scoped_guard() for the rest of the synchronisation calls. Convert to use the same for gpio_machine_hogs_mutex. Reviewed-by: Linus Walleij Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250416095645.2027695-3-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 40ae0e09fb9f..e2716e20b0e4 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -888,14 +888,12 @@ static void machine_gpiochip_add(struct gpio_chip *gc) { struct gpiod_hog *hog; - mutex_lock(&gpio_machine_hogs_mutex); + guard(mutex)(&gpio_machine_hogs_mutex); list_for_each_entry(hog, &gpio_machine_hogs, list) { if (!strcmp(gc->label, hog->chip_label)) gpiochip_machine_hog(gc, hog); } - - mutex_unlock(&gpio_machine_hogs_mutex); } static void gpiochip_setup_devs(void) @@ -4439,7 +4437,7 @@ void gpiod_add_hogs(struct gpiod_hog *hogs) { struct gpiod_hog *hog; - mutex_lock(&gpio_machine_hogs_mutex); + guard(mutex)(&gpio_machine_hogs_mutex); for (hog = &hogs[0]; hog->chip_label; hog++) { list_add_tail(&hog->list, &gpio_machine_hogs); @@ -4453,8 +4451,6 @@ void gpiod_add_hogs(struct gpiod_hog *hogs) if (gdev) gpiochip_machine_hog(gpio_device_get_chip(gdev), hog); } - - mutex_unlock(&gpio_machine_hogs_mutex); } EXPORT_SYMBOL_GPL(gpiod_add_hogs); @@ -4462,10 +4458,10 @@ void gpiod_remove_hogs(struct gpiod_hog *hogs) { struct gpiod_hog *hog; - mutex_lock(&gpio_machine_hogs_mutex); + guard(mutex)(&gpio_machine_hogs_mutex); + for (hog = &hogs[0]; hog->chip_label; hog++) list_del(&hog->list); - mutex_unlock(&gpio_machine_hogs_mutex); } EXPORT_SYMBOL_GPL(gpiod_remove_hogs); From b709d676c1029458261121da6ccf65ecbb56f73c Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 16 Apr 2025 12:55:11 +0300 Subject: [PATCH 053/123] gpiolib: Print actual error when descriptor contains an error pointer Print the actual error when descriptor contains an error pointer. This might help debugging those rare cases. Reviewed-by: Linus Walleij Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250416095645.2027695-4-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index e2716e20b0e4..e8e75da7b177 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -2443,7 +2443,7 @@ static int validate_desc(const struct gpio_desc *desc, const char *func) return 0; if (IS_ERR(desc)) { - pr_warn("%s: invalid GPIO (errorpointer)\n", func); + pr_warn("%s: invalid GPIO (errorpointer: %pe)\n", func, desc); return PTR_ERR(desc); } From df6dccb4aeab5f19c8451d960259fd1d911f3129 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 16 Apr 2025 12:55:12 +0300 Subject: [PATCH 054/123] gpiolib: Revert "Don't WARN on gpiod_put() for optional GPIO" No need to double check the pointer for NULL since gpiod_free() is using VALIDATE_DESC_VOID() which simply returns in that case. This reverts commit 1d7765ba15aca68f3bc52f59434c1c34855bbb54. Reviewed-by: Linus Walleij Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250416095645.2027695-5-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index e8e75da7b177..7ab408b44893 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -5140,8 +5140,7 @@ EXPORT_SYMBOL_GPL(gpiod_get_array_optional); */ void gpiod_put(struct gpio_desc *desc) { - if (desc) - gpiod_free(desc); + gpiod_free(desc); } EXPORT_SYMBOL_GPL(gpiod_put); From 35d9bb5f799a4d8cf6cada0e7dc93a87ca8f6755 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 16 Apr 2025 12:55:13 +0300 Subject: [PATCH 055/123] gpiolib: Move validate_desc() and Co upper in the code Move validate_desc() and Co upper in the code to be able to use in the further changes. Reviewed-by: Linus Walleij Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250416095645.2027695-6-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 62 +++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 7ab408b44893..297a124a5e44 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -356,6 +356,37 @@ static int gpiochip_find_base_unlocked(u16 ngpio) } } +/* + * This descriptor validation needs to be inserted verbatim into each + * function taking a descriptor, so we need to use a preprocessor + * macro to avoid endless duplication. If the desc is NULL it is an + * optional GPIO and calls should just bail out. + */ +static int validate_desc(const struct gpio_desc *desc, const char *func) +{ + if (!desc) + return 0; + + if (IS_ERR(desc)) { + pr_warn("%s: invalid GPIO (errorpointer: %pe)\n", func, desc); + return PTR_ERR(desc); + } + + return 1; +} + +#define VALIDATE_DESC(desc) do { \ + int __valid = validate_desc(desc, __func__); \ + if (__valid <= 0) \ + return __valid; \ + } while (0) + +#define VALIDATE_DESC_VOID(desc) do { \ + int __valid = validate_desc(desc, __func__); \ + if (__valid <= 0) \ + return; \ + } while (0) + static int gpiochip_get_direction(struct gpio_chip *gc, unsigned int offset) { int ret; @@ -2431,37 +2462,6 @@ static int gpiod_request_commit(struct gpio_desc *desc, const char *label) return ret; } -/* - * This descriptor validation needs to be inserted verbatim into each - * function taking a descriptor, so we need to use a preprocessor - * macro to avoid endless duplication. If the desc is NULL it is an - * optional GPIO and calls should just bail out. - */ -static int validate_desc(const struct gpio_desc *desc, const char *func) -{ - if (!desc) - return 0; - - if (IS_ERR(desc)) { - pr_warn("%s: invalid GPIO (errorpointer: %pe)\n", func, desc); - return PTR_ERR(desc); - } - - return 1; -} - -#define VALIDATE_DESC(desc) do { \ - int __valid = validate_desc(desc, __func__); \ - if (__valid <= 0) \ - return __valid; \ - } while (0) - -#define VALIDATE_DESC_VOID(desc) do { \ - int __valid = validate_desc(desc, __func__); \ - if (__valid <= 0) \ - return; \ - } while (0) - int gpiod_request(struct gpio_desc *desc, const char *label) { int ret = -EPROBE_DEFER; From 8c13e6a3d588320327b67727c1b9a0dea843ca74 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 16 Apr 2025 12:55:14 +0300 Subject: [PATCH 056/123] gpiolib: Call validate_desc() when VALIDATE_DESC() can't be used Call validate_desc() directly when VALIDATE_DESC() can't be used. It will print additional information useful for debugging. Reviewed-by: Linus Walleij Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250416095645.2027695-7-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 297a124a5e44..8a47d861d836 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -421,11 +421,8 @@ int gpiod_get_direction(struct gpio_desc *desc) unsigned int offset; int ret; - /* - * We cannot use VALIDATE_DESC() as we must not return 0 for a NULL - * descriptor like we usually do. - */ - if (IS_ERR_OR_NULL(desc)) + ret = validate_desc(desc, __func__); + if (ret <= 0) return -EINVAL; CLASS(gpio_chip_guard, guard)(desc); @@ -3982,13 +3979,10 @@ int gpiod_to_irq(const struct gpio_desc *desc) struct gpio_device *gdev; struct gpio_chip *gc; int offset; + int ret; - /* - * Cannot VALIDATE_DESC() here as gpiod_to_irq() consumer semantics - * requires this function to not return zero on an invalid descriptor - * but rather a negative error number. - */ - if (IS_ERR_OR_NULL(desc)) + ret = validate_desc(desc, __func__); + if (ret <= 0) return -EINVAL; gdev = desc->gdev; From 7cc868831b98eb10e8ae50a8e5a9f04674d1a2ab Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 16 Apr 2025 12:55:15 +0300 Subject: [PATCH 057/123] gpiolib: Reuse return variable in gpiod_to_irq() There are two variables for the same used in the gpiod_to_irq(). Replace the second by reusing the function top-level one. While at it, refactor the branch to have less lines of code. Reviewed-by: Linus Walleij Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250416095645.2027695-8-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 8a47d861d836..3243ef3fc7d8 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -3994,13 +3994,12 @@ int gpiod_to_irq(const struct gpio_desc *desc) offset = gpio_chip_hwgpio(desc); if (gc->to_irq) { - int retirq = gc->to_irq(gc, offset); + ret = gc->to_irq(gc, offset); + if (ret) + return ret; /* Zero means NO_IRQ */ - if (!retirq) - return -ENXIO; - - return retirq; + return -ENXIO; } #ifdef CONFIG_GPIOLIB_IRQCHIP if (gc->irq.chip) { From 550300b9a295a591e0721a31f8c964a4bc08d51c Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 16 Apr 2025 12:55:16 +0300 Subject: [PATCH 058/123] gpiolib: Remove redundant assignment of return variable In some functions the returned variable is assigned to 0 and then reassigned to the actual value. Remove redundant assignments. In one case make it more clear that the assignment is not needed. Reviewed-by: Linus Walleij Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250416095645.2027695-9-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 3243ef3fc7d8..87903847c3d7 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -1015,7 +1015,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, struct gpio_device *gdev; unsigned int desc_index; int base = 0; - int ret = 0; + int ret; /* Only allow one set() and one set_multiple(). */ if ((gc->set && gc->set_rv) || @@ -1040,11 +1040,10 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, device_set_node(&gdev->dev, gpiochip_choose_fwnode(gc)); - gdev->id = ida_alloc(&gpio_ida, GFP_KERNEL); - if (gdev->id < 0) { - ret = gdev->id; + ret = ida_alloc(&gpio_ida, GFP_KERNEL); + if (ret < 0) goto err_free_gdev; - } + gdev->id = ret; ret = dev_set_name(&gdev->dev, GPIOCHIP_NAME "%d", gdev->id); if (ret) @@ -3068,7 +3067,7 @@ int gpiod_direction_output_nonotify(struct gpio_desc *desc, int value) */ int gpiod_enable_hw_timestamp_ns(struct gpio_desc *desc, unsigned long flags) { - int ret = 0; + int ret; VALIDATE_DESC(desc); @@ -3101,7 +3100,7 @@ EXPORT_SYMBOL_GPL(gpiod_enable_hw_timestamp_ns); */ int gpiod_disable_hw_timestamp_ns(struct gpio_desc *desc, unsigned long flags) { - int ret = 0; + int ret; VALIDATE_DESC(desc); From 29f4c4da74acfbd54f5ad2dfafe1a8f93af00431 Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Fri, 18 Apr 2025 16:17:20 -0400 Subject: [PATCH 059/123] gpio: Restrict GPIO_ICH to compile testing with HAS_IOPORT When building gpio-ich.c for a platform that does not have IO port access like s390, there are several errors due to the use of inl() and outl() throughout the driver: drivers/gpio/gpio-ich.c: In function 'ichx_read_bit': include/asm-generic/io.h:578:14: error: call to '_inl' declared with attribute error: inl() requires CONFIG_HAS_IOPORT Only allow compile testing when the targeted platform selects HAS_IOPORT so that there are no compile time errors. Fixes: f3592d252f77 ("gpio: ich: enable building with COMPILE_TEST=y") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202504171941.g1yXja0j-lkp@intel.com/ Signed-off-by: Nathan Chancellor Link: https://lore.kernel.org/r/20250418-gpio-ich-fix-build-without-ioport-v1-1-83fc753438ec@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index bbbb550cac93..13b7319162c8 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -380,7 +380,7 @@ config GPIO_HLWD config GPIO_ICH tristate "Intel ICH GPIO" - depends on (X86 && LPC_ICH) || COMPILE_TEST + depends on (X86 && LPC_ICH) || (COMPILE_TEST && HAS_IOPORT) help Say yes here to support the GPIO functionality of a number of Intel ICH-based chipsets. Currently supported devices: ICH6, ICH7, ICH8 From 73dc041f8a60279faa1109957891b00d52496425 Mon Sep 17 00:00:00 2001 From: Jindong Yue Date: Wed, 16 Apr 2025 15:26:05 +0900 Subject: [PATCH 060/123] gpio: vf610: Allow building as a module Support for building it as a module for use on the Android platform, because the Android kernel(GKI) doesn't contain board-specific drivers, it requires that these drivers be built as a module and loaded into the system. Signed-off-by: Jindong Yue Link: https://lore.kernel.org/r/20250416062605.1948856-1-jindong.yue@nxp.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 2 +- drivers/gpio/gpio-vf610.c | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 13b7319162c8..9ae806f45e19 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -773,7 +773,7 @@ config GPIO_UNIPHIER Say yes here to support UniPhier GPIOs. config GPIO_VF610 - bool "VF610 GPIO support" + tristate "VF610 GPIO support" default y if SOC_VF610 depends on ARCH_MXC || COMPILE_TEST select GPIOLIB_IRQCHIP diff --git a/drivers/gpio/gpio-vf610.c b/drivers/gpio/gpio-vf610.c index 4dad7ce0c4dc..7de0d5b53d56 100644 --- a/drivers/gpio/gpio-vf610.c +++ b/drivers/gpio/gpio-vf610.c @@ -345,4 +345,6 @@ static struct platform_driver vf610_gpio_driver = { .probe = vf610_gpio_probe, }; -builtin_platform_driver(vf610_gpio_driver); +module_platform_driver(vf610_gpio_driver); +MODULE_DESCRIPTION("VF610 GPIO driver"); +MODULE_LICENSE("GPL"); From 417b0f8d08f878615de9481c6e8827fbc8b57ed2 Mon Sep 17 00:00:00 2001 From: Potin Lai Date: Wed, 9 Apr 2025 23:37:30 +0800 Subject: [PATCH 061/123] gpio: pca953x: Add support for level-triggered interrupts Adds support for level-triggered interrupts in the PCA953x GPIO expander driver. Previously, the driver only supported edge-triggered interrupts, which could lead to missed events in scenarios where an interrupt condition persists until it is explicitly cleared. By enabling level-triggered interrupts, the driver can now detect and respond to sustained interrupt conditions more reliably. Signed-off-by: Potin Lai Link: https://lore.kernel.org/r/20250409-gpio-pca953x-level-triggered-irq-v3-1-7f184d814934@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-pca953x.c | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/drivers/gpio/gpio-pca953x.c b/drivers/gpio/gpio-pca953x.c index 442435ded020..ab2c0fd428fb 100644 --- a/drivers/gpio/gpio-pca953x.c +++ b/drivers/gpio/gpio-pca953x.c @@ -215,6 +215,8 @@ struct pca953x_chip { DECLARE_BITMAP(irq_stat, MAX_LINE); DECLARE_BITMAP(irq_trig_raise, MAX_LINE); DECLARE_BITMAP(irq_trig_fall, MAX_LINE); + DECLARE_BITMAP(irq_trig_level_high, MAX_LINE); + DECLARE_BITMAP(irq_trig_level_low, MAX_LINE); #endif atomic_t wakeup_path; @@ -774,6 +776,8 @@ static void pca953x_irq_bus_sync_unlock(struct irq_data *d) pca953x_read_regs(chip, chip->regs->direction, reg_direction); bitmap_or(irq_mask, chip->irq_trig_fall, chip->irq_trig_raise, gc->ngpio); + bitmap_or(irq_mask, irq_mask, chip->irq_trig_level_high, gc->ngpio); + bitmap_or(irq_mask, irq_mask, chip->irq_trig_level_low, gc->ngpio); bitmap_complement(reg_direction, reg_direction, gc->ngpio); bitmap_and(irq_mask, irq_mask, reg_direction, gc->ngpio); @@ -791,13 +795,15 @@ static int pca953x_irq_set_type(struct irq_data *d, unsigned int type) struct device *dev = &chip->client->dev; irq_hw_number_t hwirq = irqd_to_hwirq(d); - if (!(type & IRQ_TYPE_EDGE_BOTH)) { + if (!(type & IRQ_TYPE_SENSE_MASK)) { dev_err(dev, "irq %d: unsupported type %d\n", d->irq, type); return -EINVAL; } assign_bit(hwirq, chip->irq_trig_fall, type & IRQ_TYPE_EDGE_FALLING); assign_bit(hwirq, chip->irq_trig_raise, type & IRQ_TYPE_EDGE_RISING); + assign_bit(hwirq, chip->irq_trig_level_low, type & IRQ_TYPE_LEVEL_LOW); + assign_bit(hwirq, chip->irq_trig_level_high, type & IRQ_TYPE_LEVEL_HIGH); return 0; } @@ -810,6 +816,8 @@ static void pca953x_irq_shutdown(struct irq_data *d) clear_bit(hwirq, chip->irq_trig_raise); clear_bit(hwirq, chip->irq_trig_fall); + clear_bit(hwirq, chip->irq_trig_level_low); + clear_bit(hwirq, chip->irq_trig_level_high); } static void pca953x_irq_print_chip(struct irq_data *data, struct seq_file *p) @@ -840,6 +848,7 @@ static bool pca953x_irq_pending(struct pca953x_chip *chip, unsigned long *pendin DECLARE_BITMAP(cur_stat, MAX_LINE); DECLARE_BITMAP(new_stat, MAX_LINE); DECLARE_BITMAP(trigger, MAX_LINE); + DECLARE_BITMAP(edges, MAX_LINE); int ret; ret = pca953x_read_regs(chip, chip->regs->input, cur_stat); @@ -857,13 +866,26 @@ static bool pca953x_irq_pending(struct pca953x_chip *chip, unsigned long *pendin bitmap_copy(chip->irq_stat, new_stat, gc->ngpio); - if (bitmap_empty(trigger, gc->ngpio)) - return false; + if (bitmap_empty(chip->irq_trig_level_high, gc->ngpio) && + bitmap_empty(chip->irq_trig_level_low, gc->ngpio)) { + if (bitmap_empty(trigger, gc->ngpio)) + return false; + } bitmap_and(cur_stat, chip->irq_trig_fall, old_stat, gc->ngpio); bitmap_and(old_stat, chip->irq_trig_raise, new_stat, gc->ngpio); - bitmap_or(new_stat, old_stat, cur_stat, gc->ngpio); - bitmap_and(pending, new_stat, trigger, gc->ngpio); + bitmap_or(edges, old_stat, cur_stat, gc->ngpio); + bitmap_and(pending, edges, trigger, gc->ngpio); + + bitmap_and(cur_stat, new_stat, chip->irq_trig_level_high, gc->ngpio); + bitmap_and(cur_stat, cur_stat, chip->irq_mask, gc->ngpio); + bitmap_or(pending, pending, cur_stat, gc->ngpio); + + bitmap_complement(cur_stat, new_stat, gc->ngpio); + bitmap_and(cur_stat, cur_stat, reg_direction, gc->ngpio); + bitmap_and(old_stat, cur_stat, chip->irq_trig_level_low, gc->ngpio); + bitmap_and(old_stat, old_stat, chip->irq_mask, gc->ngpio); + bitmap_or(pending, pending, old_stat, gc->ngpio); return !bitmap_empty(pending, gc->ngpio); } From f5e5631df596ade5875ba1dc5d640611745d0c0d Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 20 Feb 2025 18:20:26 +0200 Subject: [PATCH 062/123] devres: Move devm_*_action*() APIs to devres.h We have a newly created header linux/device/devres.h that gathers device managed APIs, so users won't need to include entire device.h for only these ones. Move devm_*_action*() APIs to devres.h as well. Reviewed-by: Raag Jadav Signed-off-by: Andy Shevchenko Reviewed-by: Zijun Hu Acked-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250220162238.2738038-2-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- include/linux/device.h | 38 ---------------------------------- include/linux/device/devres.h | 39 +++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/include/linux/device.h b/include/linux/device.h index 79e49fe494b7..4940db137fff 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -281,44 +281,6 @@ int __must_check device_create_bin_file(struct device *dev, void device_remove_bin_file(struct device *dev, const struct bin_attribute *attr); -/* allows to add/remove a custom action to devres stack */ -int devm_remove_action_nowarn(struct device *dev, void (*action)(void *), void *data); - -/** - * devm_remove_action() - removes previously added custom action - * @dev: Device that owns the action - * @action: Function implementing the action - * @data: Pointer to data passed to @action implementation - * - * Removes instance of @action previously added by devm_add_action(). - * Both action and data should match one of the existing entries. - */ -static inline -void devm_remove_action(struct device *dev, void (*action)(void *), void *data) -{ - WARN_ON(devm_remove_action_nowarn(dev, action, data)); -} - -void devm_release_action(struct device *dev, void (*action)(void *), void *data); - -int __devm_add_action(struct device *dev, void (*action)(void *), void *data, const char *name); -#define devm_add_action(dev, action, data) \ - __devm_add_action(dev, action, data, #action) - -static inline int __devm_add_action_or_reset(struct device *dev, void (*action)(void *), - void *data, const char *name) -{ - int ret; - - ret = __devm_add_action(dev, action, data, name); - if (ret) - action(data); - - return ret; -} -#define devm_add_action_or_reset(dev, action, data) \ - __devm_add_action_or_reset(dev, action, data, #action) - /** * devm_alloc_percpu - Resource-managed alloc_percpu * @dev: Device to allocate per-cpu memory for diff --git a/include/linux/device/devres.h b/include/linux/device/devres.h index 9b49f9915850..9cd1787ef28e 100644 --- a/include/linux/device/devres.h +++ b/include/linux/device/devres.h @@ -8,6 +8,7 @@ #include #include #include +#include struct device; struct device_node; @@ -126,4 +127,42 @@ void __iomem *devm_of_iomap(struct device *dev, struct device_node *node, int in #endif +/* allows to add/remove a custom action to devres stack */ +int devm_remove_action_nowarn(struct device *dev, void (*action)(void *), void *data); + +/** + * devm_remove_action() - removes previously added custom action + * @dev: Device that owns the action + * @action: Function implementing the action + * @data: Pointer to data passed to @action implementation + * + * Removes instance of @action previously added by devm_add_action(). + * Both action and data should match one of the existing entries. + */ +static inline +void devm_remove_action(struct device *dev, void (*action)(void *), void *data) +{ + WARN_ON(devm_remove_action_nowarn(dev, action, data)); +} + +void devm_release_action(struct device *dev, void (*action)(void *), void *data); + +int __devm_add_action(struct device *dev, void (*action)(void *), void *data, const char *name); +#define devm_add_action(dev, action, data) \ + __devm_add_action(dev, action, data, #action) + +static inline int __devm_add_action_or_reset(struct device *dev, void (*action)(void *), + void *data, const char *name) +{ + int ret; + + ret = __devm_add_action(dev, action, data, name); + if (ret) + action(data); + + return ret; +} +#define devm_add_action_or_reset(dev, action, data) \ + __devm_add_action_or_reset(dev, action, data, #action) + #endif /* _DEVICE_DEVRES_H_ */ From e383bb8f958444620d96386811aacf6a49757996 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 20 Feb 2025 18:20:27 +0200 Subject: [PATCH 063/123] devres: Add devm_is_action_added() helper In some code we would like to know if the action in device managed resources was added by devm_add_action() family of calls. Introduce a helper for that. Reviewed-by: Raag Jadav Signed-off-by: Andy Shevchenko Reviewed-by: Zijun Hu Acked-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250220162238.2738038-3-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/base/devres.c | 11 +++++++++++ include/linux/device/devres.h | 2 ++ 2 files changed, 13 insertions(+) diff --git a/drivers/base/devres.c b/drivers/base/devres.c index d8a733ea5e1a..7c20517a52c2 100644 --- a/drivers/base/devres.c +++ b/drivers/base/devres.c @@ -759,6 +759,17 @@ int __devm_add_action(struct device *dev, void (*action)(void *), void *data, co } EXPORT_SYMBOL_GPL(__devm_add_action); +bool devm_is_action_added(struct device *dev, void (*action)(void *), void *data) +{ + struct action_devres devres = { + .data = data, + .action = action, + }; + + return devres_find(dev, devm_action_release, devm_action_match, &devres); +} +EXPORT_SYMBOL_GPL(devm_is_action_added); + /** * devm_remove_action_nowarn() - removes previously added custom action * @dev: Device that owns the action diff --git a/include/linux/device/devres.h b/include/linux/device/devres.h index 9cd1787ef28e..ae696d10faff 100644 --- a/include/linux/device/devres.h +++ b/include/linux/device/devres.h @@ -165,4 +165,6 @@ static inline int __devm_add_action_or_reset(struct device *dev, void (*action)( #define devm_add_action_or_reset(dev, action, data) \ __devm_add_action_or_reset(dev, action, data, #action) +bool devm_is_action_added(struct device *dev, void (*action)(void *), void *data); + #endif /* _DEVICE_DEVRES_H_ */ From d1d52c6622a61a0ae8dd2bd2097b25c0f553d2f3 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 20 Feb 2025 18:20:28 +0200 Subject: [PATCH 064/123] gpiolib: devres: Finish the conversion to use devm_add_action() With a recently added helper we can complete the conversion of the GPIOLIB code to use devm_add_action() in all suitable cases. So do this. Signed-off-by: Andy Shevchenko Acked-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250220162238.2738038-4-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-devres.c | 90 ++++++++++------------------------- 1 file changed, 25 insertions(+), 65 deletions(-) diff --git a/drivers/gpio/gpiolib-devres.c b/drivers/gpio/gpiolib-devres.c index 08205f355ceb..840d0ad7c5ba 100644 --- a/drivers/gpio/gpiolib-devres.c +++ b/drivers/gpio/gpiolib-devres.c @@ -6,7 +6,7 @@ * Copyright (c) 2011 John Crispin */ -#include +#include #include #include #include @@ -19,32 +19,14 @@ struct fwnode_handle; struct lock_class_key; -static void devm_gpiod_release(struct device *dev, void *res) +static void devm_gpiod_release(void *desc) { - struct gpio_desc **desc = res; - - gpiod_put(*desc); + gpiod_put(desc); } -static int devm_gpiod_match(struct device *dev, void *res, void *data) +static void devm_gpiod_release_array(void *descs) { - struct gpio_desc **this = res, **gpio = data; - - return *this == *gpio; -} - -static void devm_gpiod_release_array(struct device *dev, void *res) -{ - struct gpio_descs **descs = res; - - gpiod_put_array(*descs); -} - -static int devm_gpiod_match_array(struct device *dev, void *res, void *data) -{ - struct gpio_descs **this = res, **gpios = data; - - return *this == *gpios; + gpiod_put_array(descs); } /** @@ -114,8 +96,8 @@ struct gpio_desc *__must_check devm_gpiod_get_index(struct device *dev, unsigned int idx, enum gpiod_flags flags) { - struct gpio_desc **dr; struct gpio_desc *desc; + int ret; desc = gpiod_get_index(dev, con_id, idx, flags); if (IS_ERR(desc)) @@ -126,23 +108,16 @@ struct gpio_desc *__must_check devm_gpiod_get_index(struct device *dev, * already under resource management by this device. */ if (flags & GPIOD_FLAGS_BIT_NONEXCLUSIVE) { - struct devres *dres; + bool dres; - dres = devres_find(dev, devm_gpiod_release, - devm_gpiod_match, &desc); + dres = devm_is_action_added(dev, devm_gpiod_release, desc); if (dres) return desc; } - dr = devres_alloc(devm_gpiod_release, sizeof(struct gpio_desc *), - GFP_KERNEL); - if (!dr) { - gpiod_put(desc); - return ERR_PTR(-ENOMEM); - } - - *dr = desc; - devres_add(dev, dr); + ret = devm_add_action_or_reset(dev, devm_gpiod_release, desc); + if (ret) + return ERR_PTR(ret); return desc; } @@ -171,22 +146,16 @@ struct gpio_desc *devm_fwnode_gpiod_get_index(struct device *dev, enum gpiod_flags flags, const char *label) { - struct gpio_desc **dr; struct gpio_desc *desc; - - dr = devres_alloc(devm_gpiod_release, sizeof(struct gpio_desc *), - GFP_KERNEL); - if (!dr) - return ERR_PTR(-ENOMEM); + int ret; desc = gpiod_find_and_request(dev, fwnode, con_id, index, flags, label, false); - if (IS_ERR(desc)) { - devres_free(dr); + if (IS_ERR(desc)) return desc; - } - *dr = desc; - devres_add(dev, dr); + ret = devm_add_action_or_reset(dev, devm_gpiod_release, desc); + if (ret) + return ERR_PTR(ret); return desc; } @@ -244,22 +213,16 @@ struct gpio_descs *__must_check devm_gpiod_get_array(struct device *dev, const char *con_id, enum gpiod_flags flags) { - struct gpio_descs **dr; struct gpio_descs *descs; - - dr = devres_alloc(devm_gpiod_release_array, - sizeof(struct gpio_descs *), GFP_KERNEL); - if (!dr) - return ERR_PTR(-ENOMEM); + int ret; descs = gpiod_get_array(dev, con_id, flags); - if (IS_ERR(descs)) { - devres_free(dr); + if (IS_ERR(descs)) return descs; - } - *dr = descs; - devres_add(dev, dr); + ret = devm_add_action_or_reset(dev, devm_gpiod_release_array, descs); + if (ret) + return ERR_PTR(ret); return descs; } @@ -307,8 +270,7 @@ EXPORT_SYMBOL_GPL(devm_gpiod_get_array_optional); */ void devm_gpiod_put(struct device *dev, struct gpio_desc *desc) { - WARN_ON(devres_release(dev, devm_gpiod_release, devm_gpiod_match, - &desc)); + devm_release_action(dev, devm_gpiod_release, desc); } EXPORT_SYMBOL_GPL(devm_gpiod_put); @@ -321,20 +283,19 @@ EXPORT_SYMBOL_GPL(devm_gpiod_put); * you want to hand over lifecycle management of a descriptor to another * mechanism. */ - void devm_gpiod_unhinge(struct device *dev, struct gpio_desc *desc) { int ret; if (IS_ERR_OR_NULL(desc)) return; - ret = devres_destroy(dev, devm_gpiod_release, - devm_gpiod_match, &desc); + /* * If the GPIO descriptor is requested as nonexclusive, we * may call this function several times on the same descriptor * so it is OK if devres_destroy() returns -ENOENT. */ + ret = devm_remove_action_nowarn(dev, devm_gpiod_release, desc); if (ret == -ENOENT) return; /* Anything else we should warn about */ @@ -353,8 +314,7 @@ EXPORT_SYMBOL_GPL(devm_gpiod_unhinge); */ void devm_gpiod_put_array(struct device *dev, struct gpio_descs *descs) { - WARN_ON(devres_release(dev, devm_gpiod_release_array, - devm_gpiod_match_array, &descs)); + devm_remove_action(dev, devm_gpiod_release_array, descs); } EXPORT_SYMBOL_GPL(devm_gpiod_put_array); From 57bbc60be45b750073acc7d94aaccc4e652319a1 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 23 Apr 2025 09:15:03 +0200 Subject: [PATCH 065/123] gpio: imx-scu: don't check the GPIO range Core GPIO code already does this for drivers, no need to duplicate the check. Reviewed-by: Peng Fan Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250423-gpiochip-set-rv-gpio-part2-v1-1-b22245cde81a@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-imx-scu.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/drivers/gpio/gpio-imx-scu.c b/drivers/gpio/gpio-imx-scu.c index 13baf465aedf..403d03f18122 100644 --- a/drivers/gpio/gpio-imx-scu.c +++ b/drivers/gpio/gpio-imx-scu.c @@ -37,9 +37,6 @@ static int imx_scu_gpio_get(struct gpio_chip *chip, unsigned int offset) int level; int err; - if (offset >= chip->ngpio) - return -EINVAL; - mutex_lock(&priv->lock); /* to read PIN state via scu api */ @@ -60,9 +57,6 @@ static void imx_scu_gpio_set(struct gpio_chip *chip, unsigned int offset, int va struct scu_gpio_priv *priv = gpiochip_get_data(chip); int err; - if (offset >= chip->ngpio) - return; - mutex_lock(&priv->lock); /* to set PIN output level via scu api */ @@ -77,9 +71,6 @@ static void imx_scu_gpio_set(struct gpio_chip *chip, unsigned int offset, int va static int imx_scu_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) { - if (offset >= chip->ngpio) - return -EINVAL; - return GPIO_LINE_DIRECTION_OUT; } From dd6d13abb4c363f49ddca2697a781945cf80ad89 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 23 Apr 2025 09:15:04 +0200 Subject: [PATCH 066/123] gpio: imx-scu: use lock guards Simplify the code by using lock guards from cleanup.h with the driver's mutex. Reviewed-by: Peng Fan Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250423-gpiochip-set-rv-gpio-part2-v1-2-b22245cde81a@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-imx-scu.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/drivers/gpio/gpio-imx-scu.c b/drivers/gpio/gpio-imx-scu.c index 403d03f18122..99df95e65820 100644 --- a/drivers/gpio/gpio-imx-scu.c +++ b/drivers/gpio/gpio-imx-scu.c @@ -6,8 +6,10 @@ * to control the PIN resources on SCU domain. */ +#include #include #include +#include #include #include #include @@ -37,13 +39,11 @@ static int imx_scu_gpio_get(struct gpio_chip *chip, unsigned int offset) int level; int err; - mutex_lock(&priv->lock); - - /* to read PIN state via scu api */ - err = imx_sc_misc_get_control(priv->handle, - scu_rsrc_arr[offset], 0, &level); - mutex_unlock(&priv->lock); - + scoped_guard(mutex, &priv->lock) { + /* to read PIN state via scu api */ + err = imx_sc_misc_get_control(priv->handle, + scu_rsrc_arr[offset], 0, &level); + } if (err) { dev_err(priv->dev, "SCU get failed: %d\n", err); return err; @@ -57,13 +57,11 @@ static void imx_scu_gpio_set(struct gpio_chip *chip, unsigned int offset, int va struct scu_gpio_priv *priv = gpiochip_get_data(chip); int err; - mutex_lock(&priv->lock); - - /* to set PIN output level via scu api */ - err = imx_sc_misc_set_control(priv->handle, - scu_rsrc_arr[offset], 0, value); - mutex_unlock(&priv->lock); - + scoped_guard(mutex, &priv->lock) { + /* to set PIN output level via scu api */ + err = imx_sc_misc_set_control(priv->handle, + scu_rsrc_arr[offset], 0, value); + } if (err) dev_err(priv->dev, "SCU set (%d) failed: %d\n", scu_rsrc_arr[offset], err); From 68ef71400d0e7a9049c36cd0954ecc67521b0bc8 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 23 Apr 2025 09:15:05 +0200 Subject: [PATCH 067/123] gpio: imx-scu: destroy the mutex in detach path Use devm_mutex_init() in order to clean up after the mutex debug data in detach and error path. Reviewed-by: Peng Fan Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250423-gpiochip-set-rv-gpio-part2-v1-3-b22245cde81a@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-imx-scu.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-imx-scu.c b/drivers/gpio/gpio-imx-scu.c index 99df95e65820..cf98b948f804 100644 --- a/drivers/gpio/gpio-imx-scu.c +++ b/drivers/gpio/gpio-imx-scu.c @@ -88,7 +88,10 @@ static int imx_scu_gpio_probe(struct platform_device *pdev) return ret; priv->dev = dev; - mutex_init(&priv->lock); + + ret = devm_mutex_init(&pdev->dev, &priv->lock); + if (ret) + return ret; gc = &priv->chip; gc->base = -1; From 40150dda92cd8718a367a32ac345dce05f788a16 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 23 Apr 2025 09:15:06 +0200 Subject: [PATCH 068/123] gpio: imx-scu: use new line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Reviewed-by: Peng Fan Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250423-gpiochip-set-rv-gpio-part2-v1-4-b22245cde81a@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-imx-scu.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-imx-scu.c b/drivers/gpio/gpio-imx-scu.c index cf98b948f804..1693dbf1b777 100644 --- a/drivers/gpio/gpio-imx-scu.c +++ b/drivers/gpio/gpio-imx-scu.c @@ -52,7 +52,8 @@ static int imx_scu_gpio_get(struct gpio_chip *chip, unsigned int offset) return level; } -static void imx_scu_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) +static int imx_scu_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) { struct scu_gpio_priv *priv = gpiochip_get_data(chip); int err; @@ -65,6 +66,8 @@ static void imx_scu_gpio_set(struct gpio_chip *chip, unsigned int offset, int va if (err) dev_err(priv->dev, "SCU set (%d) failed: %d\n", scu_rsrc_arr[offset], err); + + return err; } static int imx_scu_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) @@ -99,7 +102,7 @@ static int imx_scu_gpio_probe(struct platform_device *pdev) gc->ngpio = ARRAY_SIZE(scu_rsrc_arr); gc->label = dev_name(dev); gc->get = imx_scu_gpio_get; - gc->set = imx_scu_gpio_set; + gc->set_rv = imx_scu_gpio_set; gc->get_direction = imx_scu_gpio_get_direction; platform_set_drvdata(pdev, priv); From ef877a159072ca31ed183b086c41fb9951521886 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 23 Apr 2025 09:15:07 +0200 Subject: [PATCH 069/123] gpio: it87: use new line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Reviewed-by: Peng Fan Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250423-gpiochip-set-rv-gpio-part2-v1-5-b22245cde81a@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-it87.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpio-it87.c b/drivers/gpio/gpio-it87.c index f332341fd4c8..d8184b527bac 100644 --- a/drivers/gpio/gpio-it87.c +++ b/drivers/gpio/gpio-it87.c @@ -213,8 +213,7 @@ static int it87_gpio_direction_in(struct gpio_chip *chip, unsigned gpio_num) return rc; } -static void it87_gpio_set(struct gpio_chip *chip, - unsigned gpio_num, int val) +static int it87_gpio_set(struct gpio_chip *chip, unsigned int gpio_num, int val) { u8 mask, curr_vals; u16 reg; @@ -228,6 +227,8 @@ static void it87_gpio_set(struct gpio_chip *chip, outb(curr_vals | mask, reg); else outb(curr_vals & ~mask, reg); + + return 0; } static int it87_gpio_direction_out(struct gpio_chip *chip, @@ -249,7 +250,9 @@ static int it87_gpio_direction_out(struct gpio_chip *chip, /* set the output enable bit */ superio_set_mask(mask, group + it87_gpio->output_base); - it87_gpio_set(chip, gpio_num, val); + rc = it87_gpio_set(chip, gpio_num, val); + if (rc) + goto exit; superio_exit(); @@ -264,7 +267,7 @@ static const struct gpio_chip it87_template_chip = { .request = it87_gpio_request, .get = it87_gpio_get, .direction_input = it87_gpio_direction_in, - .set = it87_gpio_set, + .set_rv = it87_gpio_set, .direction_output = it87_gpio_direction_out, .base = -1 }; From 3444049044ce24c2a2bc98c6e30d189451147b63 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 23 Apr 2025 09:15:08 +0200 Subject: [PATCH 070/123] gpio: janz-ttl: use new line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Reviewed-by: Peng Fan Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250423-gpiochip-set-rv-gpio-part2-v1-6-b22245cde81a@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-janz-ttl.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-janz-ttl.c b/drivers/gpio/gpio-janz-ttl.c index cdf50e4ea165..9f548eda3888 100644 --- a/drivers/gpio/gpio-janz-ttl.c +++ b/drivers/gpio/gpio-janz-ttl.c @@ -76,7 +76,7 @@ static int ttl_get_value(struct gpio_chip *gpio, unsigned offset) return !!ret; } -static void ttl_set_value(struct gpio_chip *gpio, unsigned offset, int value) +static int ttl_set_value(struct gpio_chip *gpio, unsigned int offset, int value) { struct ttl_module *mod = dev_get_drvdata(gpio->parent); void __iomem *port; @@ -103,6 +103,8 @@ static void ttl_set_value(struct gpio_chip *gpio, unsigned offset, int value) iowrite16be(*shadow, port); spin_unlock(&mod->lock); + + return 0; } static void ttl_write_reg(struct ttl_module *mod, u8 reg, u16 val) @@ -169,7 +171,7 @@ static int ttl_probe(struct platform_device *pdev) gpio->parent = &pdev->dev; gpio->label = pdev->name; gpio->get = ttl_get_value; - gpio->set = ttl_set_value; + gpio->set_rv = ttl_set_value; gpio->owner = THIS_MODULE; /* request dynamic allocation */ From 0c5fa4ee5a2256cd2d37bd0d0dda5d4fbbb30281 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 23 Apr 2025 09:15:09 +0200 Subject: [PATCH 071/123] gpio: kempld: use new line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Reviewed-by: Peng Fan Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250423-gpiochip-set-rv-gpio-part2-v1-7-b22245cde81a@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-kempld.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-kempld.c b/drivers/gpio/gpio-kempld.c index 4ea15f08e0f4..e38e604baa22 100644 --- a/drivers/gpio/gpio-kempld.c +++ b/drivers/gpio/gpio-kempld.c @@ -63,7 +63,8 @@ static int kempld_gpio_get(struct gpio_chip *chip, unsigned offset) return !!kempld_gpio_get_bit(pld, KEMPLD_GPIO_LVL_NUM(offset), offset); } -static void kempld_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +static int kempld_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) { struct kempld_gpio_data *gpio = gpiochip_get_data(chip); struct kempld_device_data *pld = gpio->pld; @@ -71,6 +72,8 @@ static void kempld_gpio_set(struct gpio_chip *chip, unsigned offset, int value) kempld_get_mutex(pld); kempld_gpio_bitop(pld, KEMPLD_GPIO_LVL_NUM(offset), offset, value); kempld_release_mutex(pld); + + return 0; } static int kempld_gpio_direction_input(struct gpio_chip *chip, unsigned offset) @@ -166,7 +169,7 @@ static int kempld_gpio_probe(struct platform_device *pdev) chip->direction_output = kempld_gpio_direction_output; chip->get_direction = kempld_gpio_get_direction; chip->get = kempld_gpio_get; - chip->set = kempld_gpio_set; + chip->set_rv = kempld_gpio_set; chip->ngpio = kempld_gpio_pincount(pld); if (chip->ngpio == 0) { dev_err(dev, "No GPIO pins detected\n"); From 722332f58aa82b92c857b053426dd46e09988e38 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 23 Apr 2025 09:15:10 +0200 Subject: [PATCH 072/123] gpio: ljca: use new line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Reviewed-by: Sakari Ailus Reviewed-by: Peng Fan Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250423-gpiochip-set-rv-gpio-part2-v1-8-b22245cde81a@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-ljca.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpio-ljca.c b/drivers/gpio/gpio-ljca.c index 817ecb12d550..61524a9ba765 100644 --- a/drivers/gpio/gpio-ljca.c +++ b/drivers/gpio/gpio-ljca.c @@ -144,8 +144,8 @@ static int ljca_gpio_get_value(struct gpio_chip *chip, unsigned int offset) return ljca_gpio_read(ljca_gpio, offset); } -static void ljca_gpio_set_value(struct gpio_chip *chip, unsigned int offset, - int val) +static int ljca_gpio_set_value(struct gpio_chip *chip, unsigned int offset, + int val) { struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(chip); int ret; @@ -155,6 +155,8 @@ static void ljca_gpio_set_value(struct gpio_chip *chip, unsigned int offset, dev_err(chip->parent, "set value failed offset: %u val: %d ret: %d\n", offset, val, ret); + + return ret; } static int ljca_gpio_direction_input(struct gpio_chip *chip, unsigned int offset) @@ -183,7 +185,10 @@ static int ljca_gpio_direction_output(struct gpio_chip *chip, if (ret) return ret; - ljca_gpio_set_value(chip, offset, val); + ret = ljca_gpio_set_value(chip, offset, val); + if (ret) + return ret; + set_bit(offset, ljca_gpio->output_enabled); return 0; @@ -432,7 +437,7 @@ static int ljca_gpio_probe(struct auxiliary_device *auxdev, ljca_gpio->gc.direction_output = ljca_gpio_direction_output; ljca_gpio->gc.get_direction = ljca_gpio_get_direction; ljca_gpio->gc.get = ljca_gpio_get_value; - ljca_gpio->gc.set = ljca_gpio_set_value; + ljca_gpio->gc.set_rv = ljca_gpio_set_value; ljca_gpio->gc.set_config = ljca_gpio_set_config; ljca_gpio->gc.init_valid_mask = ljca_gpio_init_valid_mask; ljca_gpio->gc.can_sleep = true; From ba2ce44332142a86bb0e9246d40b97d6cc191c5d Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 23 Apr 2025 09:15:11 +0200 Subject: [PATCH 073/123] gpio: logicvc: use new line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Reviewed-by: Peng Fan Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250423-gpiochip-set-rv-gpio-part2-v1-9-b22245cde81a@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-logicvc.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/gpio/gpio-logicvc.c b/drivers/gpio/gpio-logicvc.c index 05d62011f335..19cd2847467c 100644 --- a/drivers/gpio/gpio-logicvc.c +++ b/drivers/gpio/gpio-logicvc.c @@ -61,23 +61,22 @@ static int logicvc_gpio_get(struct gpio_chip *chip, unsigned offset) return !!(value & bit); } -static void logicvc_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +static int logicvc_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) { struct logicvc_gpio *logicvc = gpiochip_get_data(chip); unsigned int reg, bit; logicvc_gpio_offset(logicvc, offset, ®, &bit); - regmap_update_bits(logicvc->regmap, reg, bit, value ? bit : 0); + return regmap_update_bits(logicvc->regmap, reg, bit, value ? bit : 0); } static int logicvc_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) { /* Pins are always configured as output, so just set the value. */ - logicvc_gpio_set(chip, offset, value); - - return 0; + return logicvc_gpio_set(chip, offset, value); } static struct regmap_config logicvc_gpio_regmap_config = { @@ -135,7 +134,7 @@ static int logicvc_gpio_probe(struct platform_device *pdev) logicvc->chip.ngpio = LOGICVC_CTRL_GPIO_BITS + LOGICVC_POWER_CTRL_GPIO_BITS; logicvc->chip.get = logicvc_gpio_get; - logicvc->chip.set = logicvc_gpio_set; + logicvc->chip.set_rv = logicvc_gpio_set; logicvc->chip.direction_output = logicvc_gpio_direction_output; return devm_gpiochip_add_data(dev, &logicvc->chip, logicvc); From 145c4d7052fd15d20e5165305b3296a026a40747 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 23 Apr 2025 09:15:12 +0200 Subject: [PATCH 074/123] gpio: loongson-64bit: use new line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Reviewed-by: Peng Fan Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250423-gpiochip-set-rv-gpio-part2-v1-10-b22245cde81a@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-loongson-64bit.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-loongson-64bit.c b/drivers/gpio/gpio-loongson-64bit.c index a9a93036f08f..26227669f026 100644 --- a/drivers/gpio/gpio-loongson-64bit.c +++ b/drivers/gpio/gpio-loongson-64bit.c @@ -105,7 +105,7 @@ static int loongson_gpio_get_direction(struct gpio_chip *chip, unsigned int pin) return GPIO_LINE_DIRECTION_OUT; } -static void loongson_gpio_set(struct gpio_chip *chip, unsigned int pin, int value) +static int loongson_gpio_set(struct gpio_chip *chip, unsigned int pin, int value) { unsigned long flags; struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip); @@ -113,6 +113,8 @@ static void loongson_gpio_set(struct gpio_chip *chip, unsigned int pin, int valu spin_lock_irqsave(&lgpio->lock, flags); loongson_commit_level(lgpio, pin, value); spin_unlock_irqrestore(&lgpio->lock, flags); + + return 0; } static int loongson_gpio_to_irq(struct gpio_chip *chip, unsigned int offset) @@ -155,7 +157,7 @@ static int loongson_gpio_init(struct device *dev, struct loongson_gpio_chip *lgp lgpio->chip.get = loongson_gpio_get; lgpio->chip.get_direction = loongson_gpio_get_direction; lgpio->chip.direction_output = loongson_gpio_direction_output; - lgpio->chip.set = loongson_gpio_set; + lgpio->chip.set_rv = loongson_gpio_set; lgpio->chip.parent = dev; spin_lock_init(&lgpio->lock); } From 7d34d9fbab771779a2b365934fdafff32bddd029 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 23 Apr 2025 09:15:13 +0200 Subject: [PATCH 075/123] gpio: loongson: use new line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Reviewed-by: Peng Fan Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250423-gpiochip-set-rv-gpio-part2-v1-11-b22245cde81a@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-loongson.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpio-loongson.c b/drivers/gpio/gpio-loongson.c index a42145873cc9..8f3668169ebf 100644 --- a/drivers/gpio/gpio-loongson.c +++ b/drivers/gpio/gpio-loongson.c @@ -48,8 +48,8 @@ static int loongson_gpio_get_value(struct gpio_chip *chip, unsigned gpio) return !!(val & BIT(gpio + LOONGSON_GPIO_IN_OFFSET)); } -static void loongson_gpio_set_value(struct gpio_chip *chip, - unsigned gpio, int value) +static int loongson_gpio_set_value(struct gpio_chip *chip, unsigned int gpio, + int value) { u32 val; @@ -61,6 +61,8 @@ static void loongson_gpio_set_value(struct gpio_chip *chip, val &= ~BIT(gpio); LOONGSON_GPIODATA = val; spin_unlock(&gpio_lock); + + return 0; } static int loongson_gpio_direction_input(struct gpio_chip *chip, unsigned gpio) @@ -104,7 +106,7 @@ static int loongson_gpio_probe(struct platform_device *pdev) gc->base = 0; gc->ngpio = LOONGSON_N_GPIO; gc->get = loongson_gpio_get_value; - gc->set = loongson_gpio_set_value; + gc->set_rv = loongson_gpio_set_value; gc->direction_input = loongson_gpio_direction_input; gc->direction_output = loongson_gpio_direction_output; From 127da457c4e01bd6e4b8a126aaf1be1a6c03fc0b Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 23 Apr 2025 09:15:14 +0200 Subject: [PATCH 076/123] gpio: lp3943: use new line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Reviewed-by: Peng Fan Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250423-gpiochip-set-rv-gpio-part2-v1-12-b22245cde81a@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-lp3943.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpio-lp3943.c b/drivers/gpio/gpio-lp3943.c index 8e58242f5123..52ab3ac4844c 100644 --- a/drivers/gpio/gpio-lp3943.c +++ b/drivers/gpio/gpio-lp3943.c @@ -147,7 +147,8 @@ static int lp3943_gpio_get(struct gpio_chip *chip, unsigned int offset) return lp3943_get_gpio_out_status(lp3943_gpio, chip, offset); } -static void lp3943_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) +static int lp3943_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) { struct lp3943_gpio *lp3943_gpio = gpiochip_get_data(chip); u8 data; @@ -157,15 +158,19 @@ static void lp3943_gpio_set(struct gpio_chip *chip, unsigned int offset, int val else data = LP3943_GPIO_OUT_LOW; - lp3943_gpio_set_mode(lp3943_gpio, offset, data); + return lp3943_gpio_set_mode(lp3943_gpio, offset, data); } static int lp3943_gpio_direction_output(struct gpio_chip *chip, unsigned int offset, int value) { struct lp3943_gpio *lp3943_gpio = gpiochip_get_data(chip); + int ret; + + ret = lp3943_gpio_set(chip, offset, value); + if (ret) + return ret; - lp3943_gpio_set(chip, offset, value); lp3943_gpio->input_mask &= ~BIT(offset); return 0; @@ -179,7 +184,7 @@ static const struct gpio_chip lp3943_gpio_chip = { .direction_input = lp3943_gpio_direction_input, .get = lp3943_gpio_get, .direction_output = lp3943_gpio_direction_output, - .set = lp3943_gpio_set, + .set_rv = lp3943_gpio_set, .base = -1, .ngpio = LP3943_MAX_GPIO, .can_sleep = 1, From 3a1e07184d78f52c07f69b40bedbbebbc417e71b Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 2 May 2025 22:37:07 +0200 Subject: [PATCH 077/123] gpio: blzp1600: remove incorrect pf_match_ptr() The blzp1600_gpio_of_match is defined unconditionally and can always be referenced by the driver, so don't use of_match_ptr() and avoid this warning: drivers/gpio/gpio-blzp1600.c:263:34: error: 'blzp1600_gpio_of_match' defined but not used [-Werror=unused-const-variable=] 263 | static const struct of_device_id blzp1600_gpio_of_match[] = { Signed-off-by: Arnd Bergmann Link: https://lore.kernel.org/r/20250502203721.2117186-1-arnd@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-blzp1600.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-blzp1600.c b/drivers/gpio/gpio-blzp1600.c index 2760a13c0801..055cb296ae54 100644 --- a/drivers/gpio/gpio-blzp1600.c +++ b/drivers/gpio/gpio-blzp1600.c @@ -269,7 +269,7 @@ MODULE_DEVICE_TABLE(of, blzp1600_gpio_of_match); static struct platform_driver blzp1600_gpio_driver = { .driver = { .name = DRIVER_NAME, - .of_match_table = of_match_ptr(blzp1600_gpio_of_match), + .of_match_table = blzp1600_gpio_of_match, }, .probe = blzp1600_gpio_probe, }; From 190b565788ed82163dec6fc8778b618cdc1644c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Draszik?= Date: Tue, 25 Mar 2025 08:27:02 +0000 Subject: [PATCH 078/123] dt-bindings: gpio: Add max77759 binding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Maxim MAX77759 is a companion PMIC for USB Type-C applications and includes Battery Charger, Fuel Gauge, temperature sensors, USB Type-C Port Controller (TCPC), NVMEM, and a GPIO expander. This describes its GPIO module. Signed-off-by: André Draszik Reviewed-by: "Rob Herring (Arm)" Acked-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20250325-max77759-mfd-v6-1-c0870ca662ba@linaro.org Signed-off-by: Lee Jones --- .../bindings/gpio/maxim,max77759-gpio.yaml | 44 +++++++++++++++++++ MAINTAINERS | 6 +++ 2 files changed, 50 insertions(+) create mode 100644 Documentation/devicetree/bindings/gpio/maxim,max77759-gpio.yaml diff --git a/Documentation/devicetree/bindings/gpio/maxim,max77759-gpio.yaml b/Documentation/devicetree/bindings/gpio/maxim,max77759-gpio.yaml new file mode 100644 index 000000000000..55734190d5eb --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/maxim,max77759-gpio.yaml @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/gpio/maxim,max77759-gpio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Maxim Integrated MAX77759 GPIO + +maintainers: + - André Draszik + +description: | + This module is part of the MAX77759 PMIC. For additional information, see + Documentation/devicetree/bindings/mfd/maxim,max77759.yaml. + + The MAX77759 is a PMIC integrating, amongst others, a GPIO controller + including interrupt support for 2 GPIO lines. + +properties: + compatible: + const: maxim,max77759-gpio + + "#gpio-cells": + const: 2 + + gpio-controller: true + + gpio-line-names: + minItems: 1 + maxItems: 2 + + "#interrupt-cells": + const: 2 + + interrupt-controller: true + +required: + - compatible + - "#gpio-cells" + - gpio-controller + - "#interrupt-cells" + - interrupt-controller + +additionalProperties: false diff --git a/MAINTAINERS b/MAINTAINERS index 96b827049501..8954f2e63c79 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14575,6 +14575,12 @@ F: Documentation/devicetree/bindings/mfd/maxim,max77714.yaml F: drivers/mfd/max77714.c F: include/linux/mfd/max77714.h +MAXIM MAX77759 PMIC MFD DRIVER +M: André Draszik +L: linux-kernel@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/*/maxim,max77759*.yaml + MAXIM MAX77802 PMIC REGULATOR DEVICE DRIVER M: Javier Martinez Canillas L: linux-kernel@vger.kernel.org From ee71546b8120593ff4c292d2d4c2250d11182746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Draszik?= Date: Tue, 25 Mar 2025 08:27:03 +0000 Subject: [PATCH 079/123] dt-bindings: nvmem: Add max77759 binding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Maxim MAX77759 is a companion PMIC for USB Type-C applications and includes Battery Charger, Fuel Gauge, temperature sensors, USB Type-C Port Controller (TCPC), NVMEM, and a GPIO expander. This describes its storage module (NVMEM). Signed-off-by: André Draszik Reviewed-by: "Rob Herring (Arm)" Link: https://lore.kernel.org/r/20250325-max77759-mfd-v6-2-c0870ca662ba@linaro.org Signed-off-by: Lee Jones --- .../bindings/nvmem/maxim,max77759-nvmem.yaml | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 Documentation/devicetree/bindings/nvmem/maxim,max77759-nvmem.yaml diff --git a/Documentation/devicetree/bindings/nvmem/maxim,max77759-nvmem.yaml b/Documentation/devicetree/bindings/nvmem/maxim,max77759-nvmem.yaml new file mode 100644 index 000000000000..1e3bd4433007 --- /dev/null +++ b/Documentation/devicetree/bindings/nvmem/maxim,max77759-nvmem.yaml @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/nvmem/maxim,max77759-nvmem.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Maxim Integrated MAX77759 Non Volatile Memory + +maintainers: + - André Draszik + +description: | + This module is part of the MAX77759 PMIC. For additional information, see + Documentation/devicetree/bindings/mfd/maxim,max77759.yaml. + + The MAX77759 is a PMIC integrating, amongst others, Non Volatile Memory + (NVMEM) with 30 bytes of storage which can be used by software to store + information or communicate with a boot loader. + +properties: + compatible: + const: maxim,max77759-nvmem + + wp-gpios: false + +required: + - compatible + +allOf: + - $ref: nvmem.yaml# + +unevaluatedProperties: false From 81fec13be95345a57cd97b7f2b823ca1ef7c9dd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Draszik?= Date: Tue, 25 Mar 2025 08:27:04 +0000 Subject: [PATCH 080/123] dt-bindings: mfd: Add max77759 binding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Maxim MAX77759 is a companion PMIC for USB Type-C applications and includes Battery Charger, Fuel Gauge, temperature sensors, USB Type-C Port Controller (TCPC), NVMEM, and a GPIO expander. This describes the top-level device. Signed-off-by: André Draszik Reviewed-by: "Rob Herring (Arm)" Link: https://lore.kernel.org/r/20250325-max77759-mfd-v6-3-c0870ca662ba@linaro.org Signed-off-by: Lee Jones --- .../bindings/mfd/maxim,max77759.yaml | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/maxim,max77759.yaml diff --git a/Documentation/devicetree/bindings/mfd/maxim,max77759.yaml b/Documentation/devicetree/bindings/mfd/maxim,max77759.yaml new file mode 100644 index 000000000000..525de9ab3c2b --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/maxim,max77759.yaml @@ -0,0 +1,99 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mfd/maxim,max77759.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Maxim Integrated MAX77759 PMIC for USB Type-C applications + +maintainers: + - André Draszik + +description: | + This is a part of device tree bindings for the MAX77759 companion Power + Management IC for USB Type-C applications. + + The MAX77759 includes Battery Charger, Fuel Gauge, temperature sensors, USB + Type-C Port Controller (TCPC), NVMEM, and a GPIO expander. + +properties: + compatible: + const: maxim,max77759 + + interrupts: + maxItems: 1 + + interrupt-controller: true + + "#interrupt-cells": + const: 2 + + reg: + maxItems: 1 + + gpio: + $ref: /schemas/gpio/maxim,max77759-gpio.yaml + + nvmem-0: + $ref: /schemas/nvmem/maxim,max77759-nvmem.yaml + +required: + - compatible + - interrupts + - reg + +additionalProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + pmic@66 { + compatible = "maxim,max77759"; + reg = <0x66>; + interrupts-extended = <&gpa8 3 IRQ_TYPE_LEVEL_LOW>; + + interrupt-controller; + #interrupt-cells = <2>; + + gpio { + compatible = "maxim,max77759-gpio"; + + gpio-controller; + #gpio-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + + nvmem-0 { + compatible = "maxim,max77759-nvmem"; + + nvmem-layout { + compatible = "fixed-layout"; + #address-cells = <1>; + #size-cells = <1>; + + reboot-mode@0 { + reg = <0x0 0x4>; + }; + + boot-reason@4 { + reg = <0x4 0x4>; + }; + + shutdown-user-flag@8 { + reg = <0x8 0x1>; + }; + + rsoc@10 { + reg = <0xa 0x2>; + }; + }; + }; + }; + }; From bb71e40db1b7a7822f434d211b4a94bab9996cc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Draszik?= Date: Fri, 9 May 2025 14:22:39 +0100 Subject: [PATCH 081/123] mfd: max77759: Add Maxim MAX77759 core driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Maxim MAX77759 is a companion PMIC for USB Type-C applications and includes Battery Charger, Fuel Gauge, temperature sensors, USB Type-C Port Controller (TCPC), NVMEM, and a GPIO expander. Fuel Gauge and TCPC have separate and independent I2C addresses, register maps, and interrupt lines and are therefore excluded from the MFD core device driver here. The GPIO and NVMEM interfaces are accessed via specific commands to the built-in microprocessor. This driver implements an API that client drivers can use for accessing those. Signed-off-by: André Draszik Acked-by: Peter Griffin Link: https://lore.kernel.org/r/20250509-max77759-mfd-v10-1-962ac15ee3ef@linaro.org Signed-off-by: Lee Jones --- MAINTAINERS | 2 + drivers/mfd/Kconfig | 20 + drivers/mfd/Makefile | 1 + drivers/mfd/max77759.c | 690 +++++++++++++++++++++++++++++++++++ include/linux/mfd/max77759.h | 165 +++++++++ 5 files changed, 878 insertions(+) create mode 100644 drivers/mfd/max77759.c create mode 100644 include/linux/mfd/max77759.h diff --git a/MAINTAINERS b/MAINTAINERS index 8954f2e63c79..8b9994301ffd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14580,6 +14580,8 @@ M: André Draszik L: linux-kernel@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/*/maxim,max77759*.yaml +F: drivers/mfd/max77759.c +F: include/linux/mfd/max77759.h MAXIM MAX77802 PMIC REGULATOR DEVICE DRIVER M: Javier Martinez Canillas diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 22b936310039..96992af22565 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -943,6 +943,26 @@ config MFD_MAX77714 drivers must be enabled in order to use each functionality of the device. +config MFD_MAX77759 + tristate "Maxim Integrated MAX77759 PMIC" + depends on I2C + depends on OF + select IRQ_DOMAIN + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + help + Say yes here to add support for Maxim Integrated MAX77759. + This is a companion Power Management IC for USB Type-C applications + with Battery Charger, Fuel Gauge, temperature sensors, USB Type-C + Port Controller (TCPC), NVMEM, and additional GPIO interfaces. + This driver provides common support for accessing the device; + additional drivers must be enabled in order to use the functionality + of the device. + + To compile this driver as a module, choose M here: the module will be + called max77759. + config MFD_MAX77843 bool "Maxim Semiconductor MAX77843 PMIC Support" depends on I2C=y diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 948cbdf42a18..5e5cc279af60 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -169,6 +169,7 @@ obj-$(CONFIG_MFD_MAX77686) += max77686.o obj-$(CONFIG_MFD_MAX77693) += max77693.o obj-$(CONFIG_MFD_MAX77705) += max77705.o obj-$(CONFIG_MFD_MAX77714) += max77714.o +obj-$(CONFIG_MFD_MAX77759) += max77759.o obj-$(CONFIG_MFD_MAX77843) += max77843.o obj-$(CONFIG_MFD_MAX8907) += max8907.o max8925-objs := max8925-core.o max8925-i2c.o diff --git a/drivers/mfd/max77759.c b/drivers/mfd/max77759.c new file mode 100644 index 000000000000..6cf6306c4a3b --- /dev/null +++ b/drivers/mfd/max77759.c @@ -0,0 +1,690 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2020 Google Inc + * Copyright 2025 Linaro Ltd. + * + * Core driver for Maxim MAX77759 companion PMIC for USB Type-C + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Chip ID as per MAX77759_PMIC_REG_PMIC_ID */ +enum { + MAX77759_CHIP_ID = 59, +}; + +enum max77759_i2c_subdev_id { + /* + * These are arbitrary and simply used to match struct + * max77759_i2c_subdev entries to the regmap pointers in struct + * max77759 during probe(). + */ + MAX77759_I2C_SUBDEV_ID_MAXQ, + MAX77759_I2C_SUBDEV_ID_CHARGER, +}; + +struct max77759_i2c_subdev { + enum max77759_i2c_subdev_id id; + const struct regmap_config *cfg; + u16 i2c_address; +}; + +static const struct regmap_range max77759_top_registers[] = { + regmap_reg_range(0x00, 0x02), /* PMIC_ID / PMIC_REVISION / OTP_REVISION */ + regmap_reg_range(0x22, 0x24), /* INTSRC / INTSRCMASK / TOPSYS_INT */ + regmap_reg_range(0x26, 0x26), /* TOPSYS_INT_MASK */ + regmap_reg_range(0x40, 0x40), /* I2C_CNFG */ + regmap_reg_range(0x50, 0x51), /* SWRESET / CONTROL_FG */ +}; + +static const struct regmap_range max77759_top_ro_registers[] = { + regmap_reg_range(0x00, 0x02), + regmap_reg_range(0x22, 0x22), +}; + +static const struct regmap_range max77759_top_volatile_registers[] = { + regmap_reg_range(0x22, 0x22), + regmap_reg_range(0x24, 0x24), +}; + +static const struct regmap_access_table max77759_top_wr_table = { + .yes_ranges = max77759_top_registers, + .n_yes_ranges = ARRAY_SIZE(max77759_top_registers), + .no_ranges = max77759_top_ro_registers, + .n_no_ranges = ARRAY_SIZE(max77759_top_ro_registers), +}; + +static const struct regmap_access_table max77759_top_rd_table = { + .yes_ranges = max77759_top_registers, + .n_yes_ranges = ARRAY_SIZE(max77759_top_registers), +}; + +static const struct regmap_access_table max77759_top_volatile_table = { + .yes_ranges = max77759_top_volatile_registers, + .n_yes_ranges = ARRAY_SIZE(max77759_top_volatile_registers), +}; + +static const struct regmap_config max77759_regmap_config_top = { + .name = "top", + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX77759_PMIC_REG_CONTROL_FG, + .wr_table = &max77759_top_wr_table, + .rd_table = &max77759_top_rd_table, + .volatile_table = &max77759_top_volatile_table, + .num_reg_defaults_raw = MAX77759_PMIC_REG_CONTROL_FG + 1, + .cache_type = REGCACHE_FLAT, +}; + +static const struct regmap_range max77759_maxq_registers[] = { + regmap_reg_range(0x60, 0x73), /* Device ID, Rev, INTx, STATUSx, MASKx */ + regmap_reg_range(0x81, 0xa1), /* AP_DATAOUTx */ + regmap_reg_range(0xb1, 0xd1), /* AP_DATAINx */ + regmap_reg_range(0xe0, 0xe0), /* UIC_SWRST */ +}; + +static const struct regmap_range max77759_maxq_ro_registers[] = { + regmap_reg_range(0x60, 0x63), /* Device ID, Rev */ + regmap_reg_range(0x68, 0x6f), /* STATUSx */ + regmap_reg_range(0xb1, 0xd1), +}; + +static const struct regmap_range max77759_maxq_volatile_registers[] = { + regmap_reg_range(0x64, 0x6f), /* INTx, STATUSx */ + regmap_reg_range(0xb1, 0xd1), + regmap_reg_range(0xe0, 0xe0), +}; + +static const struct regmap_access_table max77759_maxq_wr_table = { + .yes_ranges = max77759_maxq_registers, + .n_yes_ranges = ARRAY_SIZE(max77759_maxq_registers), + .no_ranges = max77759_maxq_ro_registers, + .n_no_ranges = ARRAY_SIZE(max77759_maxq_ro_registers), +}; + +static const struct regmap_access_table max77759_maxq_rd_table = { + .yes_ranges = max77759_maxq_registers, + .n_yes_ranges = ARRAY_SIZE(max77759_maxq_registers), +}; + +static const struct regmap_access_table max77759_maxq_volatile_table = { + .yes_ranges = max77759_maxq_volatile_registers, + .n_yes_ranges = ARRAY_SIZE(max77759_maxq_volatile_registers), +}; + +static const struct regmap_config max77759_regmap_config_maxq = { + .name = "maxq", + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX77759_MAXQ_REG_UIC_SWRST, + .wr_table = &max77759_maxq_wr_table, + .rd_table = &max77759_maxq_rd_table, + .volatile_table = &max77759_maxq_volatile_table, + .num_reg_defaults_raw = MAX77759_MAXQ_REG_UIC_SWRST + 1, + .cache_type = REGCACHE_FLAT, +}; + +static const struct regmap_range max77759_charger_registers[] = { + regmap_reg_range(0xb0, 0xcc), +}; + +static const struct regmap_range max77759_charger_ro_registers[] = { + regmap_reg_range(0xb4, 0xb8), /* INT_OK, DETAILS_0x */ +}; + +static const struct regmap_range max77759_charger_volatile_registers[] = { + regmap_reg_range(0xb0, 0xb1), /* INTx */ + regmap_reg_range(0xb4, 0xb8), +}; + +static const struct regmap_access_table max77759_charger_wr_table = { + .yes_ranges = max77759_charger_registers, + .n_yes_ranges = ARRAY_SIZE(max77759_charger_registers), + .no_ranges = max77759_charger_ro_registers, + .n_no_ranges = ARRAY_SIZE(max77759_charger_ro_registers), +}; + +static const struct regmap_access_table max77759_charger_rd_table = { + .yes_ranges = max77759_charger_registers, + .n_yes_ranges = ARRAY_SIZE(max77759_charger_registers), +}; + +static const struct regmap_access_table max77759_charger_volatile_table = { + .yes_ranges = max77759_charger_volatile_registers, + .n_yes_ranges = ARRAY_SIZE(max77759_charger_volatile_registers), +}; + +static const struct regmap_config max77759_regmap_config_charger = { + .name = "charger", + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX77759_CHGR_REG_CHG_CNFG_19, + .wr_table = &max77759_charger_wr_table, + .rd_table = &max77759_charger_rd_table, + .volatile_table = &max77759_charger_volatile_table, + .num_reg_defaults_raw = MAX77759_CHGR_REG_CHG_CNFG_19 + 1, + .cache_type = REGCACHE_FLAT, +}; + +/* + * Interrupts - with the following interrupt hierarchy: + * pmic IRQs (INTSRC) + * - MAXQ_INT: MaxQ IRQs + * - UIC_INT1 + * - APCmdResI + * - SysMsgI + * - GPIOxI + * - TOPSYS_INT: topsys + * - TOPSYS_INT + * - TSHDN_INT + * - SYSOVLO_INT + * - SYSUVLO_INT + * - FSHIP_NOT_RD + * - CHGR_INT: charger + * - CHG_INT + * - CHG_INT2 + */ +enum { + MAX77759_INT_MAXQ, + MAX77759_INT_TOPSYS, + MAX77759_INT_CHGR, +}; + +enum { + MAX77759_TOPSYS_INT_TSHDN, + MAX77759_TOPSYS_INT_SYSOVLO, + MAX77759_TOPSYS_INT_SYSUVLO, + MAX77759_TOPSYS_INT_FSHIP_NOT_RD, +}; + +enum { + MAX77759_MAXQ_INT_APCMDRESI, + MAX77759_MAXQ_INT_SYSMSGI, + MAX77759_MAXQ_INT_GPIO, + MAX77759_MAXQ_INT_UIC1, + MAX77759_MAXQ_INT_UIC2, + MAX77759_MAXQ_INT_UIC3, + MAX77759_MAXQ_INT_UIC4, +}; + +enum { + MAX77759_CHARGER_INT_1, + MAX77759_CHARGER_INT_2, +}; + +static const struct regmap_irq max77759_pmic_irqs[] = { + REGMAP_IRQ_REG(MAX77759_INT_MAXQ, 0, MAX77759_PMIC_REG_INTSRC_MAXQ), + REGMAP_IRQ_REG(MAX77759_INT_TOPSYS, 0, MAX77759_PMIC_REG_INTSRC_TOPSYS), + REGMAP_IRQ_REG(MAX77759_INT_CHGR, 0, MAX77759_PMIC_REG_INTSRC_CHGR), +}; + +static const struct regmap_irq max77759_maxq_irqs[] = { + REGMAP_IRQ_REG(MAX77759_MAXQ_INT_APCMDRESI, 0, MAX77759_MAXQ_REG_UIC_INT1_APCMDRESI), + REGMAP_IRQ_REG(MAX77759_MAXQ_INT_SYSMSGI, 0, MAX77759_MAXQ_REG_UIC_INT1_SYSMSGI), + REGMAP_IRQ_REG(MAX77759_MAXQ_INT_GPIO, 0, GENMASK(1, 0)), + REGMAP_IRQ_REG(MAX77759_MAXQ_INT_UIC1, 0, GENMASK(5, 2)), + REGMAP_IRQ_REG(MAX77759_MAXQ_INT_UIC2, 1, GENMASK(7, 0)), + REGMAP_IRQ_REG(MAX77759_MAXQ_INT_UIC3, 2, GENMASK(7, 0)), + REGMAP_IRQ_REG(MAX77759_MAXQ_INT_UIC4, 3, GENMASK(7, 0)), +}; + +static const struct regmap_irq max77759_topsys_irqs[] = { + REGMAP_IRQ_REG(MAX77759_TOPSYS_INT_TSHDN, 0, MAX77759_PMIC_REG_TOPSYS_INT_TSHDN), + REGMAP_IRQ_REG(MAX77759_TOPSYS_INT_SYSOVLO, 0, MAX77759_PMIC_REG_TOPSYS_INT_SYSOVLO), + REGMAP_IRQ_REG(MAX77759_TOPSYS_INT_SYSUVLO, 0, MAX77759_PMIC_REG_TOPSYS_INT_SYSUVLO), + REGMAP_IRQ_REG(MAX77759_TOPSYS_INT_FSHIP_NOT_RD, 0, MAX77759_PMIC_REG_TOPSYS_INT_FSHIP), +}; + +static const struct regmap_irq max77759_chgr_irqs[] = { + REGMAP_IRQ_REG(MAX77759_CHARGER_INT_1, 0, GENMASK(7, 0)), + REGMAP_IRQ_REG(MAX77759_CHARGER_INT_2, 1, GENMASK(7, 0)), +}; + +static const struct regmap_irq_chip max77759_pmic_irq_chip = { + .name = "max77759-pmic", + /* INTSRC is read-only and doesn't require clearing */ + .status_base = MAX77759_PMIC_REG_INTSRC, + .mask_base = MAX77759_PMIC_REG_INTSRCMASK, + .num_regs = 1, + .irqs = max77759_pmic_irqs, + .num_irqs = ARRAY_SIZE(max77759_pmic_irqs), +}; + +/* + * We can let regmap-irq auto-ack the topsys interrupt bits as required, but + * for all others the individual drivers need to know which interrupt bit + * exactly is set inside their interrupt handlers, and therefore we can not set + * .ack_base for those. + */ +static const struct regmap_irq_chip max77759_maxq_irq_chip = { + .name = "max77759-maxq", + .domain_suffix = "MAXQ", + .status_base = MAX77759_MAXQ_REG_UIC_INT1, + .mask_base = MAX77759_MAXQ_REG_UIC_INT1_M, + .num_regs = 4, + .irqs = max77759_maxq_irqs, + .num_irqs = ARRAY_SIZE(max77759_maxq_irqs), +}; + +static const struct regmap_irq_chip max77759_topsys_irq_chip = { + .name = "max77759-topsys", + .domain_suffix = "TOPSYS", + .status_base = MAX77759_PMIC_REG_TOPSYS_INT, + .mask_base = MAX77759_PMIC_REG_TOPSYS_INT_MASK, + .ack_base = MAX77759_PMIC_REG_TOPSYS_INT, + .num_regs = 1, + .irqs = max77759_topsys_irqs, + .num_irqs = ARRAY_SIZE(max77759_topsys_irqs), +}; + +static const struct regmap_irq_chip max77759_chrg_irq_chip = { + .name = "max77759-chgr", + .domain_suffix = "CHGR", + .status_base = MAX77759_CHGR_REG_CHG_INT, + .mask_base = MAX77759_CHGR_REG_CHG_INT_MASK, + .num_regs = 2, + .irqs = max77759_chgr_irqs, + .num_irqs = ARRAY_SIZE(max77759_chgr_irqs), +}; + +static const struct max77759_i2c_subdev max77759_i2c_subdevs[] = { + { + .id = MAX77759_I2C_SUBDEV_ID_MAXQ, + .cfg = &max77759_regmap_config_maxq, + /* I2C address is same as for sub-block 'top' */ + }, + { + .id = MAX77759_I2C_SUBDEV_ID_CHARGER, + .cfg = &max77759_regmap_config_charger, + .i2c_address = 0x69, + }, +}; + +static const struct resource max77759_gpio_resources[] = { + DEFINE_RES_IRQ_NAMED(MAX77759_MAXQ_INT_GPIO, "GPI"), +}; + +static const struct resource max77759_charger_resources[] = { + DEFINE_RES_IRQ_NAMED(MAX77759_CHARGER_INT_1, "INT1"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHARGER_INT_2, "INT2"), +}; + +static const struct mfd_cell max77759_cells[] = { + MFD_CELL_OF("max77759-nvmem", NULL, NULL, 0, 0, + "maxim,max77759-nvmem"), +}; + +static const struct mfd_cell max77759_maxq_cells[] = { + MFD_CELL_OF("max77759-gpio", max77759_gpio_resources, NULL, 0, 0, + "maxim,max77759-gpio"), +}; + +static const struct mfd_cell max77759_charger_cells[] = { + MFD_CELL_RES("max77759-charger", max77759_charger_resources), +}; + +int max77759_maxq_command(struct max77759 *max77759, + const struct max77759_maxq_command *cmd, + struct max77759_maxq_response *rsp) +{ + DEFINE_FLEX(struct max77759_maxq_response, _rsp, rsp, length, 1); + struct device *dev = regmap_get_device(max77759->regmap_maxq); + static const unsigned int timeout_ms = 200; + int ret; + + if (cmd->length > MAX77759_MAXQ_OPCODE_MAXLENGTH) + return -EINVAL; + + /* + * As a convenience for API users when issuing simple commands, rsp is + * allowed to be NULL. In that case we need a temporary here to write + * the response to, as we need to verify that the command was indeed + * completed correctly. + */ + if (!rsp) + rsp = _rsp; + + if (!rsp->length || rsp->length > MAX77759_MAXQ_OPCODE_MAXLENGTH) + return -EINVAL; + + guard(mutex)(&max77759->maxq_lock); + + reinit_completion(&max77759->cmd_done); + + /* + * MaxQ latches the message when the DATAOUT32 register is written. If + * cmd->length is shorter we still need to write 0 to it. + */ + ret = regmap_bulk_write(max77759->regmap_maxq, + MAX77759_MAXQ_REG_AP_DATAOUT0, cmd->cmd, + cmd->length); + if (!ret && cmd->length < MAX77759_MAXQ_OPCODE_MAXLENGTH) + ret = regmap_write(max77759->regmap_maxq, + MAX77759_MAXQ_REG_AP_DATAOUT32, 0); + if (ret) { + dev_err(dev, "writing command failed: %d\n", ret); + return ret; + } + + /* Wait for response from MaxQ */ + if (!wait_for_completion_timeout(&max77759->cmd_done, + msecs_to_jiffies(timeout_ms))) { + dev_err(dev, "timed out waiting for response\n"); + return -ETIMEDOUT; + } + + ret = regmap_bulk_read(max77759->regmap_maxq, + MAX77759_MAXQ_REG_AP_DATAIN0, + rsp->rsp, rsp->length); + if (ret) { + dev_err(dev, "reading response failed: %d\n", ret); + return ret; + } + + /* + * As per the protocol, the first byte of the reply will match the + * request. + */ + if (cmd->cmd[0] != rsp->rsp[0]) { + dev_err(dev, "unexpected opcode response for %#.2x: %*ph\n", + cmd->cmd[0], (int)rsp->length, rsp->rsp); + return -EIO; + } + + return 0; +} +EXPORT_SYMBOL_GPL(max77759_maxq_command); + +static irqreturn_t apcmdres_irq_handler(int irq, void *irq_data) +{ + struct max77759 *max77759 = irq_data; + + regmap_write(max77759->regmap_maxq, MAX77759_MAXQ_REG_UIC_INT1, + MAX77759_MAXQ_REG_UIC_INT1_APCMDRESI); + + complete(&max77759->cmd_done); + + return IRQ_HANDLED; +} + +static int max77759_create_i2c_subdev(struct i2c_client *client, + struct max77759 *max77759, + const struct max77759_i2c_subdev *sd) +{ + struct i2c_client *sub; + struct regmap *regmap; + int ret; + + /* + * If 'sd' has an I2C address, 'sub' will be assigned a new 'dummy' + * device, otherwise use it as-is. + */ + sub = client; + if (sd->i2c_address) { + sub = devm_i2c_new_dummy_device(&client->dev, + client->adapter, + sd->i2c_address); + + if (IS_ERR(sub)) + return dev_err_probe(&client->dev, PTR_ERR(sub), + "failed to claim I2C device %s\n", + sd->cfg->name); + } + + regmap = devm_regmap_init_i2c(sub, sd->cfg); + if (IS_ERR(regmap)) + return dev_err_probe(&sub->dev, PTR_ERR(regmap), + "regmap init for '%s' failed\n", + sd->cfg->name); + + ret = regmap_attach_dev(&client->dev, regmap, sd->cfg); + if (ret) + return dev_err_probe(&client->dev, ret, + "regmap attach of '%s' failed\n", + sd->cfg->name); + + if (sd->id == MAX77759_I2C_SUBDEV_ID_MAXQ) + max77759->regmap_maxq = regmap; + else if (sd->id == MAX77759_I2C_SUBDEV_ID_CHARGER) + max77759->regmap_charger = regmap; + + return 0; +} + +static int max77759_add_chained_irq_chip(struct device *dev, + struct regmap *regmap, + int pirq, + struct regmap_irq_chip_data *parent, + const struct regmap_irq_chip *chip, + struct regmap_irq_chip_data **data) +{ + int irq, ret; + + irq = regmap_irq_get_virq(parent, pirq); + if (irq < 0) + return dev_err_probe(dev, irq, + "failed to get parent vIRQ(%d) for chip %s\n", + pirq, chip->name); + + ret = devm_regmap_add_irq_chip(dev, regmap, irq, + IRQF_ONESHOT | IRQF_SHARED, 0, chip, + data); + if (ret) + return dev_err_probe(dev, ret, "failed to add %s IRQ chip\n", + chip->name); + + return 0; +} + +static int max77759_add_chained_maxq(struct i2c_client *client, + struct max77759 *max77759, + struct regmap_irq_chip_data *parent) +{ + struct regmap_irq_chip_data *irq_chip_data; + int apcmdres_irq; + int ret; + + ret = max77759_add_chained_irq_chip(&client->dev, + max77759->regmap_maxq, + MAX77759_INT_MAXQ, + parent, + &max77759_maxq_irq_chip, + &irq_chip_data); + if (ret) + return ret; + + init_completion(&max77759->cmd_done); + apcmdres_irq = regmap_irq_get_virq(irq_chip_data, + MAX77759_MAXQ_INT_APCMDRESI); + + ret = devm_request_threaded_irq(&client->dev, apcmdres_irq, + NULL, apcmdres_irq_handler, + IRQF_ONESHOT | IRQF_SHARED, + dev_name(&client->dev), max77759); + if (ret) + return dev_err_probe(&client->dev, ret, + "MAX77759_MAXQ_INT_APCMDRESI failed\n"); + + ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_AUTO, + max77759_maxq_cells, + ARRAY_SIZE(max77759_maxq_cells), + NULL, 0, + regmap_irq_get_domain(irq_chip_data)); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to add child devices (MaxQ)\n"); + + return 0; +} + +static int max77759_add_chained_topsys(struct i2c_client *client, + struct max77759 *max77759, + struct regmap_irq_chip_data *parent) +{ + struct regmap_irq_chip_data *irq_chip_data; + int ret; + + ret = max77759_add_chained_irq_chip(&client->dev, + max77759->regmap_top, + MAX77759_INT_TOPSYS, + parent, + &max77759_topsys_irq_chip, + &irq_chip_data); + if (ret) + return ret; + + return 0; +} + +static int max77759_add_chained_charger(struct i2c_client *client, + struct max77759 *max77759, + struct regmap_irq_chip_data *parent) +{ + struct regmap_irq_chip_data *irq_chip_data; + int ret; + + ret = max77759_add_chained_irq_chip(&client->dev, + max77759->regmap_charger, + MAX77759_INT_CHGR, + parent, + &max77759_chrg_irq_chip, + &irq_chip_data); + if (ret) + return ret; + + ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_AUTO, + max77759_charger_cells, + ARRAY_SIZE(max77759_charger_cells), + NULL, 0, + regmap_irq_get_domain(irq_chip_data)); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to add child devices (charger)\n"); + + return 0; +} + +static int max77759_probe(struct i2c_client *client) +{ + struct regmap_irq_chip_data *irq_chip_data_pmic; + struct irq_data *irq_data; + struct max77759 *max77759; + unsigned long irq_flags; + unsigned int pmic_id; + int ret; + + max77759 = devm_kzalloc(&client->dev, sizeof(*max77759), GFP_KERNEL); + if (!max77759) + return -ENOMEM; + + i2c_set_clientdata(client, max77759); + + max77759->regmap_top = devm_regmap_init_i2c(client, + &max77759_regmap_config_top); + if (IS_ERR(max77759->regmap_top)) + return dev_err_probe(&client->dev, PTR_ERR(max77759->regmap_top), + "regmap init for '%s' failed\n", + max77759_regmap_config_top.name); + + ret = regmap_read(max77759->regmap_top, + MAX77759_PMIC_REG_PMIC_ID, &pmic_id); + if (ret) + return dev_err_probe(&client->dev, ret, + "unable to read device ID\n"); + + if (pmic_id != MAX77759_CHIP_ID) + return dev_err_probe(&client->dev, -ENODEV, + "unsupported device ID %#.2x (%d)\n", + pmic_id, pmic_id); + + ret = devm_mutex_init(&client->dev, &max77759->maxq_lock); + if (ret) + return ret; + + for (int i = 0; i < ARRAY_SIZE(max77759_i2c_subdevs); i++) { + ret = max77759_create_i2c_subdev(client, max77759, + &max77759_i2c_subdevs[i]); + if (ret) + return ret; + } + + irq_data = irq_get_irq_data(client->irq); + if (!irq_data) + return dev_err_probe(&client->dev, -EINVAL, + "invalid IRQ: %d\n", client->irq); + + irq_flags = IRQF_ONESHOT | IRQF_SHARED; + irq_flags |= irqd_get_trigger_type(irq_data); + + ret = devm_regmap_add_irq_chip(&client->dev, max77759->regmap_top, + client->irq, irq_flags, 0, + &max77759_pmic_irq_chip, + &irq_chip_data_pmic); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to add IRQ chip '%s'\n", + max77759_pmic_irq_chip.name); + + ret = max77759_add_chained_maxq(client, max77759, irq_chip_data_pmic); + if (ret) + return ret; + + ret = max77759_add_chained_topsys(client, max77759, irq_chip_data_pmic); + if (ret) + return ret; + + ret = max77759_add_chained_charger(client, max77759, irq_chip_data_pmic); + if (ret) + return ret; + + return devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_AUTO, + max77759_cells, ARRAY_SIZE(max77759_cells), + NULL, 0, + regmap_irq_get_domain(irq_chip_data_pmic)); +} + +static const struct i2c_device_id max77759_i2c_id[] = { + { "max77759" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max77759_i2c_id); + +static const struct of_device_id max77759_of_id[] = { + { .compatible = "maxim,max77759", }, + { } +}; +MODULE_DEVICE_TABLE(of, max77759_of_id); + +static struct i2c_driver max77759_i2c_driver = { + .driver = { + .name = "max77759", + .of_match_table = max77759_of_id, + }, + .probe = max77759_probe, + .id_table = max77759_i2c_id, +}; +module_i2c_driver(max77759_i2c_driver); + +MODULE_AUTHOR("André Draszik "); +MODULE_DESCRIPTION("Maxim MAX77759 core driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/max77759.h b/include/linux/mfd/max77759.h new file mode 100644 index 000000000000..c6face34e385 --- /dev/null +++ b/include/linux/mfd/max77759.h @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2020 Google Inc. + * Copyright 2025 Linaro Ltd. + * + * Maxim MAX77759 core driver + */ + +#ifndef __LINUX_MFD_MAX77759_H +#define __LINUX_MFD_MAX77759_H + +#include +#include +#include + +#define MAX77759_PMIC_REG_PMIC_ID 0x00 +#define MAX77759_PMIC_REG_PMIC_REVISION 0x01 +#define MAX77759_PMIC_REG_OTP_REVISION 0x02 +#define MAX77759_PMIC_REG_INTSRC 0x22 +#define MAX77759_PMIC_REG_INTSRCMASK 0x23 +#define MAX77759_PMIC_REG_INTSRC_MAXQ BIT(3) +#define MAX77759_PMIC_REG_INTSRC_TOPSYS BIT(1) +#define MAX77759_PMIC_REG_INTSRC_CHGR BIT(0) +#define MAX77759_PMIC_REG_TOPSYS_INT 0x24 +#define MAX77759_PMIC_REG_TOPSYS_INT_MASK 0x26 +#define MAX77759_PMIC_REG_TOPSYS_INT_TSHDN BIT(6) +#define MAX77759_PMIC_REG_TOPSYS_INT_SYSOVLO BIT(5) +#define MAX77759_PMIC_REG_TOPSYS_INT_SYSUVLO BIT(4) +#define MAX77759_PMIC_REG_TOPSYS_INT_FSHIP BIT(0) +#define MAX77759_PMIC_REG_I2C_CNFG 0x40 +#define MAX77759_PMIC_REG_SWRESET 0x50 +#define MAX77759_PMIC_REG_CONTROL_FG 0x51 + +#define MAX77759_MAXQ_REG_UIC_INT1 0x64 +#define MAX77759_MAXQ_REG_UIC_INT1_APCMDRESI BIT(7) +#define MAX77759_MAXQ_REG_UIC_INT1_SYSMSGI BIT(6) +#define MAX77759_MAXQ_REG_UIC_INT1_GPIO6I BIT(1) +#define MAX77759_MAXQ_REG_UIC_INT1_GPIO5I BIT(0) +#define MAX77759_MAXQ_REG_UIC_INT1_GPIOxI(offs, en) (((en) & 1) << (offs)) +#define MAX77759_MAXQ_REG_UIC_INT1_GPIOxI_MASK(offs) \ + MAX77759_MAXQ_REG_UIC_INT1_GPIOxI(offs, ~0) +#define MAX77759_MAXQ_REG_UIC_INT2 0x65 +#define MAX77759_MAXQ_REG_UIC_INT3 0x66 +#define MAX77759_MAXQ_REG_UIC_INT4 0x67 +#define MAX77759_MAXQ_REG_UIC_UIC_STATUS1 0x68 +#define MAX77759_MAXQ_REG_UIC_UIC_STATUS2 0x69 +#define MAX77759_MAXQ_REG_UIC_UIC_STATUS3 0x6a +#define MAX77759_MAXQ_REG_UIC_UIC_STATUS4 0x6b +#define MAX77759_MAXQ_REG_UIC_UIC_STATUS5 0x6c +#define MAX77759_MAXQ_REG_UIC_UIC_STATUS6 0x6d +#define MAX77759_MAXQ_REG_UIC_UIC_STATUS7 0x6f +#define MAX77759_MAXQ_REG_UIC_UIC_STATUS8 0x6f +#define MAX77759_MAXQ_REG_UIC_INT1_M 0x70 +#define MAX77759_MAXQ_REG_UIC_INT2_M 0x71 +#define MAX77759_MAXQ_REG_UIC_INT3_M 0x72 +#define MAX77759_MAXQ_REG_UIC_INT4_M 0x73 +#define MAX77759_MAXQ_REG_AP_DATAOUT0 0x81 +#define MAX77759_MAXQ_REG_AP_DATAOUT32 0xa1 +#define MAX77759_MAXQ_REG_AP_DATAIN0 0xb1 +#define MAX77759_MAXQ_REG_UIC_SWRST 0xe0 + +#define MAX77759_CHGR_REG_CHG_INT 0xb0 +#define MAX77759_CHGR_REG_CHG_INT2 0xb1 +#define MAX77759_CHGR_REG_CHG_INT_MASK 0xb2 +#define MAX77759_CHGR_REG_CHG_INT2_MASK 0xb3 +#define MAX77759_CHGR_REG_CHG_INT_OK 0xb4 +#define MAX77759_CHGR_REG_CHG_DETAILS_00 0xb5 +#define MAX77759_CHGR_REG_CHG_DETAILS_01 0xb6 +#define MAX77759_CHGR_REG_CHG_DETAILS_02 0xb7 +#define MAX77759_CHGR_REG_CHG_DETAILS_03 0xb8 +#define MAX77759_CHGR_REG_CHG_CNFG_00 0xb9 +#define MAX77759_CHGR_REG_CHG_CNFG_01 0xba +#define MAX77759_CHGR_REG_CHG_CNFG_02 0xbb +#define MAX77759_CHGR_REG_CHG_CNFG_03 0xbc +#define MAX77759_CHGR_REG_CHG_CNFG_04 0xbd +#define MAX77759_CHGR_REG_CHG_CNFG_05 0xbe +#define MAX77759_CHGR_REG_CHG_CNFG_06 0xbf +#define MAX77759_CHGR_REG_CHG_CNFG_07 0xc0 +#define MAX77759_CHGR_REG_CHG_CNFG_08 0xc1 +#define MAX77759_CHGR_REG_CHG_CNFG_09 0xc2 +#define MAX77759_CHGR_REG_CHG_CNFG_10 0xc3 +#define MAX77759_CHGR_REG_CHG_CNFG_11 0xc4 +#define MAX77759_CHGR_REG_CHG_CNFG_12 0xc5 +#define MAX77759_CHGR_REG_CHG_CNFG_13 0xc6 +#define MAX77759_CHGR_REG_CHG_CNFG_14 0xc7 +#define MAX77759_CHGR_REG_CHG_CNFG_15 0xc8 +#define MAX77759_CHGR_REG_CHG_CNFG_16 0xc9 +#define MAX77759_CHGR_REG_CHG_CNFG_17 0xca +#define MAX77759_CHGR_REG_CHG_CNFG_18 0xcb +#define MAX77759_CHGR_REG_CHG_CNFG_19 0xcc + +/* MaxQ opcodes for max77759_maxq_command() */ +#define MAX77759_MAXQ_OPCODE_MAXLENGTH (MAX77759_MAXQ_REG_AP_DATAOUT32 - \ + MAX77759_MAXQ_REG_AP_DATAOUT0 + \ + 1) + +#define MAX77759_MAXQ_OPCODE_GPIO_TRIGGER_READ 0x21 +#define MAX77759_MAXQ_OPCODE_GPIO_TRIGGER_WRITE 0x22 +#define MAX77759_MAXQ_OPCODE_GPIO_CONTROL_READ 0x23 +#define MAX77759_MAXQ_OPCODE_GPIO_CONTROL_WRITE 0x24 +#define MAX77759_MAXQ_OPCODE_USER_SPACE_READ 0x81 +#define MAX77759_MAXQ_OPCODE_USER_SPACE_WRITE 0x82 + +/** + * struct max77759 - core max77759 internal data structure + * + * @regmap_top: Regmap for accessing TOP registers + * @maxq_lock: Lock for serializing access to MaxQ + * @regmap_maxq: Regmap for accessing MaxQ registers + * @cmd_done: Used to signal completion of a MaxQ command + * @regmap_charger: Regmap for accessing charger registers + * + * The MAX77759 comprises several sub-blocks, namely TOP, MaxQ, Charger, + * Fuel Gauge, and TCPCI. + */ +struct max77759 { + struct regmap *regmap_top; + + /* This protects MaxQ commands - only one can be active */ + struct mutex maxq_lock; + struct regmap *regmap_maxq; + struct completion cmd_done; + + struct regmap *regmap_charger; +}; + +/** + * struct max77759_maxq_command - structure containing the MaxQ command to + * send + * + * @length: The number of bytes to send. + * @cmd: The data to send. + */ +struct max77759_maxq_command { + u8 length; + u8 cmd[] __counted_by(length); +}; + +/** + * struct max77759_maxq_response - structure containing the MaxQ response + * + * @length: The number of bytes to receive. + * @rsp: The data received. Must have at least @length bytes space. + */ +struct max77759_maxq_response { + u8 length; + u8 rsp[] __counted_by(length); +}; + +/** + * max77759_maxq_command() - issue a MaxQ command and wait for the response + * and associated data + * + * @max77759: The core max77759 device handle. + * @cmd: The command to be sent. + * @rsp: Any response data associated with the command will be copied here; + * can be %NULL if the command has no response (other than ACK). + * + * Return: 0 on success, a negative error number otherwise. + */ +int max77759_maxq_command(struct max77759 *max77759, + const struct max77759_maxq_command *cmd, + struct max77759_maxq_response *rsp); + +#endif /* __LINUX_MFD_MAX77759_H */ From 89a796b950edfa290ebd9f7d9b6a837093c9005e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Draszik?= Date: Fri, 9 May 2025 14:22:40 +0100 Subject: [PATCH 082/123] gpio: max77759: Add Maxim MAX77759 gpio driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Maxim MAX77759 is a companion PMIC for USB Type-C applications and includes Battery Charger, Fuel Gauge, temperature sensors, USB Type-C Port Controller (TCPC), NVMEM, and a GPIO expander. This driver supports the GPIO functions using the platform device registered by the core MFD driver. Signed-off-by: André Draszik Acked-by: Bartosz Golaszewski Acked-by: Peter Griffin Link: https://lore.kernel.org/r/20250509-max77759-mfd-v10-2-962ac15ee3ef@linaro.org Signed-off-by: Lee Jones --- MAINTAINERS | 1 + drivers/gpio/Kconfig | 13 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-max77759.c | 530 +++++++++++++++++++++++++++++++++++ 4 files changed, 545 insertions(+) create mode 100644 drivers/gpio/gpio-max77759.c diff --git a/MAINTAINERS b/MAINTAINERS index 8b9994301ffd..5c5ff0ea4030 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14580,6 +14580,7 @@ M: André Draszik L: linux-kernel@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/*/maxim,max77759*.yaml +F: drivers/gpio/gpio-max77759.c F: drivers/mfd/max77759.c F: include/linux/mfd/max77759.h diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index f2c39bbff83a..5fcd04318ec1 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1463,6 +1463,19 @@ config GPIO_MAX77650 GPIO driver for MAX77650/77651 PMIC from Maxim Semiconductor. These chips have a single pin that can be configured as GPIO. +config GPIO_MAX77759 + tristate "Maxim Integrated MAX77759 GPIO support" + depends on MFD_MAX77759 + default MFD_MAX77759 + select GPIOLIB_IRQCHIP + help + GPIO driver for MAX77759 PMIC from Maxim Integrated. + There are two GPIOs available on these chips in total, both of + which can also generate interrupts. + + This driver can also be built as a module. If so, the module will be + called gpio-max77759. + config GPIO_PALMAS bool "TI PALMAS series PMICs GPIO" depends on MFD_PALMAS diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index af130882ffee..3b9a55f9f79e 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -105,6 +105,7 @@ obj-$(CONFIG_GPIO_MAX730X) += gpio-max730x.o obj-$(CONFIG_GPIO_MAX732X) += gpio-max732x.o obj-$(CONFIG_GPIO_MAX77620) += gpio-max77620.o obj-$(CONFIG_GPIO_MAX77650) += gpio-max77650.o +obj-$(CONFIG_GPIO_MAX77759) += gpio-max77759.o obj-$(CONFIG_GPIO_MB86S7X) += gpio-mb86s7x.o obj-$(CONFIG_GPIO_MC33880) += gpio-mc33880.o obj-$(CONFIG_GPIO_MENZ127) += gpio-menz127.o diff --git a/drivers/gpio/gpio-max77759.c b/drivers/gpio/gpio-max77759.c new file mode 100644 index 000000000000..7fe8e6f697d0 --- /dev/null +++ b/drivers/gpio/gpio-max77759.c @@ -0,0 +1,530 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright 2020 Google Inc +// Copyright 2025 Linaro Ltd. +// +// GPIO driver for Maxim MAX77759 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX77759_N_GPIOS ARRAY_SIZE(max77759_gpio_line_names) +static const char * const max77759_gpio_line_names[] = { "GPIO5", "GPIO6" }; + +struct max77759_gpio_chip { + struct regmap *map; + struct max77759 *max77759; + struct gpio_chip gc; + struct mutex maxq_lock; /* protect MaxQ r/m/w operations */ + + struct mutex irq_lock; /* protect irq bus */ + int irq_mask; + int irq_mask_changed; + int irq_trig; + int irq_trig_changed; +}; + +#define MAX77759_GPIOx_TRIGGER(offs, val) (((val) & 1) << (offs)) +#define MAX77759_GPIOx_TRIGGER_MASK(offs) MAX77759_GPIOx_TRIGGER(offs, ~0) +enum max77759_trigger_gpio_type { + MAX77759_GPIO_TRIGGER_RISING = 0, + MAX77759_GPIO_TRIGGER_FALLING = 1 +}; + +#define MAX77759_GPIOx_DIR(offs, dir) (((dir) & 1) << (2 + (3 * (offs)))) +#define MAX77759_GPIOx_DIR_MASK(offs) MAX77759_GPIOx_DIR(offs, ~0) +enum max77759_control_gpio_dir { + MAX77759_GPIO_DIR_IN = 0, + MAX77759_GPIO_DIR_OUT = 1 +}; + +#define MAX77759_GPIOx_OUTVAL(offs, val) (((val) & 1) << (3 + (3 * (offs)))) +#define MAX77759_GPIOx_OUTVAL_MASK(offs) MAX77759_GPIOx_OUTVAL(offs, ~0) + +#define MAX77759_GPIOx_INVAL_MASK(offs) (BIT(4) << (3 * (offs))) + +static int max77759_gpio_maxq_gpio_trigger_read(struct max77759_gpio_chip *chip) +{ + DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length, 1); + DEFINE_FLEX(struct max77759_maxq_response, rsp, rsp, length, 2); + int ret; + + cmd->cmd[0] = MAX77759_MAXQ_OPCODE_GPIO_TRIGGER_READ; + + ret = max77759_maxq_command(chip->max77759, cmd, rsp); + if (ret < 0) + return ret; + + return rsp->rsp[1]; +} + +static int max77759_gpio_maxq_gpio_trigger_write(struct max77759_gpio_chip *chip, + u8 trigger) +{ + DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length, 2); + + cmd->cmd[0] = MAX77759_MAXQ_OPCODE_GPIO_TRIGGER_WRITE; + cmd->cmd[1] = trigger; + + return max77759_maxq_command(chip->max77759, cmd, NULL); +} + +static int max77759_gpio_maxq_gpio_control_read(struct max77759_gpio_chip *chip) +{ + DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length, 1); + DEFINE_FLEX(struct max77759_maxq_response, rsp, rsp, length, 2); + int ret; + + cmd->cmd[0] = MAX77759_MAXQ_OPCODE_GPIO_CONTROL_READ; + + ret = max77759_maxq_command(chip->max77759, cmd, rsp); + if (ret < 0) + return ret; + + return rsp->rsp[1]; +} + +static int max77759_gpio_maxq_gpio_control_write(struct max77759_gpio_chip *chip, + u8 ctrl) +{ + DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length, 2); + + cmd->cmd[0] = MAX77759_MAXQ_OPCODE_GPIO_CONTROL_WRITE; + cmd->cmd[1] = ctrl; + + return max77759_maxq_command(chip->max77759, cmd, NULL); +} + +static int +max77759_gpio_direction_from_control(int ctrl, unsigned int offset) +{ + enum max77759_control_gpio_dir dir; + + dir = !!(ctrl & MAX77759_GPIOx_DIR_MASK(offset)); + return ((dir == MAX77759_GPIO_DIR_OUT) + ? GPIO_LINE_DIRECTION_OUT + : GPIO_LINE_DIRECTION_IN); +} + +static int max77759_gpio_get_direction(struct gpio_chip *gc, + unsigned int offset) +{ + struct max77759_gpio_chip *chip = gpiochip_get_data(gc); + int ctrl; + + ctrl = max77759_gpio_maxq_gpio_control_read(chip); + if (ctrl < 0) + return ctrl; + + return max77759_gpio_direction_from_control(ctrl, offset); +} + +static int max77759_gpio_direction_helper(struct gpio_chip *gc, + unsigned int offset, + enum max77759_control_gpio_dir dir, + int value) +{ + struct max77759_gpio_chip *chip = gpiochip_get_data(gc); + int ctrl, new_ctrl; + + guard(mutex)(&chip->maxq_lock); + + ctrl = max77759_gpio_maxq_gpio_control_read(chip); + if (ctrl < 0) + return ctrl; + + new_ctrl = ctrl & ~MAX77759_GPIOx_DIR_MASK(offset); + new_ctrl |= MAX77759_GPIOx_DIR(offset, dir); + + if (dir == MAX77759_GPIO_DIR_OUT) { + new_ctrl &= ~MAX77759_GPIOx_OUTVAL_MASK(offset); + new_ctrl |= MAX77759_GPIOx_OUTVAL(offset, value); + } + + if (new_ctrl == ctrl) + return 0; + + return max77759_gpio_maxq_gpio_control_write(chip, new_ctrl); +} + +static int max77759_gpio_direction_input(struct gpio_chip *gc, + unsigned int offset) +{ + return max77759_gpio_direction_helper(gc, offset, + MAX77759_GPIO_DIR_IN, -1); +} + +static int max77759_gpio_direction_output(struct gpio_chip *gc, + unsigned int offset, int value) +{ + return max77759_gpio_direction_helper(gc, offset, + MAX77759_GPIO_DIR_OUT, value); +} + +static int max77759_gpio_get_value(struct gpio_chip *gc, unsigned int offset) +{ + struct max77759_gpio_chip *chip = gpiochip_get_data(gc); + int ctrl, mask; + + ctrl = max77759_gpio_maxq_gpio_control_read(chip); + if (ctrl < 0) + return ctrl; + + /* + * The input status bit doesn't reflect the pin state when the GPIO is + * configured as an output. Check the direction, and inspect the input + * or output bit accordingly. + */ + mask = ((max77759_gpio_direction_from_control(ctrl, offset) + == GPIO_LINE_DIRECTION_IN) + ? MAX77759_GPIOx_INVAL_MASK(offset) + : MAX77759_GPIOx_OUTVAL_MASK(offset)); + + return !!(ctrl & mask); +} + +static int max77759_gpio_set_value(struct gpio_chip *gc, + unsigned int offset, int value) +{ + struct max77759_gpio_chip *chip = gpiochip_get_data(gc); + int ctrl, new_ctrl; + + guard(mutex)(&chip->maxq_lock); + + ctrl = max77759_gpio_maxq_gpio_control_read(chip); + if (ctrl < 0) + return ctrl; + + new_ctrl = ctrl & ~MAX77759_GPIOx_OUTVAL_MASK(offset); + new_ctrl |= MAX77759_GPIOx_OUTVAL(offset, value); + + if (new_ctrl == ctrl) + return 0; + + return max77759_gpio_maxq_gpio_control_write(chip, new_ctrl); +} + +static void max77759_gpio_irq_mask(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct max77759_gpio_chip *chip = gpiochip_get_data(gc); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + + chip->irq_mask &= ~MAX77759_MAXQ_REG_UIC_INT1_GPIOxI_MASK(hwirq); + chip->irq_mask |= MAX77759_MAXQ_REG_UIC_INT1_GPIOxI(hwirq, 1); + chip->irq_mask_changed |= MAX77759_MAXQ_REG_UIC_INT1_GPIOxI(hwirq, 1); + + gpiochip_disable_irq(gc, hwirq); +} + +static void max77759_gpio_irq_unmask(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct max77759_gpio_chip *chip = gpiochip_get_data(gc); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + + gpiochip_enable_irq(gc, hwirq); + + chip->irq_mask &= ~MAX77759_MAXQ_REG_UIC_INT1_GPIOxI_MASK(hwirq); + chip->irq_mask |= MAX77759_MAXQ_REG_UIC_INT1_GPIOxI(hwirq, 0); + chip->irq_mask_changed |= MAX77759_MAXQ_REG_UIC_INT1_GPIOxI(hwirq, 1); +} + +static int max77759_gpio_set_irq_type(struct irq_data *d, unsigned int type) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct max77759_gpio_chip *chip = gpiochip_get_data(gc); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + + chip->irq_trig &= ~MAX77759_GPIOx_TRIGGER_MASK(hwirq); + switch (type) { + case IRQ_TYPE_EDGE_RISING: + chip->irq_trig |= MAX77759_GPIOx_TRIGGER(hwirq, + MAX77759_GPIO_TRIGGER_RISING); + break; + + case IRQ_TYPE_EDGE_FALLING: + chip->irq_trig |= MAX77759_GPIOx_TRIGGER(hwirq, + MAX77759_GPIO_TRIGGER_FALLING); + break; + + default: + return -EINVAL; + } + + chip->irq_trig_changed |= MAX77759_GPIOx_TRIGGER(hwirq, 1); + + return 0; +} + +static void max77759_gpio_bus_lock(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct max77759_gpio_chip *chip = gpiochip_get_data(gc); + + mutex_lock(&chip->irq_lock); +} + +static int max77759_gpio_bus_sync_unlock_helper(struct gpio_chip *gc, + struct max77759_gpio_chip *chip) + __must_hold(&chip->maxq_lock) +{ + int ctrl, trigger, new_trigger, new_ctrl; + unsigned long irq_trig_changed; + int offset; + int ret; + + lockdep_assert_held(&chip->maxq_lock); + + ctrl = max77759_gpio_maxq_gpio_control_read(chip); + trigger = max77759_gpio_maxq_gpio_trigger_read(chip); + if (ctrl < 0 || trigger < 0) { + dev_err(gc->parent, "failed to read current state: %d / %d\n", + ctrl, trigger); + return (ctrl < 0) ? ctrl : trigger; + } + + new_trigger = trigger & ~chip->irq_trig_changed; + new_trigger |= (chip->irq_trig & chip->irq_trig_changed); + + /* change GPIO direction if required */ + new_ctrl = ctrl; + irq_trig_changed = chip->irq_trig_changed; + for_each_set_bit(offset, &irq_trig_changed, MAX77759_N_GPIOS) { + new_ctrl &= ~MAX77759_GPIOx_DIR_MASK(offset); + new_ctrl |= MAX77759_GPIOx_DIR(offset, MAX77759_GPIO_DIR_IN); + } + + if (new_trigger != trigger) { + ret = max77759_gpio_maxq_gpio_trigger_write(chip, new_trigger); + if (ret) { + dev_err(gc->parent, + "failed to write new trigger: %d\n", ret); + return ret; + } + } + + if (new_ctrl != ctrl) { + ret = max77759_gpio_maxq_gpio_control_write(chip, new_ctrl); + if (ret) { + dev_err(gc->parent, + "failed to write new control: %d\n", ret); + return ret; + } + } + + chip->irq_trig_changed = 0; + + return 0; +} + +static void max77759_gpio_bus_sync_unlock(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct max77759_gpio_chip *chip = gpiochip_get_data(gc); + int ret; + + scoped_guard(mutex, &chip->maxq_lock) { + ret = max77759_gpio_bus_sync_unlock_helper(gc, chip); + if (ret) + goto out_unlock; + } + + ret = regmap_update_bits(chip->map, + MAX77759_MAXQ_REG_UIC_INT1_M, + chip->irq_mask_changed, chip->irq_mask); + if (ret) { + dev_err(gc->parent, + "failed to update UIC_INT1 irq mask: %d\n", ret); + goto out_unlock; + } + + chip->irq_mask_changed = 0; + +out_unlock: + mutex_unlock(&chip->irq_lock); +} + +static void max77759_gpio_irq_print_chip(struct irq_data *d, struct seq_file *p) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + + seq_puts(p, dev_name(gc->parent)); +} + +static const struct irq_chip max77759_gpio_irq_chip = { + .irq_mask = max77759_gpio_irq_mask, + .irq_unmask = max77759_gpio_irq_unmask, + .irq_set_type = max77759_gpio_set_irq_type, + .irq_bus_lock = max77759_gpio_bus_lock, + .irq_bus_sync_unlock = max77759_gpio_bus_sync_unlock, + .irq_print_chip = max77759_gpio_irq_print_chip, + .flags = IRQCHIP_IMMUTABLE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, +}; + +static irqreturn_t max77759_gpio_irqhandler(int irq, void *data) +{ + struct max77759_gpio_chip *chip = data; + struct gpio_chip *gc = &chip->gc; + bool handled = false; + + /* iterate until no interrupt is pending */ + while (true) { + unsigned int uic_int1; + int ret; + unsigned long pending; + int offset; + + ret = regmap_read(chip->map, MAX77759_MAXQ_REG_UIC_INT1, + &uic_int1); + if (ret < 0) { + dev_err_ratelimited(gc->parent, + "failed to read IRQ status: %d\n", + ret); + /* + * If !handled, we have looped not even once, which + * means we should return IRQ_NONE in that case (and + * of course IRQ_HANDLED otherwise). + */ + return IRQ_RETVAL(handled); + } + + pending = uic_int1; + pending &= (MAX77759_MAXQ_REG_UIC_INT1_GPIO6I + | MAX77759_MAXQ_REG_UIC_INT1_GPIO5I); + if (!pending) + break; + + for_each_set_bit(offset, &pending, MAX77759_N_GPIOS) { + /* + * ACK interrupt by writing 1 to bit 'offset', all + * others need to be written as 0. This needs to be + * done unconditionally hence regmap_set_bits() is + * inappropriate here. + */ + regmap_write(chip->map, MAX77759_MAXQ_REG_UIC_INT1, + BIT(offset)); + + handle_nested_irq(irq_find_mapping(gc->irq.domain, + offset)); + + handled = true; + } + } + + return IRQ_RETVAL(handled); +} + +static int max77759_gpio_probe(struct platform_device *pdev) +{ + struct max77759_gpio_chip *chip; + int irq; + struct gpio_irq_chip *girq; + int ret; + unsigned long irq_flags; + struct irq_data *irqd; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->map = dev_get_regmap(pdev->dev.parent, "maxq"); + if (!chip->map) + return dev_err_probe(&pdev->dev, -ENODEV, "Missing regmap\n"); + + irq = platform_get_irq_byname(pdev, "GPI"); + if (irq < 0) + return dev_err_probe(&pdev->dev, irq, "Failed to get IRQ\n"); + + chip->max77759 = dev_get_drvdata(pdev->dev.parent); + ret = devm_mutex_init(&pdev->dev, &chip->maxq_lock); + if (ret) + return ret; + ret = devm_mutex_init(&pdev->dev, &chip->irq_lock); + if (ret) + return ret; + + chip->gc.base = -1; + chip->gc.label = dev_name(&pdev->dev); + chip->gc.parent = &pdev->dev; + chip->gc.can_sleep = true; + + chip->gc.names = max77759_gpio_line_names; + chip->gc.ngpio = MAX77759_N_GPIOS; + chip->gc.get_direction = max77759_gpio_get_direction; + chip->gc.direction_input = max77759_gpio_direction_input; + chip->gc.direction_output = max77759_gpio_direction_output; + chip->gc.get = max77759_gpio_get_value; + chip->gc.set_rv = max77759_gpio_set_value; + + girq = &chip->gc.irq; + gpio_irq_chip_set_chip(girq, &max77759_gpio_irq_chip); + /* This will let us handle the parent IRQ in the driver */ + girq->parent_handler = NULL; + girq->num_parents = 0; + girq->parents = NULL; + girq->default_type = IRQ_TYPE_NONE; + girq->handler = handle_simple_irq; + girq->threaded = true; + + ret = devm_gpiochip_add_data(&pdev->dev, &chip->gc, chip); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, + "Failed to add GPIO chip\n"); + + irq_flags = IRQF_ONESHOT | IRQF_SHARED; + irqd = irq_get_irq_data(irq); + if (irqd) + irq_flags |= irqd_get_trigger_type(irqd); + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + max77759_gpio_irqhandler, irq_flags, + dev_name(&pdev->dev), chip); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, + "Failed to request IRQ\n"); + + return ret; +} + +static const struct of_device_id max77759_gpio_of_id[] = { + { .compatible = "maxim,max77759-gpio", }, + { } +}; +MODULE_DEVICE_TABLE(of, max77759_gpio_of_id); + +static const struct platform_device_id max77759_gpio_platform_id[] = { + { "max77759-gpio", }, + { } +}; +MODULE_DEVICE_TABLE(platform, max77759_gpio_platform_id); + +static struct platform_driver max77759_gpio_driver = { + .driver = { + .name = "max77759-gpio", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .of_match_table = max77759_gpio_of_id, + }, + .probe = max77759_gpio_probe, + .id_table = max77759_gpio_platform_id, +}; + +module_platform_driver(max77759_gpio_driver); + +MODULE_AUTHOR("André Draszik "); +MODULE_DESCRIPTION("GPIO driver for Maxim MAX77759"); +MODULE_LICENSE("GPL"); From 8824dc7f947ac5916cf166bb8289af48c2b50bc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Draszik?= Date: Fri, 9 May 2025 14:22:41 +0100 Subject: [PATCH 083/123] nvmem: max77759: Add Maxim MAX77759 NVMEM driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Maxim MAX77759 is a companion PMIC for USB Type-C applications and includes Battery Charger, Fuel Gauge, temperature sensors, USB Type-C Port Controller (TCPC), NVMEM, and a GPIO expander. This driver exposes the non volatile memory using the platform device registered by the core MFD driver. Signed-off-by: André Draszik Acked-by: Srinivas Kandagatla Acked-by: Peter Griffin Link: https://lore.kernel.org/r/20250509-max77759-mfd-v10-3-962ac15ee3ef@linaro.org Signed-off-by: Lee Jones --- MAINTAINERS | 1 + drivers/nvmem/Kconfig | 12 +++ drivers/nvmem/Makefile | 2 + drivers/nvmem/max77759-nvmem.c | 145 +++++++++++++++++++++++++++++++++ 4 files changed, 160 insertions(+) create mode 100644 drivers/nvmem/max77759-nvmem.c diff --git a/MAINTAINERS b/MAINTAINERS index 5c5ff0ea4030..1fe7d9e8e44d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14582,6 +14582,7 @@ S: Maintained F: Documentation/devicetree/bindings/*/maxim,max77759*.yaml F: drivers/gpio/gpio-max77759.c F: drivers/mfd/max77759.c +F: drivers/nvmem/max77759-nvmem.c F: include/linux/mfd/max77759.h MAXIM MAX77802 PMIC REGULATOR DEVICE DRIVER diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index 8671b7c974b9..3de07ef52490 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -154,6 +154,18 @@ config NVMEM_LPC18XX_OTP To compile this driver as a module, choose M here: the module will be called nvmem_lpc18xx_otp. +config NVMEM_MAX77759 + tristate "Maxim Integrated MAX77759 NVMEM Support" + depends on MFD_MAX77759 + default MFD_MAX77759 + help + Say Y here to include support for the user-accessible storage found + in Maxim Integrated MAX77759 PMICs. This IC provides space for 30 + bytes of storage. + + This driver can also be built as a module. If so, the module + will be called nvmem-max77759. + config NVMEM_MESON_EFUSE tristate "Amlogic Meson GX eFuse Support" depends on (ARCH_MESON || COMPILE_TEST) && MESON_SM diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile index 5b77bbb6488b..a9d03cfbbd27 100644 --- a/drivers/nvmem/Makefile +++ b/drivers/nvmem/Makefile @@ -34,6 +34,8 @@ obj-$(CONFIG_NVMEM_LPC18XX_EEPROM) += nvmem_lpc18xx_eeprom.o nvmem_lpc18xx_eeprom-y := lpc18xx_eeprom.o obj-$(CONFIG_NVMEM_LPC18XX_OTP) += nvmem_lpc18xx_otp.o nvmem_lpc18xx_otp-y := lpc18xx_otp.o +obj-$(CONFIG_NVMEM_MAX77759) += nvmem-max77759.o +nvmem-max77759-y := max77759-nvmem.o obj-$(CONFIG_NVMEM_MESON_EFUSE) += nvmem_meson_efuse.o nvmem_meson_efuse-y := meson-efuse.o obj-$(CONFIG_NVMEM_MESON_MX_EFUSE) += nvmem_meson_mx_efuse.o diff --git a/drivers/nvmem/max77759-nvmem.c b/drivers/nvmem/max77759-nvmem.c new file mode 100644 index 000000000000..c9961ad0e232 --- /dev/null +++ b/drivers/nvmem/max77759-nvmem.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright 2020 Google Inc +// Copyright 2025 Linaro Ltd. +// +// NVMEM driver for Maxim MAX77759 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX77759_NVMEM_OPCODE_HEADER_LEN 3 +/* + * NVMEM commands have a three byte header (which becomes part of the command), + * so we need to subtract that. + */ +#define MAX77759_NVMEM_SIZE (MAX77759_MAXQ_OPCODE_MAXLENGTH \ + - MAX77759_NVMEM_OPCODE_HEADER_LEN) + +struct max77759_nvmem { + struct device *dev; + struct max77759 *max77759; +}; + +static int max77759_nvmem_reg_read(void *priv, unsigned int offset, + void *val, size_t bytes) +{ + struct max77759_nvmem *nvmem = priv; + DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length, + MAX77759_NVMEM_OPCODE_HEADER_LEN); + DEFINE_FLEX(struct max77759_maxq_response, rsp, rsp, length, + MAX77759_MAXQ_OPCODE_MAXLENGTH); + int ret; + + cmd->cmd[0] = MAX77759_MAXQ_OPCODE_USER_SPACE_READ; + cmd->cmd[1] = offset; + cmd->cmd[2] = bytes; + rsp->length = bytes + MAX77759_NVMEM_OPCODE_HEADER_LEN; + + ret = max77759_maxq_command(nvmem->max77759, cmd, rsp); + if (ret < 0) + return ret; + + if (memcmp(cmd->cmd, rsp->rsp, MAX77759_NVMEM_OPCODE_HEADER_LEN)) { + dev_warn(nvmem->dev, "protocol error (read)\n"); + return -EIO; + } + + memcpy(val, &rsp->rsp[MAX77759_NVMEM_OPCODE_HEADER_LEN], bytes); + + return 0; +} + +static int max77759_nvmem_reg_write(void *priv, unsigned int offset, + void *val, size_t bytes) +{ + struct max77759_nvmem *nvmem = priv; + DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length, + MAX77759_MAXQ_OPCODE_MAXLENGTH); + DEFINE_FLEX(struct max77759_maxq_response, rsp, rsp, length, + MAX77759_MAXQ_OPCODE_MAXLENGTH); + int ret; + + cmd->cmd[0] = MAX77759_MAXQ_OPCODE_USER_SPACE_WRITE; + cmd->cmd[1] = offset; + cmd->cmd[2] = bytes; + memcpy(&cmd->cmd[MAX77759_NVMEM_OPCODE_HEADER_LEN], val, bytes); + cmd->length = bytes + MAX77759_NVMEM_OPCODE_HEADER_LEN; + rsp->length = cmd->length; + + ret = max77759_maxq_command(nvmem->max77759, cmd, rsp); + if (ret < 0) + return ret; + + if (memcmp(cmd->cmd, rsp->rsp, cmd->length)) { + dev_warn(nvmem->dev, "protocol error (write)\n"); + return -EIO; + } + + return 0; +} + +static int max77759_nvmem_probe(struct platform_device *pdev) +{ + struct nvmem_config config = { + .dev = &pdev->dev, + .name = dev_name(&pdev->dev), + .id = NVMEM_DEVID_NONE, + .type = NVMEM_TYPE_EEPROM, + .ignore_wp = true, + .size = MAX77759_NVMEM_SIZE, + .word_size = sizeof(u8), + .stride = sizeof(u8), + .reg_read = max77759_nvmem_reg_read, + .reg_write = max77759_nvmem_reg_write, + }; + struct max77759_nvmem *nvmem; + + nvmem = devm_kzalloc(&pdev->dev, sizeof(*nvmem), GFP_KERNEL); + if (!nvmem) + return -ENOMEM; + + nvmem->dev = &pdev->dev; + nvmem->max77759 = dev_get_drvdata(pdev->dev.parent); + + config.priv = nvmem; + + return PTR_ERR_OR_ZERO(devm_nvmem_register(config.dev, &config)); +} + +static const struct of_device_id max77759_nvmem_of_id[] = { + { .compatible = "maxim,max77759-nvmem", }, + { } +}; +MODULE_DEVICE_TABLE(of, max77759_nvmem_of_id); + +static const struct platform_device_id max77759_nvmem_platform_id[] = { + { "max77759-nvmem", }, + { } +}; +MODULE_DEVICE_TABLE(platform, max77759_nvmem_platform_id); + +static struct platform_driver max77759_nvmem_driver = { + .driver = { + .name = "max77759-nvmem", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .of_match_table = max77759_nvmem_of_id, + }, + .probe = max77759_nvmem_probe, + .id_table = max77759_nvmem_platform_id, +}; + +module_platform_driver(max77759_nvmem_driver); + +MODULE_AUTHOR("André Draszik "); +MODULE_DESCRIPTION("NVMEM driver for Maxim MAX77759"); +MODULE_LICENSE("GPL"); From 30d15b8949828811e9df3dafc12e3d523ed6e154 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 6 May 2025 11:01:44 +0200 Subject: [PATCH 084/123] gpio: lp873x: use new GPIO line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Link: https://lore.kernel.org/r/20250506-gpiochip-set-rv-gpio-part3-v1-1-0fbdea5a9667@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-lp873x.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpio/gpio-lp873x.c b/drivers/gpio/gpio-lp873x.c index 5c79ba1f229c..1908ed302e92 100644 --- a/drivers/gpio/gpio-lp873x.c +++ b/drivers/gpio/gpio-lp873x.c @@ -58,14 +58,14 @@ static int lp873x_gpio_get(struct gpio_chip *chip, unsigned int offset) return val & BIT(offset * BITS_PER_GPO); } -static void lp873x_gpio_set(struct gpio_chip *chip, unsigned int offset, - int value) +static int lp873x_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) { struct lp873x_gpio *gpio = gpiochip_get_data(chip); - regmap_update_bits(gpio->lp873->regmap, LP873X_REG_GPO_CTRL, - BIT(offset * BITS_PER_GPO), - value ? BIT(offset * BITS_PER_GPO) : 0); + return regmap_update_bits(gpio->lp873->regmap, LP873X_REG_GPO_CTRL, + BIT(offset * BITS_PER_GPO), + value ? BIT(offset * BITS_PER_GPO) : 0); } static int lp873x_gpio_request(struct gpio_chip *gc, unsigned int offset) @@ -124,7 +124,7 @@ static const struct gpio_chip template_chip = { .direction_input = lp873x_gpio_direction_input, .direction_output = lp873x_gpio_direction_output, .get = lp873x_gpio_get, - .set = lp873x_gpio_set, + .set_rv = lp873x_gpio_set, .set_config = lp873x_gpio_set_config, .base = -1, .ngpio = 2, From 978d23c1db9a303f2fab817de28087ada5859a9a Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 6 May 2025 11:01:45 +0200 Subject: [PATCH 085/123] gpio: lp87565: use new GPIO line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Link: https://lore.kernel.org/r/20250506-gpiochip-set-rv-gpio-part3-v1-2-0fbdea5a9667@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-lp87565.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/gpio/gpio-lp87565.c b/drivers/gpio/gpio-lp87565.c index d3ce027de081..8ea687d5d028 100644 --- a/drivers/gpio/gpio-lp87565.c +++ b/drivers/gpio/gpio-lp87565.c @@ -30,13 +30,13 @@ static int lp87565_gpio_get(struct gpio_chip *chip, unsigned int offset) return !!(val & BIT(offset)); } -static void lp87565_gpio_set(struct gpio_chip *chip, unsigned int offset, - int value) +static int lp87565_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) { struct lp87565_gpio *gpio = gpiochip_get_data(chip); - regmap_update_bits(gpio->map, LP87565_REG_GPIO_OUT, - BIT(offset), value ? BIT(offset) : 0); + return regmap_update_bits(gpio->map, LP87565_REG_GPIO_OUT, + BIT(offset), value ? BIT(offset) : 0); } static int lp87565_gpio_get_direction(struct gpio_chip *chip, @@ -69,8 +69,11 @@ static int lp87565_gpio_direction_output(struct gpio_chip *chip, unsigned int offset, int value) { struct lp87565_gpio *gpio = gpiochip_get_data(chip); + int ret; - lp87565_gpio_set(chip, offset, value); + ret = lp87565_gpio_set(chip, offset, value); + if (ret) + return ret; return regmap_update_bits(gpio->map, LP87565_REG_GPIO_CONFIG, @@ -136,7 +139,7 @@ static const struct gpio_chip template_chip = { .direction_input = lp87565_gpio_direction_input, .direction_output = lp87565_gpio_direction_output, .get = lp87565_gpio_get, - .set = lp87565_gpio_set, + .set_rv = lp87565_gpio_set, .set_config = lp87565_gpio_set_config, .base = -1, .ngpio = 3, From 4f71abfef92d9e302c7c62744fa806076b3ba819 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 6 May 2025 11:01:46 +0200 Subject: [PATCH 086/123] gpio: lpc18xx: use new GPIO line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Link: https://lore.kernel.org/r/20250506-gpiochip-set-rv-gpio-part3-v1-3-0fbdea5a9667@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-lpc18xx.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-lpc18xx.c b/drivers/gpio/gpio-lpc18xx.c index 2cf9fb4637a2..8bb8a87ec1e9 100644 --- a/drivers/gpio/gpio-lpc18xx.c +++ b/drivers/gpio/gpio-lpc18xx.c @@ -263,10 +263,14 @@ static int lpc18xx_gpio_pin_ic_probe(struct lpc18xx_gpio_chip *gc) return ret; } -static void lpc18xx_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +static int lpc18xx_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) { struct lpc18xx_gpio_chip *gc = gpiochip_get_data(chip); + writeb(value ? 1 : 0, gc->base + offset); + + return 0; } static int lpc18xx_gpio_get(struct gpio_chip *chip, unsigned offset) @@ -316,7 +320,7 @@ static const struct gpio_chip lpc18xx_chip = { .free = gpiochip_generic_free, .direction_input = lpc18xx_gpio_direction_input, .direction_output = lpc18xx_gpio_direction_output, - .set = lpc18xx_gpio_set, + .set_rv = lpc18xx_gpio_set, .get = lpc18xx_gpio_get, .ngpio = LPC18XX_MAX_PORTS * LPC18XX_PINS_PER_PORT, .owner = THIS_MODULE, From 1a7b6b536d509203325bccd88ae42499b45f5765 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 6 May 2025 11:01:47 +0200 Subject: [PATCH 087/123] gpio: lpc32xx: use new GPIO line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Link: https://lore.kernel.org/r/20250506-gpiochip-set-rv-gpio-part3-v1-4-0fbdea5a9667@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-lpc32xx.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/drivers/gpio/gpio-lpc32xx.c b/drivers/gpio/gpio-lpc32xx.c index c097e310c9e8..6668b8bd9f1e 100644 --- a/drivers/gpio/gpio-lpc32xx.c +++ b/drivers/gpio/gpio-lpc32xx.c @@ -340,28 +340,34 @@ static int lpc32xx_gpio_dir_out_always(struct gpio_chip *chip, unsigned pin, return 0; } -static void lpc32xx_gpio_set_value_p012(struct gpio_chip *chip, unsigned pin, - int value) +static int lpc32xx_gpio_set_value_p012(struct gpio_chip *chip, + unsigned int pin, int value) { struct lpc32xx_gpio_chip *group = gpiochip_get_data(chip); __set_gpio_level_p012(group, pin, value); + + return 0; } -static void lpc32xx_gpio_set_value_p3(struct gpio_chip *chip, unsigned pin, - int value) +static int lpc32xx_gpio_set_value_p3(struct gpio_chip *chip, + unsigned int pin, int value) { struct lpc32xx_gpio_chip *group = gpiochip_get_data(chip); __set_gpio_level_p3(group, pin, value); + + return 0; } -static void lpc32xx_gpo_set_value(struct gpio_chip *chip, unsigned pin, - int value) +static int lpc32xx_gpo_set_value(struct gpio_chip *chip, unsigned int pin, + int value) { struct lpc32xx_gpio_chip *group = gpiochip_get_data(chip); __set_gpo_level_p3(group, pin, value); + + return 0; } static int lpc32xx_gpo_get_value(struct gpio_chip *chip, unsigned pin) @@ -401,7 +407,7 @@ static struct lpc32xx_gpio_chip lpc32xx_gpiochip[] = { .direction_input = lpc32xx_gpio_dir_input_p012, .get = lpc32xx_gpio_get_value_p012, .direction_output = lpc32xx_gpio_dir_output_p012, - .set = lpc32xx_gpio_set_value_p012, + .set_rv = lpc32xx_gpio_set_value_p012, .request = lpc32xx_gpio_request, .to_irq = lpc32xx_gpio_to_irq_p01, .base = LPC32XX_GPIO_P0_GRP, @@ -417,7 +423,7 @@ static struct lpc32xx_gpio_chip lpc32xx_gpiochip[] = { .direction_input = lpc32xx_gpio_dir_input_p012, .get = lpc32xx_gpio_get_value_p012, .direction_output = lpc32xx_gpio_dir_output_p012, - .set = lpc32xx_gpio_set_value_p012, + .set_rv = lpc32xx_gpio_set_value_p012, .request = lpc32xx_gpio_request, .to_irq = lpc32xx_gpio_to_irq_p01, .base = LPC32XX_GPIO_P1_GRP, @@ -433,7 +439,7 @@ static struct lpc32xx_gpio_chip lpc32xx_gpiochip[] = { .direction_input = lpc32xx_gpio_dir_input_p012, .get = lpc32xx_gpio_get_value_p012, .direction_output = lpc32xx_gpio_dir_output_p012, - .set = lpc32xx_gpio_set_value_p012, + .set_rv = lpc32xx_gpio_set_value_p012, .request = lpc32xx_gpio_request, .base = LPC32XX_GPIO_P2_GRP, .ngpio = LPC32XX_GPIO_P2_MAX, @@ -448,7 +454,7 @@ static struct lpc32xx_gpio_chip lpc32xx_gpiochip[] = { .direction_input = lpc32xx_gpio_dir_input_p3, .get = lpc32xx_gpio_get_value_p3, .direction_output = lpc32xx_gpio_dir_output_p3, - .set = lpc32xx_gpio_set_value_p3, + .set_rv = lpc32xx_gpio_set_value_p3, .request = lpc32xx_gpio_request, .to_irq = lpc32xx_gpio_to_irq_gpio_p3, .base = LPC32XX_GPIO_P3_GRP, @@ -476,7 +482,7 @@ static struct lpc32xx_gpio_chip lpc32xx_gpiochip[] = { .chip = { .label = "gpo_p3", .direction_output = lpc32xx_gpio_dir_out_always, - .set = lpc32xx_gpo_set_value, + .set_rv = lpc32xx_gpo_set_value, .get = lpc32xx_gpo_get_value, .request = lpc32xx_gpio_request, .base = LPC32XX_GPO_P3_GRP, From 2e6b7f5ffe08d42196b31322e6443a9874c79a03 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 6 May 2025 11:01:48 +0200 Subject: [PATCH 088/123] gpio: madera: use new GPIO line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Reviewed-by: Charles Keepax Link: https://lore.kernel.org/r/20250506-gpiochip-set-rv-gpio-part3-v1-5-0fbdea5a9667@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-madera.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/drivers/gpio/gpio-madera.c b/drivers/gpio/gpio-madera.c index 8f38303fcbc4..e73e72d62bc8 100644 --- a/drivers/gpio/gpio-madera.c +++ b/drivers/gpio/gpio-madera.c @@ -87,23 +87,17 @@ static int madera_gpio_direction_out(struct gpio_chip *chip, MADERA_GP1_LVL_MASK, reg_val); } -static void madera_gpio_set(struct gpio_chip *chip, unsigned int offset, - int value) +static int madera_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) { struct madera_gpio *madera_gpio = gpiochip_get_data(chip); struct madera *madera = madera_gpio->madera; unsigned int reg_offset = 2 * offset; unsigned int reg_val = value ? MADERA_GP1_LVL : 0; - int ret; - ret = regmap_update_bits(madera->regmap, - MADERA_GPIO1_CTRL_1 + reg_offset, - MADERA_GP1_LVL_MASK, reg_val); - - /* set() doesn't return an error so log a warning */ - if (ret) - dev_warn(madera->dev, "Failed to write to 0x%x (%d)\n", - MADERA_GPIO1_CTRL_1 + reg_offset, ret); + return regmap_update_bits(madera->regmap, + MADERA_GPIO1_CTRL_1 + reg_offset, + MADERA_GP1_LVL_MASK, reg_val); } static const struct gpio_chip madera_gpio_chip = { @@ -115,7 +109,7 @@ static const struct gpio_chip madera_gpio_chip = { .direction_input = madera_gpio_direction_in, .get = madera_gpio_get, .direction_output = madera_gpio_direction_out, - .set = madera_gpio_set, + .set_rv = madera_gpio_set, .set_config = gpiochip_generic_config, .can_sleep = true, }; From 75e80b69e1367b95a98107fc38351b5b865c568c Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 6 May 2025 11:01:49 +0200 Subject: [PATCH 089/123] gpio: max3191x: remove unused callbacks This driver is input-only and as such doesn't need to define empty set() and direction_output() callbacks. Remove them. Link: https://lore.kernel.org/r/20250506-gpiochip-set-rv-gpio-part3-v1-6-0fbdea5a9667@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-max3191x.c | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/drivers/gpio/gpio-max3191x.c b/drivers/gpio/gpio-max3191x.c index fc0708ab5192..6e6504ab740a 100644 --- a/drivers/gpio/gpio-max3191x.c +++ b/drivers/gpio/gpio-max3191x.c @@ -103,19 +103,6 @@ static int max3191x_direction_input(struct gpio_chip *gpio, unsigned int offset) return 0; } -static int max3191x_direction_output(struct gpio_chip *gpio, - unsigned int offset, int value) -{ - return -EINVAL; -} - -static void max3191x_set(struct gpio_chip *gpio, unsigned int offset, int value) -{ } - -static void max3191x_set_multiple(struct gpio_chip *gpio, unsigned long *mask, - unsigned long *bits) -{ } - static unsigned int max3191x_wordlen(struct max3191x_chip *max3191x) { return max3191x->mode == STATUS_BYTE_ENABLED ? 2 : 1; @@ -421,9 +408,6 @@ static int max3191x_probe(struct spi_device *spi) max3191x->gpio.get_direction = max3191x_get_direction; max3191x->gpio.direction_input = max3191x_direction_input; - max3191x->gpio.direction_output = max3191x_direction_output; - max3191x->gpio.set = max3191x_set; - max3191x->gpio.set_multiple = max3191x_set_multiple; max3191x->gpio.get = max3191x_get; max3191x->gpio.get_multiple = max3191x_get_multiple; max3191x->gpio.set_config = max3191x_set_config; From 1938913798082cd284ab59afcdf68c2bb5ba1686 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 6 May 2025 11:01:50 +0200 Subject: [PATCH 090/123] gpio: max730x: use new GPIO line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Link: https://lore.kernel.org/r/20250506-gpiochip-set-rv-gpio-part3-v1-7-0fbdea5a9667@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-max730x.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpio-max730x.c b/drivers/gpio/gpio-max730x.c index e688c13c8cc3..75d414d8c992 100644 --- a/drivers/gpio/gpio-max730x.c +++ b/drivers/gpio/gpio-max730x.c @@ -143,18 +143,21 @@ static int max7301_get(struct gpio_chip *chip, unsigned offset) return level; } -static void max7301_set(struct gpio_chip *chip, unsigned offset, int value) +static int max7301_set(struct gpio_chip *chip, unsigned int offset, int value) { struct max7301 *ts = gpiochip_get_data(chip); + int ret; /* First 4 pins are unused in the controller */ offset += 4; mutex_lock(&ts->lock); - __max7301_set(ts, offset, value); + ret = __max7301_set(ts, offset, value); mutex_unlock(&ts->lock); + + return ret; } int __max730x_probe(struct max7301 *ts) @@ -185,7 +188,7 @@ int __max730x_probe(struct max7301 *ts) ts->chip.direction_input = max7301_direction_input; ts->chip.get = max7301_get; ts->chip.direction_output = max7301_direction_output; - ts->chip.set = max7301_set; + ts->chip.set_rv = max7301_set; ts->chip.ngpio = PIN_NUMBER; ts->chip.can_sleep = true; From 66a6d0e5a77889cbec98152dadda70dd79f1678c Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 6 May 2025 11:01:51 +0200 Subject: [PATCH 091/123] gpio: max732x: use new GPIO line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Link: https://lore.kernel.org/r/20250506-gpiochip-set-rv-gpio-part3-v1-8-0fbdea5a9667@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-max732x.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/gpio/gpio-max732x.c b/drivers/gpio/gpio-max732x.c index 49d362907bc7..d5ffedb086af 100644 --- a/drivers/gpio/gpio-max732x.c +++ b/drivers/gpio/gpio-max732x.c @@ -225,16 +225,19 @@ static void max732x_gpio_set_mask(struct gpio_chip *gc, unsigned off, int mask, mutex_unlock(&chip->lock); } -static void max732x_gpio_set_value(struct gpio_chip *gc, unsigned off, int val) +static int max732x_gpio_set_value(struct gpio_chip *gc, unsigned int off, + int val) { unsigned base = off & ~0x7; uint8_t mask = 1u << (off & 0x7); max732x_gpio_set_mask(gc, base, mask, val << (off & 0x7)); + + return 0; } -static void max732x_gpio_set_multiple(struct gpio_chip *gc, - unsigned long *mask, unsigned long *bits) +static int max732x_gpio_set_multiple(struct gpio_chip *gc, + unsigned long *mask, unsigned long *bits) { unsigned mask_lo = mask[0] & 0xff; unsigned mask_hi = (mask[0] >> 8) & 0xff; @@ -243,6 +246,8 @@ static void max732x_gpio_set_multiple(struct gpio_chip *gc, max732x_gpio_set_mask(gc, 0, mask_lo, bits[0] & 0xff); if (mask_hi) max732x_gpio_set_mask(gc, 8, mask_hi, (bits[0] >> 8) & 0xff); + + return 0; } static int max732x_gpio_direction_input(struct gpio_chip *gc, unsigned off) @@ -580,8 +585,8 @@ static int max732x_setup_gpio(struct max732x_chip *chip, gc->direction_input = max732x_gpio_direction_input; if (chip->dir_output) { gc->direction_output = max732x_gpio_direction_output; - gc->set = max732x_gpio_set_value; - gc->set_multiple = max732x_gpio_set_multiple; + gc->set_rv = max732x_gpio_set_value; + gc->set_multiple_rv = max732x_gpio_set_multiple; } gc->get = max732x_gpio_get_value; gc->can_sleep = true; From 8f9da4401b82faa788b663402ea699ccfd756597 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 6 May 2025 11:01:52 +0200 Subject: [PATCH 092/123] gpio: max77620: use new GPIO line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Link: https://lore.kernel.org/r/20250506-gpiochip-set-rv-gpio-part3-v1-9-0fbdea5a9667@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-max77620.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/gpio/gpio-max77620.c b/drivers/gpio/gpio-max77620.c index 8c2a5609161f..af7af8e40afe 100644 --- a/drivers/gpio/gpio-max77620.c +++ b/drivers/gpio/gpio-max77620.c @@ -223,20 +223,17 @@ static int max77620_gpio_set_debounce(struct max77620_gpio *mgpio, return ret; } -static void max77620_gpio_set(struct gpio_chip *gc, unsigned int offset, - int value) +static int max77620_gpio_set(struct gpio_chip *gc, unsigned int offset, + int value) { struct max77620_gpio *mgpio = gpiochip_get_data(gc); u8 val; - int ret; val = (value) ? MAX77620_CNFG_GPIO_OUTPUT_VAL_HIGH : MAX77620_CNFG_GPIO_OUTPUT_VAL_LOW; - ret = regmap_update_bits(mgpio->rmap, GPIO_REG_ADDR(offset), - MAX77620_CNFG_GPIO_OUTPUT_VAL_MASK, val); - if (ret < 0) - dev_err(mgpio->dev, "CNFG_GPIO_OUT update failed: %d\n", ret); + return regmap_update_bits(mgpio->rmap, GPIO_REG_ADDR(offset), + MAX77620_CNFG_GPIO_OUTPUT_VAL_MASK, val); } static int max77620_gpio_set_config(struct gpio_chip *gc, unsigned int offset, @@ -314,7 +311,7 @@ static int max77620_gpio_probe(struct platform_device *pdev) mgpio->gpio_chip.direction_input = max77620_gpio_dir_input; mgpio->gpio_chip.get = max77620_gpio_get; mgpio->gpio_chip.direction_output = max77620_gpio_dir_output; - mgpio->gpio_chip.set = max77620_gpio_set; + mgpio->gpio_chip.set_rv = max77620_gpio_set; mgpio->gpio_chip.set_config = max77620_gpio_set_config; mgpio->gpio_chip.ngpio = MAX77620_GPIO_NR; mgpio->gpio_chip.can_sleep = 1; From dff5a31dcbd260eb61f0128876910809f04f1e3b Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 6 May 2025 11:01:53 +0200 Subject: [PATCH 093/123] gpio: mb86s7x: use new GPIO line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Link: https://lore.kernel.org/r/20250506-gpiochip-set-rv-gpio-part3-v1-10-0fbdea5a9667@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mb86s7x.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-mb86s7x.c b/drivers/gpio/gpio-mb86s7x.c index 7ee891ef6905..5ee2991ecdfd 100644 --- a/drivers/gpio/gpio-mb86s7x.c +++ b/drivers/gpio/gpio-mb86s7x.c @@ -119,7 +119,7 @@ static int mb86s70_gpio_get(struct gpio_chip *gc, unsigned gpio) return !!(readl(gchip->base + PDR(gpio)) & OFFSET(gpio)); } -static void mb86s70_gpio_set(struct gpio_chip *gc, unsigned gpio, int value) +static int mb86s70_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value) { struct mb86s70_gpio_chip *gchip = gpiochip_get_data(gc); unsigned long flags; @@ -135,6 +135,8 @@ static void mb86s70_gpio_set(struct gpio_chip *gc, unsigned gpio, int value) writel(val, gchip->base + PDR(gpio)); spin_unlock_irqrestore(&gchip->lock, flags); + + return 0; } static int mb86s70_gpio_to_irq(struct gpio_chip *gc, unsigned int offset) @@ -178,7 +180,7 @@ static int mb86s70_gpio_probe(struct platform_device *pdev) gchip->gc.request = mb86s70_gpio_request; gchip->gc.free = mb86s70_gpio_free; gchip->gc.get = mb86s70_gpio_get; - gchip->gc.set = mb86s70_gpio_set; + gchip->gc.set_rv = mb86s70_gpio_set; gchip->gc.to_irq = mb86s70_gpio_to_irq; gchip->gc.label = dev_name(&pdev->dev); gchip->gc.ngpio = 32; From 51227589e8388b452b4315907d2de5f7d5a06e87 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 6 May 2025 11:01:54 +0200 Subject: [PATCH 094/123] gpio: mc33880: use new GPIO line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Link: https://lore.kernel.org/r/20250506-gpiochip-set-rv-gpio-part3-v1-11-0fbdea5a9667@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mc33880.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpio-mc33880.c b/drivers/gpio/gpio-mc33880.c index 5fb357d7b78a..e68956104161 100644 --- a/drivers/gpio/gpio-mc33880.c +++ b/drivers/gpio/gpio-mc33880.c @@ -57,15 +57,18 @@ static int __mc33880_set(struct mc33880 *mc, unsigned offset, int value) } -static void mc33880_set(struct gpio_chip *chip, unsigned offset, int value) +static int mc33880_set(struct gpio_chip *chip, unsigned int offset, int value) { struct mc33880 *mc = gpiochip_get_data(chip); + int ret; mutex_lock(&mc->lock); - __mc33880_set(mc, offset, value); + ret = __mc33880_set(mc, offset, value); mutex_unlock(&mc->lock); + + return ret; } static int mc33880_probe(struct spi_device *spi) @@ -100,7 +103,7 @@ static int mc33880_probe(struct spi_device *spi) mc->spi = spi; mc->chip.label = DRIVER_NAME; - mc->chip.set = mc33880_set; + mc->chip.set_rv = mc33880_set; mc->chip.base = pdata->base; mc->chip.ngpio = PIN_NUMBER; mc->chip.can_sleep = true; From d3f960365b8c35449d22b780383dc9b40d96203e Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 6 May 2025 11:01:55 +0200 Subject: [PATCH 095/123] gpio: ml-ioh: use new GPIO line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Link: https://lore.kernel.org/r/20250506-gpiochip-set-rv-gpio-part3-v1-12-0fbdea5a9667@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-ml-ioh.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-ml-ioh.c b/drivers/gpio/gpio-ml-ioh.c index 48e3768a830e..12cf36f9ca63 100644 --- a/drivers/gpio/gpio-ml-ioh.c +++ b/drivers/gpio/gpio-ml-ioh.c @@ -89,7 +89,7 @@ struct ioh_gpio { static const int num_ports[] = {6, 12, 16, 16, 15, 16, 16, 12}; -static void ioh_gpio_set(struct gpio_chip *gpio, unsigned nr, int val) +static int ioh_gpio_set(struct gpio_chip *gpio, unsigned int nr, int val) { u32 reg_val; struct ioh_gpio *chip = gpiochip_get_data(gpio); @@ -104,6 +104,8 @@ static void ioh_gpio_set(struct gpio_chip *gpio, unsigned nr, int val) iowrite32(reg_val, &chip->reg->regs[chip->ch].po); spin_unlock_irqrestore(&chip->spinlock, flags); + + return 0; } static int ioh_gpio_get(struct gpio_chip *gpio, unsigned nr) @@ -222,7 +224,7 @@ static void ioh_gpio_setup(struct ioh_gpio *chip, int num_port) gpio->direction_input = ioh_gpio_direction_input; gpio->get = ioh_gpio_get; gpio->direction_output = ioh_gpio_direction_output; - gpio->set = ioh_gpio_set; + gpio->set_rv = ioh_gpio_set; gpio->dbg_show = NULL; gpio->base = -1; gpio->ngpio = num_port; From 1275c70c4c1ca764cbf9ced01fb4286e4f49747e Mon Sep 17 00:00:00 2001 From: Aaron Kling Date: Tue, 29 Apr 2025 16:33:31 -0500 Subject: [PATCH 096/123] dt-bindings: gpio: tegra186: Add gpio-ranges Add optional gpio-ranges property. Signed-off-by: Aaron Kling Reviewed-by: Linus Walleij Acked-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20250429-tegra186-pinctrl-v1-2-722c7c42394e@gmail.com Signed-off-by: Bartosz Golaszewski --- .../devicetree/bindings/gpio/nvidia,tegra186-gpio.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/gpio/nvidia,tegra186-gpio.yaml b/Documentation/devicetree/bindings/gpio/nvidia,tegra186-gpio.yaml index 4ef06b2ff1ff..065f5761a93f 100644 --- a/Documentation/devicetree/bindings/gpio/nvidia,tegra186-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/nvidia,tegra186-gpio.yaml @@ -111,6 +111,9 @@ properties: gpio-controller: true + gpio-ranges: + maxItems: 1 + "#gpio-cells": description: | Indicates how many cells are used in a consumer's GPIO specifier. In the From 0a45c1a002705c8622738fbd2a2913acf886ea27 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Wed, 7 May 2025 15:07:25 +0200 Subject: [PATCH 097/123] gpio: mxc: configure dynamic GPIO base for CONFIG_GPIO_SYSFS=n i.MX GPIO numbering has been deterministic since commit 7e6086d9e54a ("gpio/mxc: specify gpio base for device tree probe"), a year after device tree support was first added back in 2011. Reverting to dynamically allocated GPIO base now would break most systems making use of the sysfs API. These systems will be eventually broken by the removal of the sysfs API, but that would result in GPIO scripts not working instead of essentially toggling at random according to probe order, which would happen if we unconditionally set base to -1. Yet, the warning is annoying and has resulted in many rejected attempts to remove it over the years[1][2][3]. As the i.MX GPIO driver is device tree only, GPIO_SYSFS is the only consumer of the deterministic GPIO numbering. Let's therefore restrict the static base and the warning that comes with it to configurations with CONFIG_GPIO_SYSFS enabled. [1]: https://lore.kernel.org/all/20230226205319.1013332-1-dario.binacchi@amarulasolutions.com/ [2]: https://lore.kernel.org/all/20230506085928.933737-2-haibo.chen@nxp.com/ [3]: https://lore.kernel.org/all/20241121145515.3087855-1-catalin.popescu@leica-geosystems.com/ Signed-off-by: Ahmad Fatoum Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250507-b4-imx-gpio-base-warning-v2-1-d43d09e2c6bf@pengutronix.de Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mxc.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-mxc.c b/drivers/gpio/gpio-mxc.c index 619b6fb9d833..38504c9486b0 100644 --- a/drivers/gpio/gpio-mxc.c +++ b/drivers/gpio/gpio-mxc.c @@ -490,7 +490,14 @@ static int mxc_gpio_probe(struct platform_device *pdev) port->gc.request = mxc_gpio_request; port->gc.free = mxc_gpio_free; port->gc.to_irq = mxc_gpio_to_irq; - port->gc.base = of_alias_get_id(np, "gpio") * 32; + /* + * Driver is DT-only, so a fixed base needs only be maintained for legacy + * userspace with sysfs interface. + */ + if (IS_ENABLED(CONFIG_GPIO_SYSFS)) + port->gc.base = of_alias_get_id(np, "gpio") * 32; + else /* silence boot time warning */ + port->gc.base = -1; err = devm_gpiochip_add_data(&pdev->dev, &port->gc, port); if (err) From 833c086f22ecebe576af42051733796d1690dd71 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Wed, 7 May 2025 19:28:01 +0200 Subject: [PATCH 098/123] gpio: TODO: add item about GPIO drivers reading struct gpio_chip::base drivers/pinctrl/pinctrl-at91.c uses struct gpio_chip::base to find out which bit to set in a register: dev_dbg(npct->dev, "enable pin %u as GPIO\n", offset); mask = 1 << (offset - chip->base); This adds a non-obvious dependency on the global numberspace from the GPIO driver itself, even if all consumers use the descriptor API. More such instances may exist and will need to be fixed in the quest for removal of the global numberspace, so add a reminder about it to the TODO list. Link: https://lore.kernel.org/all/1d00c056-3d61-4c22-bedd-3bae0bf1ddc4@pengutronix.de/ Suggested-by: Bartosz Golaszewski Signed-off-by: Ahmad Fatoum Link: https://lore.kernel.org/r/20250507-gpio-chip-base-readback-v1-1-ade56e38480b@pengutronix.de Signed-off-by: Bartosz Golaszewski --- drivers/gpio/TODO | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpio/TODO b/drivers/gpio/TODO index b5f0a7a2e1bf..c1f4c63a7605 100644 --- a/drivers/gpio/TODO +++ b/drivers/gpio/TODO @@ -44,6 +44,13 @@ Work items: to a machine description such as device tree, ACPI or fwnode that implicitly does not use global GPIO numbers. +- Fix drivers to not read back struct gpio_chip::base. Some drivers do + that and would be broken by attempts to poison it or make it dynamic. + Example in AT91 pinctrl driver: + https://lore.kernel.org/all/1d00c056-3d61-4c22-bedd-3bae0bf1ddc4@pengutronix.de/ + This particular driver is also DT-only, so with the above fixed, the + base can be made dynamic (set to -1) if CONFIG_GPIO_SYSFS is disabled. + - When this work is complete (will require some of the items in the following ongoing work as well) we can delete the old global numberspace accessors from and eventually delete From 7b04f98027afd2cc329d00191dfc8284b382565d Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 9 May 2025 12:45:32 +0800 Subject: [PATCH 099/123] gpio: bcm-kona: make irq_chip immutable The gpiolib is moving to make irq_chip immutable, otherwise there is warning: "not an immutable chip, please consider fixing it!" The bcm_gpio_irq_chip already has irq hooks configured correctly, bcm_kona_gpio_irq_mask/bcm_kona_gpio_irq_unmask calls gpiochip_disable_irq/ gpiochip_enable_irq, and bcm_kona_gpio_irq_reqres/irq_release_resources calls gpiochip_reqres_irq/gpiochip_relres_irq. So just need to flag it as IRQCHIP_IMMUTABLE. Signed-off-by: Peng Fan Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250509-gpio-v1-1-639377c98288@nxp.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-bcm-kona.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpio/gpio-bcm-kona.c b/drivers/gpio/gpio-bcm-kona.c index 17c287dc7471..8f22cb36004d 100644 --- a/drivers/gpio/gpio-bcm-kona.c +++ b/drivers/gpio/gpio-bcm-kona.c @@ -516,6 +516,7 @@ static struct irq_chip bcm_gpio_irq_chip = { .irq_set_type = bcm_kona_gpio_irq_set_type, .irq_request_resources = bcm_kona_gpio_irq_reqres, .irq_release_resources = bcm_kona_gpio_irq_relres, + .flags = IRQCHIP_IMMUTABLE, }; static struct of_device_id const bcm_kona_gpio_of_match[] = { From a30be40bf1d4437646b6885e7e4e71530e6f82c1 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 9 May 2025 12:45:33 +0800 Subject: [PATCH 100/123] gpio: grgpio: Make irq_chip immutable Kernel warns about mutable irq_chips: "not an immutable chip, please consider fixing!" Constify grgpio_irq_chip, flag the irq_chip as IRQCHIP_IMMUTABLE, add the new helper functions, and call the appropriate gpiolib functions. Signed-off-by: Peng Fan Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250509-gpio-v1-2-639377c98288@nxp.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-grgpio.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-grgpio.c b/drivers/gpio/gpio-grgpio.c index 30a0522ae735..68cca7751c0e 100644 --- a/drivers/gpio/gpio-grgpio.c +++ b/drivers/gpio/gpio-grgpio.c @@ -170,6 +170,8 @@ static void grgpio_irq_mask(struct irq_data *d) grgpio_set_imask(priv, offset, 0); raw_spin_unlock_irqrestore(&priv->gc.bgpio_lock, flags); + + gpiochip_disable_irq(&priv->gc, d->hwirq); } static void grgpio_irq_unmask(struct irq_data *d) @@ -178,6 +180,7 @@ static void grgpio_irq_unmask(struct irq_data *d) int offset = d->hwirq; unsigned long flags; + gpiochip_enable_irq(&priv->gc, d->hwirq); raw_spin_lock_irqsave(&priv->gc.bgpio_lock, flags); grgpio_set_imask(priv, offset, 1); @@ -185,11 +188,13 @@ static void grgpio_irq_unmask(struct irq_data *d) raw_spin_unlock_irqrestore(&priv->gc.bgpio_lock, flags); } -static struct irq_chip grgpio_irq_chip = { +static const struct irq_chip grgpio_irq_chip = { .name = "grgpio", .irq_mask = grgpio_irq_mask, .irq_unmask = grgpio_irq_unmask, .irq_set_type = grgpio_irq_set_type, + .flags = IRQCHIP_IMMUTABLE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, }; static irqreturn_t grgpio_irq_handler(int irq, void *dev) From 289e42df1358abf85e49a140f7224c2abd170b2e Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 9 May 2025 12:45:34 +0800 Subject: [PATCH 101/123] gpio: lpc18xx: Make irq_chip immutable Kernel warns about mutable irq_chips: "not an immutable chip, please consider fixing!" Constify lpc18xx_gpio_pin_ic, flag the irq_chip as IRQCHIP_IMMUTABLE, add the new helper functions, and call the appropriate gpiolib functions. Signed-off-by: Peng Fan Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250509-gpio-v1-3-639377c98288@nxp.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-lpc18xx.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-lpc18xx.c b/drivers/gpio/gpio-lpc18xx.c index 8bb8a87ec1e9..fffc13da673e 100644 --- a/drivers/gpio/gpio-lpc18xx.c +++ b/drivers/gpio/gpio-lpc18xx.c @@ -42,6 +42,7 @@ struct lpc18xx_gpio_pin_ic { void __iomem *base; struct irq_domain *domain; struct raw_spinlock lock; + struct gpio_chip *gpio; }; struct lpc18xx_gpio_chip { @@ -74,6 +75,7 @@ static void lpc18xx_gpio_pin_ic_mask(struct irq_data *d) { struct lpc18xx_gpio_pin_ic *ic = d->chip_data; u32 type = irqd_get_trigger_type(d); + irq_hw_number_t hwirq = irqd_to_hwirq(d); raw_spin_lock(&ic->lock); @@ -88,12 +90,17 @@ static void lpc18xx_gpio_pin_ic_mask(struct irq_data *d) raw_spin_unlock(&ic->lock); irq_chip_mask_parent(d); + + gpiochip_disable_irq(ic->gpio, hwirq); } static void lpc18xx_gpio_pin_ic_unmask(struct irq_data *d) { struct lpc18xx_gpio_pin_ic *ic = d->chip_data; u32 type = irqd_get_trigger_type(d); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + + gpiochip_enable_irq(ic->gpio, hwirq); raw_spin_lock(&ic->lock); @@ -149,13 +156,14 @@ static int lpc18xx_gpio_pin_ic_set_type(struct irq_data *d, unsigned int type) return 0; } -static struct irq_chip lpc18xx_gpio_pin_ic = { +static const struct irq_chip lpc18xx_gpio_pin_ic = { .name = "LPC18xx GPIO pin", .irq_mask = lpc18xx_gpio_pin_ic_mask, .irq_unmask = lpc18xx_gpio_pin_ic_unmask, .irq_eoi = lpc18xx_gpio_pin_ic_eoi, .irq_set_type = lpc18xx_gpio_pin_ic_set_type, - .flags = IRQCHIP_SET_TYPE_MASKED, + .flags = IRQCHIP_IMMUTABLE | IRQCHIP_SET_TYPE_MASKED, + GPIOCHIP_IRQ_RESOURCE_HELPERS, }; static int lpc18xx_gpio_pin_ic_domain_alloc(struct irq_domain *domain, @@ -251,6 +259,7 @@ static int lpc18xx_gpio_pin_ic_probe(struct lpc18xx_gpio_chip *gc) goto free_iomap; } + ic->gpio = &gc->gpio; gc->pin_ic = ic; return 0; From 7688a54d5b53f2e3160c00f19b00bc601fa3ec98 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 9 May 2025 12:45:35 +0800 Subject: [PATCH 102/123] gpio: mpc8xxx: Make irq_chip immutable Kernel warns about mutable irq_chips: "not an immutable chip, please consider fixing!" Flag the irq_chip as IRQCHIP_IMMUTABLE, add the new helper functions, and call the appropriate gpiolib functions. Signed-off-by: Peng Fan Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250509-gpio-v1-4-639377c98288@nxp.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mpc8xxx.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/gpio/gpio-mpc8xxx.c b/drivers/gpio/gpio-mpc8xxx.c index 0cd4c36ae8aa..805ac9607b56 100644 --- a/drivers/gpio/gpio-mpc8xxx.c +++ b/drivers/gpio/gpio-mpc8xxx.c @@ -123,9 +123,12 @@ static irqreturn_t mpc8xxx_gpio_irq_cascade(int irq, void *data) static void mpc8xxx_irq_unmask(struct irq_data *d) { struct mpc8xxx_gpio_chip *mpc8xxx_gc = irq_data_get_irq_chip_data(d); + irq_hw_number_t hwirq = irqd_to_hwirq(d); struct gpio_chip *gc = &mpc8xxx_gc->gc; unsigned long flags; + gpiochip_enable_irq(gc, hwirq); + raw_spin_lock_irqsave(&mpc8xxx_gc->lock, flags); gc->write_reg(mpc8xxx_gc->regs + GPIO_IMR, @@ -138,6 +141,7 @@ static void mpc8xxx_irq_unmask(struct irq_data *d) static void mpc8xxx_irq_mask(struct irq_data *d) { struct mpc8xxx_gpio_chip *mpc8xxx_gc = irq_data_get_irq_chip_data(d); + irq_hw_number_t hwirq = irqd_to_hwirq(d); struct gpio_chip *gc = &mpc8xxx_gc->gc; unsigned long flags; @@ -148,6 +152,8 @@ static void mpc8xxx_irq_mask(struct irq_data *d) & ~mpc_pin2mask(irqd_to_hwirq(d))); raw_spin_unlock_irqrestore(&mpc8xxx_gc->lock, flags); + + gpiochip_disable_irq(gc, hwirq); } static void mpc8xxx_irq_ack(struct irq_data *d) @@ -244,6 +250,8 @@ static struct irq_chip mpc8xxx_irq_chip = { .irq_ack = mpc8xxx_irq_ack, /* this might get overwritten in mpc8xxx_probe() */ .irq_set_type = mpc8xxx_irq_set_type, + .flags = IRQCHIP_IMMUTABLE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, }; static int mpc8xxx_gpio_irq_map(struct irq_domain *h, unsigned int irq, From 62be72bdb00ef52a0d0f02ce077e36b1f48ef0ae Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 9 May 2025 12:45:36 +0800 Subject: [PATCH 103/123] gpio: davinci: Update irq chip data Use "struct davinci_gpio_controller *chips" as irq chip data to prepare for immutable irq chip, then it will be easy to get gpio_chip pointer in irq mask/unmask. No functional change. Signed-off-by: Peng Fan Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250509-gpio-v1-5-639377c98288@nxp.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-davinci.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/drivers/gpio/gpio-davinci.c b/drivers/gpio/gpio-davinci.c index 63fc7888c1d4..227ba588c8c9 100644 --- a/drivers/gpio/gpio-davinci.c +++ b/drivers/gpio/gpio-davinci.c @@ -68,15 +68,6 @@ static inline u32 __gpio_mask(unsigned gpio) return 1 << (gpio % 32); } -static inline struct davinci_gpio_regs __iomem *irq2regs(struct irq_data *d) -{ - struct davinci_gpio_regs __iomem *g; - - g = (__force struct davinci_gpio_regs __iomem *)irq_data_get_irq_chip_data(d); - - return g; -} - static int davinci_gpio_irq_setup(struct platform_device *pdev); /*--------------------------------------------------------------------------*/ @@ -255,7 +246,9 @@ static int davinci_gpio_probe(struct platform_device *pdev) static void gpio_irq_mask(struct irq_data *d) { - struct davinci_gpio_regs __iomem *g = irq2regs(d); + struct davinci_gpio_controller *chips = irq_data_get_irq_chip_data(d); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + struct davinci_gpio_regs __iomem *g = chips->regs[hwirq / 32]; uintptr_t mask = (uintptr_t)irq_data_get_irq_handler_data(d); writel_relaxed(mask, &g->clr_falling); @@ -264,7 +257,9 @@ static void gpio_irq_mask(struct irq_data *d) static void gpio_irq_unmask(struct irq_data *d) { - struct davinci_gpio_regs __iomem *g = irq2regs(d); + struct davinci_gpio_controller *chips = irq_data_get_irq_chip_data(d); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + struct davinci_gpio_regs __iomem *g = chips->regs[hwirq / 32]; uintptr_t mask = (uintptr_t)irq_data_get_irq_handler_data(d); unsigned status = irqd_get_trigger_type(d); @@ -399,12 +394,11 @@ davinci_gpio_irq_map(struct irq_domain *d, unsigned int irq, { struct davinci_gpio_controller *chips = (struct davinci_gpio_controller *)d->host_data; - struct davinci_gpio_regs __iomem *g = chips->regs[hw / 32]; irq_set_chip_and_handler_name(irq, &gpio_irqchip, handle_simple_irq, "davinci_gpio"); irq_set_irq_type(irq, IRQ_TYPE_NONE); - irq_set_chip_data(irq, (__force void *)g); + irq_set_chip_data(irq, (__force void *)chips); irq_set_handler_data(irq, (void *)(uintptr_t)__gpio_mask(hw)); return 0; From 3f50bb3124d76653de0bcfe251faa357711e3ae6 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 9 May 2025 12:45:37 +0800 Subject: [PATCH 104/123] gpio: davinci: Make irq_chip immutable Kernel warns about mutable irq_chips: "not an immutable chip, please consider fixing!" Constify gpio_irqchip, flag the irq_chip as IRQCHIP_IMMUTABLE, add the new helper functions, and call the appropriate gpiolib functions. Signed-off-by: Peng Fan Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250509-gpio-v1-6-639377c98288@nxp.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-davinci.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-davinci.c b/drivers/gpio/gpio-davinci.c index 227ba588c8c9..bd1821ecb2ea 100644 --- a/drivers/gpio/gpio-davinci.c +++ b/drivers/gpio/gpio-davinci.c @@ -253,6 +253,8 @@ static void gpio_irq_mask(struct irq_data *d) writel_relaxed(mask, &g->clr_falling); writel_relaxed(mask, &g->clr_rising); + + gpiochip_disable_irq(&chips->chip, hwirq); } static void gpio_irq_unmask(struct irq_data *d) @@ -263,6 +265,8 @@ static void gpio_irq_unmask(struct irq_data *d) uintptr_t mask = (uintptr_t)irq_data_get_irq_handler_data(d); unsigned status = irqd_get_trigger_type(d); + gpiochip_enable_irq(&chips->chip, hwirq); + status &= IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING; if (!status) status = IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING; @@ -281,12 +285,13 @@ static int gpio_irq_type(struct irq_data *d, unsigned trigger) return 0; } -static struct irq_chip gpio_irqchip = { +static const struct irq_chip gpio_irqchip = { .name = "GPIO", .irq_unmask = gpio_irq_unmask, .irq_mask = gpio_irq_mask, .irq_set_type = gpio_irq_type, - .flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_SKIP_SET_WAKE, + .flags = IRQCHIP_IMMUTABLE | IRQCHIP_SET_TYPE_MASKED | IRQCHIP_SKIP_SET_WAKE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, }; static void gpio_irq_handler(struct irq_desc *desc) From 580b3264cb252cae00fa62d58443af09c25f7d61 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 9 May 2025 12:45:38 +0800 Subject: [PATCH 105/123] gpio: xgene-sb: Make irq_chip immutable Kernel warns about mutable irq_chips: "not an immutable chip, please consider fixing!" Constify xgene_gpio_sb_irq_chip, flag the irq_chip as IRQCHIP_IMMUTABLE, add the new helper functions, and call the appropriate gpiolib functions. Signed-off-by: Peng Fan Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250509-gpio-v1-7-639377c98288@nxp.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-xgene-sb.c | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpio-xgene-sb.c b/drivers/gpio/gpio-xgene-sb.c index 48b829733b15..b51b1fa726bb 100644 --- a/drivers/gpio/gpio-xgene-sb.c +++ b/drivers/gpio/gpio-xgene-sb.c @@ -103,12 +103,32 @@ static int xgene_gpio_sb_irq_set_type(struct irq_data *d, unsigned int type) return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH); } -static struct irq_chip xgene_gpio_sb_irq_chip = { +static void xgene_gpio_sb_irq_mask(struct irq_data *d) +{ + struct xgene_gpio_sb *priv = irq_data_get_irq_chip_data(d); + + irq_chip_mask_parent(d); + + gpiochip_disable_irq(&priv->gc, d->hwirq); +} + +static void xgene_gpio_sb_irq_unmask(struct irq_data *d) +{ + struct xgene_gpio_sb *priv = irq_data_get_irq_chip_data(d); + + gpiochip_enable_irq(&priv->gc, d->hwirq); + + irq_chip_unmask_parent(d); +} + +static const struct irq_chip xgene_gpio_sb_irq_chip = { .name = "sbgpio", .irq_eoi = irq_chip_eoi_parent, - .irq_mask = irq_chip_mask_parent, - .irq_unmask = irq_chip_unmask_parent, + .irq_mask = xgene_gpio_sb_irq_mask, + .irq_unmask = xgene_gpio_sb_irq_unmask, .irq_set_type = xgene_gpio_sb_irq_set_type, + .flags = IRQCHIP_IMMUTABLE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, }; static int xgene_gpio_sb_to_irq(struct gpio_chip *gc, u32 gpio) From 2993d2dd8ff4e8257a88d4d3f8ffc6df95928b94 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 9 May 2025 12:45:39 +0800 Subject: [PATCH 106/123] gpio: timberdale: Make irq_chip immutable Kernel warns about mutable irq_chips: "not an immutable chip, please consider fixing!" Constify timbgpio_irqchip, flag the irq_chip as IRQCHIP_IMMUTABLE, add the new helper functions, and call the appropriate gpiolib functions. Signed-off-by: Peng Fan Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250509-gpio-v1-8-639377c98288@nxp.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-timberdale.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-timberdale.c b/drivers/gpio/gpio-timberdale.c index fad979797486..cb303a26f4d3 100644 --- a/drivers/gpio/gpio-timberdale.c +++ b/drivers/gpio/gpio-timberdale.c @@ -103,20 +103,26 @@ static void timbgpio_irq_disable(struct irq_data *d) { struct timbgpio *tgpio = irq_data_get_irq_chip_data(d); int offset = d->irq - tgpio->irq_base; + irq_hw_number_t hwirq = irqd_to_hwirq(d); unsigned long flags; spin_lock_irqsave(&tgpio->lock, flags); tgpio->last_ier &= ~(1UL << offset); iowrite32(tgpio->last_ier, tgpio->membase + TGPIO_IER); spin_unlock_irqrestore(&tgpio->lock, flags); + + gpiochip_disable_irq(&tgpio->gpio, hwirq); } static void timbgpio_irq_enable(struct irq_data *d) { struct timbgpio *tgpio = irq_data_get_irq_chip_data(d); int offset = d->irq - tgpio->irq_base; + irq_hw_number_t hwirq = irqd_to_hwirq(d); unsigned long flags; + gpiochip_enable_irq(&tgpio->gpio, hwirq); + spin_lock_irqsave(&tgpio->lock, flags); tgpio->last_ier |= 1UL << offset; iowrite32(tgpio->last_ier, tgpio->membase + TGPIO_IER); @@ -205,11 +211,13 @@ static void timbgpio_irq(struct irq_desc *desc) iowrite32(tgpio->last_ier, tgpio->membase + TGPIO_IER); } -static struct irq_chip timbgpio_irqchip = { +static const struct irq_chip timbgpio_irqchip = { .name = "GPIO", .irq_enable = timbgpio_irq_enable, .irq_disable = timbgpio_irq_disable, .irq_set_type = timbgpio_irq_type, + .flags = IRQCHIP_IMMUTABLE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, }; static int timbgpio_probe(struct platform_device *pdev) From 20117cf426b677e7aced4e7a1b2b37f6080a46dc Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 9 May 2025 12:45:40 +0800 Subject: [PATCH 107/123] gpio: pxa: Make irq_chip immutable Kernel warns about mutable irq_chips: "not an immutable chip, please consider fixing!" Constify pxa_muxed_gpio_chip, flag the irq_chip as IRQCHIP_IMMUTABLE, add the new helper functions, and call the appropriate gpiolib functions. Signed-off-by: Peng Fan Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250509-gpio-v1-9-639377c98288@nxp.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-pxa.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-pxa.c b/drivers/gpio/gpio-pxa.c index 91cea97255fa..530ddeaba2f1 100644 --- a/drivers/gpio/gpio-pxa.c +++ b/drivers/gpio/gpio-pxa.c @@ -497,6 +497,8 @@ static void pxa_mask_muxed_gpio(struct irq_data *d) gfer = readl_relaxed(base + GFER_OFFSET) & ~GPIO_bit(gpio); writel_relaxed(grer, base + GRER_OFFSET); writel_relaxed(gfer, base + GFER_OFFSET); + + gpiochip_disable_irq(&pchip->chip, gpio); } static int pxa_gpio_set_wake(struct irq_data *d, unsigned int on) @@ -516,17 +518,21 @@ static void pxa_unmask_muxed_gpio(struct irq_data *d) unsigned int gpio = irqd_to_hwirq(d); struct pxa_gpio_bank *c = gpio_to_pxabank(&pchip->chip, gpio); + gpiochip_enable_irq(&pchip->chip, gpio); + c->irq_mask |= GPIO_bit(gpio); update_edge_detect(c); } -static struct irq_chip pxa_muxed_gpio_chip = { +static const struct irq_chip pxa_muxed_gpio_chip = { .name = "GPIO", .irq_ack = pxa_ack_muxed_gpio, .irq_mask = pxa_mask_muxed_gpio, .irq_unmask = pxa_unmask_muxed_gpio, .irq_set_type = pxa_gpio_irq_type, .irq_set_wake = pxa_gpio_set_wake, + .flags = IRQCHIP_IMMUTABLE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, }; static int pxa_gpio_nums(struct platform_device *pdev) From e2d9a7ead8ffbf562c4f38d3115f98d1933e360a Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 19 May 2025 13:10:41 +0200 Subject: [PATCH 108/123] gpio: pxa: select GPIOLIB_IRQCHIP This driver uses gpiochip_irq_reqres() and gpiochip_irq_relres() which are only built with GPIOLIB_IRQCHIP=y. Add the missing Kconfig select. Fixes: 20117cf426b6 ("gpio: pxa: Make irq_chip immutable") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202505181429.mzyIatOU-lkp@intel.com/ Acked-by: Peng Fan Link: https://lore.kernel.org/r/20250519-gpio-irq-kconfig-fixes-v1-1-fe6ba1c6116d@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index bbc71cdde9ed..dbf503d82286 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -566,6 +566,7 @@ config GPIO_POLARFIRE_SOC config GPIO_PXA bool "PXA GPIO support" depends on ARCH_PXA || ARCH_MMP || COMPILE_TEST + select GPIOLIB_IRQCHIP help Say yes here to support the PXA GPIO device. From 8da238b15cf5eb8a8dd318bd47e4d6050f5ca5d0 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 19 May 2025 13:10:42 +0200 Subject: [PATCH 109/123] gpio: mpc8xxx: select GPIOLIB_IRQCHIP This driver uses gpiochip_irq_reqres() and gpiochip_irq_relres() which are only built with GPIOLIB_IRQCHIP=y. Add the missing Kconfig select. Fixes: 7688a54d5b53 ("gpio: mpc8xxx: Make irq_chip immutable") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202505180309.1nosQMkI-lkp@intel.com/ Acked-by: Peng Fan Link: https://lore.kernel.org/r/20250519-gpio-irq-kconfig-fixes-v1-2-fe6ba1c6116d@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index dbf503d82286..a559d5bb8249 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -479,7 +479,7 @@ config GPIO_MPC8XXX FSL_SOC_BOOKE || PPC_86xx || ARCH_LAYERSCAPE || ARM || \ COMPILE_TEST select GPIO_GENERIC - select IRQ_DOMAIN + select GPIOLIB_IRQCHIP help Say Y here if you're going to use hardware that connects to the MPC512x/831x/834x/837x/8572/8610/QorIQ GPIOs. From 274fd5fe911956cdebeeed65981b7182d57c2773 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 19 May 2025 13:10:43 +0200 Subject: [PATCH 110/123] gpiolib: remove unneeded #ifdef We are already within another `#ifdef CONFIG_GPIOLIB_IRQCHIP` in gpiochip_to_irq() so there's no need for another guard. Remove it. Acked-by: Peng Fan Link: https://lore.kernel.org/r/20250519-gpio-irq-kconfig-fixes-v1-3-fe6ba1c6116d@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 87903847c3d7..a64647c9db75 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -1888,7 +1888,6 @@ static int gpiochip_to_irq(struct gpio_chip *gc, unsigned int offset) { struct irq_domain *domain = gc->irq.domain; -#ifdef CONFIG_GPIOLIB_IRQCHIP /* * Avoid race condition with other code, which tries to lookup * an IRQ before the irqchip has been properly registered, @@ -1896,7 +1895,6 @@ static int gpiochip_to_irq(struct gpio_chip *gc, unsigned int offset) */ if (!gc->irq.initialized) return -EPROBE_DEFER; -#endif if (!gpiochip_irqchip_irq_valid(gc, offset)) return -ENXIO; From f89a915e68de2327d6fd9e69c2aae730d6fe51c6 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 9 Apr 2025 16:27:53 +0300 Subject: [PATCH 111/123] gpiolib: acpi: Use temporary variable for struct acpi_gpio_info Use temporary variable to access the struct acpi_gpio_info members. This will help further changes to be cleaner. No functional change intended. Acked-by: Mika Westerberg Signed-off-by: Andy Shevchenko --- drivers/gpio/gpiolib-acpi.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c index b3fcb9d5a39f..dfa4f9a5a5e6 100644 --- a/drivers/gpio/gpiolib-acpi.c +++ b/drivers/gpio/gpiolib-acpi.c @@ -753,6 +753,7 @@ static int acpi_populate_gpio_lookup(struct acpi_resource *ares, void *data) { struct acpi_gpio_lookup *lookup = data; struct acpi_gpio_params *params = &lookup->params; + struct acpi_gpio_info *info = &lookup->info; if (ares->type != ACPI_RESOURCE_TYPE_GPIO) return 1; @@ -763,7 +764,7 @@ static int acpi_populate_gpio_lookup(struct acpi_resource *ares, void *data) struct gpio_desc *desc; u16 pin_index; - if (lookup->info.quirks & ACPI_GPIO_QUIRK_ONLY_GPIOIO && gpioint) + if (info->quirks & ACPI_GPIO_QUIRK_ONLY_GPIOIO && gpioint) params->crs_entry_index++; if (lookup->n++ != params->crs_entry_index) @@ -773,16 +774,16 @@ static int acpi_populate_gpio_lookup(struct acpi_resource *ares, void *data) if (pin_index >= agpio->pin_table_length) return 1; - if (lookup->info.quirks & ACPI_GPIO_QUIRK_ABSOLUTE_NUMBER) + if (info->quirks & ACPI_GPIO_QUIRK_ABSOLUTE_NUMBER) desc = gpio_to_desc(agpio->pin_table[pin_index]); else desc = acpi_get_gpiod(agpio->resource_source.string_ptr, agpio->pin_table[pin_index]); lookup->desc = desc; - lookup->info.pin_config = agpio->pin_config; - lookup->info.debounce = agpio->debounce_timeout; - lookup->info.gpioint = gpioint; - lookup->info.wake_capable = acpi_gpio_irq_is_wake(&lookup->info.adev->dev, agpio); + info->pin_config = agpio->pin_config; + info->debounce = agpio->debounce_timeout; + info->gpioint = gpioint; + info->wake_capable = acpi_gpio_irq_is_wake(&info->adev->dev, agpio); /* * Polarity and triggering are only specified for GpioInt @@ -791,14 +792,14 @@ static int acpi_populate_gpio_lookup(struct acpi_resource *ares, void *data) * - ACPI_ACTIVE_LOW == GPIO_ACTIVE_LOW * - ACPI_ACTIVE_HIGH == GPIO_ACTIVE_HIGH */ - if (lookup->info.gpioint) { - lookup->info.polarity = agpio->polarity; - lookup->info.triggering = agpio->triggering; + if (info->gpioint) { + info->polarity = agpio->polarity; + info->triggering = agpio->triggering; } else { - lookup->info.polarity = params->active_low; + info->polarity = params->active_low; } - lookup->info.flags = acpi_gpio_to_gpiod_flags(agpio, lookup->info.polarity); + info->flags = acpi_gpio_to_gpiod_flags(agpio, info->polarity); } return 1; @@ -806,7 +807,8 @@ static int acpi_populate_gpio_lookup(struct acpi_resource *ares, void *data) static int acpi_gpio_resource_lookup(struct acpi_gpio_lookup *lookup) { - struct acpi_device *adev = lookup->info.adev; + struct acpi_gpio_info *info = &lookup->info; + struct acpi_device *adev = info->adev; struct list_head res_list; int ret; @@ -831,6 +833,7 @@ static int acpi_gpio_property_lookup(struct fwnode_handle *fwnode, const char *p { struct fwnode_reference_args args; struct acpi_gpio_params *params = &lookup->params; + struct acpi_gpio_info *info = &lookup->info; unsigned int index = params->crs_entry_index; unsigned int quirks = 0; int ret; @@ -858,8 +861,8 @@ static int acpi_gpio_property_lookup(struct fwnode_handle *fwnode, const char *p params->line_index = args.args[1]; params->active_low = !!args.args[2]; - lookup->info.adev = to_acpi_device_node(args.fwnode); - lookup->info.quirks = quirks; + info->adev = to_acpi_device_node(args.fwnode); + info->quirks = quirks; return 0; } From b24fd5bc8e6d6b6006db65b5956c2c2cd0ee5a7b Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 13 May 2025 13:00:31 +0300 Subject: [PATCH 112/123] gpiolib: acpi: Switch to use enum in acpi_gpio_in_ignore_list() Switch to use enum instead of pointers in acpi_gpio_in_ignore_list() which moves towards isolating the GPIO ACPI and quirk APIs. It will helps splitting them completely in the next changes. No functional changes. Reviewed-by: Hans de Goede Acked-by: Mika Westerberg Signed-off-by: Andy Shevchenko --- drivers/gpio/gpiolib-acpi.c | 21 ++++++++++++++++----- drivers/gpio/gpiolib-acpi.h | 8 ++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c index 3c9535d767e7..fb573b5f0ba1 100644 --- a/drivers/gpio/gpiolib-acpi.c +++ b/drivers/gpio/gpiolib-acpi.c @@ -350,14 +350,25 @@ static struct gpio_desc *acpi_request_own_gpiod(struct gpio_chip *chip, return desc; } -static bool acpi_gpio_in_ignore_list(const char *ignore_list, const char *controller_in, - unsigned int pin_in) +bool acpi_gpio_in_ignore_list(enum acpi_gpio_ignore_list list, const char *controller_in, + unsigned int pin_in) { - const char *controller, *pin_str; + const char *ignore_list, *controller, *pin_str; unsigned int pin; char *endp; int len; + switch (list) { + case ACPI_GPIO_IGNORE_WAKE: + ignore_list = ignore_wake; + break; + case ACPI_GPIO_IGNORE_INTERRUPT: + ignore_list = ignore_interrupt; + break; + default: + return false; + } + controller = ignore_list; while (controller) { pin_str = strchr(controller, '@'); @@ -394,7 +405,7 @@ static bool acpi_gpio_irq_is_wake(struct device *parent, if (agpio->wake_capable != ACPI_WAKE_CAPABLE) return false; - if (acpi_gpio_in_ignore_list(ignore_wake, dev_name(parent), pin)) { + if (acpi_gpio_in_ignore_list(ACPI_GPIO_IGNORE_WAKE, dev_name(parent), pin)) { dev_info(parent, "Ignoring wakeup on pin %u\n", pin); return false; } @@ -437,7 +448,7 @@ static acpi_status acpi_gpiochip_alloc_event(struct acpi_resource *ares, if (!handler) return AE_OK; - if (acpi_gpio_in_ignore_list(ignore_interrupt, dev_name(chip->parent), pin)) { + if (acpi_gpio_in_ignore_list(ACPI_GPIO_IGNORE_INTERRUPT, dev_name(chip->parent), pin)) { dev_info(chip->parent, "Ignoring interrupt on pin %u\n", pin); return AE_OK; } diff --git a/drivers/gpio/gpiolib-acpi.h b/drivers/gpio/gpiolib-acpi.h index 7e1c51d04040..ef0b1a3c85d7 100644 --- a/drivers/gpio/gpiolib-acpi.h +++ b/drivers/gpio/gpiolib-acpi.h @@ -58,4 +58,12 @@ static inline int acpi_gpio_count(const struct fwnode_handle *fwnode, } #endif +enum acpi_gpio_ignore_list { + ACPI_GPIO_IGNORE_WAKE, + ACPI_GPIO_IGNORE_INTERRUPT, +}; + +bool acpi_gpio_in_ignore_list(enum acpi_gpio_ignore_list list, + const char *controller_in, unsigned int pin_in); + #endif /* GPIOLIB_ACPI_H */ From 7c010d463372140006bf96985a306d6cbfc6e118 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 9 Apr 2025 16:27:54 +0300 Subject: [PATCH 113/123] gpiolib: acpi: Make sure we fill struct acpi_gpio_info The previous refactoring missed the filling of the struct acpi_gpio_info and that's how the lot of the code got eliminated. Restore those pieces by passing the pointer all down in the call stack. With this, the code grows by ~6%, but in conjunction with the previous refactoring it still gives -387 bytes add/remove: 2/0 grow/shrink: 5/1 up/down: 852/-35 (817) Function old new delta acpi_dev_gpio_irq_wake_get_by 129 695 +566 acpi_find_gpio 216 354 +138 acpi_find_gpio.__UNIQUE_ID_ddebug504 - 56 +56 acpi_dev_gpio_irq_wake_get_by.__UNIQUE_ID_ddebug506 - 56 +56 acpi_populate_gpio_lookup 536 548 +12 acpi_gpio_property_lookup 414 426 +12 acpi_get_gpiod_by_index 307 319 +12 __acpi_find_gpio 638 603 -35 Total: Before=14154, After=14971, chg +5.77% As a positive side effect, it improves memory footprint for struct acpi_gpio_lookup. `pahole` difference before and after: - /* size: 64, cachelines: 1, members: 4 */ - /* member types with holes: 1, total: 1 */ + /* size: 32, cachelines: 1, members: 4 */ Reported-by: Kees Bakker Closes: https://lore.kernel.org/r/9715c8dd-38df-48fd-a9d1-7a78163dc989@ijzerbout.nl Fixes: 8b4f52ef7a41 ("gpiolib: acpi: Deduplicate some code in __acpi_find_gpio()") Acked-by: Mika Westerberg Signed-off-by: Andy Shevchenko --- drivers/gpio/gpiolib-acpi.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c index dfa4f9a5a5e6..3c9535d767e7 100644 --- a/drivers/gpio/gpiolib-acpi.c +++ b/drivers/gpio/gpiolib-acpi.c @@ -743,8 +743,8 @@ static int acpi_gpio_update_gpiod_lookup_flags(unsigned long *lookupflags, } struct acpi_gpio_lookup { - struct acpi_gpio_info info; struct acpi_gpio_params params; + struct acpi_gpio_info *info; struct gpio_desc *desc; int n; }; @@ -753,7 +753,7 @@ static int acpi_populate_gpio_lookup(struct acpi_resource *ares, void *data) { struct acpi_gpio_lookup *lookup = data; struct acpi_gpio_params *params = &lookup->params; - struct acpi_gpio_info *info = &lookup->info; + struct acpi_gpio_info *info = lookup->info; if (ares->type != ACPI_RESOURCE_TYPE_GPIO) return 1; @@ -807,7 +807,7 @@ static int acpi_populate_gpio_lookup(struct acpi_resource *ares, void *data) static int acpi_gpio_resource_lookup(struct acpi_gpio_lookup *lookup) { - struct acpi_gpio_info *info = &lookup->info; + struct acpi_gpio_info *info = lookup->info; struct acpi_device *adev = info->adev; struct list_head res_list; int ret; @@ -833,7 +833,7 @@ static int acpi_gpio_property_lookup(struct fwnode_handle *fwnode, const char *p { struct fwnode_reference_args args; struct acpi_gpio_params *params = &lookup->params; - struct acpi_gpio_info *info = &lookup->info; + struct acpi_gpio_info *info = lookup->info; unsigned int index = params->crs_entry_index; unsigned int quirks = 0; int ret; @@ -894,8 +894,8 @@ static int acpi_gpio_property_lookup(struct fwnode_handle *fwnode, const char *p static int acpi_get_gpiod_by_index(struct acpi_device *adev, const char *propname, struct acpi_gpio_lookup *lookup) { - struct acpi_gpio_info *info = &lookup->info; struct acpi_gpio_params *params = &lookup->params; + struct acpi_gpio_info *info = lookup->info; int ret; if (propname) { @@ -976,6 +976,7 @@ __acpi_find_gpio(struct fwnode_handle *fwnode, const char *con_id, unsigned int memset(&lookup, 0, sizeof(lookup)); lookup.params.crs_entry_index = idx; + lookup.info = info; /* Try first from _DSD */ for_each_gpio_property_name(propname, con_id) { From a594877663d1e3d5cf57ec8af739582fc5c47cec Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 13 May 2025 13:00:32 +0300 Subject: [PATCH 114/123] gpiolib: acpi: Handle deferred list via new API Introduce a new API and handle deferred list via it which moves towards isolating the GPIO ACPI and quirk APIs. It will helps splitting them completely in the next changes. No functional changes. Reviewed-by: Hans de Goede Acked-by: Mika Westerberg Signed-off-by: Andy Shevchenko --- drivers/gpio/gpiolib-acpi.c | 52 +++++++++++++++++++++++-------------- drivers/gpio/gpiolib-acpi.h | 5 ++++ 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c index fb573b5f0ba1..ac5f8ec6e72b 100644 --- a/drivers/gpio/gpiolib-acpi.c +++ b/drivers/gpio/gpiolib-acpi.c @@ -350,6 +350,27 @@ static struct gpio_desc *acpi_request_own_gpiod(struct gpio_chip *chip, return desc; } +bool acpi_gpio_add_to_deferred_list(struct list_head *list) +{ + bool defer; + + mutex_lock(&acpi_gpio_deferred_req_irqs_lock); + defer = !acpi_gpio_deferred_req_irqs_done; + if (defer) + list_add(list, &acpi_gpio_deferred_req_irqs_list); + mutex_unlock(&acpi_gpio_deferred_req_irqs_lock); + + return defer; +} + +void acpi_gpio_remove_from_deferred_list(struct list_head *list) +{ + mutex_lock(&acpi_gpio_deferred_req_irqs_lock); + if (!list_empty(list)) + list_del_init(list); + mutex_unlock(&acpi_gpio_deferred_req_irqs_lock); +} + bool acpi_gpio_in_ignore_list(enum acpi_gpio_ignore_list list, const char *controller_in, unsigned int pin_in) { @@ -536,7 +557,6 @@ void acpi_gpiochip_request_interrupts(struct gpio_chip *chip) struct acpi_gpio_chip *acpi_gpio; acpi_handle handle; acpi_status status; - bool defer; if (!chip->parent || !chip->to_irq) return; @@ -555,14 +575,7 @@ void acpi_gpiochip_request_interrupts(struct gpio_chip *chip) acpi_walk_resources(handle, METHOD_NAME__AEI, acpi_gpiochip_alloc_event, acpi_gpio); - mutex_lock(&acpi_gpio_deferred_req_irqs_lock); - defer = !acpi_gpio_deferred_req_irqs_done; - if (defer) - list_add(&acpi_gpio->deferred_req_irqs_list_entry, - &acpi_gpio_deferred_req_irqs_list); - mutex_unlock(&acpi_gpio_deferred_req_irqs_lock); - - if (defer) + if (acpi_gpio_add_to_deferred_list(&acpi_gpio->deferred_req_irqs_list_entry)) return; acpi_gpiochip_request_irqs(acpi_gpio); @@ -594,10 +607,7 @@ void acpi_gpiochip_free_interrupts(struct gpio_chip *chip) if (ACPI_FAILURE(status)) return; - mutex_lock(&acpi_gpio_deferred_req_irqs_lock); - if (!list_empty(&acpi_gpio->deferred_req_irqs_list_entry)) - list_del_init(&acpi_gpio->deferred_req_irqs_list_entry); - mutex_unlock(&acpi_gpio_deferred_req_irqs_lock); + acpi_gpio_remove_from_deferred_list(&acpi_gpio->deferred_req_irqs_list_entry); list_for_each_entry_safe_reverse(event, ep, &acpi_gpio->events, node) { if (event->irq_requested) { @@ -615,6 +625,14 @@ void acpi_gpiochip_free_interrupts(struct gpio_chip *chip) } EXPORT_SYMBOL_GPL(acpi_gpiochip_free_interrupts); +void __init acpi_gpio_process_deferred_list(struct list_head *list) +{ + struct acpi_gpio_chip *acpi_gpio, *tmp; + + list_for_each_entry_safe(acpi_gpio, tmp, list, deferred_req_irqs_list_entry) + acpi_gpiochip_request_irqs(acpi_gpio); +} + int acpi_dev_add_driver_gpios(struct acpi_device *adev, const struct acpi_gpio_mapping *gpios) { @@ -1503,14 +1521,8 @@ int acpi_gpio_count(const struct fwnode_handle *fwnode, const char *con_id) /* Run deferred acpi_gpiochip_request_irqs() */ static int __init acpi_gpio_handle_deferred_request_irqs(void) { - struct acpi_gpio_chip *acpi_gpio, *tmp; - mutex_lock(&acpi_gpio_deferred_req_irqs_lock); - list_for_each_entry_safe(acpi_gpio, tmp, - &acpi_gpio_deferred_req_irqs_list, - deferred_req_irqs_list_entry) - acpi_gpiochip_request_irqs(acpi_gpio); - + acpi_gpio_process_deferred_list(&acpi_gpio_deferred_req_irqs_list); acpi_gpio_deferred_req_irqs_done = true; mutex_unlock(&acpi_gpio_deferred_req_irqs_lock); diff --git a/drivers/gpio/gpiolib-acpi.h b/drivers/gpio/gpiolib-acpi.h index ef0b1a3c85d7..8249977e6140 100644 --- a/drivers/gpio/gpiolib-acpi.h +++ b/drivers/gpio/gpiolib-acpi.h @@ -58,6 +58,11 @@ static inline int acpi_gpio_count(const struct fwnode_handle *fwnode, } #endif +void acpi_gpio_process_deferred_list(struct list_head *list); + +bool acpi_gpio_add_to_deferred_list(struct list_head *list); +void acpi_gpio_remove_from_deferred_list(struct list_head *list); + enum acpi_gpio_ignore_list { ACPI_GPIO_IGNORE_WAKE, ACPI_GPIO_IGNORE_INTERRUPT, From 5666a8777add09d1167de308df2147983486a0af Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 13 May 2025 13:00:33 +0300 Subject: [PATCH 115/123] gpiolib: acpi: Add acpi_gpio_need_run_edge_events_on_boot() getter Add acpi_gpio_need_run_edge_events_on_boot() getter which moves towards isolating the GPIO ACPI and quirk APIs. It will helps splitting them completely in the next changes. No functional changes. Reviewed-by: Hans de Goede Acked-by: Mika Westerberg Signed-off-by: Andy Shevchenko --- drivers/gpio/gpiolib-acpi.c | 7 ++++++- drivers/gpio/gpiolib-acpi.h | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c index ac5f8ec6e72b..609e3a7f9636 100644 --- a/drivers/gpio/gpiolib-acpi.c +++ b/drivers/gpio/gpiolib-acpi.c @@ -268,7 +268,7 @@ static void acpi_gpiochip_request_irq(struct acpi_gpio_chip *acpi_gpio, event->irq_requested = true; /* Make sure we trigger the initial state of edge-triggered IRQs */ - if (run_edge_events_on_boot && + if (acpi_gpio_need_run_edge_events_on_boot() && (event->irqflags & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING))) { value = gpiod_get_raw_value_cansleep(event->desc); if (((event->irqflags & IRQF_TRIGGER_RISING) && value == 1) || @@ -371,6 +371,11 @@ void acpi_gpio_remove_from_deferred_list(struct list_head *list) mutex_unlock(&acpi_gpio_deferred_req_irqs_lock); } +int acpi_gpio_need_run_edge_events_on_boot(void) +{ + return run_edge_events_on_boot; +} + bool acpi_gpio_in_ignore_list(enum acpi_gpio_ignore_list list, const char *controller_in, unsigned int pin_in) { diff --git a/drivers/gpio/gpiolib-acpi.h b/drivers/gpio/gpiolib-acpi.h index 8249977e6140..a90267470a4e 100644 --- a/drivers/gpio/gpiolib-acpi.h +++ b/drivers/gpio/gpiolib-acpi.h @@ -63,6 +63,8 @@ void acpi_gpio_process_deferred_list(struct list_head *list); bool acpi_gpio_add_to_deferred_list(struct list_head *list); void acpi_gpio_remove_from_deferred_list(struct list_head *list); +int acpi_gpio_need_run_edge_events_on_boot(void); + enum acpi_gpio_ignore_list { ACPI_GPIO_IGNORE_WAKE, ACPI_GPIO_IGNORE_INTERRUPT, From 92dc572852ddcae687590cb159189004d58e382e Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 13 May 2025 13:00:34 +0300 Subject: [PATCH 116/123] gpiolib: acpi: Move quirks to a separate file The gpiolib-acpi.c is huge enough even without DMI quirks. Move them to a separate file for a better maintenance. No functional change intended. Reviewed-by: Hans de Goede Acked-by: Mika Westerberg Signed-off-by: Andy Shevchenko --- drivers/gpio/Makefile | 1 + .../{gpiolib-acpi.c => gpiolib-acpi-core.c} | 346 ----------------- drivers/gpio/gpiolib-acpi-quirks.c | 363 ++++++++++++++++++ 3 files changed, 364 insertions(+), 346 deletions(-) rename drivers/gpio/{gpiolib-acpi.c => gpiolib-acpi-core.c} (79%) create mode 100644 drivers/gpio/gpiolib-acpi-quirks.c diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index af130882ffee..d9b44b482cc7 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_OF_GPIO) += gpiolib-of.o obj-$(CONFIG_GPIO_CDEV) += gpiolib-cdev.o obj-$(CONFIG_GPIO_SYSFS) += gpiolib-sysfs.o obj-$(CONFIG_GPIO_ACPI) += gpiolib-acpi.o +gpiolib-acpi-y := gpiolib-acpi-core.o gpiolib-acpi-quirks.o obj-$(CONFIG_GPIOLIB) += gpiolib-swnode.o # Device drivers. Generally keep list sorted alphabetically diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi-core.c similarity index 79% rename from drivers/gpio/gpiolib-acpi.c rename to drivers/gpio/gpiolib-acpi-core.c index 609e3a7f9636..12b24a717e43 100644 --- a/drivers/gpio/gpiolib-acpi.c +++ b/drivers/gpio/gpiolib-acpi-core.c @@ -23,29 +23,6 @@ #include "gpiolib.h" #include "gpiolib-acpi.h" -static int run_edge_events_on_boot = -1; -module_param(run_edge_events_on_boot, int, 0444); -MODULE_PARM_DESC(run_edge_events_on_boot, - "Run edge _AEI event-handlers at boot: 0=no, 1=yes, -1=auto"); - -static char *ignore_wake; -module_param(ignore_wake, charp, 0444); -MODULE_PARM_DESC(ignore_wake, - "controller@pin combos on which to ignore the ACPI wake flag " - "ignore_wake=controller@pin[,controller@pin[,...]]"); - -static char *ignore_interrupt; -module_param(ignore_interrupt, charp, 0444); -MODULE_PARM_DESC(ignore_interrupt, - "controller@pin combos on which to ignore interrupt " - "ignore_interrupt=controller@pin[,controller@pin[,...]]"); - -struct acpi_gpiolib_dmi_quirk { - bool no_edge_events_on_boot; - char *ignore_wake; - char *ignore_interrupt; -}; - /** * struct acpi_gpio_event - ACPI GPIO event handler data * @@ -115,17 +92,6 @@ struct acpi_gpio_info { unsigned int quirks; }; -/* - * For GPIO chips which call acpi_gpiochip_request_interrupts() before late_init - * (so builtin drivers) we register the ACPI GpioInt IRQ handlers from a - * late_initcall_sync() handler, so that other builtin drivers can register their - * OpRegions before the event handlers can run. This list contains GPIO chips - * for which the acpi_gpiochip_request_irqs() call has been deferred. - */ -static DEFINE_MUTEX(acpi_gpio_deferred_req_irqs_lock); -static LIST_HEAD(acpi_gpio_deferred_req_irqs_list); -static bool acpi_gpio_deferred_req_irqs_done; - static int acpi_gpiochip_find(struct gpio_chip *gc, const void *data) { /* First check the actual GPIO device */ @@ -350,79 +316,6 @@ static struct gpio_desc *acpi_request_own_gpiod(struct gpio_chip *chip, return desc; } -bool acpi_gpio_add_to_deferred_list(struct list_head *list) -{ - bool defer; - - mutex_lock(&acpi_gpio_deferred_req_irqs_lock); - defer = !acpi_gpio_deferred_req_irqs_done; - if (defer) - list_add(list, &acpi_gpio_deferred_req_irqs_list); - mutex_unlock(&acpi_gpio_deferred_req_irqs_lock); - - return defer; -} - -void acpi_gpio_remove_from_deferred_list(struct list_head *list) -{ - mutex_lock(&acpi_gpio_deferred_req_irqs_lock); - if (!list_empty(list)) - list_del_init(list); - mutex_unlock(&acpi_gpio_deferred_req_irqs_lock); -} - -int acpi_gpio_need_run_edge_events_on_boot(void) -{ - return run_edge_events_on_boot; -} - -bool acpi_gpio_in_ignore_list(enum acpi_gpio_ignore_list list, const char *controller_in, - unsigned int pin_in) -{ - const char *ignore_list, *controller, *pin_str; - unsigned int pin; - char *endp; - int len; - - switch (list) { - case ACPI_GPIO_IGNORE_WAKE: - ignore_list = ignore_wake; - break; - case ACPI_GPIO_IGNORE_INTERRUPT: - ignore_list = ignore_interrupt; - break; - default: - return false; - } - - controller = ignore_list; - while (controller) { - pin_str = strchr(controller, '@'); - if (!pin_str) - goto err; - - len = pin_str - controller; - if (len == strlen(controller_in) && - strncmp(controller, controller_in, len) == 0) { - pin = simple_strtoul(pin_str + 1, &endp, 10); - if (*endp != 0 && *endp != ',') - goto err; - - if (pin == pin_in) - return true; - } - - controller = strchr(controller, ','); - if (controller) - controller++; - } - - return false; -err: - pr_err_once("Error: Invalid value for gpiolib_acpi.ignore_...: %s\n", ignore_list); - return false; -} - static bool acpi_gpio_irq_is_wake(struct device *parent, const struct acpi_resource_gpio *agpio) { @@ -1522,242 +1415,3 @@ int acpi_gpio_count(const struct fwnode_handle *fwnode, const char *con_id) } return count ? count : -ENOENT; } - -/* Run deferred acpi_gpiochip_request_irqs() */ -static int __init acpi_gpio_handle_deferred_request_irqs(void) -{ - mutex_lock(&acpi_gpio_deferred_req_irqs_lock); - acpi_gpio_process_deferred_list(&acpi_gpio_deferred_req_irqs_list); - acpi_gpio_deferred_req_irqs_done = true; - mutex_unlock(&acpi_gpio_deferred_req_irqs_lock); - - return 0; -} -/* We must use _sync so that this runs after the first deferred_probe run */ -late_initcall_sync(acpi_gpio_handle_deferred_request_irqs); - -static const struct dmi_system_id gpiolib_acpi_quirks[] __initconst = { - { - /* - * The Minix Neo Z83-4 has a micro-USB-B id-pin handler for - * a non existing micro-USB-B connector which puts the HDMI - * DDC pins in GPIO mode, breaking HDMI support. - */ - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "MINIX"), - DMI_MATCH(DMI_PRODUCT_NAME, "Z83-4"), - }, - .driver_data = &(struct acpi_gpiolib_dmi_quirk) { - .no_edge_events_on_boot = true, - }, - }, - { - /* - * The Terra Pad 1061 has a micro-USB-B id-pin handler, which - * instead of controlling the actual micro-USB-B turns the 5V - * boost for its USB-A connector off. The actual micro-USB-B - * connector is wired for charging only. - */ - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Wortmann_AG"), - DMI_MATCH(DMI_PRODUCT_NAME, "TERRA_PAD_1061"), - }, - .driver_data = &(struct acpi_gpiolib_dmi_quirk) { - .no_edge_events_on_boot = true, - }, - }, - { - /* - * The Dell Venue 10 Pro 5055, with Bay Trail SoC + TI PMIC uses an - * external embedded-controller connected via I2C + an ACPI GPIO - * event handler on INT33FFC:02 pin 12, causing spurious wakeups. - */ - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Venue 10 Pro 5055"), - }, - .driver_data = &(struct acpi_gpiolib_dmi_quirk) { - .ignore_wake = "INT33FC:02@12", - }, - }, - { - /* - * HP X2 10 models with Cherry Trail SoC + TI PMIC use an - * external embedded-controller connected via I2C + an ACPI GPIO - * event handler on INT33FF:01 pin 0, causing spurious wakeups. - * When suspending by closing the LID, the power to the USB - * keyboard is turned off, causing INT0002 ACPI events to - * trigger once the XHCI controller notices the keyboard is - * gone. So INT0002 events cause spurious wakeups too. Ignoring - * EC wakes breaks wakeup when opening the lid, the user needs - * to press the power-button to wakeup the system. The - * alternative is suspend simply not working, which is worse. - */ - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "HP"), - DMI_MATCH(DMI_PRODUCT_NAME, "HP x2 Detachable 10-p0XX"), - }, - .driver_data = &(struct acpi_gpiolib_dmi_quirk) { - .ignore_wake = "INT33FF:01@0,INT0002:00@2", - }, - }, - { - /* - * HP X2 10 models with Bay Trail SoC + AXP288 PMIC use an - * external embedded-controller connected via I2C + an ACPI GPIO - * event handler on INT33FC:02 pin 28, causing spurious wakeups. - */ - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), - DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion x2 Detachable"), - DMI_MATCH(DMI_BOARD_NAME, "815D"), - }, - .driver_data = &(struct acpi_gpiolib_dmi_quirk) { - .ignore_wake = "INT33FC:02@28", - }, - }, - { - /* - * HP X2 10 models with Cherry Trail SoC + AXP288 PMIC use an - * external embedded-controller connected via I2C + an ACPI GPIO - * event handler on INT33FF:01 pin 0, causing spurious wakeups. - */ - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "HP"), - DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion x2 Detachable"), - DMI_MATCH(DMI_BOARD_NAME, "813E"), - }, - .driver_data = &(struct acpi_gpiolib_dmi_quirk) { - .ignore_wake = "INT33FF:01@0", - }, - }, - { - /* - * Interrupt storm caused from edge triggered floating pin - * Found in BIOS UX325UAZ.300 - * https://bugzilla.kernel.org/show_bug.cgi?id=216208 - */ - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), - DMI_MATCH(DMI_PRODUCT_NAME, "ZenBook UX325UAZ_UM325UAZ"), - }, - .driver_data = &(struct acpi_gpiolib_dmi_quirk) { - .ignore_interrupt = "AMDI0030:00@18", - }, - }, - { - /* - * Spurious wakeups from TP_ATTN# pin - * Found in BIOS 1.7.8 - * https://gitlab.freedesktop.org/drm/amd/-/issues/1722#note_1720627 - */ - .matches = { - DMI_MATCH(DMI_BOARD_NAME, "NL5xNU"), - }, - .driver_data = &(struct acpi_gpiolib_dmi_quirk) { - .ignore_wake = "ELAN0415:00@9", - }, - }, - { - /* - * Spurious wakeups from TP_ATTN# pin - * Found in BIOS 1.7.8 - * https://gitlab.freedesktop.org/drm/amd/-/issues/1722#note_1720627 - */ - .matches = { - DMI_MATCH(DMI_BOARD_NAME, "NL5xRU"), - }, - .driver_data = &(struct acpi_gpiolib_dmi_quirk) { - .ignore_wake = "ELAN0415:00@9", - }, - }, - { - /* - * Spurious wakeups from TP_ATTN# pin - * Found in BIOS 1.7.7 - */ - .matches = { - DMI_MATCH(DMI_BOARD_NAME, "NH5xAx"), - }, - .driver_data = &(struct acpi_gpiolib_dmi_quirk) { - .ignore_wake = "SYNA1202:00@16", - }, - }, - { - /* - * On the Peaq C1010 2-in-1 INT33FC:00 pin 3 is connected to - * a "dolby" button. At the ACPI level an _AEI event-handler - * is connected which sets an ACPI variable to 1 on both - * edges. This variable can be polled + cleared to 0 using - * WMI. But since the variable is set on both edges the WMI - * interface is pretty useless even when polling. - * So instead the x86-android-tablets code instantiates - * a gpio-keys platform device for it. - * Ignore the _AEI handler for the pin, so that it is not busy. - */ - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "PEAQ"), - DMI_MATCH(DMI_PRODUCT_NAME, "PEAQ PMM C1010 MD99187"), - }, - .driver_data = &(struct acpi_gpiolib_dmi_quirk) { - .ignore_interrupt = "INT33FC:00@3", - }, - }, - { - /* - * Spurious wakeups from TP_ATTN# pin - * Found in BIOS 0.35 - * https://gitlab.freedesktop.org/drm/amd/-/issues/3073 - */ - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "GPD"), - DMI_MATCH(DMI_PRODUCT_NAME, "G1619-04"), - }, - .driver_data = &(struct acpi_gpiolib_dmi_quirk) { - .ignore_wake = "PNP0C50:00@8", - }, - }, - { - /* - * Spurious wakeups from GPIO 11 - * Found in BIOS 1.04 - * https://gitlab.freedesktop.org/drm/amd/-/issues/3954 - */ - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Acer"), - DMI_MATCH(DMI_PRODUCT_FAMILY, "Acer Nitro V 14"), - }, - .driver_data = &(struct acpi_gpiolib_dmi_quirk) { - .ignore_interrupt = "AMDI0030:00@11", - }, - }, - {} /* Terminating entry */ -}; - -static int __init acpi_gpio_setup_params(void) -{ - const struct acpi_gpiolib_dmi_quirk *quirk = NULL; - const struct dmi_system_id *id; - - id = dmi_first_match(gpiolib_acpi_quirks); - if (id) - quirk = id->driver_data; - - if (run_edge_events_on_boot < 0) { - if (quirk && quirk->no_edge_events_on_boot) - run_edge_events_on_boot = 0; - else - run_edge_events_on_boot = 1; - } - - if (ignore_wake == NULL && quirk && quirk->ignore_wake) - ignore_wake = quirk->ignore_wake; - - if (ignore_interrupt == NULL && quirk && quirk->ignore_interrupt) - ignore_interrupt = quirk->ignore_interrupt; - - return 0; -} - -/* Directly after dmi_setup() which runs as core_initcall() */ -postcore_initcall(acpi_gpio_setup_params); diff --git a/drivers/gpio/gpiolib-acpi-quirks.c b/drivers/gpio/gpiolib-acpi-quirks.c new file mode 100644 index 000000000000..219667315b2c --- /dev/null +++ b/drivers/gpio/gpiolib-acpi-quirks.c @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ACPI quirks for GPIO ACPI helpers + * + * Author: Hans de Goede + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpiolib-acpi.h" + +static int run_edge_events_on_boot = -1; +module_param(run_edge_events_on_boot, int, 0444); +MODULE_PARM_DESC(run_edge_events_on_boot, + "Run edge _AEI event-handlers at boot: 0=no, 1=yes, -1=auto"); + +static char *ignore_wake; +module_param(ignore_wake, charp, 0444); +MODULE_PARM_DESC(ignore_wake, + "controller@pin combos on which to ignore the ACPI wake flag " + "ignore_wake=controller@pin[,controller@pin[,...]]"); + +static char *ignore_interrupt; +module_param(ignore_interrupt, charp, 0444); +MODULE_PARM_DESC(ignore_interrupt, + "controller@pin combos on which to ignore interrupt " + "ignore_interrupt=controller@pin[,controller@pin[,...]]"); + +/* + * For GPIO chips which call acpi_gpiochip_request_interrupts() before late_init + * (so builtin drivers) we register the ACPI GpioInt IRQ handlers from a + * late_initcall_sync() handler, so that other builtin drivers can register their + * OpRegions before the event handlers can run. This list contains GPIO chips + * for which the acpi_gpiochip_request_irqs() call has been deferred. + */ +static DEFINE_MUTEX(acpi_gpio_deferred_req_irqs_lock); +static LIST_HEAD(acpi_gpio_deferred_req_irqs_list); +static bool acpi_gpio_deferred_req_irqs_done; + +bool acpi_gpio_add_to_deferred_list(struct list_head *list) +{ + bool defer; + + mutex_lock(&acpi_gpio_deferred_req_irqs_lock); + defer = !acpi_gpio_deferred_req_irqs_done; + if (defer) + list_add(list, &acpi_gpio_deferred_req_irqs_list); + mutex_unlock(&acpi_gpio_deferred_req_irqs_lock); + + return defer; +} + +void acpi_gpio_remove_from_deferred_list(struct list_head *list) +{ + mutex_lock(&acpi_gpio_deferred_req_irqs_lock); + if (!list_empty(list)) + list_del_init(list); + mutex_unlock(&acpi_gpio_deferred_req_irqs_lock); +} + +int acpi_gpio_need_run_edge_events_on_boot(void) +{ + return run_edge_events_on_boot; +} + +bool acpi_gpio_in_ignore_list(enum acpi_gpio_ignore_list list, + const char *controller_in, unsigned int pin_in) +{ + const char *ignore_list, *controller, *pin_str; + unsigned int pin; + char *endp; + int len; + + switch (list) { + case ACPI_GPIO_IGNORE_WAKE: + ignore_list = ignore_wake; + break; + case ACPI_GPIO_IGNORE_INTERRUPT: + ignore_list = ignore_interrupt; + break; + default: + return false; + } + + controller = ignore_list; + while (controller) { + pin_str = strchr(controller, '@'); + if (!pin_str) + goto err; + + len = pin_str - controller; + if (len == strlen(controller_in) && + strncmp(controller, controller_in, len) == 0) { + pin = simple_strtoul(pin_str + 1, &endp, 10); + if (*endp != 0 && *endp != ',') + goto err; + + if (pin == pin_in) + return true; + } + + controller = strchr(controller, ','); + if (controller) + controller++; + } + + return false; +err: + pr_err_once("Error: Invalid value for gpiolib_acpi.ignore_...: %s\n", ignore_list); + return false; +} + +/* Run deferred acpi_gpiochip_request_irqs() */ +static int __init acpi_gpio_handle_deferred_request_irqs(void) +{ + mutex_lock(&acpi_gpio_deferred_req_irqs_lock); + acpi_gpio_process_deferred_list(&acpi_gpio_deferred_req_irqs_list); + acpi_gpio_deferred_req_irqs_done = true; + mutex_unlock(&acpi_gpio_deferred_req_irqs_lock); + + return 0; +} +/* We must use _sync so that this runs after the first deferred_probe run */ +late_initcall_sync(acpi_gpio_handle_deferred_request_irqs); + +struct acpi_gpiolib_dmi_quirk { + bool no_edge_events_on_boot; + char *ignore_wake; + char *ignore_interrupt; +}; + +static const struct dmi_system_id gpiolib_acpi_quirks[] __initconst = { + { + /* + * The Minix Neo Z83-4 has a micro-USB-B id-pin handler for + * a non existing micro-USB-B connector which puts the HDMI + * DDC pins in GPIO mode, breaking HDMI support. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "MINIX"), + DMI_MATCH(DMI_PRODUCT_NAME, "Z83-4"), + }, + .driver_data = &(struct acpi_gpiolib_dmi_quirk) { + .no_edge_events_on_boot = true, + }, + }, + { + /* + * The Terra Pad 1061 has a micro-USB-B id-pin handler, which + * instead of controlling the actual micro-USB-B turns the 5V + * boost for its USB-A connector off. The actual micro-USB-B + * connector is wired for charging only. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Wortmann_AG"), + DMI_MATCH(DMI_PRODUCT_NAME, "TERRA_PAD_1061"), + }, + .driver_data = &(struct acpi_gpiolib_dmi_quirk) { + .no_edge_events_on_boot = true, + }, + }, + { + /* + * The Dell Venue 10 Pro 5055, with Bay Trail SoC + TI PMIC uses an + * external embedded-controller connected via I2C + an ACPI GPIO + * event handler on INT33FFC:02 pin 12, causing spurious wakeups. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Venue 10 Pro 5055"), + }, + .driver_data = &(struct acpi_gpiolib_dmi_quirk) { + .ignore_wake = "INT33FC:02@12", + }, + }, + { + /* + * HP X2 10 models with Cherry Trail SoC + TI PMIC use an + * external embedded-controller connected via I2C + an ACPI GPIO + * event handler on INT33FF:01 pin 0, causing spurious wakeups. + * When suspending by closing the LID, the power to the USB + * keyboard is turned off, causing INT0002 ACPI events to + * trigger once the XHCI controller notices the keyboard is + * gone. So INT0002 events cause spurious wakeups too. Ignoring + * EC wakes breaks wakeup when opening the lid, the user needs + * to press the power-button to wakeup the system. The + * alternative is suspend simply not working, which is worse. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HP"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP x2 Detachable 10-p0XX"), + }, + .driver_data = &(struct acpi_gpiolib_dmi_quirk) { + .ignore_wake = "INT33FF:01@0,INT0002:00@2", + }, + }, + { + /* + * HP X2 10 models with Bay Trail SoC + AXP288 PMIC use an + * external embedded-controller connected via I2C + an ACPI GPIO + * event handler on INT33FC:02 pin 28, causing spurious wakeups. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion x2 Detachable"), + DMI_MATCH(DMI_BOARD_NAME, "815D"), + }, + .driver_data = &(struct acpi_gpiolib_dmi_quirk) { + .ignore_wake = "INT33FC:02@28", + }, + }, + { + /* + * HP X2 10 models with Cherry Trail SoC + AXP288 PMIC use an + * external embedded-controller connected via I2C + an ACPI GPIO + * event handler on INT33FF:01 pin 0, causing spurious wakeups. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HP"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion x2 Detachable"), + DMI_MATCH(DMI_BOARD_NAME, "813E"), + }, + .driver_data = &(struct acpi_gpiolib_dmi_quirk) { + .ignore_wake = "INT33FF:01@0", + }, + }, + { + /* + * Interrupt storm caused from edge triggered floating pin + * Found in BIOS UX325UAZ.300 + * https://bugzilla.kernel.org/show_bug.cgi?id=216208 + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "ZenBook UX325UAZ_UM325UAZ"), + }, + .driver_data = &(struct acpi_gpiolib_dmi_quirk) { + .ignore_interrupt = "AMDI0030:00@18", + }, + }, + { + /* + * Spurious wakeups from TP_ATTN# pin + * Found in BIOS 1.7.8 + * https://gitlab.freedesktop.org/drm/amd/-/issues/1722#note_1720627 + */ + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "NL5xNU"), + }, + .driver_data = &(struct acpi_gpiolib_dmi_quirk) { + .ignore_wake = "ELAN0415:00@9", + }, + }, + { + /* + * Spurious wakeups from TP_ATTN# pin + * Found in BIOS 1.7.8 + * https://gitlab.freedesktop.org/drm/amd/-/issues/1722#note_1720627 + */ + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "NL5xRU"), + }, + .driver_data = &(struct acpi_gpiolib_dmi_quirk) { + .ignore_wake = "ELAN0415:00@9", + }, + }, + { + /* + * Spurious wakeups from TP_ATTN# pin + * Found in BIOS 1.7.7 + */ + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "NH5xAx"), + }, + .driver_data = &(struct acpi_gpiolib_dmi_quirk) { + .ignore_wake = "SYNA1202:00@16", + }, + }, + { + /* + * On the Peaq C1010 2-in-1 INT33FC:00 pin 3 is connected to + * a "dolby" button. At the ACPI level an _AEI event-handler + * is connected which sets an ACPI variable to 1 on both + * edges. This variable can be polled + cleared to 0 using + * WMI. But since the variable is set on both edges the WMI + * interface is pretty useless even when polling. + * So instead the x86-android-tablets code instantiates + * a gpio-keys platform device for it. + * Ignore the _AEI handler for the pin, so that it is not busy. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "PEAQ"), + DMI_MATCH(DMI_PRODUCT_NAME, "PEAQ PMM C1010 MD99187"), + }, + .driver_data = &(struct acpi_gpiolib_dmi_quirk) { + .ignore_interrupt = "INT33FC:00@3", + }, + }, + { + /* + * Spurious wakeups from TP_ATTN# pin + * Found in BIOS 0.35 + * https://gitlab.freedesktop.org/drm/amd/-/issues/3073 + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_MATCH(DMI_PRODUCT_NAME, "G1619-04"), + }, + .driver_data = &(struct acpi_gpiolib_dmi_quirk) { + .ignore_wake = "PNP0C50:00@8", + }, + }, + { + /* + * Spurious wakeups from GPIO 11 + * Found in BIOS 1.04 + * https://gitlab.freedesktop.org/drm/amd/-/issues/3954 + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "Acer Nitro V 14"), + }, + .driver_data = &(struct acpi_gpiolib_dmi_quirk) { + .ignore_interrupt = "AMDI0030:00@11", + }, + }, + {} /* Terminating entry */ +}; + +static int __init acpi_gpio_setup_params(void) +{ + const struct acpi_gpiolib_dmi_quirk *quirk = NULL; + const struct dmi_system_id *id; + + id = dmi_first_match(gpiolib_acpi_quirks); + if (id) + quirk = id->driver_data; + + if (run_edge_events_on_boot < 0) { + if (quirk && quirk->no_edge_events_on_boot) + run_edge_events_on_boot = 0; + else + run_edge_events_on_boot = 1; + } + + if (ignore_wake == NULL && quirk && quirk->ignore_wake) + ignore_wake = quirk->ignore_wake; + + if (ignore_interrupt == NULL && quirk && quirk->ignore_interrupt) + ignore_interrupt = quirk->ignore_interrupt; + + return 0; +} + +/* Directly after dmi_setup() which runs as core_initcall() */ +postcore_initcall(acpi_gpio_setup_params); From ec0c0aab15243bbc6140a7cedd01e2dd9ad19878 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 16 May 2025 12:52:34 +0300 Subject: [PATCH 117/123] gpiolib-acpi: Update file references in the Documentation and MAINTAINERS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The recent changes in the gpiolib-acpi.c need also updates in the Documentation and MAINTAINERS. Do the necessary changes here. Fixes: babb541af627 ("gpiolib: acpi: Move quirks to a separate file") Reported-by: Stephen Rothwell Closes: https://lore.kernel.org/r/20250516193436.09bdf8cc@canb.auug.org.au Reviewed-by: Randy Dunlap Tested-by: Randy Dunlap Acked-by: Ilpo Järvinen Signed-off-by: Andy Shevchenko --- Documentation/driver-api/gpio/index.rst | 2 +- Documentation/translations/zh_CN/driver-api/gpio/index.rst | 2 +- MAINTAINERS | 2 +- drivers/platform/x86/intel/int0002_vgpio.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/driver-api/gpio/index.rst b/Documentation/driver-api/gpio/index.rst index 34b57cee3391..43f6a3afe10b 100644 --- a/Documentation/driver-api/gpio/index.rst +++ b/Documentation/driver-api/gpio/index.rst @@ -27,7 +27,7 @@ Core ACPI support ============ -.. kernel-doc:: drivers/gpio/gpiolib-acpi.c +.. kernel-doc:: drivers/gpio/gpiolib-acpi-core.c :export: Device tree support diff --git a/Documentation/translations/zh_CN/driver-api/gpio/index.rst b/Documentation/translations/zh_CN/driver-api/gpio/index.rst index e4d54724a1b5..f64a69f771ca 100644 --- a/Documentation/translations/zh_CN/driver-api/gpio/index.rst +++ b/Documentation/translations/zh_CN/driver-api/gpio/index.rst @@ -42,7 +42,7 @@ ACPI支持 该API在以下内核代码中: -drivers/gpio/gpiolib-acpi.c +drivers/gpio/gpiolib-acpi-core.c 设备树支持 ========== diff --git a/MAINTAINERS b/MAINTAINERS index 96b827049501..d1290bbb6ac6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10105,7 +10105,7 @@ L: linux-acpi@vger.kernel.org S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-gpio-intel.git F: Documentation/firmware-guide/acpi/gpio-properties.rst -F: drivers/gpio/gpiolib-acpi.c +F: drivers/gpio/gpiolib-acpi-*.c F: drivers/gpio/gpiolib-acpi.h GPIO AGGREGATOR diff --git a/drivers/platform/x86/intel/int0002_vgpio.c b/drivers/platform/x86/intel/int0002_vgpio.c index 3b48cd7a4075..b7b98343fdc6 100644 --- a/drivers/platform/x86/intel/int0002_vgpio.c +++ b/drivers/platform/x86/intel/int0002_vgpio.c @@ -23,7 +23,7 @@ * ACPI mechanisms, this is not a real GPIO at all. * * This driver will bind to the INT0002 device, and register as a GPIO - * controller, letting gpiolib-acpi.c call the _L02 handler as it would + * controller, letting gpiolib-acpi call the _L02 handler as it would * for a real GPIO controller. */ From 4347566404a0f01e9dc654a0b806a582ee63f68b Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Wed, 21 May 2025 15:20:48 +0800 Subject: [PATCH 118/123] gpio: davinci: select GPIOLIB_IRQCHIP This driver uses gpiochip_irq_reqres() and gpiochip_irq_relres() which are only built with GPIOLIB_IRQCHIP=y. Add the missing Kconfig select. Fixes: 3f50bb3124d7 ("gpio: davinci: Make irq_chip immutable") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202505210606.PudPm5pC-lkp@intel.com/ Signed-off-by: Peng Fan Link: https://lore.kernel.org/r/20250521072048.1053190-1-peng.fan@oss.nxp.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index a559d5bb8249..c3465fd82b08 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -253,6 +253,7 @@ config GPIO_DAVINCI tristate "TI Davinci/Keystone GPIO support" default y if ARCH_DAVINCI depends on ((ARM || ARM64) && (ARCH_DAVINCI || ARCH_KEYSTONE || ARCH_K3)) || COMPILE_TEST + select GPIOLIB_IRQCHIP help Say yes here to enable GPIO support for TI Davinci/Keystone SoCs. From 4e9d73034196ac8ab496bb47583197b36ba13327 Mon Sep 17 00:00:00 2001 From: Haibo Chen Date: Tue, 20 May 2025 11:46:12 +0800 Subject: [PATCH 119/123] dt-bindings: gpio: vf610: add ngpios and gpio-reserved-ranges Add optional ngpios and gpio-reserved-ranges property. Reviewed-by: Frank Li Signed-off-by: Haibo Chen Acked-by: Conor Dooley Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20250520-gpio-dts-v3-1-04771c6cf325@nxp.com Signed-off-by: Bartosz Golaszewski --- Documentation/devicetree/bindings/gpio/gpio-vf610.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Documentation/devicetree/bindings/gpio/gpio-vf610.yaml b/Documentation/devicetree/bindings/gpio/gpio-vf610.yaml index 4fb32e9aec0a..a31f64b6d40b 100644 --- a/Documentation/devicetree/bindings/gpio/gpio-vf610.yaml +++ b/Documentation/devicetree/bindings/gpio/gpio-vf610.yaml @@ -70,6 +70,13 @@ properties: minItems: 1 maxItems: 4 + gpio-reserved-ranges: true + + ngpios: + minimum: 1 + maximum: 32 + default: 32 + patternProperties: "^.+-hog(-[0-9]+)?$": type: object From 86fabf1b174069465ea12b4140cb35543fd0c738 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 22 May 2025 15:11:50 +0800 Subject: [PATCH 120/123] gpio: bcm-kona: select GPIOLIB_IRQCHIP This driver uses gpiochip_irq_reqres() and gpiochip_irq_relres() which are only built with GPIOLIB_IRQCHIP=y. Add the missing Kconfig select. Fixes: 7b04f98027af ("gpio: bcm-kona: make irq_chip immutable") Signed-off-by: Peng Fan Link: https://lore.kernel.org/r/20250522-gpio-fix-v1-1-98ceae7c4c3c@nxp.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index c3465fd82b08..ac0c646451a5 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -201,6 +201,7 @@ config GPIO_RASPBERRYPI_EXP config GPIO_BCM_KONA bool "Broadcom Kona GPIO" depends on ARCH_BCM_MOBILE || COMPILE_TEST + select GPIOLIB_IRQCHIP help Turn on GPIO support for Broadcom "Kona" chips. From 3dff3563cc11e90f046902f0b2be40e82f5ee251 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 22 May 2025 15:11:51 +0800 Subject: [PATCH 121/123] gpio: grgpio: select GPIOLIB_IRQCHIP This driver uses gpiochip_irq_reqres() and gpiochip_irq_relres() which are only built with GPIOLIB_IRQCHIP=y. Add the missing Kconfig select. Fixes: a30be40bf1d4 ("gpio: grgpio: Make irq_chip immutable") Signed-off-by: Peng Fan Link: https://lore.kernel.org/r/20250522-gpio-fix-v1-2-98ceae7c4c3c@nxp.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index ac0c646451a5..2ff53b00cf7c 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -354,7 +354,7 @@ config GPIO_GRGPIO tristate "Aeroflex Gaisler GRGPIO support" depends on OF || COMPILE_TEST select GPIO_GENERIC - select IRQ_DOMAIN + select GPIOLIB_IRQCHIP help Select this to support Aeroflex Gaisler GRGPIO cores from the GRLIB VHDL IP core library. From 01e4397d0c1c9ae434c574f45359ad0c226a864b Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 22 May 2025 15:11:52 +0800 Subject: [PATCH 122/123] gpio: lpc18xx: select GPIOLIB_IRQCHIP This driver uses gpiochip_irq_reqres() and gpiochip_irq_relres() which are only built with GPIOLIB_IRQCHIP=y. Add the missing Kconfig select. Fixes: 289e42df1358 ("gpio: lpc18xx: Make irq_chip immutable") Signed-off-by: Peng Fan Link: https://lore.kernel.org/r/20250522-gpio-fix-v1-3-98ceae7c4c3c@nxp.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 2ff53b00cf7c..c75fc8fc7bb2 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -438,6 +438,7 @@ config GPIO_LPC18XX default y if ARCH_LPC18XX depends on OF_GPIO && (ARCH_LPC18XX || COMPILE_TEST) select IRQ_DOMAIN_HIERARCHY + select GPIOLIB_IRQCHIP help Select this option to enable GPIO driver for NXP LPC18XX/43XX devices. From 8b8ef309093ff196967a21ee51a9cdb404a13bf6 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 22 May 2025 15:11:53 +0800 Subject: [PATCH 123/123] gpio: timberdale: select GPIOLIB_IRQCHIP This driver uses gpiochip_irq_reqres() and gpiochip_irq_relres() which are only built with GPIOLIB_IRQCHIP=y. Add the missing Kconfig select. Fixes: 2993d2dd8ff4 ("gpio: timberdale: Make irq_chip immutable") Signed-off-by: Peng Fan Link: https://lore.kernel.org/r/20250522-gpio-fix-v1-4-98ceae7c4c3c@nxp.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index c75fc8fc7bb2..44f922e10db2 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1557,6 +1557,7 @@ config GPIO_TC3589X config GPIO_TIMBERDALE bool "Support for timberdale GPIO IP" depends on MFD_TIMBERDALE + select GPIOLIB_IRQCHIP help Add support for the GPIO IP in the timberdale FPGA.