From 0ba6f1ed3808b1f095fbdb490006f0ecd00f52bd Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 22 Sep 2025 11:54:02 +0200 Subject: [PATCH 01/80] gpiolib: remove unnecessary 'out of memory' messages We don't need to add additional logs when returning -ENOMEM so remove unnecessary error messages. Reviewed-by: Linus Walleij 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 9952e412da50..55c983cfeb75 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -2316,10 +2316,8 @@ int gpiochip_add_pingroup_range(struct gpio_chip *gc, int ret; pin_range = kzalloc(sizeof(*pin_range), GFP_KERNEL); - if (!pin_range) { - chip_err(gc, "failed to allocate pin ranges\n"); + if (!pin_range) return -ENOMEM; - } /* Use local offset as range ID */ pin_range->range.id = gpio_offset; @@ -2379,10 +2377,8 @@ int gpiochip_add_pin_range_with_pins(struct gpio_chip *gc, int ret; pin_range = kzalloc(sizeof(*pin_range), GFP_KERNEL); - if (!pin_range) { - chip_err(gc, "failed to allocate pin ranges\n"); + if (!pin_range) return -ENOMEM; - } /* Use local offset as range ID */ pin_range->range.id = gpio_offset; From d4f335b410ddbe3e99f48f8b5ea78a25041274f1 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 22 Sep 2025 11:54:03 +0200 Subject: [PATCH 02/80] gpiolib: rename GPIO chip printk macros The chip_$level() macros take struct gpio_chip as argument so make it follow the convention of using the 'gpiochip_' prefix. Reviewed-by: Linus Walleij Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-cdev.c | 2 +- drivers/gpio/gpiolib-sysfs.c | 2 +- drivers/gpio/gpiolib.c | 80 ++++++++++++++++++------------------ drivers/gpio/gpiolib.h | 8 ++-- 4 files changed, 45 insertions(+), 47 deletions(-) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index 175836467f21..ddc452b5ee23 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -2823,7 +2823,7 @@ int gpiolib_cdev_register(struct gpio_device *gdev, dev_t devt) if (!gc) return -ENODEV; - chip_dbg(gc, "added GPIO chardev (%d:%d)\n", MAJOR(devt), gdev->id); + gpiochip_dbg(gc, "added GPIO chardev (%d:%d)\n", MAJOR(devt), gdev->id); return 0; } diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c index 9a849245b358..7d5fc1ea2aa5 100644 --- a/drivers/gpio/gpiolib-sysfs.c +++ b/drivers/gpio/gpiolib-sysfs.c @@ -1091,7 +1091,7 @@ static int gpiofind_sysfs_register(struct gpio_chip *gc, const void *data) ret = gpiochip_sysfs_register(gdev); if (ret) - chip_err(gc, "failed to register the sysfs entry: %d\n", ret); + gpiochip_err(gc, "failed to register the sysfs entry: %d\n", ret); return 0; } diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 55c983cfeb75..ba5df8a233fe 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -921,8 +921,8 @@ static void gpiochip_machine_hog(struct gpio_chip *gc, struct gpiod_hog *hog) desc = gpiochip_get_desc(gc, hog->chip_hwnum); if (IS_ERR(desc)) { - chip_err(gc, "%s: unable to get GPIO desc: %ld\n", __func__, - PTR_ERR(desc)); + gpiochip_err(gc, "%s: unable to get GPIO desc: %ld\n", + __func__, PTR_ERR(desc)); return; } @@ -1124,7 +1124,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, ret = gpiodev_add_to_list_unlocked(gdev); if (ret) { - chip_err(gc, "GPIO integer space overlap, cannot add chip\n"); + gpiochip_err(gc, "GPIO integer space overlap, cannot add chip\n"); goto err_free_label; } } @@ -1528,8 +1528,7 @@ static void gpiochip_set_hierarchical_irqchip(struct gpio_chip *gc, &parent_hwirq, &parent_type); if (ret) { - chip_err(gc, "skip set-up on hwirq %d\n", - i); + gpiochip_err(gc, "skip set-up on hwirq %d\n", i); continue; } @@ -1542,15 +1541,14 @@ static void gpiochip_set_hierarchical_irqchip(struct gpio_chip *gc, ret = irq_domain_alloc_irqs(gc->irq.domain, 1, NUMA_NO_NODE, &fwspec); if (ret < 0) { - chip_err(gc, - "can not allocate irq for GPIO line %d parent hwirq %d in hierarchy domain: %d\n", - i, parent_hwirq, - ret); + gpiochip_err(gc, + "can not allocate irq for GPIO line %d parent hwirq %d in hierarchy domain: %d\n", + i, parent_hwirq, ret); } } } - chip_err(gc, "%s unknown fwnode type proceed anyway\n", __func__); + gpiochip_err(gc, "%s unknown fwnode type proceed anyway\n", __func__); return; } @@ -1602,15 +1600,15 @@ static int gpiochip_hierarchy_irq_domain_alloc(struct irq_domain *d, if (ret) return ret; - chip_dbg(gc, "allocate IRQ %d, hwirq %lu\n", irq, hwirq); + gpiochip_dbg(gc, "allocate IRQ %d, hwirq %lu\n", irq, hwirq); ret = girq->child_to_parent_hwirq(gc, hwirq, type, &parent_hwirq, &parent_type); if (ret) { - chip_err(gc, "can't look up hwirq %lu\n", hwirq); + gpiochip_err(gc, "can't look up hwirq %lu\n", hwirq); return ret; } - chip_dbg(gc, "found parent hwirq %u\n", parent_hwirq); + gpiochip_dbg(gc, "found parent hwirq %u\n", parent_hwirq); /* * We set handle_bad_irq because the .set_type() should @@ -1631,8 +1629,8 @@ static int gpiochip_hierarchy_irq_domain_alloc(struct irq_domain *d, if (ret) return ret; - chip_dbg(gc, "alloc_irqs_parent for %d parent hwirq %d\n", - irq, parent_hwirq); + gpiochip_dbg(gc, "alloc_irqs_parent for %d parent hwirq %d\n", + irq, parent_hwirq); irq_set_lockdep_class(irq, gc->irq.lock_key, gc->irq.request_key); ret = irq_domain_alloc_irqs_parent(d, irq, 1, &gpio_parent_fwspec); /* @@ -1642,9 +1640,9 @@ static int gpiochip_hierarchy_irq_domain_alloc(struct irq_domain *d, if (irq_domain_is_msi(d->parent) && (ret == -EEXIST)) ret = 0; if (ret) - chip_err(gc, - "failed to allocate parent hwirq %d for hwirq %lu\n", - parent_hwirq, hwirq); + gpiochip_err(gc, + "failed to allocate parent hwirq %d for hwirq %lu\n", + parent_hwirq, hwirq); return ret; } @@ -1720,7 +1718,7 @@ static struct irq_domain *gpiochip_hierarchy_create_domain(struct gpio_chip *gc) if (!gc->irq.child_to_parent_hwirq || !gc->irq.fwnode) { - chip_err(gc, "missing irqdomain vital data\n"); + gpiochip_err(gc, "missing irqdomain vital data\n"); return ERR_PTR(-EINVAL); } @@ -1993,7 +1991,7 @@ static void gpiochip_set_irq_hooks(struct gpio_chip *gc) if (irqchip->flags & IRQCHIP_IMMUTABLE) return; - chip_warn(gc, "not an immutable chip, please consider fixing it!\n"); + gpiochip_warn(gc, "not an immutable chip, please consider fixing it!\n"); if (!irqchip->irq_request_resources && !irqchip->irq_release_resources) { @@ -2009,8 +2007,8 @@ static void gpiochip_set_irq_hooks(struct gpio_chip *gc) * ...and if so, give a gentle warning that this is bad * practice. */ - chip_info(gc, - "detected irqchip that is shared with multiple gpiochips: please fix the driver.\n"); + gpiochip_info(gc, + "detected irqchip that is shared with multiple gpiochips: please fix the driver.\n"); return; } @@ -2039,7 +2037,8 @@ static int gpiochip_irqchip_add_allocated_domain(struct gpio_chip *gc, return -EINVAL; if (gc->to_irq) - chip_warn(gc, "to_irq is redefined in %s and you shouldn't rely on it\n", __func__); + gpiochip_warn(gc, "to_irq is redefined in %s and you shouldn't rely on it\n", + __func__); gc->to_irq = gpiochip_to_irq; gc->irq.domain = domain; @@ -2080,7 +2079,7 @@ static int gpiochip_add_irqchip(struct gpio_chip *gc, return 0; if (gc->irq.parent_handler && gc->can_sleep) { - chip_err(gc, "you cannot have chained interrupts on a chip that may sleep\n"); + gpiochip_err(gc, "you cannot have chained interrupts on a chip that may sleep\n"); return -EINVAL; } @@ -2336,7 +2335,7 @@ int gpiochip_add_pingroup_range(struct gpio_chip *gc, pinctrl_add_gpio_range(pctldev, &pin_range->range); - chip_dbg(gc, "created GPIO range %d->%d ==> %s PINGRP %s\n", + gpiochip_dbg(gc, "created GPIO range %d->%d ==> %s PINGRP %s\n", gpio_offset, gpio_offset + pin_range->range.npins - 1, pinctrl_dev_get_devname(pctldev), pin_group); @@ -2392,19 +2391,18 @@ int gpiochip_add_pin_range_with_pins(struct gpio_chip *gc, &pin_range->range); if (IS_ERR(pin_range->pctldev)) { ret = PTR_ERR(pin_range->pctldev); - chip_err(gc, "could not create pin range\n"); + gpiochip_err(gc, "could not create pin range\n"); kfree(pin_range); return ret; } if (pin_range->range.pins) - chip_dbg(gc, "created GPIO range %d->%d ==> %s %d sparse PIN range { %d, ... }", - gpio_offset, gpio_offset + npins - 1, - pinctl_name, npins, pins[0]); + gpiochip_dbg(gc, "created GPIO range %d->%d ==> %s %d sparse PIN range { %d, ... }", + gpio_offset, gpio_offset + npins - 1, + pinctl_name, npins, pins[0]); else - chip_dbg(gc, "created GPIO range %d->%d ==> %s PIN %d->%d\n", - gpio_offset, gpio_offset + npins - 1, - pinctl_name, - pin_offset, pin_offset + npins - 1); + gpiochip_dbg(gc, "created GPIO range %d->%d ==> %s PIN %d->%d\n", + gpio_offset, gpio_offset + npins - 1, pinctl_name, + pin_offset, pin_offset + npins - 1); list_add_tail(&pin_range->node, &gdev->pin_ranges); @@ -2614,7 +2612,7 @@ struct gpio_desc *gpiochip_request_own_desc(struct gpio_chip *gc, int ret; if (IS_ERR(desc)) { - chip_err(gc, "failed to get GPIO %s descriptor\n", name); + gpiochip_err(gc, "failed to get GPIO %s descriptor\n", name); return desc; } @@ -2625,7 +2623,7 @@ struct gpio_desc *gpiochip_request_own_desc(struct gpio_chip *gc, ret = gpiod_configure_flags(desc, label, lflags, dflags); if (ret) { gpiod_free_commit(desc); - chip_err(gc, "setup of own GPIO %s failed\n", name); + gpiochip_err(gc, "setup of own GPIO %s failed\n", name); return ERR_PTR(ret); } @@ -4052,8 +4050,8 @@ int gpiochip_lock_as_irq(struct gpio_chip *gc, unsigned int offset) int dir = gpiod_get_direction(desc); if (dir < 0) { - chip_err(gc, "%s: cannot get GPIO direction\n", - __func__); + gpiochip_err(gc, "%s: cannot get GPIO direction\n", + __func__); return dir; } } @@ -4061,9 +4059,9 @@ int gpiochip_lock_as_irq(struct gpio_chip *gc, unsigned int offset) /* To be valid for IRQ the line needs to be input or open drain */ if (test_bit(GPIOD_FLAG_IS_OUT, &desc->flags) && !test_bit(GPIOD_FLAG_OPEN_DRAIN, &desc->flags)) { - chip_err(gc, - "%s: tried to flag a GPIO set as output for IRQ\n", - __func__); + gpiochip_err(gc, + "%s: tried to flag a GPIO set as output for IRQ\n", + __func__); return -EIO; } @@ -4140,7 +4138,7 @@ int gpiochip_reqres_irq(struct gpio_chip *gc, unsigned int offset) ret = gpiochip_lock_as_irq(gc, offset); if (ret) { - chip_err(gc, "unable to lock HW IRQ %u for IRQ\n", offset); + gpiochip_err(gc, "unable to lock HW IRQ %u for IRQ\n", offset); module_put(gc->gpiodev->owner); return ret; } diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index 2a003a7311e7..6ee29d022239 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -309,13 +309,13 @@ do { \ /* With chip prefix */ -#define chip_err(gc, fmt, ...) \ +#define gpiochip_err(gc, fmt, ...) \ dev_err(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__) -#define chip_warn(gc, fmt, ...) \ +#define gpiochip_warn(gc, fmt, ...) \ dev_warn(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__) -#define chip_info(gc, fmt, ...) \ +#define gpiochip_info(gc, fmt, ...) \ dev_info(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__) -#define chip_dbg(gc, fmt, ...) \ +#define gpiochip_dbg(gc, fmt, ...) \ dev_dbg(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__) #endif /* GPIOLIB_H */ From 3f0be1783a8ff57f77e6f9a12621903b5a496d40 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 22 Sep 2025 11:54:04 +0200 Subject: [PATCH 03/80] gpiolib: reuse macro code in GPIO descriptor printk helpers A lot of code in gpiod_$level() macros is duplicated across all definitions. Create an intermediate macro which allows us to reuse the low-level code. Reviewed-by: Linus Walleij Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.h | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index 6ee29d022239..dd96b2c2e16e 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -283,29 +283,17 @@ static inline int gpio_chip_hwgpio(const struct gpio_desc *desc) /* With descriptor prefix */ -#define gpiod_err(desc, fmt, ...) \ +#define __gpiod_pr(level, desc, fmt, ...) \ do { \ scoped_guard(srcu, &desc->gdev->desc_srcu) { \ - pr_err("gpio-%d (%s): " fmt, desc_to_gpio(desc), \ - gpiod_get_label(desc) ? : "?", ##__VA_ARGS__); \ + pr_##level("gpio-%d (%s): " fmt, desc_to_gpio(desc), \ + gpiod_get_label(desc) ?: "?", ##__VA_ARGS__); \ } \ } while (0) -#define gpiod_warn(desc, fmt, ...) \ -do { \ - scoped_guard(srcu, &desc->gdev->desc_srcu) { \ - pr_warn("gpio-%d (%s): " fmt, desc_to_gpio(desc), \ - gpiod_get_label(desc) ? : "?", ##__VA_ARGS__); \ - } \ -} while (0) - -#define gpiod_dbg(desc, fmt, ...) \ -do { \ - scoped_guard(srcu, &desc->gdev->desc_srcu) { \ - pr_debug("gpio-%d (%s): " fmt, desc_to_gpio(desc), \ - gpiod_get_label(desc) ? : "?", ##__VA_ARGS__); \ - } \ -} while (0) +#define gpiod_err(desc, fmt, ...) __gpiod_pr(err, desc, fmt, ##__VA_ARGS__) +#define gpiod_warn(desc, fmt, ...) __gpiod_pr(warn, desc, fmt, ##__VA_ARGS__) +#define gpiod_dbg(desc, fmt, ...) __gpiod_pr(debug, desc, fmt, ##__VA_ARGS__) /* With chip prefix */ From 1540b799d271b545bf04726906184bdf29ab272b Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 22 Sep 2025 11:54:05 +0200 Subject: [PATCH 04/80] gpiolib: reuse macro code in GPIO chip printk helpers The arguments passed to dev_$level() macros are duplicated across the gpiochip_$level() macros so put them under an intermediate wrapper. While at it: wrap it in a do-while guard. Reviewed-by: Linus Walleij Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.h | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index dd96b2c2e16e..b4c5369f8a33 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -297,13 +297,14 @@ do { \ /* With chip prefix */ -#define gpiochip_err(gc, fmt, ...) \ - dev_err(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__) -#define gpiochip_warn(gc, fmt, ...) \ - dev_warn(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__) -#define gpiochip_info(gc, fmt, ...) \ - dev_info(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__) -#define gpiochip_dbg(gc, fmt, ...) \ - dev_dbg(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__) +#define __gpiochip_pr(level, gc, fmt, ...) \ +do { \ + dev_##level(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__); \ +} while (0) + +#define gpiochip_err(gc, fmt, ...) __gpiochip_pr(err, gc, fmt, ##__VA_ARGS__) +#define gpiochip_warn(gc, fmt, ...) __gpiochip_pr(warn, gc, fmt, ##__VA_ARGS__) +#define gpiochip_info(gc, fmt, ...) __gpiochip_pr(info, gc, fmt, ##__VA_ARGS__) +#define gpiochip_dbg(gc, fmt, ...) __gpiochip_pr(dbg, gc, fmt, ##__VA_ARGS__) #endif /* GPIOLIB_H */ From 383760e3faa4d3df6e399d207e2930a785380c4e Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 22 Sep 2025 11:58:41 +0200 Subject: [PATCH 05/80] gpio: virtuser: check the return value of gpiod_set_value() We converted gpiod_set_value() and its variants to return an integer to indicate failures. Check the return value where it's ignored currently so that user-space agents controlling the virtual user module can get notified about errors. Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-virtuser.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/gpio/gpio-virtuser.c b/drivers/gpio/gpio-virtuser.c index a10eab7d2617..37f2ce20f1ae 100644 --- a/drivers/gpio/gpio-virtuser.c +++ b/drivers/gpio/gpio-virtuser.c @@ -500,9 +500,7 @@ static int gpio_virtuser_value_set(void *data, u64 val) if (val > 1) return -EINVAL; - gpiod_set_value_cansleep(ld->ad.desc, (int)val); - - return 0; + return gpiod_set_value_cansleep(ld->ad.desc, (int)val); } DEFINE_DEBUGFS_ATTRIBUTE(gpio_virtuser_value_fops, @@ -543,7 +541,7 @@ static void gpio_virtuser_set_value_atomic(struct irq_work *work) struct gpio_virtuser_irq_work_context *ctx = to_gpio_virtuser_irq_work_context(work); - gpiod_set_value(ctx->desc, ctx->val); + ctx->ret = gpiod_set_value(ctx->desc, ctx->val); complete(&ctx->work_completion); } @@ -562,7 +560,7 @@ static int gpio_virtuser_value_atomic_set(void *data, u64 val) gpio_virtuser_irq_work_queue_sync(&ctx); - return 0; + return ctx.ret; } DEFINE_DEBUGFS_ATTRIBUTE(gpio_virtuser_value_atomic_fops, From 411b39bd1ac68c44f9e3c7dce5e095f6a71598c1 Mon Sep 17 00:00:00 2001 From: Alex Tran Date: Wed, 1 Oct 2025 22:57:27 -0700 Subject: [PATCH 06/80] gpio: grgpio: call request_irq after incrementing the reference count Remove extraneous dropping of the lock just to call 'request_irq' and locking again afterwards. Increment reference count before calling 'request_irq'. Rollback reference count if 'request_irq' fails. Suggested-by: Bartosz Golaszewski Signed-off-by: Alex Tran Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-grgpio.c | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/drivers/gpio/gpio-grgpio.c b/drivers/gpio/gpio-grgpio.c index 0c0f97fa14fc..e4fa84e22726 100644 --- a/drivers/gpio/gpio-grgpio.c +++ b/drivers/gpio/gpio-grgpio.c @@ -46,7 +46,7 @@ /* Structure for an irq of the core - called an underlying irq */ struct grgpio_uirq { - u8 refcnt; /* Reference counter to manage requesting/freeing of uirq */ + atomic_t refcnt; /* Reference counter to manage requesting/freeing of uirq */ u8 uirq; /* Underlying irq of the gpio driver */ }; @@ -242,30 +242,22 @@ static int grgpio_irq_map(struct irq_domain *d, unsigned int irq, irq, offset); gpio_generic_chip_lock_irqsave(&priv->chip, flags); - - /* Request underlying irq if not already requested */ lirq->irq = irq; uirq = &priv->uirqs[lirq->index]; - if (uirq->refcnt == 0) { - /* - * FIXME: This is not how locking works at all, you can't just - * release the lock for a moment to do something that can't - * sleep... - */ - gpio_generic_chip_unlock_irqrestore(&priv->chip, flags); + gpio_generic_chip_unlock_irqrestore(&priv->chip, flags); + + /* Request underlying irq if not already requested */ + if (atomic_fetch_add(1, &uirq->refcnt) == 0) { ret = request_irq(uirq->uirq, grgpio_irq_handler, 0, dev_name(priv->dev), priv); if (ret) { dev_err(priv->dev, "Could not request underlying irq %d\n", uirq->uirq); + atomic_dec(&uirq->refcnt); /* rollback */ return ret; } - gpio_generic_chip_lock_irqsave(&priv->chip, flags); } - uirq->refcnt++; - - gpio_generic_chip_unlock_irqrestore(&priv->chip, flags); /* Setup irq */ irq_set_chip_data(irq, priv); @@ -306,8 +298,7 @@ static void grgpio_irq_unmap(struct irq_domain *d, unsigned int irq) if (index >= 0) { uirq = &priv->uirqs[lirq->index]; - uirq->refcnt--; - if (uirq->refcnt == 0) { + if (atomic_dec_and_test(&uirq->refcnt)) { gpio_generic_chip_unlock_irqrestore(&priv->chip, flags); free_irq(uirq->uirq, priv); return; @@ -434,6 +425,7 @@ static int grgpio_probe(struct platform_device *ofdev) continue; } priv->uirqs[lirq->index].uirq = ret; + atomic_set(&priv->uirqs[lirq->index].refcnt, 0); } } From d5896130a8781de5ac8970dbb7083ce4cd6fe57a Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Tue, 14 Oct 2025 18:53:50 +0300 Subject: [PATCH 07/80] dt-bindings: gpio: add QIXIS FPGA based GPIO controller Add a device tree binding for the QIXIS FPGA based GPIO controller. Depending on the board, the QIXIS FPGA exposes registers which act as a GPIO controller, each with 8 GPIO lines of fixed direction. Since each QIXIS FPGA layout has its particularities, add a separate compatible string for each board/GPIO register combination supported. Since these GPIO controllers are trivial, make use of the newly added trivial-gpio.yaml file instead of creating an entirely new one. Signed-off-by: Ioana Ciornei Acked-by: Rob Herring (Arm) Reviewed-by: Frank Li Acked-by: Bartosz Golaszewski Signed-off-by: Bartosz Golaszewski --- Documentation/devicetree/bindings/gpio/trivial-gpio.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/gpio/trivial-gpio.yaml b/Documentation/devicetree/bindings/gpio/trivial-gpio.yaml index c994177de940..3f4bbd57fc52 100644 --- a/Documentation/devicetree/bindings/gpio/trivial-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/trivial-gpio.yaml @@ -22,6 +22,8 @@ properties: - cznic,moxtet-gpio - dlg,slg7xl45106 - fcs,fxl6408 + - fsl,ls1046aqds-fpga-gpio-stat-pres2 + - fsl,lx2160ardb-fpga-gpio-sfp - gateworks,pld-gpio - ibm,ppc4xx-gpio - loongson,ls1x-gpio From ae495810cffe29c3c30a757bd48b0bb035fc3098 Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Tue, 14 Oct 2025 18:53:53 +0300 Subject: [PATCH 08/80] gpio: regmap: add the .fixed_direction_output configuration parameter There are GPIO controllers such as the one present in the LX2160ARDB QIXIS FPGA which have fixed-direction input and output GPIO lines mixed together in a single register. This cannot be modeled using the gpio-regmap as-is since there is no way to present the true direction of a GPIO line. In order to make this use case possible, add a new configuration parameter - fixed_direction_output - into the gpio_regmap_config structure. This will enable user drivers to provide a bitmap that represents the fixed direction of the GPIO lines. Signed-off-by: Ioana Ciornei Acked-by: Bartosz Golaszewski Reviewed-by: Michael Walle Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-regmap.c | 26 ++++++++++++++++++++++++-- include/linux/gpio/regmap.h | 5 +++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c index ab9e4077fa60..f4267af00027 100644 --- a/drivers/gpio/gpio-regmap.c +++ b/drivers/gpio/gpio-regmap.c @@ -31,6 +31,7 @@ struct gpio_regmap { unsigned int reg_clr_base; unsigned int reg_dir_in_base; unsigned int reg_dir_out_base; + unsigned long *fixed_direction_output; #ifdef CONFIG_REGMAP_IRQ int regmap_irq_line; @@ -134,6 +135,13 @@ static int gpio_regmap_get_direction(struct gpio_chip *chip, unsigned int base, val, reg, mask; int invert, ret; + if (gpio->fixed_direction_output) { + if (test_bit(offset, gpio->fixed_direction_output)) + return GPIO_LINE_DIRECTION_OUT; + else + return GPIO_LINE_DIRECTION_IN; + } + if (gpio->reg_dat_base && !gpio->reg_set_base) return GPIO_LINE_DIRECTION_IN; if (gpio->reg_set_base && !gpio->reg_dat_base) @@ -284,6 +292,17 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config goto err_free_gpio; } + if (config->fixed_direction_output) { + gpio->fixed_direction_output = bitmap_alloc(chip->ngpio, + GFP_KERNEL); + if (!gpio->fixed_direction_output) { + ret = -ENOMEM; + goto err_free_gpio; + } + bitmap_copy(gpio->fixed_direction_output, + config->fixed_direction_output, chip->ngpio); + } + /* if not set, assume there is only one register */ gpio->ngpio_per_reg = config->ngpio_per_reg; if (!gpio->ngpio_per_reg) @@ -300,7 +319,7 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config ret = gpiochip_add_data(chip, gpio); if (ret < 0) - goto err_free_gpio; + goto err_free_bitmap; #ifdef CONFIG_REGMAP_IRQ if (config->regmap_irq_chip) { @@ -309,7 +328,7 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config config->regmap_irq_line, config->regmap_irq_flags, 0, config->regmap_irq_chip, &gpio->irq_chip_data); if (ret) - goto err_free_gpio; + goto err_free_bitmap; irq_domain = regmap_irq_get_domain(gpio->irq_chip_data); } else @@ -326,6 +345,8 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config err_remove_gpiochip: gpiochip_remove(chip); +err_free_bitmap: + bitmap_free(gpio->fixed_direction_output); err_free_gpio: kfree(gpio); return ERR_PTR(ret); @@ -344,6 +365,7 @@ void gpio_regmap_unregister(struct gpio_regmap *gpio) #endif gpiochip_remove(&gpio->gpio_chip); + bitmap_free(gpio->fixed_direction_output); kfree(gpio); } EXPORT_SYMBOL_GPL(gpio_regmap_unregister); diff --git a/include/linux/gpio/regmap.h b/include/linux/gpio/regmap.h index 622a2939ebe0..87983a5f3681 100644 --- a/include/linux/gpio/regmap.h +++ b/include/linux/gpio/regmap.h @@ -38,6 +38,10 @@ struct regmap; * offset to a register/bitmask pair. If not * given the default gpio_regmap_simple_xlate() * is used. + * @fixed_direction_output: + * (Optional) Bitmap representing the fixed direction of + * the GPIO lines. Useful when there are GPIO lines with a + * fixed direction mixed together in the same register. * @drvdata: (Optional) Pointer to driver specific data which is * not used by gpio-remap but is provided "as is" to the * driver callback(s). @@ -85,6 +89,7 @@ struct gpio_regmap_config { int reg_stride; int ngpio_per_reg; struct irq_domain *irq_domain; + unsigned long *fixed_direction_output; #ifdef CONFIG_REGMAP_IRQ struct regmap_irq_chip *regmap_irq_chip; From e88500247dc3267787abc837848b001c1237f692 Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Tue, 14 Oct 2025 18:53:54 +0300 Subject: [PATCH 09/80] gpio: add QIXIS FPGA GPIO controller Add support for the GPIO controller found on some QIXIS FPGAs in Layerscape boards such as LX2160ARDB and LS1046AQDS. This driver is using gpio-regmap. A GPIO controller has a maximum of 8 lines (all found in the same register). Even within the same controller, the GPIO lines' direction is fixed, which mean that both input and output lines are found in the same register. This is why the driver also passed to gpio-regmap the newly added .fixed_direction_output bitmap to represent the true direction of the lines. Signed-off-by: Ioana Ciornei Reviewed-by: Frank Li Reviewed-by: Linus Walleij Acked-by: Bartosz Golaszewski Reviewed-by: Michael Walle # for the gpio-regmap part Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 9 +++ drivers/gpio/Makefile | 1 + drivers/gpio/gpio-qixis-fpga.c | 107 +++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 drivers/gpio/gpio-qixis-fpga.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 7ee3afbc2b05..2dcfbc05980c 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1565,6 +1565,15 @@ config GPIO_PMIC_EIC_SPRD help Say yes here to support Spreadtrum PMIC EIC device. +config GPIO_QIXIS_FPGA + tristate "NXP QIXIS FPGA GPIO support" + depends on MFD_SIMPLE_MFD_I2C || COMPILE_TEST + select GPIO_REGMAP + help + This enables support for the GPIOs found in the QIXIS FPGA which is + integrated on some NXP Layerscape boards such as LX2160ARDB and + LS1046AQDS. + config GPIO_RC5T583 bool "RICOH RC5T583 GPIO" depends on MFD_RC5T583 diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index ec296fa14bfd..ee260a0809d3 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -146,6 +146,7 @@ obj-$(CONFIG_GPIO_PL061) += gpio-pl061.o obj-$(CONFIG_GPIO_PMIC_EIC_SPRD) += gpio-pmic-eic-sprd.o obj-$(CONFIG_GPIO_POLARFIRE_SOC) += gpio-mpfs.o obj-$(CONFIG_GPIO_PXA) += gpio-pxa.o +obj-$(CONFIG_GPIO_QIXIS_FPGA) += gpio-qixis-fpga.o obj-$(CONFIG_GPIO_RASPBERRYPI_EXP) += gpio-raspberrypi-exp.o obj-$(CONFIG_GPIO_RC5T583) += gpio-rc5t583.o obj-$(CONFIG_GPIO_RCAR) += gpio-rcar.o diff --git a/drivers/gpio/gpio-qixis-fpga.c b/drivers/gpio/gpio-qixis-fpga.c new file mode 100644 index 000000000000..048a2cac4f0f --- /dev/null +++ b/drivers/gpio/gpio-qixis-fpga.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Layerscape GPIO QIXIS FPGA driver + * + * Copyright 2025 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct qixis_cpld_gpio_config { + u64 output_lines; +}; + +static const struct qixis_cpld_gpio_config lx2160ardb_sfp_cfg = { + .output_lines = BIT(0), +}; + +static const struct qixis_cpld_gpio_config ls1046aqds_stat_pres2_cfg = { + .output_lines = 0x0, +}; + +static const struct regmap_config regmap_config_8r_8v = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int qixis_cpld_gpio_probe(struct platform_device *pdev) +{ + DECLARE_BITMAP(fixed_direction_output, 8); + const struct qixis_cpld_gpio_config *cfg; + struct gpio_regmap_config config = {0}; + struct regmap *regmap; + void __iomem *reg; + u32 base; + int ret; + + if (!pdev->dev.parent) + return -ENODEV; + + cfg = device_get_match_data(&pdev->dev); + + ret = device_property_read_u32(&pdev->dev, "reg", &base); + if (ret) + return ret; + + regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!regmap) { + /* In case there is no regmap configured by the parent device, + * create our own from the MMIO space. + */ + reg = devm_platform_ioremap_resource(pdev, 0); + if (!reg) + return -ENODEV; + + regmap = devm_regmap_init_mmio(&pdev->dev, reg, ®map_config_8r_8v); + if (!regmap) + return -ENODEV; + + /* In this case, the offset of our register is 0 inside the + * regmap area that we just created. + */ + base = 0; + } + config.reg_dat_base = GPIO_REGMAP_ADDR(base); + config.reg_set_base = GPIO_REGMAP_ADDR(base); + + config.drvdata = (void *)cfg; + config.regmap = regmap; + config.parent = &pdev->dev; + config.ngpio_per_reg = 8; + config.ngpio = 8; + + bitmap_from_u64(fixed_direction_output, cfg->output_lines); + config.fixed_direction_output = fixed_direction_output; + + return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(&pdev->dev, &config)); +} + +static const struct of_device_id qixis_cpld_gpio_of_match[] = { + { + .compatible = "fsl,lx2160ardb-fpga-gpio-sfp", + .data = &lx2160ardb_sfp_cfg, + }, + { + .compatible = "fsl,ls1046aqds-fpga-gpio-stat-pres2", + .data = &ls1046aqds_stat_pres2_cfg, + }, + + {} +}; +MODULE_DEVICE_TABLE(of, qixis_cpld_gpio_of_match); + +static struct platform_driver qixis_cpld_gpio_driver = { + .probe = qixis_cpld_gpio_probe, + .driver = { + .name = "gpio-qixis-cpld", + .of_match_table = qixis_cpld_gpio_of_match, + }, +}; +module_platform_driver(qixis_cpld_gpio_driver); From 9f0fa1801fe4503eb119a4523a59a494768fda5d Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Wed, 8 Oct 2025 12:43:09 +0200 Subject: [PATCH 10/80] gpio: pca953x: enable latch only on edge-triggered inputs The latched input feature of the pca953x GPIO controller is useful when an input is configured to trigger interrupts on rising or falling edges, because it allows retrieving which edge type caused a given interrupt even if the pin state changes again before the interrupt handler has a chance to run. But for level-triggered interrupts, reading the latched input state can cause an active interrupt condition to be missed, e.g. if an active-low signal (for which an IRQ_TYPE_LEVEL_LOW interrupt has been configured) triggers an interrupt when switching to the inactive state, but then becomes active again before the interrupt handler has a chance to run: in this case, if the interrupt handler reads the latched input state, it will wrongly assume that the interrupt is not pending. Fix the above issue by enabling the latch only on edge-triggered inputs, instead of all interrupt-enabled inputs. Signed-off-by: Francesco Lavra Reviewed-by: Ian Ray Reviewed-by: Martyn Welch Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-pca953x.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-pca953x.c b/drivers/gpio/gpio-pca953x.c index b46927f55038..9e72439f6c92 100644 --- a/drivers/gpio/gpio-pca953x.c +++ b/drivers/gpio/gpio-pca953x.c @@ -854,10 +854,13 @@ static void pca953x_irq_bus_sync_unlock(struct irq_data *d) int level; if (chip->driver_data & PCA_PCAL) { + DECLARE_BITMAP(latched_inputs, MAX_LINE); guard(mutex)(&chip->i2c_lock); - /* Enable latch on interrupt-enabled inputs */ - pca953x_write_regs(chip, PCAL953X_IN_LATCH, chip->irq_mask); + /* Enable latch on edge-triggered interrupt-enabled inputs */ + bitmap_or(latched_inputs, chip->irq_trig_fall, chip->irq_trig_raise, gc->ngpio); + bitmap_and(latched_inputs, latched_inputs, chip->irq_mask, gc->ngpio); + pca953x_write_regs(chip, PCAL953X_IN_LATCH, latched_inputs); bitmap_complement(irq_mask, chip->irq_mask, gc->ngpio); From f75db6f7f907c10bf4d45a6cfdae03bb1b631841 Mon Sep 17 00:00:00 2001 From: Kartik Rajput Date: Fri, 10 Oct 2025 15:43:30 +0530 Subject: [PATCH 11/80] gpio: tegra186: Use generic macro for port definitions Introduce a generic macro TEGRA_GPIO_PORT to define SoC specific ports macros. This simplifies the code and avoids unnecessary duplication. Suggested-by: Jon Hunter Signed-off-by: Kartik Rajput Reviewed-by: Jon Hunter Acked-by: Thierry Reding Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-tegra186.c | 87 +++++++++++------------------------- 1 file changed, 25 insertions(+), 62 deletions(-) diff --git a/drivers/gpio/gpio-tegra186.c b/drivers/gpio/gpio-tegra186.c index 4d3db6e06eeb..7ea541d6d537 100644 --- a/drivers/gpio/gpio-tegra186.c +++ b/drivers/gpio/gpio-tegra186.c @@ -1002,14 +1002,17 @@ static int tegra186_gpio_probe(struct platform_device *pdev) return devm_gpiochip_add_data(&pdev->dev, &gpio->gpio, gpio); } -#define TEGRA186_MAIN_GPIO_PORT(_name, _bank, _port, _pins) \ - [TEGRA186_MAIN_GPIO_PORT_##_name] = { \ - .name = #_name, \ - .bank = _bank, \ - .port = _port, \ - .pins = _pins, \ +#define TEGRA_GPIO_PORT(_prefix, _name, _bank, _port, _pins) \ + [_prefix##_GPIO_PORT_##_name] = { \ + .name = #_name, \ + .bank = _bank, \ + .port = _port, \ + .pins = _pins, \ } +#define TEGRA186_MAIN_GPIO_PORT(_name, _bank, _port, _pins) \ + TEGRA_GPIO_PORT(TEGRA186_MAIN, _name, _bank, _port, _pins) + static const struct tegra_gpio_port tegra186_main_ports[] = { TEGRA186_MAIN_GPIO_PORT( A, 2, 0, 7), TEGRA186_MAIN_GPIO_PORT( B, 3, 0, 7), @@ -1045,13 +1048,8 @@ static const struct tegra_gpio_soc tegra186_main_soc = { .has_vm_support = false, }; -#define TEGRA186_AON_GPIO_PORT(_name, _bank, _port, _pins) \ - [TEGRA186_AON_GPIO_PORT_##_name] = { \ - .name = #_name, \ - .bank = _bank, \ - .port = _port, \ - .pins = _pins, \ - } +#define TEGRA186_AON_GPIO_PORT(_name, _bank, _port, _pins) \ + TEGRA_GPIO_PORT(TEGRA186_AON, _name, _bank, _port, _pins) static const struct tegra_gpio_port tegra186_aon_ports[] = { TEGRA186_AON_GPIO_PORT( S, 0, 1, 5), @@ -1073,13 +1071,8 @@ static const struct tegra_gpio_soc tegra186_aon_soc = { .has_vm_support = false, }; -#define TEGRA194_MAIN_GPIO_PORT(_name, _bank, _port, _pins) \ - [TEGRA194_MAIN_GPIO_PORT_##_name] = { \ - .name = #_name, \ - .bank = _bank, \ - .port = _port, \ - .pins = _pins, \ - } +#define TEGRA194_MAIN_GPIO_PORT(_name, _bank, _port, _pins) \ + TEGRA_GPIO_PORT(TEGRA194_MAIN, _name, _bank, _port, _pins) static const struct tegra_gpio_port tegra194_main_ports[] = { TEGRA194_MAIN_GPIO_PORT( A, 1, 2, 8), @@ -1129,13 +1122,8 @@ static const struct tegra_gpio_soc tegra194_main_soc = { .has_vm_support = true, }; -#define TEGRA194_AON_GPIO_PORT(_name, _bank, _port, _pins) \ - [TEGRA194_AON_GPIO_PORT_##_name] = { \ - .name = #_name, \ - .bank = _bank, \ - .port = _port, \ - .pins = _pins, \ - } +#define TEGRA194_AON_GPIO_PORT(_name, _bank, _port, _pins) \ + TEGRA_GPIO_PORT(TEGRA194_AON, _name, _bank, _port, _pins) static const struct tegra_gpio_port tegra194_aon_ports[] = { TEGRA194_AON_GPIO_PORT(AA, 0, 3, 8), @@ -1155,13 +1143,8 @@ static const struct tegra_gpio_soc tegra194_aon_soc = { .has_vm_support = false, }; -#define TEGRA234_MAIN_GPIO_PORT(_name, _bank, _port, _pins) \ - [TEGRA234_MAIN_GPIO_PORT_##_name] = { \ - .name = #_name, \ - .bank = _bank, \ - .port = _port, \ - .pins = _pins, \ - } +#define TEGRA234_MAIN_GPIO_PORT(_name, _bank, _port, _pins) \ + TEGRA_GPIO_PORT(TEGRA234_MAIN, _name, _bank, _port, _pins) static const struct tegra_gpio_port tegra234_main_ports[] = { TEGRA234_MAIN_GPIO_PORT( A, 0, 0, 8), @@ -1200,13 +1183,8 @@ static const struct tegra_gpio_soc tegra234_main_soc = { .has_vm_support = true, }; -#define TEGRA234_AON_GPIO_PORT(_name, _bank, _port, _pins) \ - [TEGRA234_AON_GPIO_PORT_##_name] = { \ - .name = #_name, \ - .bank = _bank, \ - .port = _port, \ - .pins = _pins, \ - } +#define TEGRA234_AON_GPIO_PORT(_name, _bank, _port, _pins) \ + TEGRA_GPIO_PORT(TEGRA234_AON, _name, _bank, _port, _pins) static const struct tegra_gpio_port tegra234_aon_ports[] = { TEGRA234_AON_GPIO_PORT(AA, 0, 4, 8), @@ -1227,13 +1205,8 @@ static const struct tegra_gpio_soc tegra234_aon_soc = { .has_vm_support = false, }; -#define TEGRA241_MAIN_GPIO_PORT(_name, _bank, _port, _pins) \ - [TEGRA241_MAIN_GPIO_PORT_##_name] = { \ - .name = #_name, \ - .bank = _bank, \ - .port = _port, \ - .pins = _pins, \ - } +#define TEGRA241_MAIN_GPIO_PORT(_name, _bank, _port, _pins) \ + TEGRA_GPIO_PORT(TEGRA241_MAIN, _name, _bank, _port, _pins) static const struct tegra_gpio_port tegra241_main_ports[] = { TEGRA241_MAIN_GPIO_PORT(A, 0, 0, 8), @@ -1258,13 +1231,8 @@ static const struct tegra_gpio_soc tegra241_main_soc = { .has_vm_support = false, }; -#define TEGRA241_AON_GPIO_PORT(_name, _bank, _port, _pins) \ - [TEGRA241_AON_GPIO_PORT_##_name] = { \ - .name = #_name, \ - .bank = _bank, \ - .port = _port, \ - .pins = _pins, \ - } +#define TEGRA241_AON_GPIO_PORT(_name, _bank, _port, _pins) \ + TEGRA_GPIO_PORT(TEGRA241_AON, _name, _bank, _port, _pins) static const struct tegra_gpio_port tegra241_aon_ports[] = { TEGRA241_AON_GPIO_PORT(AA, 0, 0, 8), @@ -1280,13 +1248,8 @@ static const struct tegra_gpio_soc tegra241_aon_soc = { .has_vm_support = false, }; -#define TEGRA256_MAIN_GPIO_PORT(_name, _bank, _port, _pins) \ - [TEGRA256_MAIN_GPIO_PORT_##_name] = { \ - .name = #_name, \ - .bank = _bank, \ - .port = _port, \ - .pins = _pins, \ - } +#define TEGRA256_MAIN_GPIO_PORT(_name, _bank, _port, _pins) \ + TEGRA_GPIO_PORT(TEGRA256_MAIN, _name, _bank, _port, _pins) static const struct tegra_gpio_port tegra256_main_ports[] = { TEGRA256_MAIN_GPIO_PORT(A, 0, 0, 8), From 9631a10083d843b57b371d406235e2f2a3e49285 Mon Sep 17 00:00:00 2001 From: Prathamesh Shete Date: Fri, 10 Oct 2025 15:43:31 +0530 Subject: [PATCH 12/80] gpio: tegra186: Add support for Tegra410 Extend the existing Tegra186 GPIO controller driver with support for the GPIO controller found on Tegra410. Tegra410 supports two GPIO controllers referred to as 'COMPUTE' and 'SYSTEM'. Co-developed-by: Nathan Hartman Signed-off-by: Nathan Hartman Signed-off-by: Prathamesh Shete Signed-off-by: Kartik Rajput Acked-by: Thierry Reding Reviewed-by: Jon Hunter Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-tegra186.c | 76 +++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-tegra186.c b/drivers/gpio/gpio-tegra186.c index 7ea541d6d537..83ecdc876985 100644 --- a/drivers/gpio/gpio-tegra186.c +++ b/drivers/gpio/gpio-tegra186.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2016-2022 NVIDIA Corporation + * Copyright (c) 2016-2025 NVIDIA Corporation * * Author: Thierry Reding * Dipen Patel @@ -69,6 +69,30 @@ #define TEGRA186_GPIO_INTERRUPT_STATUS(x) (0x100 + (x) * 4) +/* Tegra410 GPIOs implemented by the COMPUTE GPIO controller */ +#define TEGRA410_COMPUTE_GPIO_PORT_A 0 +#define TEGRA410_COMPUTE_GPIO_PORT_B 1 +#define TEGRA410_COMPUTE_GPIO_PORT_C 2 +#define TEGRA410_COMPUTE_GPIO_PORT_D 3 +#define TEGRA410_COMPUTE_GPIO_PORT_E 4 + +/* Tegra410 GPIOs implemented by the SYSTEM GPIO controller */ +#define TEGRA410_SYSTEM_GPIO_PORT_A 0 +#define TEGRA410_SYSTEM_GPIO_PORT_B 1 +#define TEGRA410_SYSTEM_GPIO_PORT_C 2 +#define TEGRA410_SYSTEM_GPIO_PORT_D 3 +#define TEGRA410_SYSTEM_GPIO_PORT_E 4 +#define TEGRA410_SYSTEM_GPIO_PORT_I 5 +#define TEGRA410_SYSTEM_GPIO_PORT_J 6 +#define TEGRA410_SYSTEM_GPIO_PORT_K 7 +#define TEGRA410_SYSTEM_GPIO_PORT_L 8 +#define TEGRA410_SYSTEM_GPIO_PORT_M 9 +#define TEGRA410_SYSTEM_GPIO_PORT_N 10 +#define TEGRA410_SYSTEM_GPIO_PORT_P 11 +#define TEGRA410_SYSTEM_GPIO_PORT_Q 12 +#define TEGRA410_SYSTEM_GPIO_PORT_R 13 +#define TEGRA410_SYSTEM_GPIO_PORT_V 14 + struct tegra_gpio_port { const char *name; unsigned int bank; @@ -1267,6 +1291,54 @@ static const struct tegra_gpio_soc tegra256_main_soc = { .has_vm_support = true, }; +#define TEGRA410_COMPUTE_GPIO_PORT(_name, _bank, _port, _pins) \ + TEGRA_GPIO_PORT(TEGRA410_COMPUTE, _name, _bank, _port, _pins) + +static const struct tegra_gpio_port tegra410_compute_ports[] = { + TEGRA410_COMPUTE_GPIO_PORT(A, 0, 0, 3), + TEGRA410_COMPUTE_GPIO_PORT(B, 1, 0, 8), + TEGRA410_COMPUTE_GPIO_PORT(C, 1, 1, 3), + TEGRA410_COMPUTE_GPIO_PORT(D, 2, 0, 8), + TEGRA410_COMPUTE_GPIO_PORT(E, 2, 1, 8), +}; + +static const struct tegra_gpio_soc tegra410_compute_soc = { + .num_ports = ARRAY_SIZE(tegra410_compute_ports), + .ports = tegra410_compute_ports, + .name = "tegra410-gpio-compute", + .num_irqs_per_bank = 8, + .instance = 0, +}; + +#define TEGRA410_SYSTEM_GPIO_PORT(_name, _bank, _port, _pins) \ + TEGRA_GPIO_PORT(TEGRA410_SYSTEM, _name, _bank, _port, _pins) + +static const struct tegra_gpio_port tegra410_system_ports[] = { + TEGRA410_SYSTEM_GPIO_PORT(A, 0, 0, 7), + TEGRA410_SYSTEM_GPIO_PORT(B, 0, 1, 8), + TEGRA410_SYSTEM_GPIO_PORT(C, 0, 2, 8), + TEGRA410_SYSTEM_GPIO_PORT(D, 0, 3, 8), + TEGRA410_SYSTEM_GPIO_PORT(E, 0, 4, 6), + TEGRA410_SYSTEM_GPIO_PORT(I, 1, 0, 8), + TEGRA410_SYSTEM_GPIO_PORT(J, 1, 1, 7), + TEGRA410_SYSTEM_GPIO_PORT(K, 1, 2, 7), + TEGRA410_SYSTEM_GPIO_PORT(L, 1, 3, 7), + TEGRA410_SYSTEM_GPIO_PORT(M, 2, 0, 7), + TEGRA410_SYSTEM_GPIO_PORT(N, 2, 1, 6), + TEGRA410_SYSTEM_GPIO_PORT(P, 2, 2, 8), + TEGRA410_SYSTEM_GPIO_PORT(Q, 2, 3, 3), + TEGRA410_SYSTEM_GPIO_PORT(R, 2, 4, 2), + TEGRA410_SYSTEM_GPIO_PORT(V, 1, 4, 2), +}; + +static const struct tegra_gpio_soc tegra410_system_soc = { + .num_ports = ARRAY_SIZE(tegra410_system_ports), + .ports = tegra410_system_ports, + .name = "tegra410-gpio-system", + .num_irqs_per_bank = 8, + .instance = 0, +}; + static const struct of_device_id tegra186_gpio_of_match[] = { { .compatible = "nvidia,tegra186-gpio", @@ -1302,6 +1374,8 @@ static const struct acpi_device_id tegra186_gpio_acpi_match[] = { { .id = "NVDA0408", .driver_data = (kernel_ulong_t)&tegra194_aon_soc }, { .id = "NVDA0508", .driver_data = (kernel_ulong_t)&tegra241_main_soc }, { .id = "NVDA0608", .driver_data = (kernel_ulong_t)&tegra241_aon_soc }, + { .id = "NVDA0708", .driver_data = (kernel_ulong_t)&tegra410_compute_soc }, + { .id = "NVDA0808", .driver_data = (kernel_ulong_t)&tegra410_system_soc }, {} }; MODULE_DEVICE_TABLE(acpi, tegra186_gpio_acpi_match); From 8d0d46da40c878d082b92771355faba8036aecc7 Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Mon, 13 Oct 2025 15:07:14 +0200 Subject: [PATCH 13/80] gpio: mm-lantiq: Drop legacy-of-mm-gpiochip.h header from GPIO driver Remove legacy-of-mm-gpiochip.h header file. The above mentioned file provides an OF API that's deprecated. There is no agnostic alternatives to it and we have to open code the logic which was hidden behind of_mm_gpiochip_add_data(). Note, most of the GPIO drivers are using their own labeling schemas and resource retrieval that only a few may gain of the code deduplication, so whenever alternative is appear we can move drivers again to use that one. [Text copied from commit 34064c8267a6 ("powerpc/8xx: Drop legacy-of-mm-gpiochip.h header")] Signed-off-by: Christophe Leroy Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 1 - drivers/gpio/gpio-mm-lantiq.c | 47 +++++++++++++++++++---------------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 2dcfbc05980c..154d31b75070 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -476,7 +476,6 @@ config GPIO_MENZ127 config GPIO_MM_LANTIQ bool "Lantiq Memory mapped GPIOs" depends on LANTIQ && SOC_XWAY - select OF_GPIO_MM_GPIOCHIP help This enables support for memory mapped GPIOs on the External Bus Unit (EBU) found on Lantiq SoCs. The GPIOs are output only as they are diff --git a/drivers/gpio/gpio-mm-lantiq.c b/drivers/gpio/gpio-mm-lantiq.c index 8f1405733d98..3d2e24d61475 100644 --- a/drivers/gpio/gpio-mm-lantiq.c +++ b/drivers/gpio/gpio-mm-lantiq.c @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -27,7 +26,8 @@ #define LTQ_EBU_WP 0x80000000 /* write protect bit */ struct ltq_mm { - struct of_mm_gpio_chip mmchip; + struct gpio_chip gc; + void __iomem *regs; u16 shadow; /* shadow the latches state */ }; @@ -44,7 +44,7 @@ static void ltq_mm_apply(struct ltq_mm *chip) spin_lock_irqsave(&ebu_lock, flags); ltq_ebu_w32(LTQ_EBU_BUSCON, LTQ_EBU_BUSCON1); - __raw_writew(chip->shadow, chip->mmchip.regs); + __raw_writew(chip->shadow, chip->regs); ltq_ebu_w32(LTQ_EBU_BUSCON | LTQ_EBU_WP, LTQ_EBU_BUSCON1); spin_unlock_irqrestore(&ebu_lock, flags); } @@ -87,19 +87,19 @@ static int ltq_mm_dir_out(struct gpio_chip *gc, unsigned offset, int value) * ltq_mm_save_regs() - Set initial values of GPIO pins * @mm_gc: pointer to memory mapped GPIO chip structure */ -static void ltq_mm_save_regs(struct of_mm_gpio_chip *mm_gc) +static void ltq_mm_save_regs(struct ltq_mm *chip) { - struct ltq_mm *chip = - container_of(mm_gc, struct ltq_mm, mmchip); - /* tell the ebu controller which memory address we will be using */ - ltq_ebu_w32(CPHYSADDR(chip->mmchip.regs) | 0x1, LTQ_EBU_ADDRSEL1); + ltq_ebu_w32(CPHYSADDR((__force void *)chip->regs) | 0x1, LTQ_EBU_ADDRSEL1); ltq_mm_apply(chip); } static int ltq_mm_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct gpio_chip *gc; struct ltq_mm *chip; u32 shadow; @@ -107,25 +107,29 @@ static int ltq_mm_probe(struct platform_device *pdev) if (!chip) return -ENOMEM; - platform_set_drvdata(pdev, chip); + gc = &chip->gc; - chip->mmchip.gc.ngpio = 16; - chip->mmchip.gc.direction_output = ltq_mm_dir_out; - chip->mmchip.gc.set = ltq_mm_set; - chip->mmchip.save_regs = ltq_mm_save_regs; + gc->base = -1; + gc->ngpio = 16; + gc->direction_output = ltq_mm_dir_out; + gc->set = ltq_mm_set; + gc->parent = dev; + gc->owner = THIS_MODULE; + gc->label = devm_kasprintf(dev, GFP_KERNEL, "%pOF", np); + if (!gc->label) + return -ENOMEM; + + chip->regs = devm_of_iomap(dev, np, 0, NULL); + if (IS_ERR(chip->regs)) + return PTR_ERR(chip->regs); + + ltq_mm_save_regs(chip); /* store the shadow value if one was passed by the devicetree */ if (!of_property_read_u32(pdev->dev.of_node, "lantiq,shadow", &shadow)) chip->shadow = shadow; - return of_mm_gpiochip_add_data(pdev->dev.of_node, &chip->mmchip, chip); -} - -static void ltq_mm_remove(struct platform_device *pdev) -{ - struct ltq_mm *chip = platform_get_drvdata(pdev); - - of_mm_gpiochip_remove(&chip->mmchip); + return devm_gpiochip_add_data(dev, gc, chip); } static const struct of_device_id ltq_mm_match[] = { @@ -136,7 +140,6 @@ MODULE_DEVICE_TABLE(of, ltq_mm_match); static struct platform_driver ltq_mm_driver = { .probe = ltq_mm_probe, - .remove = ltq_mm_remove, .driver = { .name = "gpio-mm-ltq", .of_match_table = ltq_mm_match, From eba11116f39533d2e38cc5898014f2c95f32d23a Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Mon, 13 Oct 2025 15:07:15 +0200 Subject: [PATCH 14/80] gpiolib: of: Get rid of Last user of linux/gpio/legacy-of-mm-gpiochip.h is gone. Remove linux/gpio/legacy-of-mm-gpiochip.h and CONFIG_OF_GPIO_MM_GPIOCHIP Signed-off-by: Christophe Leroy Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 8 --- drivers/gpio/TODO | 11 --- drivers/gpio/gpiolib-of.c | 79 ---------------------- include/linux/gpio/legacy-of-mm-gpiochip.h | 36 ---------- 4 files changed, 134 deletions(-) delete mode 100644 include/linux/gpio/legacy-of-mm-gpiochip.h diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 154d31b75070..ce237398fa00 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -42,14 +42,6 @@ config GPIOLIB_IRQCHIP select IRQ_DOMAIN bool -config OF_GPIO_MM_GPIOCHIP - bool - help - This adds support for the legacy 'struct of_mm_gpio_chip' interface - from PowerPC. Existing drivers using this interface need to select - this symbol, but new drivers should use the generic gpio-regmap - infrastructure instead. - config DEBUG_GPIO bool "Debug GPIO calls" depends on DEBUG_KERNEL diff --git a/drivers/gpio/TODO b/drivers/gpio/TODO index 8ed74e05903a..5acaeab029ec 100644 --- a/drivers/gpio/TODO +++ b/drivers/gpio/TODO @@ -86,17 +86,6 @@ Work items: ------------------------------------------------------------------------------- -Get rid of - -Work items: - -- Get rid of struct of_mm_gpio_chip altogether: use the generic MMIO - GPIO for all current users (see below). Delete struct of_mm_gpio_chip, - to_of_mm_gpio_chip(), of_mm_gpiochip_add_data(), of_mm_gpiochip_remove(), - CONFIG_OF_GPIO_MM_GPIOCHIP from the kernel. - -------------------------------------------------------------------------------- - Collect drivers Collect GPIO drivers from arch/* and other places that should be placed diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c index fad4edf9cc5c..8657379e9165 100644 --- a/drivers/gpio/gpiolib-of.c +++ b/drivers/gpio/gpiolib-of.c @@ -1031,85 +1031,6 @@ static int of_gpio_threecell_xlate(struct gpio_chip *gc, return gpiospec->args[1]; } -#if IS_ENABLED(CONFIG_OF_GPIO_MM_GPIOCHIP) -#include -/** - * of_mm_gpiochip_add_data - Add memory mapped GPIO chip (bank) - * @np: device node of the GPIO chip - * @mm_gc: pointer to the of_mm_gpio_chip allocated structure - * @data: driver data to store in the struct gpio_chip - * - * To use this function you should allocate and fill mm_gc with: - * - * 1) In the gpio_chip structure: - * - all the callbacks - * - of_gpio_n_cells - * - of_xlate callback (optional) - * - * 3) In the of_mm_gpio_chip structure: - * - save_regs callback (optional) - * - * If succeeded, this function will map bank's memory and will - * do all necessary work for you. Then you'll able to use .regs - * to manage GPIOs from the callbacks. - * - * Returns: - * 0 on success, or negative errno on failure. - */ -int of_mm_gpiochip_add_data(struct device_node *np, - struct of_mm_gpio_chip *mm_gc, - void *data) -{ - int ret = -ENOMEM; - struct gpio_chip *gc = &mm_gc->gc; - - gc->label = kasprintf(GFP_KERNEL, "%pOF", np); - if (!gc->label) - goto err0; - - mm_gc->regs = of_iomap(np, 0); - if (!mm_gc->regs) - goto err1; - - gc->base = -1; - - if (mm_gc->save_regs) - mm_gc->save_regs(mm_gc); - - fwnode_handle_put(mm_gc->gc.fwnode); - mm_gc->gc.fwnode = fwnode_handle_get(of_fwnode_handle(np)); - - ret = gpiochip_add_data(gc, data); - if (ret) - goto err2; - - return 0; -err2: - of_node_put(np); - iounmap(mm_gc->regs); -err1: - kfree(gc->label); -err0: - pr_err("%pOF: GPIO chip registration failed with status %d\n", np, ret); - return ret; -} -EXPORT_SYMBOL_GPL(of_mm_gpiochip_add_data); - -/** - * of_mm_gpiochip_remove - Remove memory mapped GPIO chip (bank) - * @mm_gc: pointer to the of_mm_gpio_chip allocated structure - */ -void of_mm_gpiochip_remove(struct of_mm_gpio_chip *mm_gc) -{ - struct gpio_chip *gc = &mm_gc->gc; - - gpiochip_remove(gc); - iounmap(mm_gc->regs); - kfree(gc->label); -} -EXPORT_SYMBOL_GPL(of_mm_gpiochip_remove); -#endif - #ifdef CONFIG_PINCTRL static int of_gpiochip_add_pin_range(struct gpio_chip *chip) { diff --git a/include/linux/gpio/legacy-of-mm-gpiochip.h b/include/linux/gpio/legacy-of-mm-gpiochip.h deleted file mode 100644 index 2e2bd3b19cc3..000000000000 --- a/include/linux/gpio/legacy-of-mm-gpiochip.h +++ /dev/null @@ -1,36 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * OF helpers for the old of_mm_gpio_chip, used on ppc32 and nios2, - * do not use in new code. - * - * Copyright (c) 2007-2008 MontaVista Software, Inc. - * - * Author: Anton Vorontsov - */ - -#ifndef __LINUX_GPIO_LEGACY_OF_MM_GPIO_CHIP_H -#define __LINUX_GPIO_LEGACY_OF_MM_GPIO_CHIP_H - -#include -#include - -/* - * OF GPIO chip for memory mapped banks - */ -struct of_mm_gpio_chip { - struct gpio_chip gc; - void (*save_regs)(struct of_mm_gpio_chip *mm_gc); - void __iomem *regs; -}; - -static inline struct of_mm_gpio_chip *to_of_mm_gpio_chip(struct gpio_chip *gc) -{ - return container_of(gc, struct of_mm_gpio_chip, gc); -} - -extern int of_mm_gpiochip_add_data(struct device_node *np, - struct of_mm_gpio_chip *mm_gc, - void *data); -extern void of_mm_gpiochip_remove(struct of_mm_gpio_chip *mm_gc); - -#endif /* __LINUX_GPIO_LEGACY_OF_MM_GPIO_CHIP_H */ From 6f5976c0cc0977b1fd168d6ecbf3fb36cf041524 Mon Sep 17 00:00:00 2001 From: Michael Roth Date: Sat, 18 Oct 2025 12:14:04 +0200 Subject: [PATCH 15/80] gpio: pca953x: clarify log messages about auto increment feature The probe messages currently print "using AI" and "using no AI", which can be confusing to users unfamiliar with the datasheet term. Clarify these by spelling out "auto increment", which is the meaning of the AI bit described in the register map. No functional change, only clearer log wording and matching comment update. Signed-off-by: Michael Roth Link: https://lore.kernel.org/r/20251018101404.3630905-1-mail@mroth.net Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-pca953x.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpio-pca953x.c b/drivers/gpio/gpio-pca953x.c index 9e72439f6c92..0a3916cc2772 100644 --- a/drivers/gpio/gpio-pca953x.c +++ b/drivers/gpio/gpio-pca953x.c @@ -306,7 +306,7 @@ static inline u8 pca953x_get_bit_mask(struct pca953x_chip *chip, unsigned int of * Interrupt mask register 0x40 + 5 * bank_size RW * Interrupt status register 0x40 + 6 * bank_size R * - * - Registers with bit 0x80 set, the AI bit + * - Registers with bit 0x80 set, the AI bit (auto increment) * The bit is cleared and the registers fall into one of the * categories above. */ @@ -1206,10 +1206,10 @@ static int pca953x_probe(struct i2c_client *client) pca953x_setup_gpio(chip, chip->driver_data & PCA_GPIO_MASK); if (NBANK(chip) > 2 || PCA_CHIP_TYPE(chip->driver_data) == PCA957X_TYPE) { - dev_info(dev, "using AI\n"); + dev_info(dev, "using auto increment\n"); regmap_config = &pca953x_ai_i2c_regmap; } else { - dev_info(dev, "using no AI\n"); + dev_info(dev, "using no auto increment\n"); regmap_config = &pca953x_i2c_regmap; } From eb7f1c8415bbbb81f8674a490a5da7c22599a012 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 19 Oct 2025 10:31:38 +0200 Subject: [PATCH 16/80] gpio: mvebu: Slightly optimize mvebu_gpio_irq_handler() In the main loop of mvebu_gpio_irq_handler() some calls to irq_find_mapping() can be saved. There is no point to find an irq number before checking if this something has to be done. By testing first, some calls can be saved. Signed-off-by: Christophe JAILLET Link: https://lore.kernel.org/r/7190f5def0489ed3f40435449c86cd7c710e6dd4.1760862679.git.christophe.jaillet@wanadoo.fr Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mvebu.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-mvebu.c b/drivers/gpio/gpio-mvebu.c index ac799fced950..22c36b79e249 100644 --- a/drivers/gpio/gpio-mvebu.c +++ b/drivers/gpio/gpio-mvebu.c @@ -573,11 +573,10 @@ static void mvebu_gpio_irq_handler(struct irq_desc *desc) for (i = 0; i < mvchip->chip.ngpio; i++) { int irq; - irq = irq_find_mapping(mvchip->domain, i); - if (!(cause & BIT(i))) continue; + irq = irq_find_mapping(mvchip->domain, i); type = irq_get_trigger_type(irq); if ((type & IRQ_TYPE_SENSE_MASK) == IRQ_TYPE_EDGE_BOTH) { /* Swap polarity (race with GPIO line) */ From e0a6ec724e5b87a0b162912b5d30bb6f066677e2 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 20 Oct 2025 09:20:28 +0200 Subject: [PATCH 17/80] gpio: qixis-fpga: add missing module description A kernel module must have a license and should have a description. Add missing MODULE_LICENSE(), MODULE_DESCRIPTION() and throw in a MODULE_AUTHOR() for good measure. This fixes the following build issues: ERROR: modpost: missing MODULE_LICENSE() in drivers/gpio/gpio-qixis-fpga.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/gpio/gpio-qixis-fpga.o Reported-by: Mark Brown Closes: https://lore.kernel.org/all/aPJW8HIke5pj3doX@sirena.org.uk/ Fixes: e88500247dc3 ("gpio: add QIXIS FPGA GPIO controller") Link: https://lore.kernel.org/r/20251020072028.21423-1-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-qixis-fpga.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpio/gpio-qixis-fpga.c b/drivers/gpio/gpio-qixis-fpga.c index 048a2cac4f0f..54c2c76822d5 100644 --- a/drivers/gpio/gpio-qixis-fpga.c +++ b/drivers/gpio/gpio-qixis-fpga.c @@ -105,3 +105,7 @@ static struct platform_driver qixis_cpld_gpio_driver = { }, }; module_platform_driver(qixis_cpld_gpio_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ioana Ciornei "); +MODULE_DESCRIPTION("Layerscape GPIO QIXIS FPGA driver"); From d09ec3dc7fea7b2f5f5a159470d43cced39e994d Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 15 Oct 2025 17:16:05 +0200 Subject: [PATCH 18/80] gpio: latch: remove unneeded include This driver no longer uses any GPIOLIB internal symbols. We can drop the gpiolib.h include. Link: https://lore.kernel.org/r/20251015151605.71203-1-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-latch.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/gpio/gpio-latch.c b/drivers/gpio/gpio-latch.c index c64aaa896766..452a9ce61488 100644 --- a/drivers/gpio/gpio-latch.c +++ b/drivers/gpio/gpio-latch.c @@ -48,8 +48,6 @@ #include #include -#include "gpiolib.h" - struct gpio_latch_priv { struct gpio_chip gc; struct gpio_descs *clk_gpios; From df900536e85819f6168783d5f6b3908d47811fdd Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 16 Oct 2025 11:09:25 +0200 Subject: [PATCH 19/80] gpio: rename gpio_chip_hwgpio() to gpiod_hwgpio() This function takes a GPIO descriptor as first argument. Make its naming consistent with the rest of the GPIO codebase and use the gpiod_ prefix. Reviewed-by: Linus Walleij Reviewed-by: Andrew Jeffery Link: https://lore.kernel.org/r/20251016-aspeed-gpiolib-include-v1-1-31201c06d124@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-aspeed.c | 6 ++--- drivers/gpio/gpiolib-cdev.c | 12 +++++----- drivers/gpio/gpiolib-sysfs.c | 14 +++++------ drivers/gpio/gpiolib.c | 46 ++++++++++++++++++------------------ drivers/gpio/gpiolib.h | 2 +- 5 files changed, 40 insertions(+), 40 deletions(-) diff --git a/drivers/gpio/gpio-aspeed.c b/drivers/gpio/gpio-aspeed.c index 7953a9c4e36d..3da37a0fda3f 100644 --- a/drivers/gpio/gpio-aspeed.c +++ b/drivers/gpio/gpio-aspeed.c @@ -24,7 +24,7 @@ /* * These two headers aren't meant to be used by GPIO drivers. We need - * them in order to access gpio_chip_hwgpio() which we need to implement + * them in order to access gpiod_hwgpio() which we need to implement * the aspeed specific API which allows the coprocessor to request * access to some GPIOs and to arbitrate between coprocessor and ARM. */ @@ -942,7 +942,7 @@ int aspeed_gpio_copro_grab_gpio(struct gpio_desc *desc, { struct gpio_chip *chip = gpiod_to_chip(desc); struct aspeed_gpio *gpio = gpiochip_get_data(chip); - int rc = 0, bindex, offset = gpio_chip_hwgpio(desc); + int rc = 0, bindex, offset = gpiod_hwgpio(desc); const struct aspeed_gpio_bank *bank = to_bank(offset); if (!aspeed_gpio_support_copro(gpio)) @@ -987,7 +987,7 @@ int aspeed_gpio_copro_release_gpio(struct gpio_desc *desc) { struct gpio_chip *chip = gpiod_to_chip(desc); struct aspeed_gpio *gpio = gpiochip_get_data(chip); - int rc = 0, bindex, offset = gpio_chip_hwgpio(desc); + int rc = 0, bindex, offset = gpiod_hwgpio(desc); if (!aspeed_gpio_support_copro(gpio)) return -EOPNOTSUPP; diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index ddc452b5ee23..ed064e6f268c 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -676,7 +676,7 @@ static enum hte_return process_hw_ts_thread(void *p) } le.line_seqno = line->line_seqno; le.seqno = (lr->num_lines == 1) ? le.line_seqno : line->req_seqno; - le.offset = gpio_chip_hwgpio(line->desc); + le.offset = gpiod_hwgpio(line->desc); linereq_put_event(lr, &le); @@ -793,7 +793,7 @@ static irqreturn_t edge_irq_thread(int irq, void *p) line->line_seqno++; le.line_seqno = line->line_seqno; le.seqno = (lr->num_lines == 1) ? le.line_seqno : line->req_seqno; - le.offset = gpio_chip_hwgpio(line->desc); + le.offset = gpiod_hwgpio(line->desc); linereq_put_event(lr, &le); @@ -891,7 +891,7 @@ static void debounce_work_func(struct work_struct *work) lr = line->req; le.timestamp_ns = line_event_timestamp(line); - le.offset = gpio_chip_hwgpio(line->desc); + le.offset = gpiod_hwgpio(line->desc); #ifdef CONFIG_HTE if (edflags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE) { /* discard events except the last one */ @@ -1591,7 +1591,7 @@ static void linereq_show_fdinfo(struct seq_file *out, struct file *file) for (i = 0; i < lr->num_lines; i++) seq_printf(out, "gpio-line:\t%d\n", - gpio_chip_hwgpio(lr->lines[i].desc)); + gpiod_hwgpio(lr->lines[i].desc)); } #endif @@ -2244,7 +2244,7 @@ static void gpio_desc_to_lineinfo(struct gpio_desc *desc, return; memset(info, 0, sizeof(*info)); - info->offset = gpio_chip_hwgpio(desc); + info->offset = gpiod_hwgpio(desc); if (desc->name) strscpy(info->name, desc->name, sizeof(info->name)); @@ -2549,7 +2549,7 @@ static int lineinfo_changed_notify(struct notifier_block *nb, struct lineinfo_changed_ctx *ctx; struct gpio_desc *desc = data; - if (!test_bit(gpio_chip_hwgpio(desc), cdev->watched_lines)) + if (!test_bit(gpiod_hwgpio(desc), cdev->watched_lines)) return NOTIFY_DONE; /* diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c index 7d5fc1ea2aa5..cd553acf3055 100644 --- a/drivers/gpio/gpiolib-sysfs.c +++ b/drivers/gpio/gpiolib-sysfs.c @@ -244,7 +244,7 @@ static int gpio_sysfs_request_irq(struct gpiod_data *data, unsigned char flags) * Remove this redundant call (along with the corresponding unlock) * when those drivers have been fixed. */ - ret = gpiochip_lock_as_irq(guard.gc, gpio_chip_hwgpio(desc)); + ret = gpiochip_lock_as_irq(guard.gc, gpiod_hwgpio(desc)); if (ret < 0) goto err_clr_bits; @@ -258,7 +258,7 @@ static int gpio_sysfs_request_irq(struct gpiod_data *data, unsigned char flags) return 0; err_unlock: - gpiochip_unlock_as_irq(guard.gc, gpio_chip_hwgpio(desc)); + gpiochip_unlock_as_irq(guard.gc, gpiod_hwgpio(desc)); err_clr_bits: clear_bit(GPIOD_FLAG_EDGE_RISING, &desc->flags); clear_bit(GPIOD_FLAG_EDGE_FALLING, &desc->flags); @@ -280,7 +280,7 @@ static void gpio_sysfs_free_irq(struct gpiod_data *data) data->irq_flags = 0; free_irq(data->irq, data); - gpiochip_unlock_as_irq(guard.gc, gpio_chip_hwgpio(desc)); + gpiochip_unlock_as_irq(guard.gc, gpiod_hwgpio(desc)); clear_bit(GPIOD_FLAG_EDGE_RISING, &desc->flags); clear_bit(GPIOD_FLAG_EDGE_FALLING, &desc->flags); } @@ -478,10 +478,10 @@ static int export_gpio_desc(struct gpio_desc *desc) if (!guard.gc) return -ENODEV; - offset = gpio_chip_hwgpio(desc); + offset = gpiod_hwgpio(desc); if (!gpiochip_line_is_valid(guard.gc, offset)) { pr_debug_ratelimited("%s: GPIO %d masked\n", __func__, - gpio_chip_hwgpio(desc)); + gpiod_hwgpio(desc)); return -EINVAL; } @@ -823,7 +823,7 @@ int gpiod_export(struct gpio_desc *desc, bool direction_may_change) } desc_data->chip_attr_group.name = kasprintf(GFP_KERNEL, "gpio%u", - gpio_chip_hwgpio(desc)); + gpiod_hwgpio(desc)); if (!desc_data->chip_attr_group.name) { status = -ENOMEM; goto err_put_dirent; @@ -843,7 +843,7 @@ int gpiod_export(struct gpio_desc *desc, bool direction_may_change) if (status) goto err_free_name; - path = kasprintf(GFP_KERNEL, "gpio%u/value", gpio_chip_hwgpio(desc)); + path = kasprintf(GFP_KERNEL, "gpio%u/value", gpiod_hwgpio(desc)); if (!path) { status = -ENOMEM; goto err_remove_groups; diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index ba5df8a233fe..5a450dac8f3a 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -443,7 +443,7 @@ int gpiod_get_direction(struct gpio_desc *desc) if (!guard.gc) return -ENODEV; - offset = gpio_chip_hwgpio(desc); + offset = gpiod_hwgpio(desc); flags = READ_ONCE(desc->flags); /* @@ -2446,7 +2446,7 @@ static int gpiod_request_commit(struct gpio_desc *desc, const char *label) if (test_and_set_bit(GPIOD_FLAG_REQUESTED, &desc->flags)) return -EBUSY; - offset = gpio_chip_hwgpio(desc); + offset = gpiod_hwgpio(desc); if (!gpiochip_line_is_valid(guard.gc, offset)) return -EINVAL; @@ -2508,7 +2508,7 @@ static void gpiod_free_commit(struct gpio_desc *desc) if (guard.gc && test_bit(GPIOD_FLAG_REQUESTED, &flags)) { if (guard.gc->free) - guard.gc->free(guard.gc, gpio_chip_hwgpio(desc)); + guard.gc->free(guard.gc, gpiod_hwgpio(desc)); clear_bit(GPIOD_FLAG_ACTIVE_LOW, &flags); clear_bit(GPIOD_FLAG_REQUESTED, &flags); @@ -2668,7 +2668,7 @@ int gpio_do_set_config(struct gpio_desc *desc, unsigned long config) if (!guard.gc->set_config) return -ENOTSUPP; - ret = guard.gc->set_config(guard.gc, gpio_chip_hwgpio(desc), config); + ret = guard.gc->set_config(guard.gc, gpiod_hwgpio(desc), config); if (ret > 0) ret = -EBADE; @@ -2699,7 +2699,7 @@ static int gpio_set_config_with_argument_optional(struct gpio_desc *desc, u32 argument) { struct device *dev = &desc->gdev->dev; - int gpio = gpio_chip_hwgpio(desc); + int gpio = gpiod_hwgpio(desc); int ret; ret = gpio_set_config_with_argument(desc, mode, argument); @@ -2862,9 +2862,9 @@ int gpiod_direction_input_nonotify(struct gpio_desc *desc) */ if (guard.gc->direction_input) { ret = gpiochip_direction_input(guard.gc, - gpio_chip_hwgpio(desc)); + gpiod_hwgpio(desc)); } else if (guard.gc->get_direction) { - dir = gpiochip_get_direction(guard.gc, gpio_chip_hwgpio(desc)); + dir = gpiochip_get_direction(guard.gc, gpiod_hwgpio(desc)); if (dir < 0) return dir; @@ -2923,12 +2923,12 @@ static int gpiod_direction_output_raw_commit(struct gpio_desc *desc, int value) if (guard.gc->direction_output) { ret = gpiochip_direction_output(guard.gc, - gpio_chip_hwgpio(desc), val); + gpiod_hwgpio(desc), val); } else { /* Check that we are in output mode if we can */ if (guard.gc->get_direction) { dir = gpiochip_get_direction(guard.gc, - gpio_chip_hwgpio(desc)); + gpiod_hwgpio(desc)); if (dir < 0) return dir; @@ -2943,7 +2943,7 @@ static int gpiod_direction_output_raw_commit(struct gpio_desc *desc, int value) * If we can't actively set the direction, we are some * output-only chip, so just drive the output as desired. */ - ret = gpiochip_set(guard.gc, gpio_chip_hwgpio(desc), val); + ret = gpiochip_set(guard.gc, gpiod_hwgpio(desc), val); if (ret) return ret; } @@ -3094,7 +3094,7 @@ int gpiod_enable_hw_timestamp_ns(struct gpio_desc *desc, unsigned long flags) } ret = guard.gc->en_hw_timestamp(guard.gc, - gpio_chip_hwgpio(desc), flags); + gpiod_hwgpio(desc), flags); if (ret) gpiod_warn(desc, "%s: hw ts request failed\n", __func__); @@ -3126,7 +3126,7 @@ int gpiod_disable_hw_timestamp_ns(struct gpio_desc *desc, unsigned long flags) return -ENOTSUPP; } - ret = guard.gc->dis_hw_timestamp(guard.gc, gpio_chip_hwgpio(desc), + ret = guard.gc->dis_hw_timestamp(guard.gc, gpiod_hwgpio(desc), flags); if (ret) gpiod_warn(desc, "%s: hw ts release failed\n", __func__); @@ -3257,7 +3257,7 @@ static int gpiochip_get(struct gpio_chip *gc, unsigned int offset) static int gpio_chip_get_value(struct gpio_chip *gc, const struct gpio_desc *desc) { - return gc->get ? gpiochip_get(gc, gpio_chip_hwgpio(desc)) : -EIO; + return gc->get ? gpiochip_get(gc, gpiod_hwgpio(desc)) : -EIO; } /* I/O calls are only valid after configuration completed; the relevant @@ -3417,7 +3417,7 @@ int gpiod_get_array_value_complex(bool raw, bool can_sleep, first = i; do { const struct gpio_desc *desc = desc_array[i]; - int hwgpio = gpio_chip_hwgpio(desc); + int hwgpio = gpiod_hwgpio(desc); __set_bit(hwgpio, mask); i++; @@ -3439,7 +3439,7 @@ int gpiod_get_array_value_complex(bool raw, bool can_sleep, for (j = first; j < i; ) { const struct gpio_desc *desc = desc_array[j]; - int hwgpio = gpio_chip_hwgpio(desc); + int hwgpio = gpiod_hwgpio(desc); int value = test_bit(hwgpio, bits); if (!raw && test_bit(GPIOD_FLAG_ACTIVE_LOW, &desc->flags)) @@ -3576,7 +3576,7 @@ EXPORT_SYMBOL_GPL(gpiod_get_array_value); */ static int gpio_set_open_drain_value_commit(struct gpio_desc *desc, bool value) { - int ret = 0, offset = gpio_chip_hwgpio(desc); + int ret = 0, offset = gpiod_hwgpio(desc); CLASS(gpio_chip_guard, guard)(desc); if (!guard.gc) @@ -3605,7 +3605,7 @@ static int gpio_set_open_drain_value_commit(struct gpio_desc *desc, bool value) */ static int gpio_set_open_source_value_commit(struct gpio_desc *desc, bool value) { - int ret = 0, offset = gpio_chip_hwgpio(desc); + int ret = 0, offset = gpiod_hwgpio(desc); CLASS(gpio_chip_guard, guard)(desc); if (!guard.gc) @@ -3637,7 +3637,7 @@ static int gpiod_set_raw_value_commit(struct gpio_desc *desc, bool value) return -ENODEV; trace_gpio_value(desc_to_gpio(desc), 0, value); - return gpiochip_set(guard.gc, gpio_chip_hwgpio(desc), value); + return gpiochip_set(guard.gc, gpiod_hwgpio(desc), value); } /* @@ -3760,7 +3760,7 @@ int gpiod_set_array_value_complex(bool raw, bool can_sleep, do { struct gpio_desc *desc = desc_array[i]; - int hwgpio = gpio_chip_hwgpio(desc); + int hwgpio = gpiod_hwgpio(desc); int value = test_bit(i, value_bitmap); if (unlikely(!test_bit(GPIOD_FLAG_IS_OUT, &desc->flags))) @@ -4000,7 +4000,7 @@ int gpiod_to_irq(const struct gpio_desc *desc) if (!gc) return -ENODEV; - offset = gpio_chip_hwgpio(desc); + offset = gpiod_hwgpio(desc); if (gc->to_irq) { ret = gc->to_irq(gc, offset); if (ret) @@ -4957,7 +4957,7 @@ int gpiod_hog(struct gpio_desc *desc, const char *name, if (test_and_set_bit(GPIOD_FLAG_IS_HOGGED, &desc->flags)) return 0; - hwnum = gpio_chip_hwgpio(desc); + hwnum = gpiod_hwgpio(desc); local_desc = gpiochip_request_own_desc(guard.gc, hwnum, name, lflags, dflags); @@ -5038,7 +5038,7 @@ struct gpio_descs *__must_check gpiod_get_array(struct device *dev, * If pin hardware number of array member 0 is also 0, select * its chip as a candidate for fast bitmap processing path. */ - if (descs->ndescs == 0 && gpio_chip_hwgpio(desc) == 0) { + if (descs->ndescs == 0 && gpiod_hwgpio(desc) == 0) { struct gpio_descs *array; bitmap_size = BITS_TO_LONGS(gdev->ngpio > count ? @@ -5083,7 +5083,7 @@ struct gpio_descs *__must_check gpiod_get_array(struct device *dev, * Detect array members which belong to the 'fast' chip * but their pins are not in hardware order. */ - else if (gpio_chip_hwgpio(desc) != descs->ndescs) { + else if (gpiod_hwgpio(desc) != descs->ndescs) { /* * Don't use fast path if all array members processed so * far belong to the same chip as this one but its pin diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index b4c5369f8a33..62d4c15b74f5 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -276,7 +276,7 @@ const char *gpiod_get_label(struct gpio_desc *desc); /* * Return the GPIO number of the passed descriptor relative to its chip */ -static inline int gpio_chip_hwgpio(const struct gpio_desc *desc) +static inline int gpiod_hwgpio(const struct gpio_desc *desc) { return desc - &desc->gdev->descs[0]; } From d19f6451c6feefd6537b97efa5f3859681f243cb Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 16 Oct 2025 11:09:26 +0200 Subject: [PATCH 20/80] gpio: export gpiod_hwgpio() Reading the GPIO hardware number from a descriptor is a valid use-case outside of the GPIO core. Export the symbol to consumers of GPIO descriptors. Reviewed-by: Linus Walleij Reviewed-by: Andrew Jeffery Link: https://lore.kernel.org/r/20251016-aspeed-gpiolib-include-v1-2-31201c06d124@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 13 +++++++++++++ drivers/gpio/gpiolib.h | 8 -------- include/linux/gpio/consumer.h | 2 ++ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 5a450dac8f3a..a81981336b36 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -235,6 +235,19 @@ int desc_to_gpio(const struct gpio_desc *desc) } EXPORT_SYMBOL_GPL(desc_to_gpio); +/** + * gpiod_hwgpio - Return the GPIO number of the passed descriptor relative to + * its chip. + * @desc: GPIO descriptor + * + * Returns: + * Hardware offset of the GPIO represented by the descriptor. + */ +int gpiod_hwgpio(const struct gpio_desc *desc) +{ + return desc - &desc->gdev->descs[0]; +} +EXPORT_SYMBOL_GPL(gpiod_hwgpio); /** * gpiod_to_chip - Return the GPIO chip to which a GPIO descriptor belongs diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index 62d4c15b74f5..14e6a9807a89 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -273,14 +273,6 @@ int gpiochip_get_ngpios(struct gpio_chip *gc, struct device *dev); struct gpio_desc *gpiochip_get_desc(struct gpio_chip *gc, unsigned int hwnum); const char *gpiod_get_label(struct gpio_desc *desc); -/* - * Return the GPIO number of the passed descriptor relative to its chip - */ -static inline int gpiod_hwgpio(const struct gpio_desc *desc) -{ - return desc - &desc->gdev->descs[0]; -} - /* With descriptor prefix */ #define __gpiod_pr(level, desc, fmt, ...) \ diff --git a/include/linux/gpio/consumer.h b/include/linux/gpio/consumer.h index 00df68c51405..994d46874d56 100644 --- a/include/linux/gpio/consumer.h +++ b/include/linux/gpio/consumer.h @@ -171,6 +171,8 @@ int gpiod_set_consumer_name(struct gpio_desc *desc, const char *name); struct gpio_desc *gpio_to_desc(unsigned gpio); int desc_to_gpio(const struct gpio_desc *desc); +int gpiod_hwgpio(const struct gpio_desc *desc); + struct gpio_desc *fwnode_gpiod_get_index(struct fwnode_handle *fwnode, const char *con_id, int index, enum gpiod_flags flags, From 0efa5b2ca6fa7baab4c523b34cfb9495ec143d61 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 16 Oct 2025 11:09:27 +0200 Subject: [PATCH 21/80] gpio: aspeed: remove unneeded include This driver no longer uses any symbols from the GPIOLIB internal header. We can now drop the gpiolib.h include. Reviewed-by: Linus Walleij Reviewed-by: Andrew Jeffery Link: https://lore.kernel.org/r/20251016-aspeed-gpiolib-include-v1-3-31201c06d124@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-aspeed.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpio/gpio-aspeed.c b/drivers/gpio/gpio-aspeed.c index 3da37a0fda3f..2e0ae953dd99 100644 --- a/drivers/gpio/gpio-aspeed.c +++ b/drivers/gpio/gpio-aspeed.c @@ -29,7 +29,6 @@ * access to some GPIOs and to arbitrate between coprocessor and ARM. */ #include -#include "gpiolib.h" /* Non-constant mask variant of FIELD_GET() and FIELD_PREP() */ #define field_get(_mask, _reg) (((_reg) & (_mask)) >> (ffs(_mask) - 1)) From 523ebae1cdcf8056dfe090f31284d1e5f5d1b73f Mon Sep 17 00:00:00 2001 From: Mary Strodl Date: Tue, 14 Oct 2025 09:35:27 -0400 Subject: [PATCH 22/80] gpio: mpsse: propagate error from direction_input Not sure how I missed this, but errors encountered when setting the direction to input weren't being propagated to the caller. Signed-off-by: Mary Strodl Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20251014133530.3592716-2-mstrodl@csh.rit.edu Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mpsse.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-mpsse.c b/drivers/gpio/gpio-mpsse.c index 9f42bb30b4ec..c508d9e33054 100644 --- a/drivers/gpio/gpio-mpsse.c +++ b/drivers/gpio/gpio-mpsse.c @@ -261,9 +261,8 @@ static int gpio_mpsse_direction_input(struct gpio_chip *chip, guard(mutex)(&priv->io_mutex); priv->gpio_dir[bank] &= ~BIT(bank_offset); - gpio_mpsse_set_bank(priv, bank); - return 0; + return gpio_mpsse_set_bank(priv, bank); } static int gpio_mpsse_get_direction(struct gpio_chip *chip, From 179ef1127d7a4f09f0e741fa9f30b8a8e7886271 Mon Sep 17 00:00:00 2001 From: Mary Strodl Date: Tue, 14 Oct 2025 09:35:28 -0400 Subject: [PATCH 23/80] gpio: mpsse: ensure worker is torn down When an IRQ worker is running, unplugging the device would cause a crash. The sealevel hardware this driver was written for was not hotpluggable, so I never realized it. This change uses a spinlock to protect a list of workers, which it tears down on disconnect. Signed-off-by: Mary Strodl Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20251014133530.3592716-3-mstrodl@csh.rit.edu Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mpsse.c | 106 +++++++++++++++++++++++++++++++++++--- 1 file changed, 99 insertions(+), 7 deletions(-) diff --git a/drivers/gpio/gpio-mpsse.c b/drivers/gpio/gpio-mpsse.c index c508d9e33054..6ec940f6b371 100644 --- a/drivers/gpio/gpio-mpsse.c +++ b/drivers/gpio/gpio-mpsse.c @@ -10,6 +10,7 @@ #include #include #include +#include #include struct mpsse_priv { @@ -17,8 +18,10 @@ struct mpsse_priv { struct usb_device *udev; /* USB device encompassing all MPSSEs */ struct usb_interface *intf; /* USB interface for this MPSSE */ u8 intf_id; /* USB interface number for this MPSSE */ - struct work_struct irq_work; /* polling work thread */ + struct list_head workers; /* polling work threads */ struct mutex irq_mutex; /* lock over irq_data */ + struct mutex irq_race; /* race for polling worker teardown */ + raw_spinlock_t irq_spin; /* protects worker list */ atomic_t irq_type[16]; /* pin -> edge detection type */ atomic_t irq_enabled; int id; @@ -34,6 +37,14 @@ struct mpsse_priv { struct mutex io_mutex; /* sync I/O with disconnect */ }; +struct mpsse_worker { + struct mpsse_priv *priv; + struct work_struct work; + atomic_t cancelled; + struct list_head list; /* linked list */ + struct list_head destroy; /* teardown linked list */ +}; + struct bulk_desc { bool tx; /* direction of bulk transfer */ u8 *data; /* input (tx) or output (rx) */ @@ -283,18 +294,62 @@ static int gpio_mpsse_get_direction(struct gpio_chip *chip, return ret; } -static void gpio_mpsse_poll(struct work_struct *work) +/* + * Stops all workers except `my_worker`. + * Safe to call only when `irq_race` is held. + */ +static void gpio_mpsse_stop_all_except(struct mpsse_priv *priv, + struct mpsse_worker *my_worker) +{ + struct mpsse_worker *worker, *worker_tmp; + struct list_head destructors = LIST_HEAD_INIT(destructors); + + scoped_guard(raw_spinlock_irqsave, &priv->irq_spin) { + list_for_each_entry_safe(worker, worker_tmp, + &priv->workers, list) { + /* Don't stop ourselves */ + if (worker == my_worker) + continue; + + list_del(&worker->list); + + /* Give worker a chance to terminate itself */ + atomic_set(&worker->cancelled, 1); + /* Keep track of stuff to cancel */ + INIT_LIST_HEAD(&worker->destroy); + list_add(&worker->destroy, &destructors); + } + } + + list_for_each_entry_safe(worker, worker_tmp, + &destructors, destroy) { + list_del(&worker->destroy); + cancel_work_sync(&worker->work); + kfree(worker); + } +} + +static void gpio_mpsse_poll(struct work_struct *my_work) { unsigned long pin_mask, pin_states, flags; int irq_enabled, offset, err, value, fire_irq, irq, old_value[16], irq_type[16]; - struct mpsse_priv *priv = container_of(work, struct mpsse_priv, - irq_work); + struct mpsse_worker *my_worker = container_of(my_work, struct mpsse_worker, work); + struct mpsse_priv *priv = my_worker->priv; for (offset = 0; offset < priv->gpio.ngpio; ++offset) old_value[offset] = -1; - while ((irq_enabled = atomic_read(&priv->irq_enabled))) { + /* + * We only want one worker. Workers race to acquire irq_race and tear + * down all other workers. This is a cond guard so that we don't deadlock + * trying to cancel a worker. + */ + scoped_cond_guard(mutex_try, return, &priv->irq_race) + gpio_mpsse_stop_all_except(priv, my_worker); + + while ((irq_enabled = atomic_read(&priv->irq_enabled)) && + !atomic_read(&my_worker->cancelled)) { usleep_range(MPSSE_POLL_INTERVAL, MPSSE_POLL_INTERVAL + 1000); /* Cleanup will trigger at the end of the loop */ guard(mutex)(&priv->irq_mutex); @@ -369,21 +424,45 @@ static int gpio_mpsse_set_irq_type(struct irq_data *irqd, unsigned int type) static void gpio_mpsse_irq_disable(struct irq_data *irqd) { + struct mpsse_worker *worker; struct mpsse_priv *priv = irq_data_get_irq_chip_data(irqd); atomic_and(~BIT(irqd->hwirq), &priv->irq_enabled); gpiochip_disable_irq(&priv->gpio, irqd->hwirq); + + /* + * Can't actually do teardown in IRQ context (it blocks). + * As a result, these workers will stick around until irq is reenabled + * or device gets disconnected + */ + scoped_guard(raw_spinlock_irqsave, &priv->irq_spin) + list_for_each_entry(worker, &priv->workers, list) + atomic_set(&worker->cancelled, 1); } static void gpio_mpsse_irq_enable(struct irq_data *irqd) { + struct mpsse_worker *worker; struct mpsse_priv *priv = irq_data_get_irq_chip_data(irqd); gpiochip_enable_irq(&priv->gpio, irqd->hwirq); /* If no-one else was using the IRQ, enable it */ if (!atomic_fetch_or(BIT(irqd->hwirq), &priv->irq_enabled)) { - INIT_WORK(&priv->irq_work, gpio_mpsse_poll); - schedule_work(&priv->irq_work); + /* + * Can't be devm because it uses a non-raw spinlock (illegal in + * this context, where a raw spinlock is held by our caller) + */ + worker = kzalloc(sizeof(*worker), GFP_NOWAIT); + if (!worker) + return; + + worker->priv = priv; + INIT_LIST_HEAD(&worker->list); + INIT_WORK(&worker->work, gpio_mpsse_poll); + schedule_work(&worker->work); + + scoped_guard(raw_spinlock_irqsave, &priv->irq_spin) + list_add(&worker->list, &priv->workers); } } @@ -435,6 +514,12 @@ static int gpio_mpsse_probe(struct usb_interface *interface, if (err) return err; + err = devm_mutex_init(dev, &priv->irq_race); + if (err) + return err; + + raw_spin_lock_init(&priv->irq_spin); + priv->gpio.label = devm_kasprintf(dev, GFP_KERNEL, "gpio-mpsse.%d.%d", priv->id, priv->intf_id); @@ -505,6 +590,13 @@ static void gpio_mpsse_disconnect(struct usb_interface *intf) { struct mpsse_priv *priv = usb_get_intfdata(intf); + /* + * Lock prevents double-free of worker from here and the teardown + * step at the beginning of gpio_mpsse_poll + */ + scoped_guard(mutex, &priv->irq_race) + gpio_mpsse_stop_all_except(priv, NULL); + priv->intf = NULL; usb_set_intfdata(intf, NULL); usb_put_dev(priv->udev); From f13b0f72af238d63bb9a2e417657da8b45d72544 Mon Sep 17 00:00:00 2001 From: Mary Strodl Date: Tue, 14 Oct 2025 09:35:29 -0400 Subject: [PATCH 24/80] gpio: mpsse: add quirk support Builds out a facility for specifying compatible lines directions and labels for MPSSE-based devices. * dir_in/out are bitmask of lines that can go in/out. 1 means compatible, 0 means incompatible. * names is an array of line names which will be exposed to userspace. Also changes the chip label format to include some more useful information about the device to help identify it from userspace. Signed-off-by: Mary Strodl Reviewed-by: Dan Carpenter Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20251014133530.3592716-4-mstrodl@csh.rit.edu Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mpsse.c | 109 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 106 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpio-mpsse.c b/drivers/gpio/gpio-mpsse.c index 6ec940f6b371..5ca00dd51a63 100644 --- a/drivers/gpio/gpio-mpsse.c +++ b/drivers/gpio/gpio-mpsse.c @@ -29,6 +29,9 @@ struct mpsse_priv { u8 gpio_outputs[2]; /* Output states for GPIOs [L, H] */ u8 gpio_dir[2]; /* Directions for GPIOs [L, H] */ + unsigned long dir_in; /* Bitmask of valid input pins */ + unsigned long dir_out; /* Bitmask of valid output pins */ + u8 *bulk_in_buf; /* Extra recv buffer to grab status bytes */ struct usb_endpoint_descriptor *bulk_in; @@ -54,6 +57,14 @@ struct bulk_desc { int timeout; }; +#define MPSSE_NGPIO 16 + +struct mpsse_quirk { + const char *names[MPSSE_NGPIO]; /* Pin names, if applicable */ + unsigned long dir_in; /* Bitmask of valid input pins */ + unsigned long dir_out; /* Bitmask of valid output pins */ +}; + static const struct usb_device_id gpio_mpsse_table[] = { { USB_DEVICE(0x0c52, 0xa064) }, /* SeaLevel Systems, Inc. */ { } /* Terminating entry */ @@ -171,6 +182,32 @@ static int gpio_mpsse_get_bank(struct mpsse_priv *priv, u8 bank) return buf; } +static int mpsse_ensure_supported(struct gpio_chip *chip, + unsigned long mask, int direction) +{ + unsigned long supported, unsupported; + char *type = "input"; + struct mpsse_priv *priv = gpiochip_get_data(chip); + + supported = priv->dir_in; + if (direction == GPIO_LINE_DIRECTION_OUT) { + supported = priv->dir_out; + type = "output"; + } + + /* An invalid bit was in the provided mask */ + unsupported = mask & ~supported; + if (unsupported) { + dev_err(&priv->udev->dev, + "mpsse: GPIO %lu doesn't support %s\n", + find_first_bit(&unsupported, sizeof(unsupported) * 8), + type); + return -EOPNOTSUPP; + } + + return 0; +} + static int gpio_mpsse_set_multiple(struct gpio_chip *chip, unsigned long *mask, unsigned long *bits) { @@ -178,6 +215,10 @@ static int gpio_mpsse_set_multiple(struct gpio_chip *chip, unsigned long *mask, int ret; struct mpsse_priv *priv = gpiochip_get_data(chip); + ret = mpsse_ensure_supported(chip, *mask, GPIO_LINE_DIRECTION_OUT); + if (ret) + return ret; + guard(mutex)(&priv->io_mutex); for_each_set_clump8(i, bank_mask, mask, chip->ngpio) { bank = i / 8; @@ -205,6 +246,10 @@ static int gpio_mpsse_get_multiple(struct gpio_chip *chip, unsigned long *mask, int ret; struct mpsse_priv *priv = gpiochip_get_data(chip); + ret = mpsse_ensure_supported(chip, *mask, GPIO_LINE_DIRECTION_IN); + if (ret) + return ret; + guard(mutex)(&priv->io_mutex); for_each_set_clump8(i, bank_mask, mask, chip->ngpio) { bank = i / 8; @@ -253,10 +298,15 @@ static int gpio_mpsse_gpio_set(struct gpio_chip *chip, unsigned int offset, static int gpio_mpsse_direction_output(struct gpio_chip *chip, unsigned int offset, int value) { + int ret; struct mpsse_priv *priv = gpiochip_get_data(chip); int bank = (offset & 8) >> 3; int bank_offset = offset & 7; + ret = mpsse_ensure_supported(chip, BIT(offset), GPIO_LINE_DIRECTION_OUT); + if (ret) + return ret; + scoped_guard(mutex, &priv->io_mutex) priv->gpio_dir[bank] |= BIT(bank_offset); @@ -266,10 +316,15 @@ static int gpio_mpsse_direction_output(struct gpio_chip *chip, static int gpio_mpsse_direction_input(struct gpio_chip *chip, unsigned int offset) { + int ret; struct mpsse_priv *priv = gpiochip_get_data(chip); int bank = (offset & 8) >> 3; int bank_offset = offset & 7; + ret = mpsse_ensure_supported(chip, BIT(offset), GPIO_LINE_DIRECTION_IN); + if (ret) + return ret; + guard(mutex)(&priv->io_mutex); priv->gpio_dir[bank] &= ~BIT(bank_offset); @@ -482,18 +537,49 @@ static void gpio_mpsse_ida_remove(void *data) ida_free(&gpio_mpsse_ida, priv->id); } +static int mpsse_init_valid_mask(struct gpio_chip *chip, + unsigned long *valid_mask, + unsigned int ngpios) +{ + struct mpsse_priv *priv = gpiochip_get_data(chip); + + if (WARN_ON(priv == NULL)) + return -ENODEV; + + *valid_mask = priv->dir_in | priv->dir_out; + + return 0; +} + +static void mpsse_irq_init_valid_mask(struct gpio_chip *chip, + unsigned long *valid_mask, + unsigned int ngpios) +{ + struct mpsse_priv *priv = gpiochip_get_data(chip); + + if (WARN_ON(priv == NULL)) + return; + + /* Can only use IRQ on input capable pins */ + *valid_mask = priv->dir_in; +} + static int gpio_mpsse_probe(struct usb_interface *interface, const struct usb_device_id *id) { struct mpsse_priv *priv; struct device *dev; + char *serial; int err; + struct mpsse_quirk *quirk = (void *)id->driver_info; dev = &interface->dev; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; + INIT_LIST_HEAD(&priv->workers); + priv->udev = usb_get_dev(interface_to_usbdev(interface)); priv->intf = interface; priv->intf_id = interface->cur_altsetting->desc.bInterfaceNumber; @@ -520,9 +606,15 @@ static int gpio_mpsse_probe(struct usb_interface *interface, raw_spin_lock_init(&priv->irq_spin); + serial = priv->udev->serial; + if (!serial) + serial = "NONE"; + priv->gpio.label = devm_kasprintf(dev, GFP_KERNEL, - "gpio-mpsse.%d.%d", - priv->id, priv->intf_id); + "MPSSE%04x:%04x.%d.%d.%s", + id->idVendor, id->idProduct, + priv->intf_id, priv->id, + serial); if (!priv->gpio.label) return -ENOMEM; @@ -536,10 +628,20 @@ static int gpio_mpsse_probe(struct usb_interface *interface, priv->gpio.get_multiple = gpio_mpsse_get_multiple; priv->gpio.set_multiple = gpio_mpsse_set_multiple; priv->gpio.base = -1; - priv->gpio.ngpio = 16; + priv->gpio.ngpio = MPSSE_NGPIO; priv->gpio.offset = priv->intf_id * priv->gpio.ngpio; priv->gpio.can_sleep = 1; + if (quirk) { + priv->dir_out = quirk->dir_out; + priv->dir_in = quirk->dir_in; + priv->gpio.names = quirk->names; + priv->gpio.init_valid_mask = mpsse_init_valid_mask; + } else { + priv->dir_in = U16_MAX; + priv->dir_out = U16_MAX; + } + err = usb_find_common_endpoints(interface->cur_altsetting, &priv->bulk_in, &priv->bulk_out, NULL, NULL); @@ -578,6 +680,7 @@ static int gpio_mpsse_probe(struct usb_interface *interface, priv->gpio.irq.parents = NULL; priv->gpio.irq.default_type = IRQ_TYPE_NONE; priv->gpio.irq.handler = handle_simple_irq; + priv->gpio.irq.init_valid_mask = mpsse_irq_init_valid_mask; err = devm_gpiochip_add_data(dev, &priv->gpio, priv); if (err) From 03ac8183c9a5f0a635184d3f4eceb47480fcd4a7 Mon Sep 17 00:00:00 2001 From: Mary Strodl Date: Tue, 14 Oct 2025 09:35:30 -0400 Subject: [PATCH 25/80] gpio: mpsse: support bryx radio interface kit This device is powered by an FT232H, which is very similar to the FT2232H this driver was written for. The key difference is it has only one MPSSE instead of two. As a result, it presents only one USB interface to the system, which conveniently "just works" out of the box with this driver. The brik exposes only two GPIO lines which are hardware limited to only be useful in one direction. As a result, I've restricted things on the driver side to refuse to configure any other lines. This device, unlike the sealevel device I wrote this driver for originally, is hotpluggable, which makes for all sorts of weird edgecases. I've tried my best to stress-test the parts that could go wrong, but given the new usecase, more heads taking a critical look at the teardown and synchronization bits on the driver as a whole would be appreciated. Signed-off-by: Mary Strodl Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20251014133530.3592716-5-mstrodl@csh.rit.edu Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mpsse.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/gpio/gpio-mpsse.c b/drivers/gpio/gpio-mpsse.c index 5ca00dd51a63..ace652ba4df1 100644 --- a/drivers/gpio/gpio-mpsse.c +++ b/drivers/gpio/gpio-mpsse.c @@ -65,8 +65,19 @@ struct mpsse_quirk { unsigned long dir_out; /* Bitmask of valid output pins */ }; +static struct mpsse_quirk bryx_brik_quirk = { + .names = { + [3] = "Push to Talk", + [5] = "Channel Activity", + }, + .dir_out = BIT(3), /* Push to Talk */ + .dir_in = BIT(5), /* Channel Activity */ +}; + static const struct usb_device_id gpio_mpsse_table[] = { { USB_DEVICE(0x0c52, 0xa064) }, /* SeaLevel Systems, Inc. */ + { USB_DEVICE(0x0403, 0x6988), /* FTDI, assigned to Bryx */ + .driver_info = (kernel_ulong_t)&bryx_brik_quirk}, { } /* Terminating entry */ }; From d5376026f9269601e239545e2ec4aea0cc62bf2a Mon Sep 17 00:00:00 2001 From: Vaibhav Gupta Date: Thu, 16 Oct 2025 16:36:13 +0000 Subject: [PATCH 26/80] gpio: bt8xx: use generic power management Switch to the generic PCI power management framework and remove legacy callbacks like .suspend() and .resume(). With the generic framework, the standard PCI related work like: - pci_save/restore_state() - pci_enable/disable_device() - pci_set_power_state() is handled by the PCI core and this driver should implement only gpio-bt8xx specific operations in its respective callback functions. Signed-off-by: Vaibhav Gupta Reviewed-by: Bjorn Helgaas Link: https://lore.kernel.org/r/20251016163618.1355923-1-vaibhavgupta40@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-bt8xx.c | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/drivers/gpio/gpio-bt8xx.c b/drivers/gpio/gpio-bt8xx.c index 05401da03ca3..324eeb77dbd5 100644 --- a/drivers/gpio/gpio-bt8xx.c +++ b/drivers/gpio/gpio-bt8xx.c @@ -52,10 +52,8 @@ struct bt8xxgpio { struct pci_dev *pdev; struct gpio_chip gpio; -#ifdef CONFIG_PM u32 saved_outen; u32 saved_data; -#endif }; #define bgwrite(dat, adr) writel((dat), bg->mmio+(adr)) @@ -224,9 +222,10 @@ static void bt8xxgpio_remove(struct pci_dev *pdev) pci_disable_device(pdev); } -#ifdef CONFIG_PM -static int bt8xxgpio_suspend(struct pci_dev *pdev, pm_message_t state) + +static int bt8xxgpio_suspend(struct device *dev) { + struct pci_dev *pdev = to_pci_dev(dev); struct bt8xxgpio *bg = pci_get_drvdata(pdev); scoped_guard(spinlock_irqsave, &bg->lock) { @@ -238,23 +237,13 @@ static int bt8xxgpio_suspend(struct pci_dev *pdev, pm_message_t state) bgwrite(0x0, BT848_GPIO_OUT_EN); } - pci_save_state(pdev); - pci_disable_device(pdev); - pci_set_power_state(pdev, pci_choose_state(pdev, state)); - return 0; } -static int bt8xxgpio_resume(struct pci_dev *pdev) +static int bt8xxgpio_resume(struct device *dev) { + struct pci_dev *pdev = to_pci_dev(dev); struct bt8xxgpio *bg = pci_get_drvdata(pdev); - int err; - - pci_set_power_state(pdev, PCI_D0); - err = pci_enable_device(pdev); - if (err) - return err; - pci_restore_state(pdev); guard(spinlock_irqsave)(&bg->lock); @@ -267,10 +256,8 @@ static int bt8xxgpio_resume(struct pci_dev *pdev) return 0; } -#else -#define bt8xxgpio_suspend NULL -#define bt8xxgpio_resume NULL -#endif /* CONFIG_PM */ + +static DEFINE_SIMPLE_DEV_PM_OPS(bt8xxgpio_pm_ops, bt8xxgpio_suspend, bt8xxgpio_resume); static const struct pci_device_id bt8xxgpio_pci_tbl[] = { { PCI_DEVICE(PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT848) }, @@ -286,8 +273,7 @@ static struct pci_driver bt8xxgpio_pci_driver = { .id_table = bt8xxgpio_pci_tbl, .probe = bt8xxgpio_probe, .remove = bt8xxgpio_remove, - .suspend = bt8xxgpio_suspend, - .resume = bt8xxgpio_resume, + .driver.pm = &bt8xxgpio_pm_ops, }; module_pci_driver(bt8xxgpio_pci_driver); From ed2bd02d24947e36c9438bee1449d9bf87671b16 Mon Sep 17 00:00:00 2001 From: Sander Vanheule Date: Tue, 21 Oct 2025 16:23:56 +0200 Subject: [PATCH 27/80] gpio: regmap: Force writes for aliased data regs GPIO chips often have data input and output fields aliased to the same offset. Since gpio-regmap performs a value update before the direction update (to prevent glitches), a pin currently configured as input may cause regmap_update_bits() to not perform a write. This may cause unexpected line states when the current input state equals the requested output state: OUT IN OUT DIR ''''''\...|.../'''''' pin ....../'''|'''\...... (1) (2) (3) 1. Line was configurad as out-low, but is reconfigured to input. External logic results in high value. 2. Set output value high. regmap_update_bits() sees the value is already high and discards the register write. 3. Line is switched to output, maintaining the stale output config (low) instead of the requested config (high). By switching to regmap_write_bits(), a write of the requested output value can be forced, irrespective of the read state. Do this only for aliased registers, so the more efficient regmap_update_bits() can still be used for distinct registers. Signed-off-by: Sander Vanheule Reviewed-by: Michael Walle Link: https://lore.kernel.org/r/20251021142407.307753-2-sander@svanheule.net Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-regmap.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c index f4267af00027..08f42ac4b89d 100644 --- a/drivers/gpio/gpio-regmap.c +++ b/drivers/gpio/gpio-regmap.c @@ -94,7 +94,7 @@ static int gpio_regmap_set(struct gpio_chip *chip, unsigned int offset, { struct gpio_regmap *gpio = gpiochip_get_data(chip); unsigned int base = gpio_regmap_addr(gpio->reg_set_base); - unsigned int reg, mask; + unsigned int reg, mask, mask_val; int ret; ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); @@ -102,9 +102,15 @@ static int gpio_regmap_set(struct gpio_chip *chip, unsigned int offset, return ret; if (val) - ret = regmap_update_bits(gpio->regmap, reg, mask, mask); + mask_val = mask; else - ret = regmap_update_bits(gpio->regmap, reg, mask, 0); + mask_val = 0; + + /* ignore input values which shadow the old output value */ + if (gpio->reg_dat_base == gpio->reg_set_base) + ret = regmap_write_bits(gpio->regmap, reg, mask, mask_val); + else + ret = regmap_update_bits(gpio->regmap, reg, mask, mask_val); return ret; } From 897396b418d1720aac39585b208aada708b5b433 Mon Sep 17 00:00:00 2001 From: Sander Vanheule Date: Tue, 21 Oct 2025 16:23:57 +0200 Subject: [PATCH 28/80] gpio: regmap: Bypass cache for aliased inputs GPIO chips often have data input and output registers aliased to the same offset. The output register is non-valitile and could in theory be cached. The input register however is volatile by nature and hence should not be cached, resulting in different requirements for reads and writes. The generic gpio chip implementation stores a shadow value of the pin output data, which is updated and written to hardware on output data changes in bgpio_set(), bgpio_set_set(). Pin input values are always obtained by reading the aliased data register from hardware. For gpio-regmap the situation is more complex as the output data could be in multiple registers, but we can use the regmap cache to shadow the output values when marking the data registers as non-volatile. By using regmap_read_bypassed() we can still treat the input values as volatile, irrespective of the regmap config. This ensures proper functioning of writing the output register with regmap_write_bits(), which will then use and update the cache only on data writes, gaining some performance from the cached output values. Signed-off-by: Sander Vanheule Reviewed-by: Linus Walleij Reviewed-by: Michael Walle Link: https://lore.kernel.org/r/20251021142407.307753-3-sander@svanheule.net Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-regmap.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c index 08f42ac4b89d..e5ba38e65c10 100644 --- a/drivers/gpio/gpio-regmap.c +++ b/drivers/gpio/gpio-regmap.c @@ -82,7 +82,11 @@ static int gpio_regmap_get(struct gpio_chip *chip, unsigned int offset) if (ret) return ret; - ret = regmap_read(gpio->regmap, reg, &val); + /* ensure we don't spoil any register cache with pin input values */ + if (gpio->reg_dat_base == gpio->reg_set_base) + ret = regmap_read_bypassed(gpio->regmap, reg, &val); + else + ret = regmap_read(gpio->regmap, reg, &val); if (ret) return ret; From 9452252dff94ff2cbcd33d3418c2b95ff74bdba5 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 24 Oct 2025 09:19:21 +0300 Subject: [PATCH 29/80] gpio: qixis-fpga: Fix a NULL vs IS_ERR() bug in probe() The devm_platform_ioremap_resource() function doesn't return NULL, it returns error pointers. Fix the checking to match. Fixes: e88500247dc3 ("gpio: add QIXIS FPGA GPIO controller") Signed-off-by: Dan Carpenter Reviewed-by: Michael Walle Link: https://lore.kernel.org/r/aPsaaf0h343Ba7c1@stanley.mountain Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-qixis-fpga.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-qixis-fpga.c b/drivers/gpio/gpio-qixis-fpga.c index 54c2c76822d5..6e67f43ac0bd 100644 --- a/drivers/gpio/gpio-qixis-fpga.c +++ b/drivers/gpio/gpio-qixis-fpga.c @@ -56,8 +56,8 @@ static int qixis_cpld_gpio_probe(struct platform_device *pdev) * create our own from the MMIO space. */ reg = devm_platform_ioremap_resource(pdev, 0); - if (!reg) - return -ENODEV; + if (IS_ERR(reg)) + return PTR_ERR(reg); regmap = devm_regmap_init_mmio(&pdev->dev, reg, ®map_config_8r_8v); if (!regmap) From 3cde66094575a5b1310a7631d28761bd3dfcea63 Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Thu, 23 Oct 2025 17:03:46 +0800 Subject: [PATCH 30/80] gpio: loongson-64bit: Switch to dynamic allocate GPIO base in byte mode gpiolib want to get completely rid of static gpiobase allocation, so switch to dynamic allocate GPIO base in byte mode, also can avoid warning message: [1.529974] gpio gpiochip0: Static allocation of GPIO base is deprecated, use dynamic allocation. Reported-by: Hongliang Wang Signed-off-by: Binbin Zhou Reviewed-by: Huacai Chen Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20251023090346.1995894-1-zhoubinbin@loongson.cn Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-loongson-64bit.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpio/gpio-loongson-64bit.c b/drivers/gpio/gpio-loongson-64bit.c index 02f181cb219e..d4e291b275f0 100644 --- a/drivers/gpio/gpio-loongson-64bit.c +++ b/drivers/gpio/gpio-loongson-64bit.c @@ -312,6 +312,7 @@ static int loongson_gpio_init(struct platform_device *pdev, struct loongson_gpio lgpio->chip.gc.direction_output = loongson_gpio_direction_output; lgpio->chip.gc.set = loongson_gpio_set; lgpio->chip.gc.parent = &pdev->dev; + lgpio->chip.gc.base = -1; spin_lock_init(&lgpio->lock); } From 7e061b462b3d43a1f85519f5aebdc77cbbe648c0 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 27 Oct 2025 14:48:02 +0100 Subject: [PATCH 31/80] gpio: mmio: use lock guards Shrink the code by a couple lines and improve lock management by using lock guards from cleanup.h. Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20251027-gpio-mmio-refactor-v1-1-b0de7cd5a4b9@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mmio.c | 40 ++++++++++++++-------------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/drivers/gpio/gpio-mmio.c b/drivers/gpio/gpio-mmio.c index 7d6dd36cf1ae..95ebbdf04343 100644 --- a/drivers/gpio/gpio-mmio.c +++ b/drivers/gpio/gpio-mmio.c @@ -41,6 +41,7 @@ o ` ~~~~\___/~~~~ ` controller in FPGA is ,.` */ #include +#include #include #include #include @@ -229,9 +230,8 @@ static int bgpio_set(struct gpio_chip *gc, unsigned int gpio, int val) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); unsigned long mask = bgpio_line2mask(gc, gpio); - unsigned long flags; - raw_spin_lock_irqsave(&chip->lock, flags); + guard(raw_spinlock)(&chip->lock); if (val) chip->sdata |= mask; @@ -240,8 +240,6 @@ static int bgpio_set(struct gpio_chip *gc, unsigned int gpio, int val) chip->write_reg(chip->reg_dat, chip->sdata); - raw_spin_unlock_irqrestore(&chip->lock, flags); - return 0; } @@ -262,9 +260,9 @@ static int bgpio_set_with_clear(struct gpio_chip *gc, unsigned int gpio, static int bgpio_set_set(struct gpio_chip *gc, unsigned int gpio, int val) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); - unsigned long mask = bgpio_line2mask(gc, gpio), flags; + unsigned long mask = bgpio_line2mask(gc, gpio); - raw_spin_lock_irqsave(&chip->lock, flags); + guard(raw_spinlock)(&chip->lock); if (val) chip->sdata |= mask; @@ -273,8 +271,6 @@ static int bgpio_set_set(struct gpio_chip *gc, unsigned int gpio, int val) chip->write_reg(chip->reg_set, chip->sdata); - raw_spin_unlock_irqrestore(&chip->lock, flags); - return 0; } @@ -303,9 +299,9 @@ static void bgpio_set_multiple_single_reg(struct gpio_chip *gc, void __iomem *reg) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); - unsigned long flags, set_mask, clear_mask; + unsigned long set_mask, clear_mask; - raw_spin_lock_irqsave(&chip->lock, flags); + guard(raw_spinlock)(&chip->lock); bgpio_multiple_get_masks(gc, mask, bits, &set_mask, &clear_mask); @@ -313,8 +309,6 @@ static void bgpio_set_multiple_single_reg(struct gpio_chip *gc, chip->sdata &= ~clear_mask; chip->write_reg(reg, chip->sdata); - - raw_spin_unlock_irqrestore(&chip->lock, flags); } static int bgpio_set_multiple(struct gpio_chip *gc, unsigned long *mask, @@ -394,18 +388,15 @@ static int bgpio_simple_dir_out(struct gpio_chip *gc, unsigned int gpio, static int bgpio_dir_in(struct gpio_chip *gc, unsigned int gpio) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); - unsigned long flags; - raw_spin_lock_irqsave(&chip->lock, flags); + scoped_guard(raw_spinlock, &chip->lock) { + chip->sdir &= ~bgpio_line2mask(gc, gpio); - chip->sdir &= ~bgpio_line2mask(gc, gpio); - - if (chip->reg_dir_in) - chip->write_reg(chip->reg_dir_in, ~chip->sdir); - if (chip->reg_dir_out) - chip->write_reg(chip->reg_dir_out, chip->sdir); - - raw_spin_unlock_irqrestore(&chip->lock, flags); + if (chip->reg_dir_in) + chip->write_reg(chip->reg_dir_in, ~chip->sdir); + if (chip->reg_dir_out) + chip->write_reg(chip->reg_dir_out, chip->sdir); + } return bgpio_dir_return(gc, gpio, false); } @@ -437,9 +428,8 @@ static int bgpio_get_dir(struct gpio_chip *gc, unsigned int gpio) static void bgpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); - unsigned long flags; - raw_spin_lock_irqsave(&chip->lock, flags); + guard(raw_spinlock)(&chip->lock); chip->sdir |= bgpio_line2mask(gc, gpio); @@ -447,8 +437,6 @@ static void bgpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) chip->write_reg(chip->reg_dir_in, ~chip->sdir); if (chip->reg_dir_out) chip->write_reg(chip->reg_dir_out, chip->sdir); - - raw_spin_unlock_irqrestore(&chip->lock, flags); } static int bgpio_dir_out_dir_first(struct gpio_chip *gc, unsigned int gpio, From 13172171f5c44df67e8882d983fb50d9b27477ad Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 27 Oct 2025 14:48:03 +0100 Subject: [PATCH 32/80] gpio: mmio: drop the "bgpio" prefix The "bgpio" prefix is a historical left-over. We no longer use it in any user-facing symbol. Let's drop it from the module's internals as well and replace it with "gpio_mmio_". Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20251027-gpio-mmio-refactor-v1-2-b0de7cd5a4b9@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mmio.c | 299 ++++++++++++++++++++------------------- 1 file changed, 150 insertions(+), 149 deletions(-) diff --git a/drivers/gpio/gpio-mmio.c b/drivers/gpio/gpio-mmio.c index 95ebbdf04343..b3a26a06260b 100644 --- a/drivers/gpio/gpio-mmio.c +++ b/drivers/gpio/gpio-mmio.c @@ -62,69 +62,69 @@ o ` ~~~~\___/~~~~ ` controller in FPGA is ,.` #include "gpiolib.h" -static void bgpio_write8(void __iomem *reg, unsigned long data) +static void gpio_mmio_write8(void __iomem *reg, unsigned long data) { writeb(data, reg); } -static unsigned long bgpio_read8(void __iomem *reg) +static unsigned long gpio_mmio_read8(void __iomem *reg) { return readb(reg); } -static void bgpio_write16(void __iomem *reg, unsigned long data) +static void gpio_mmio_write16(void __iomem *reg, unsigned long data) { writew(data, reg); } -static unsigned long bgpio_read16(void __iomem *reg) +static unsigned long gpio_mmio_read16(void __iomem *reg) { return readw(reg); } -static void bgpio_write32(void __iomem *reg, unsigned long data) +static void gpio_mmio_write32(void __iomem *reg, unsigned long data) { writel(data, reg); } -static unsigned long bgpio_read32(void __iomem *reg) +static unsigned long gpio_mmio_read32(void __iomem *reg) { return readl(reg); } #if BITS_PER_LONG >= 64 -static void bgpio_write64(void __iomem *reg, unsigned long data) +static void gpio_mmio_write64(void __iomem *reg, unsigned long data) { writeq(data, reg); } -static unsigned long bgpio_read64(void __iomem *reg) +static unsigned long gpio_mmio_read64(void __iomem *reg) { return readq(reg); } #endif /* BITS_PER_LONG >= 64 */ -static void bgpio_write16be(void __iomem *reg, unsigned long data) +static void gpio_mmio_write16be(void __iomem *reg, unsigned long data) { iowrite16be(data, reg); } -static unsigned long bgpio_read16be(void __iomem *reg) +static unsigned long gpio_mmio_read16be(void __iomem *reg) { return ioread16be(reg); } -static void bgpio_write32be(void __iomem *reg, unsigned long data) +static void gpio_mmio_write32be(void __iomem *reg, unsigned long data) { iowrite32be(data, reg); } -static unsigned long bgpio_read32be(void __iomem *reg) +static unsigned long gpio_mmio_read32be(void __iomem *reg) { return ioread32be(reg); } -static unsigned long bgpio_line2mask(struct gpio_chip *gc, unsigned int line) +static unsigned long gpio_mmio_line2mask(struct gpio_chip *gc, unsigned int line) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); @@ -133,10 +133,10 @@ static unsigned long bgpio_line2mask(struct gpio_chip *gc, unsigned int line) return BIT(line); } -static int bgpio_get_set(struct gpio_chip *gc, unsigned int gpio) +static int gpio_mmio_get_set(struct gpio_chip *gc, unsigned int gpio) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); - unsigned long pinmask = bgpio_line2mask(gc, gpio); + unsigned long pinmask = gpio_mmio_line2mask(gc, gpio); bool dir = !!(chip->sdir & pinmask); if (dir) @@ -149,8 +149,8 @@ static int bgpio_get_set(struct gpio_chip *gc, unsigned int gpio) * This assumes that the bits in the GPIO register are in native endianness. * We only assign the function pointer if we have that. */ -static int bgpio_get_set_multiple(struct gpio_chip *gc, unsigned long *mask, - unsigned long *bits) +static int gpio_mmio_get_set_multiple(struct gpio_chip *gc, unsigned long *mask, + unsigned long *bits) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); unsigned long get_mask = 0, set_mask = 0; @@ -169,18 +169,18 @@ static int bgpio_get_set_multiple(struct gpio_chip *gc, unsigned long *mask, return 0; } -static int bgpio_get(struct gpio_chip *gc, unsigned int gpio) +static int gpio_mmio_get(struct gpio_chip *gc, unsigned int gpio) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); - return !!(chip->read_reg(chip->reg_dat) & bgpio_line2mask(gc, gpio)); + return !!(chip->read_reg(chip->reg_dat) & gpio_mmio_line2mask(gc, gpio)); } /* * This only works if the bits in the GPIO register are in native endianness. */ -static int bgpio_get_multiple(struct gpio_chip *gc, unsigned long *mask, - unsigned long *bits) +static int gpio_mmio_get_multiple(struct gpio_chip *gc, unsigned long *mask, + unsigned long *bits) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); @@ -193,8 +193,8 @@ static int bgpio_get_multiple(struct gpio_chip *gc, unsigned long *mask, /* * With big endian mirrored bit order it becomes more tedious. */ -static int bgpio_get_multiple_be(struct gpio_chip *gc, unsigned long *mask, - unsigned long *bits) +static int gpio_mmio_get_multiple_be(struct gpio_chip *gc, unsigned long *mask, + unsigned long *bits) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); unsigned long readmask = 0; @@ -206,7 +206,7 @@ static int bgpio_get_multiple_be(struct gpio_chip *gc, unsigned long *mask, /* Create a mirrored mask */ for_each_set_bit(bit, mask, gc->ngpio) - readmask |= bgpio_line2mask(gc, bit); + readmask |= gpio_mmio_line2mask(gc, bit); /* Read the register */ val = chip->read_reg(chip->reg_dat) & readmask; @@ -216,20 +216,20 @@ static int bgpio_get_multiple_be(struct gpio_chip *gc, unsigned long *mask, * in bit 0 ... line 31 in bit 31 for a 32bit register. */ for_each_set_bit(bit, &val, gc->ngpio) - *bits |= bgpio_line2mask(gc, bit); + *bits |= gpio_mmio_line2mask(gc, bit); return 0; } -static int bgpio_set_none(struct gpio_chip *gc, unsigned int gpio, int val) +static int gpio_mmio_set_none(struct gpio_chip *gc, unsigned int gpio, int val) { return 0; } -static int bgpio_set(struct gpio_chip *gc, unsigned int gpio, int val) +static int gpio_mmio_set(struct gpio_chip *gc, unsigned int gpio, int val) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); - unsigned long mask = bgpio_line2mask(gc, gpio); + unsigned long mask = gpio_mmio_line2mask(gc, gpio); guard(raw_spinlock)(&chip->lock); @@ -243,11 +243,11 @@ static int bgpio_set(struct gpio_chip *gc, unsigned int gpio, int val) return 0; } -static int bgpio_set_with_clear(struct gpio_chip *gc, unsigned int gpio, - int val) +static int gpio_mmio_set_with_clear(struct gpio_chip *gc, unsigned int gpio, + int val) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); - unsigned long mask = bgpio_line2mask(gc, gpio); + unsigned long mask = gpio_mmio_line2mask(gc, gpio); if (val) chip->write_reg(chip->reg_set, mask); @@ -257,10 +257,10 @@ static int bgpio_set_with_clear(struct gpio_chip *gc, unsigned int gpio, return 0; } -static int bgpio_set_set(struct gpio_chip *gc, unsigned int gpio, int val) +static int gpio_mmio_set_set(struct gpio_chip *gc, unsigned int gpio, int val) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); - unsigned long mask = bgpio_line2mask(gc, gpio); + unsigned long mask = gpio_mmio_line2mask(gc, gpio); guard(raw_spinlock)(&chip->lock); @@ -274,10 +274,11 @@ static int bgpio_set_set(struct gpio_chip *gc, unsigned int gpio, int val) return 0; } -static void bgpio_multiple_get_masks(struct gpio_chip *gc, - unsigned long *mask, unsigned long *bits, - unsigned long *set_mask, - unsigned long *clear_mask) +static void gpio_mmio_multiple_get_masks(struct gpio_chip *gc, + unsigned long *mask, + unsigned long *bits, + unsigned long *set_mask, + unsigned long *clear_mask) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); int i; @@ -287,23 +288,23 @@ static void bgpio_multiple_get_masks(struct gpio_chip *gc, for_each_set_bit(i, mask, chip->bits) { if (test_bit(i, bits)) - *set_mask |= bgpio_line2mask(gc, i); + *set_mask |= gpio_mmio_line2mask(gc, i); else - *clear_mask |= bgpio_line2mask(gc, i); + *clear_mask |= gpio_mmio_line2mask(gc, i); } } -static void bgpio_set_multiple_single_reg(struct gpio_chip *gc, - unsigned long *mask, - unsigned long *bits, - void __iomem *reg) +static void gpio_mmio_set_multiple_single_reg(struct gpio_chip *gc, + unsigned long *mask, + unsigned long *bits, + void __iomem *reg) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); unsigned long set_mask, clear_mask; guard(raw_spinlock)(&chip->lock); - bgpio_multiple_get_masks(gc, mask, bits, &set_mask, &clear_mask); + gpio_mmio_multiple_get_masks(gc, mask, bits, &set_mask, &clear_mask); chip->sdata |= set_mask; chip->sdata &= ~clear_mask; @@ -311,34 +312,34 @@ static void bgpio_set_multiple_single_reg(struct gpio_chip *gc, chip->write_reg(reg, chip->sdata); } -static int bgpio_set_multiple(struct gpio_chip *gc, unsigned long *mask, - unsigned long *bits) -{ - struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); - - bgpio_set_multiple_single_reg(gc, mask, bits, chip->reg_dat); - - return 0; -} - -static int bgpio_set_multiple_set(struct gpio_chip *gc, unsigned long *mask, +static int gpio_mmio_set_multiple(struct gpio_chip *gc, unsigned long *mask, unsigned long *bits) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); - bgpio_set_multiple_single_reg(gc, mask, bits, chip->reg_set); + gpio_mmio_set_multiple_single_reg(gc, mask, bits, chip->reg_dat); return 0; } -static int bgpio_set_multiple_with_clear(struct gpio_chip *gc, - unsigned long *mask, - unsigned long *bits) +static int gpio_mmio_set_multiple_set(struct gpio_chip *gc, unsigned long *mask, + unsigned long *bits) +{ + struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); + + gpio_mmio_set_multiple_single_reg(gc, mask, bits, chip->reg_set); + + return 0; +} + +static int gpio_mmio_set_multiple_with_clear(struct gpio_chip *gc, + unsigned long *mask, + unsigned long *bits) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); unsigned long set_mask, clear_mask; - bgpio_multiple_get_masks(gc, mask, bits, &set_mask, &clear_mask); + gpio_mmio_multiple_get_masks(gc, mask, bits, &set_mask, &clear_mask); if (set_mask) chip->write_reg(chip->reg_set, set_mask); @@ -348,7 +349,8 @@ static int bgpio_set_multiple_with_clear(struct gpio_chip *gc, return 0; } -static int bgpio_dir_return(struct gpio_chip *gc, unsigned int gpio, bool dir_out) +static int gpio_mmio_dir_return(struct gpio_chip *gc, unsigned int gpio, + bool dir_out) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); @@ -361,36 +363,36 @@ static int bgpio_dir_return(struct gpio_chip *gc, unsigned int gpio, bool dir_ou return pinctrl_gpio_direction_input(gc, gpio); } -static int bgpio_dir_in_err(struct gpio_chip *gc, unsigned int gpio) +static int gpio_mmio_dir_in_err(struct gpio_chip *gc, unsigned int gpio) { return -EINVAL; } -static int bgpio_simple_dir_in(struct gpio_chip *gc, unsigned int gpio) +static int gpio_mmio_simple_dir_in(struct gpio_chip *gc, unsigned int gpio) { - return bgpio_dir_return(gc, gpio, false); + return gpio_mmio_dir_return(gc, gpio, false); } -static int bgpio_dir_out_err(struct gpio_chip *gc, unsigned int gpio, - int val) +static int gpio_mmio_dir_out_err(struct gpio_chip *gc, unsigned int gpio, + int val) { return -EINVAL; } -static int bgpio_simple_dir_out(struct gpio_chip *gc, unsigned int gpio, - int val) +static int gpio_mmio_simple_dir_out(struct gpio_chip *gc, unsigned int gpio, + int val) { gc->set(gc, gpio, val); - return bgpio_dir_return(gc, gpio, true); + return gpio_mmio_dir_return(gc, gpio, true); } -static int bgpio_dir_in(struct gpio_chip *gc, unsigned int gpio) +static int gpio_mmio_dir_in(struct gpio_chip *gc, unsigned int gpio) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); scoped_guard(raw_spinlock, &chip->lock) { - chip->sdir &= ~bgpio_line2mask(gc, gpio); + chip->sdir &= ~gpio_mmio_line2mask(gc, gpio); if (chip->reg_dir_in) chip->write_reg(chip->reg_dir_in, ~chip->sdir); @@ -398,40 +400,40 @@ static int bgpio_dir_in(struct gpio_chip *gc, unsigned int gpio) chip->write_reg(chip->reg_dir_out, chip->sdir); } - return bgpio_dir_return(gc, gpio, false); + return gpio_mmio_dir_return(gc, gpio, false); } -static int bgpio_get_dir(struct gpio_chip *gc, unsigned int gpio) +static int gpio_mmio_get_dir(struct gpio_chip *gc, unsigned int gpio) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); /* Return 0 if output, 1 if input */ if (chip->dir_unreadable) { - if (chip->sdir & bgpio_line2mask(gc, gpio)) + if (chip->sdir & gpio_mmio_line2mask(gc, gpio)) return GPIO_LINE_DIRECTION_OUT; return GPIO_LINE_DIRECTION_IN; } if (chip->reg_dir_out) { - if (chip->read_reg(chip->reg_dir_out) & bgpio_line2mask(gc, gpio)) + if (chip->read_reg(chip->reg_dir_out) & gpio_mmio_line2mask(gc, gpio)) return GPIO_LINE_DIRECTION_OUT; return GPIO_LINE_DIRECTION_IN; } if (chip->reg_dir_in) - if (!(chip->read_reg(chip->reg_dir_in) & bgpio_line2mask(gc, gpio))) + if (!(chip->read_reg(chip->reg_dir_in) & gpio_mmio_line2mask(gc, gpio))) return GPIO_LINE_DIRECTION_OUT; return GPIO_LINE_DIRECTION_IN; } -static void bgpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) +static void gpio_mmio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); guard(raw_spinlock)(&chip->lock); - chip->sdir |= bgpio_line2mask(gc, gpio); + chip->sdir |= gpio_mmio_line2mask(gc, gpio); if (chip->reg_dir_in) chip->write_reg(chip->reg_dir_in, ~chip->sdir); @@ -439,47 +441,47 @@ static void bgpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) chip->write_reg(chip->reg_dir_out, chip->sdir); } -static int bgpio_dir_out_dir_first(struct gpio_chip *gc, unsigned int gpio, - int val) +static int gpio_mmio_dir_out_dir_first(struct gpio_chip *gc, unsigned int gpio, + int val) { - bgpio_dir_out(gc, gpio, val); + gpio_mmio_dir_out(gc, gpio, val); gc->set(gc, gpio, val); - return bgpio_dir_return(gc, gpio, true); + return gpio_mmio_dir_return(gc, gpio, true); } -static int bgpio_dir_out_val_first(struct gpio_chip *gc, unsigned int gpio, - int val) +static int gpio_mmio_dir_out_val_first(struct gpio_chip *gc, unsigned int gpio, + int val) { gc->set(gc, gpio, val); - bgpio_dir_out(gc, gpio, val); - return bgpio_dir_return(gc, gpio, true); + gpio_mmio_dir_out(gc, gpio, val); + return gpio_mmio_dir_return(gc, gpio, true); } -static int bgpio_setup_accessors(struct device *dev, - struct gpio_generic_chip *chip, - bool byte_be) +static int gpio_mmio_setup_accessors(struct device *dev, + struct gpio_generic_chip *chip, + bool byte_be) { switch (chip->bits) { case 8: - chip->read_reg = bgpio_read8; - chip->write_reg = bgpio_write8; + chip->read_reg = gpio_mmio_read8; + chip->write_reg = gpio_mmio_write8; break; case 16: if (byte_be) { - chip->read_reg = bgpio_read16be; - chip->write_reg = bgpio_write16be; + chip->read_reg = gpio_mmio_read16be; + chip->write_reg = gpio_mmio_write16be; } else { - chip->read_reg = bgpio_read16; - chip->write_reg = bgpio_write16; + chip->read_reg = gpio_mmio_read16; + chip->write_reg = gpio_mmio_write16; } break; case 32: if (byte_be) { - chip->read_reg = bgpio_read32be; - chip->write_reg = bgpio_write32be; + chip->read_reg = gpio_mmio_read32be; + chip->write_reg = gpio_mmio_write32be; } else { - chip->read_reg = bgpio_read32; - chip->write_reg = bgpio_write32; + chip->read_reg = gpio_mmio_read32; + chip->write_reg = gpio_mmio_write32; } break; #if BITS_PER_LONG >= 64 @@ -489,8 +491,8 @@ static int bgpio_setup_accessors(struct device *dev, "64 bit big endian byte order unsupported\n"); return -EINVAL; } else { - chip->read_reg = bgpio_read64; - chip->write_reg = bgpio_write64; + chip->read_reg = gpio_mmio_read64; + chip->write_reg = gpio_mmio_write64; } break; #endif /* BITS_PER_LONG >= 64 */ @@ -524,8 +526,8 @@ static int bgpio_setup_accessors(struct device *dev, * - an input direction register (named "dirin") where a 1 bit indicates * the GPIO is an input. */ -static int bgpio_setup_io(struct gpio_generic_chip *chip, - const struct gpio_generic_chip_config *cfg) +static int gpio_mmio_setup_io(struct gpio_generic_chip *chip, + const struct gpio_generic_chip_config *cfg) { struct gpio_chip *gc = &chip->gc; @@ -536,25 +538,25 @@ static int bgpio_setup_io(struct gpio_generic_chip *chip, if (cfg->set && cfg->clr) { chip->reg_set = cfg->set; chip->reg_clr = cfg->clr; - gc->set = bgpio_set_with_clear; - gc->set_multiple = bgpio_set_multiple_with_clear; + gc->set = gpio_mmio_set_with_clear; + gc->set_multiple = gpio_mmio_set_multiple_with_clear; } else if (cfg->set && !cfg->clr) { chip->reg_set = cfg->set; - gc->set = bgpio_set_set; - gc->set_multiple = bgpio_set_multiple_set; + gc->set = gpio_mmio_set_set; + gc->set_multiple = gpio_mmio_set_multiple_set; } else if (cfg->flags & GPIO_GENERIC_NO_OUTPUT) { - gc->set = bgpio_set_none; + gc->set = gpio_mmio_set_none; gc->set_multiple = NULL; } else { - gc->set = bgpio_set; - gc->set_multiple = bgpio_set_multiple; + gc->set = gpio_mmio_set; + gc->set_multiple = gpio_mmio_set_multiple; } if (!(cfg->flags & GPIO_GENERIC_UNREADABLE_REG_SET) && (cfg->flags & GPIO_GENERIC_READ_OUTPUT_REG_SET)) { - gc->get = bgpio_get_set; + gc->get = gpio_mmio_get_set; if (!chip->be_bits) - gc->get_multiple = bgpio_get_set_multiple; + gc->get_multiple = gpio_mmio_get_set_multiple; /* * We deliberately avoid assigning the ->get_multiple() call * for big endian mirrored registers which are ALSO reflecting @@ -563,18 +565,18 @@ static int bgpio_setup_io(struct gpio_generic_chip *chip, * reading each line individually in that fringe case. */ } else { - gc->get = bgpio_get; + gc->get = gpio_mmio_get; if (chip->be_bits) - gc->get_multiple = bgpio_get_multiple_be; + gc->get_multiple = gpio_mmio_get_multiple_be; else - gc->get_multiple = bgpio_get_multiple; + gc->get_multiple = gpio_mmio_get_multiple; } return 0; } -static int bgpio_setup_direction(struct gpio_generic_chip *chip, - const struct gpio_generic_chip_config *cfg) +static int gpio_mmio_setup_direction(struct gpio_generic_chip *chip, + const struct gpio_generic_chip_config *cfg) { struct gpio_chip *gc = &chip->gc; @@ -582,27 +584,27 @@ static int bgpio_setup_direction(struct gpio_generic_chip *chip, chip->reg_dir_out = cfg->dirout; chip->reg_dir_in = cfg->dirin; if (cfg->flags & GPIO_GENERIC_NO_SET_ON_INPUT) - gc->direction_output = bgpio_dir_out_dir_first; + gc->direction_output = gpio_mmio_dir_out_dir_first; else - gc->direction_output = bgpio_dir_out_val_first; - gc->direction_input = bgpio_dir_in; - gc->get_direction = bgpio_get_dir; + gc->direction_output = gpio_mmio_dir_out_val_first; + gc->direction_input = gpio_mmio_dir_in; + gc->get_direction = gpio_mmio_get_dir; } else { if (cfg->flags & GPIO_GENERIC_NO_OUTPUT) - gc->direction_output = bgpio_dir_out_err; + gc->direction_output = gpio_mmio_dir_out_err; else - gc->direction_output = bgpio_simple_dir_out; + gc->direction_output = gpio_mmio_simple_dir_out; if (cfg->flags & GPIO_GENERIC_NO_INPUT) - gc->direction_input = bgpio_dir_in_err; + gc->direction_input = gpio_mmio_dir_in_err; else - gc->direction_input = bgpio_simple_dir_in; + gc->direction_input = gpio_mmio_simple_dir_in; } return 0; } -static int bgpio_request(struct gpio_chip *gc, unsigned int gpio_pin) +static int gpio_mmio_request(struct gpio_chip *gc, unsigned int gpio_pin) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); @@ -641,23 +643,23 @@ int gpio_generic_chip_init(struct gpio_generic_chip *chip, gc->parent = dev; gc->label = dev_name(dev); gc->base = -1; - gc->request = bgpio_request; + gc->request = gpio_mmio_request; chip->be_bits = !!(flags & GPIO_GENERIC_BIG_ENDIAN); ret = gpiochip_get_ngpios(gc, dev); if (ret) gc->ngpio = chip->bits; - ret = bgpio_setup_io(chip, cfg); + ret = gpio_mmio_setup_io(chip, cfg); if (ret) return ret; - ret = bgpio_setup_accessors(dev, chip, + ret = gpio_mmio_setup_accessors(dev, chip, flags & GPIO_GENERIC_BIG_ENDIAN_BYTE_ORDER); if (ret) return ret; - ret = bgpio_setup_direction(chip, cfg); + ret = gpio_mmio_setup_direction(chip, cfg); if (ret) return ret; @@ -668,7 +670,7 @@ int gpio_generic_chip_init(struct gpio_generic_chip *chip, } chip->sdata = chip->read_reg(chip->reg_dat); - if (gc->set == bgpio_set_set && + if (gc->set == gpio_mmio_set_set && !(flags & GPIO_GENERIC_UNREADABLE_REG_SET)) chip->sdata = chip->read_reg(chip->reg_set); @@ -700,9 +702,8 @@ EXPORT_SYMBOL_GPL(gpio_generic_chip_init); #if IS_ENABLED(CONFIG_GPIO_GENERIC_PLATFORM) -static void __iomem *bgpio_map(struct platform_device *pdev, - const char *name, - resource_size_t sane_sz) +static void __iomem *gpio_mmio_map(struct platform_device *pdev, + const char *name, resource_size_t sane_sz) { struct resource *r; resource_size_t sz; @@ -718,16 +719,16 @@ static void __iomem *bgpio_map(struct platform_device *pdev, return devm_ioremap_resource(&pdev->dev, r); } -static const struct of_device_id bgpio_of_match[] = { +static const struct of_device_id gpio_mmio_of_match[] = { { .compatible = "brcm,bcm6345-gpio" }, { .compatible = "wd,mbl-gpio" }, { .compatible = "ni,169445-nand-gpio" }, { .compatible = "intel,ixp4xx-expansion-bus-mmio-gpio" }, { } }; -MODULE_DEVICE_TABLE(of, bgpio_of_match); +MODULE_DEVICE_TABLE(of, gpio_mmio_of_match); -static int bgpio_pdev_probe(struct platform_device *pdev) +static int gpio_mmio_pdev_probe(struct platform_device *pdev) { struct gpio_generic_chip_config config; struct gpio_generic_chip *gen_gc; @@ -750,23 +751,23 @@ static int bgpio_pdev_probe(struct platform_device *pdev) sz = resource_size(r); - dat = bgpio_map(pdev, "dat", sz); + dat = gpio_mmio_map(pdev, "dat", sz); if (IS_ERR(dat)) return PTR_ERR(dat); - set = bgpio_map(pdev, "set", sz); + set = gpio_mmio_map(pdev, "set", sz); if (IS_ERR(set)) return PTR_ERR(set); - clr = bgpio_map(pdev, "clr", sz); + clr = gpio_mmio_map(pdev, "clr", sz); if (IS_ERR(clr)) return PTR_ERR(clr); - dirout = bgpio_map(pdev, "dirout", sz); + dirout = gpio_mmio_map(pdev, "dirout", sz); if (IS_ERR(dirout)) return PTR_ERR(dirout); - dirin = bgpio_map(pdev, "dirin", sz); + dirin = gpio_mmio_map(pdev, "dirin", sz); if (IS_ERR(dirin)) return PTR_ERR(dirin); @@ -812,25 +813,25 @@ static int bgpio_pdev_probe(struct platform_device *pdev) return devm_gpiochip_add_data(&pdev->dev, &gen_gc->gc, NULL); } -static const struct platform_device_id bgpio_id_table[] = { +static const struct platform_device_id gpio_mmio_id_table[] = { { .name = "basic-mmio-gpio", .driver_data = 0, }, { } }; -MODULE_DEVICE_TABLE(platform, bgpio_id_table); +MODULE_DEVICE_TABLE(platform, gpio_mmio_id_table); -static struct platform_driver bgpio_driver = { +static struct platform_driver gpio_mmio_driver = { .driver = { .name = "basic-mmio-gpio", - .of_match_table = bgpio_of_match, + .of_match_table = gpio_mmio_of_match, }, - .id_table = bgpio_id_table, - .probe = bgpio_pdev_probe, + .id_table = gpio_mmio_id_table, + .probe = gpio_mmio_pdev_probe, }; -module_platform_driver(bgpio_driver); +module_platform_driver(gpio_mmio_driver); #endif /* CONFIG_GPIO_GENERIC_PLATFORM */ From bac88be0d2a83daf761129828e7ae3c79cc260c2 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 29 Oct 2025 10:11:38 +0100 Subject: [PATCH 33/80] gpio: mm-lantiq: update kernel docs Update kernel docs which are now outdated following the conversion to using the modern GPIO provider API. Cc: Christophe Leroy Fixes: 8d0d46da40c8 ("gpio: mm-lantiq: Drop legacy-of-mm-gpiochip.h header from GPIO driver") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202510290348.IpSNHCxr-lkp@intel.com/ Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20251029091138.7995-1-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mm-lantiq.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/gpio/gpio-mm-lantiq.c b/drivers/gpio/gpio-mm-lantiq.c index 3d2e24d61475..1bd98c50a459 100644 --- a/drivers/gpio/gpio-mm-lantiq.c +++ b/drivers/gpio/gpio-mm-lantiq.c @@ -52,8 +52,8 @@ static void ltq_mm_apply(struct ltq_mm *chip) /** * ltq_mm_set() - gpio_chip->set - set gpios. * @gc: Pointer to gpio_chip device structure. - * @gpio: GPIO signal number. - * @val: Value to be written to specified signal. + * @offset: GPIO signal number. + * @value: Value to be written to specified signal. * * Set the shadow value and call ltq_mm_apply. Always returns 0. */ @@ -73,8 +73,8 @@ static int ltq_mm_set(struct gpio_chip *gc, unsigned int offset, int value) /** * ltq_mm_dir_out() - gpio_chip->dir_out - set gpio direction. * @gc: Pointer to gpio_chip device structure. - * @gpio: GPIO signal number. - * @val: Value to be written to specified signal. + * @offset: GPIO signal number. + * @value: Value to be written to specified signal. * * Same as ltq_mm_set, always returns 0. */ @@ -85,7 +85,7 @@ static int ltq_mm_dir_out(struct gpio_chip *gc, unsigned offset, int value) /** * ltq_mm_save_regs() - Set initial values of GPIO pins - * @mm_gc: pointer to memory mapped GPIO chip structure + * @chip: Pointer to our private data structure. */ static void ltq_mm_save_regs(struct ltq_mm *chip) { From b6d31cd41814a33c1a22b8c676131820440cc44e Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Fri, 31 Oct 2025 12:16:28 +0100 Subject: [PATCH 34/80] gpio: cdev: replace use of system_wq with system_percpu_wq Currently if a user enqueue a work item using schedule_delayed_work() the used wq is "system_wq" (per-cpu wq) while queue_delayed_work() use WORK_CPU_UNBOUND (used when a cpu is not specified). The same applies to schedule_work() that is using system_wq and queue_work(), that makes use again of WORK_CPU_UNBOUND. This lack of consistency cannot be addressed without refactoring the API. system_wq should be the per-cpu workqueue, yet in this name nothing makes that clear, so replace system_wq with system_percpu_wq. The old wq (system_wq) will be kept for a few release cycles. Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Link: https://lore.kernel.org/r/20251031111628.143924-2-marco.crivellari@suse.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-cdev.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index ed064e6f268c..c437f14132b8 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -700,7 +700,7 @@ static enum hte_return process_hw_ts(struct hte_ts_data *ts, void *p) if (READ_ONCE(line->sw_debounced)) { line->total_discard_seq++; line->last_seqno = ts->seq; - mod_delayed_work(system_wq, &line->work, + mod_delayed_work(system_percpu_wq, &line->work, usecs_to_jiffies(READ_ONCE(line->desc->debounce_period_us))); } else { if (unlikely(ts->seq < line->line_seqno)) @@ -841,7 +841,7 @@ static irqreturn_t debounce_irq_handler(int irq, void *p) { struct line *line = p; - mod_delayed_work(system_wq, &line->work, + mod_delayed_work(system_percpu_wq, &line->work, usecs_to_jiffies(READ_ONCE(line->desc->debounce_period_us))); return IRQ_HANDLED; From e511d484cbe44fe48a1b9f621f6a947c72503f9e Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 12 Nov 2025 14:55:36 +0100 Subject: [PATCH 35/80] arm64: select HAVE_SHARED_GPIOS for ARCH_QCOM Some qualcomm platforms use shared GPIOs. Enable support for them by selecting the Kconfig switch provided by GPIOLIB. Acked-by: Linus Walleij Acked-by: Bjorn Andersson Acked-by: Arnd Bergmann Link: https://lore.kernel.org/r/20251112-gpio-shared-v4-7-b51f97b1abd8@linaro.org Signed-off-by: Bartosz Golaszewski --- arch/arm64/Kconfig.platforms | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms index 13173795c43d..3dbff0261f0a 100644 --- a/arch/arm64/Kconfig.platforms +++ b/arch/arm64/Kconfig.platforms @@ -316,6 +316,7 @@ config ARCH_QCOM select GPIOLIB select PINCTRL select HAVE_PWRCTRL if PCI + select HAVE_SHARED_GPIOS help This enables support for the ARMv8 based Qualcomm chipsets. From 61e1fd2abca4c551fb40afcb733a31de1991c656 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 12 Nov 2025 10:32:01 +0100 Subject: [PATCH 36/80] gpiolib: legacy: Make sure we kill gpio_request_one() first Make sure we kill gpio_request_one() first by converting it to use legacy APIs that will be alive a bit longer. In particular, this also shows the code we will use in another function to make it die independently. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20251112093608.1481030-2-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-legacy.c | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/drivers/gpio/gpiolib-legacy.c b/drivers/gpio/gpiolib-legacy.c index 3bc93ccadb5b..35cb7fca634e 100644 --- a/drivers/gpio/gpiolib-legacy.c +++ b/drivers/gpio/gpiolib-legacy.c @@ -34,30 +34,20 @@ EXPORT_SYMBOL_GPL(gpio_free); */ int gpio_request_one(unsigned gpio, unsigned long flags, const char *label) { - struct gpio_desc *desc; int err; - /* Compatibility: assume unavailable "valid" GPIOs will appear later */ - desc = gpio_to_desc(gpio); - if (!desc) - return -EPROBE_DEFER; - - err = gpiod_request(desc, label); + err = gpio_request(gpio, label); if (err) return err; if (flags & GPIOF_IN) - err = gpiod_direction_input(desc); + err = gpio_direction_input(gpio); else - err = gpiod_direction_output_raw(desc, !!(flags & GPIOF_OUT_INIT_HIGH)); + err = gpio_direction_output(gpio, !!(flags & GPIOF_OUT_INIT_HIGH)); if (err) - goto free_gpio; + gpio_free(gpio); - return 0; - - free_gpio: - gpiod_free(desc); return err; } EXPORT_SYMBOL_GPL(gpio_request_one); From ade570c138a509c11b5d016a227009f2f399fd4a Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 12 Nov 2025 10:32:02 +0100 Subject: [PATCH 37/80] gpiolib: legacy: Allow to kill devm_gpio_request_one() independently Allow to kill devm_gpio_request_one() independently by converting it to use legacy APIs that will be alive a bit longer. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20251112093608.1481030-3-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-legacy.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/drivers/gpio/gpiolib-legacy.c b/drivers/gpio/gpiolib-legacy.c index 35cb7fca634e..ef3f2ef30cf2 100644 --- a/drivers/gpio/gpiolib-legacy.c +++ b/drivers/gpio/gpiolib-legacy.c @@ -68,11 +68,9 @@ int gpio_request(unsigned gpio, const char *label) } EXPORT_SYMBOL_GPL(gpio_request); -static void devm_gpio_release(struct device *dev, void *res) +static void devm_gpio_release(void *gpio) { - unsigned *gpio = res; - - gpio_free(*gpio); + gpio_free((unsigned)(unsigned long)gpio); } /** @@ -90,22 +88,22 @@ static void devm_gpio_release(struct device *dev, void *res) int devm_gpio_request_one(struct device *dev, unsigned gpio, unsigned long flags, const char *label) { - unsigned *dr; int rc; - dr = devres_alloc(devm_gpio_release, sizeof(unsigned), GFP_KERNEL); - if (!dr) - return -ENOMEM; + rc = gpio_request(gpio, label); + if (rc) + return rc; + + if (flags & GPIOF_IN) + rc = gpio_direction_input(gpio); + else + rc = gpio_direction_output(gpio, !!(flags & GPIOF_OUT_INIT_HIGH)); - rc = gpio_request_one(gpio, flags, label); if (rc) { - devres_free(dr); + gpio_free(gpio); return rc; } - *dr = gpio; - devres_add(dev, dr); - - return 0; + return devm_add_action_or_reset(dev, devm_gpio_release, (void *)(unsigned long)gpio); } EXPORT_SYMBOL_GPL(devm_gpio_request_one); From 67f9b828d4e5e47caf3472a399c25c3c0ddc824a Mon Sep 17 00:00:00 2001 From: Kartik Rajput Date: Thu, 13 Nov 2025 22:01:12 +0530 Subject: [PATCH 38/80] gpio: tegra186: Fix GPIO name collisions for Tegra410 On Tegra410, Compute and System GPIOs have same port names. This results in the same GPIO names for both Compute and System GPIOs during initialization in `tegra186_gpio_probe()`, which results in following warnings: kernel: gpio gpiochip1: Detected name collision for GPIO name 'PA.00' kernel: gpio gpiochip1: Detected name collision for GPIO name 'PA.01' kernel: gpio gpiochip1: Detected name collision for GPIO name 'PA.02' kernel: gpio gpiochip1: Detected name collision for GPIO name 'PB.00' kernel: gpio gpiochip1: Detected name collision for GPIO name 'PB.01' ... Add GPIO name prefix in the SoC data and use it to initialize the GPIO name. Port names remain unchanged for previous SoCs. On Tegra410, Compute GPIOs are named COMPUTE-P.GPIO, and System GPIOs are named SYSTEM-P.GPIO. Fixes: 9631a10083d8 ("gpio: tegra186: Add support for Tegra410") Signed-off-by: Kartik Rajput Acked-by: Thierry Reding Reviewed-by: Jon Hunter Link: https://lore.kernel.org/r/20251113163112.885900-1-kkartik@nvidia.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-tegra186.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-tegra186.c b/drivers/gpio/gpio-tegra186.c index 83ecdc876985..b1498b59a921 100644 --- a/drivers/gpio/gpio-tegra186.c +++ b/drivers/gpio/gpio-tegra186.c @@ -109,6 +109,7 @@ struct tegra_gpio_soc { const struct tegra_gpio_port *ports; unsigned int num_ports; const char *name; + const char *prefix; unsigned int instance; unsigned int num_irqs_per_bank; @@ -940,8 +941,12 @@ static int tegra186_gpio_probe(struct platform_device *pdev) char *name; for (j = 0; j < port->pins; j++) { - name = devm_kasprintf(gpio->gpio.parent, GFP_KERNEL, - "P%s.%02x", port->name, j); + if (gpio->soc->prefix) + name = devm_kasprintf(gpio->gpio.parent, GFP_KERNEL, "%s-P%s.%02x", + gpio->soc->prefix, port->name, j); + else + name = devm_kasprintf(gpio->gpio.parent, GFP_KERNEL, "P%s.%02x", + port->name, j); if (!name) return -ENOMEM; @@ -1306,6 +1311,7 @@ static const struct tegra_gpio_soc tegra410_compute_soc = { .num_ports = ARRAY_SIZE(tegra410_compute_ports), .ports = tegra410_compute_ports, .name = "tegra410-gpio-compute", + .prefix = "COMPUTE", .num_irqs_per_bank = 8, .instance = 0, }; @@ -1335,6 +1341,7 @@ static const struct tegra_gpio_soc tegra410_system_soc = { .num_ports = ARRAY_SIZE(tegra410_system_ports), .ports = tegra410_system_ports, .name = "tegra410-gpio-system", + .prefix = "SYSTEM", .num_irqs_per_bank = 8, .instance = 0, }; From bb7c963d0008f6d652ca7c7aa1aedae3d03425c7 Mon Sep 17 00:00:00 2001 From: Pierre-Henry Moussay Date: Mon, 17 Nov 2025 15:59:18 +0000 Subject: [PATCH 39/80] dt-bindings: gpio: mpfs-gpio: Add pic64gx GPIO compatibility pic64gx GPIO is compatible with mpfs-gpio controller, add it with a fallback. Signed-off-by: Pierre-Henry Moussay Signed-off-by: Conor Dooley Acked-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20251117-grumbly-oversized-2215fe887181@spud Signed-off-by: Bartosz Golaszewski --- .../devicetree/bindings/gpio/microchip,mpfs-gpio.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/gpio/microchip,mpfs-gpio.yaml b/Documentation/devicetree/bindings/gpio/microchip,mpfs-gpio.yaml index d78da7dd2a56..184432d24ea1 100644 --- a/Documentation/devicetree/bindings/gpio/microchip,mpfs-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/microchip,mpfs-gpio.yaml @@ -11,7 +11,10 @@ maintainers: properties: compatible: - items: + oneOf: + - items: + - const: microchip,pic64gx-gpio + - const: microchip,mpfs-gpio - enum: - microchip,mpfs-gpio - microchip,coregpio-rtl-v3 From 01be9047988d15850ca15d146c5f4aeb5de2f569 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 18 Nov 2025 21:04:59 +0100 Subject: [PATCH 40/80] gpio: shared: fix a NULL-pointer dereference The fact that CONFIG_OF is enabled does not mean that the device tree is populated and that of_root points to a valid device node. Check if it's NULL before trying to traverse the tree. Fixes: a060b8c511ab ("gpiolib: implement low-level, shared GPIO support") Reported-by: Mark Brown Closes: https://lore.kernel.org/all/dbe20642-9662-40af-a593-c1263baea73b@sirena.org.uk/ Tested-by: Mark Brown Link: https://lore.kernel.org/r/20251118200459.13969-1-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-shared.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c index c22eaf05eef2..4ce574a21850 100644 --- a/drivers/gpio/gpiolib-shared.c +++ b/drivers/gpio/gpiolib-shared.c @@ -205,7 +205,10 @@ static int gpio_shared_of_traverse(struct device_node *curr) static int gpio_shared_of_scan(void) { - return gpio_shared_of_traverse(of_root); + if (of_root) + return gpio_shared_of_traverse(of_root); + + return 0; } #else static int gpio_shared_of_scan(void) From 8ad236f8a457c88906261411bcafa1a91fa96124 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 18 Nov 2025 11:54:51 +0100 Subject: [PATCH 41/80] gpio: shared: extend the ifdef guard to gpio_shared_find_entry() While this function is supposed to be used by all scanning functions, so far we only have a single one for OF trees. Once we add support for ACPI and software nodes, we'll drop the CONFIG_OF guard around this routine but in order to avoid build warnings, let's extend it to cover it in the meantime. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202511180232.EItKeYjY-lkp@intel.com/ Link: https://lore.kernel.org/r/20251118-gpiolib-shared-of-guard-v1-1-e4ef149a2e0b@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-shared.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c index 4ce574a21850..3803b5c938f9 100644 --- a/drivers/gpio/gpiolib-shared.c +++ b/drivers/gpio/gpiolib-shared.c @@ -58,6 +58,7 @@ static LIST_HEAD(gpio_shared_list); static DEFINE_MUTEX(gpio_shared_lock); static DEFINE_IDA(gpio_shared_ida); +#if IS_ENABLED(CONFIG_OF) static struct gpio_shared_entry * gpio_shared_find_entry(struct fwnode_handle *controller_node, unsigned int offset) @@ -72,7 +73,6 @@ gpio_shared_find_entry(struct fwnode_handle *controller_node, return NULL; } -#if IS_ENABLED(CONFIG_OF) static int gpio_shared_of_traverse(struct device_node *curr) { struct gpio_shared_entry *entry; From 5ef5f3c2245e13c62adf4cb0980cdd7bd72c59d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Levente=20R=C3=A9v=C3=A9sz?= Date: Wed, 12 Nov 2025 23:48:20 +0100 Subject: [PATCH 42/80] Documentation: gpio: Add a compatibility and feature list for PCA953x MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I went through all the datasheets and created this note listing chip functions and register layouts. Signed-off-by: Levente Révész Signed-off-by: Andy Shevchenko Acked-by: Randy Dunlap Tested-by: Randy Dunlap Reviewed-by: Linus Walleij Reviewed-by: Bagas Sanjaya Link: https://lore.kernel.org/r/20251112224924.2091880-1-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- Documentation/driver-api/gpio/index.rst | 1 + Documentation/driver-api/gpio/pca953x.rst | 552 ++++++++++++++++++++++ 2 files changed, 553 insertions(+) create mode 100644 Documentation/driver-api/gpio/pca953x.rst diff --git a/Documentation/driver-api/gpio/index.rst b/Documentation/driver-api/gpio/index.rst index 87929840e85a..bee58f709b9a 100644 --- a/Documentation/driver-api/gpio/index.rst +++ b/Documentation/driver-api/gpio/index.rst @@ -15,6 +15,7 @@ Contents: legacy-boards drivers-on-gpio bt8xxgpio + pca953x Core ==== diff --git a/Documentation/driver-api/gpio/pca953x.rst b/Documentation/driver-api/gpio/pca953x.rst new file mode 100644 index 000000000000..4bd7cf1120cb --- /dev/null +++ b/Documentation/driver-api/gpio/pca953x.rst @@ -0,0 +1,552 @@ +============================================ +PCA953x I²C GPIO expander compatibility list +============================================ + +:Author: Levente Révész + +I went through all the datasheets and created this note listing +chip functions and register layouts. + +Overview of chips +================= + +Chips with the basic 4 registers +-------------------------------- + +These chips have 4 register banks: input, output, invert and direction. +Each of these banks contains (lines/8) registers, one for each GPIO port. + +Banks offset is always a power of 2: + +- 4 lines -> bank offset is 1 +- 8 lines -> bank offset is 1 +- 16 lines -> bank offset is 2 +- 24 lines -> bank offset is 4 +- 32 lines -> bank offset is 4 +- 40 lines -> bank offset is 8 + +For example, register layout of GPIO expander with 24 lines: + ++------+-----------------+--------+ +| addr | function | bank | ++======+=================+========+ +| 00 | input port0 | | ++------+-----------------+ | +| 01 | input port1 | bank 0 | ++------+-----------------+ | +| 02 | input port2 | | ++------+-----------------+--------+ +| 03 | n/a | | ++------+-----------------+--------+ +| 04 | output port0 | | ++------+-----------------+ | +| 05 | output port1 | bank 1 | ++------+-----------------+ | +| 06 | output port2 | | ++------+-----------------+--------+ +| 07 | n/a | | ++------+-----------------+--------+ +| 08 | invert port0 | | ++------+-----------------+ | +| 09 | invert port1 | bank 2 | ++------+-----------------+ | +| 0A | invert port2 | | ++------+-----------------+--------+ +| 0B | n/a | | ++------+-----------------+--------+ +| 0C | direction port0 | | ++------+-----------------+ | +| 0D | direction port1 | bank 3 | ++------+-----------------+ | +| 0E | direction port2 | | ++------+-----------------+--------+ +| 0F | n/a | | ++------+-----------------+--------+ + +.. note:: + This is followed by all supported chips, except by pcal6534. + +The table below shows the offsets for each of the compatible chips: + +========== ===== ========= ===== ====== ====== ========= +compatible lines interrupt input output invert direction +========== ===== ========= ===== ====== ====== ========= +pca9536 4 no 00 01 02 03 +pca9537 4 yes 00 01 02 03 +pca6408 8 yes 00 01 02 03 +tca6408 8 yes 00 01 02 03 +pca9534 8 yes 00 01 02 03 +pca9538 8 yes 00 01 02 03 +pca9554 8 yes 00 01 02 03 +tca9554 8 yes 00 01 02 03 +pca9556 8 no 00 01 02 03 +pca9557 8 no 00 01 02 03 +pca6107 8 yes 00 01 02 03 +pca6416 16 yes 00 02 04 06 +tca6416 16 yes 00 02 04 06 +pca9535 16 yes 00 02 04 06 +pca9539 16 yes 00 02 04 06 +tca9539 16 yes 00 02 04 06 +pca9555 16 yes 00 02 04 06 +max7318 16 yes 00 02 04 06 +tca6424 24 yes 00 04 08 0C +========== ===== ========= ===== ====== ====== ========= + +Chips with additional timeout_en register +----------------------------------------- + +These Maxim chips have a bus timeout function which can be enabled in +the timeout_en register. This is present in only two chips. Defaults to +timeout disabled. + +========== ===== ========= ===== ====== ====== ========= ========== +compatible lines interrupt input output invert direction timeout_en +========== ===== ========= ===== ====== ====== ========= ========== +max7310 8 no 00 01 02 03 04 +max7312 16 yes 00 02 04 06 08 +========== ===== ========= ===== ====== ====== ========= ========== + +Chips with additional int_mask register +--------------------------------------- + +These chips have an interrupt mask register in addition to the 4 basic +registers. The interrupt masks default to all interrupts disabled. To +use interrupts with these chips, the driver has to set the int_mask +register. + +========== ===== ========= ===== ====== ====== ========= ======== +compatible lines interrupt input output invert direction int_mask +========== ===== ========= ===== ====== ====== ========= ======== +pca9505 40 yes 00 08 10 18 20 +pca9506 40 yes 00 08 10 18 20 +========== ===== ========= ===== ====== ====== ========= ======== + +Chips with additional int_mask and out_conf registers +----------------------------------------------------- + +This chip has an interrupt mask register, and an output port +configuration register, which can select between push-pull and +open-drain modes. Each bit controls two lines. Both of these registers +are present in PCAL chips as well, albeit the out_conf works +differently. + +========== ===== ========= ===== ====== ====== ========= ======== ======== +compatible lines interrupt input output invert direction int_mask out_conf +========== ===== ========= ===== ====== ====== ========= ======== ======== +pca9698 40 yes 00 08 10 18 20 28 +========== ===== ========= ===== ====== ====== ========= ======== ======== + +pca9698 also has a "master output" register for setting all outputs per +port to the same value simultaneously, and a chip specific mode register +for various additional chip settings. + +========== ============= ==== +compatible master_output mode +========== ============= ==== +pca9698 29 2A +========== ============= ==== + +Chips with LED blink and intensity control +------------------------------------------ + +These Maxim chips have no invert register. + +They have two sets of output registers (output0 and output1). An internal +timer alternates the effective output between the values set in these +registers, if blink mode is enabled in the blink register. The +master_intensity register and the intensity registers together define +the PWM intensity value for each pair of outputs. + +These chips can be used as simple GPIO expanders if the driver handles the +input, output0 and direction registers. + +========== ===== ========= ===== ======= ========= ======= ================ ===== ========= +compatible lines interrupt input output0 direction output1 master_intensity blink intensity +========== ===== ========= ===== ======= ========= ======= ================ ===== ========= +max7315 8 yes 00 01 03 09 0E 0F 10 +max7313 16 yes 00 02 06 0A 0E 0F 10 +========== ===== ========= ===== ======= ========= ======= ================ ===== ========= + +Basic PCAL chips +---------------- + +========== ===== ========= ===== ====== ====== ========= +compatible lines interrupt input output invert direction +========== ===== ========= ===== ====== ====== ========= +pcal6408 8 yes 00 01 02 03 +pcal9554b 8 yes 00 01 02 03 +pcal6416 16 yes 00 02 04 06 +pcal9535 16 yes 00 02 04 06 +pcal9555a 16 yes 00 02 04 06 +========== ===== ========= ===== ====== ====== ========= + +These chips have several additional features: + + 1. output drive strength setting (out_strength) + 2. input latch (in_latch) + 3. pull-up/pull-down (pull_in, pull_sel) + 4. push-pull/open-drain outputs (out_conf) + 5. interrupt mask and interrupt status (int_mask, int_status) + +========== ============ ======== ======= ======== ======== ========== ======== +compatible out_strength in_latch pull_en pull_sel int_mask int_status out_conf +========== ============ ======== ======= ======== ======== ========== ======== +pcal6408 40 42 43 44 45 46 4F +pcal9554b 40 42 43 44 45 46 4F +pcal6416 40 44 46 48 4A 4C 4F +pcal9535 40 44 46 48 4A 4C 4F +pcal9555a 40 44 46 48 4A 4C 4F +========== ============ ======== ======= ======== ======== ========== ======== + +Currently the driver has support for the input latch, pull-up/pull-down +and uses int_mask and int_status for interrupts. + +PCAL chips with extended interrupt and output configuration functions +--------------------------------------------------------------------- + +========== ===== ========= ===== ====== ====== ========= +compatible lines interrupt input output invert direction +========== ===== ========= ===== ====== ====== ========= +pcal6524 24 yes 00 04 08 0C +pcal6534 34 yes 00 05 0A 0F +========== ===== ========= ===== ====== ====== ========= + +These chips have the full PCAL register set, plus the following functions: + + 1. interrupt event selection: level, rising, falling, any edge + 2. clear interrupt status per line + 3. read input without clearing interrupt status + 4. individual output config (push-pull/open-drain) per output line + 5. debounce inputs + +========== ============ ======== ======= ======== ======== ========== ======== +compatible out_strength in_latch pull_en pull_sel int_mask int_status out_conf +========== ============ ======== ======= ======== ======== ========== ======== +pcal6524 40 48 4C 50 54 58 5C +pcal6534 30 3A 3F 44 49 4E 53 +========== ============ ======== ======= ======== ======== ========== ======== + +========== ======== ========= ============ ============== ======== ============== +compatible int_edge int_clear input_status indiv_out_conf debounce debounce_count +========== ======== ========= ============ ============== ======== ============== +pcal6524 60 68 6C 70 74 76 +pcal6534 54 5E 63 68 6D 6F +========== ======== ========= ============ ============== ======== ============== + +As can be seen in the table above, pcal6534 does not follow the usual +bank spacing rule. Its banks are closely packed instead. + +PCA957X chips with a completely different register layout +--------------------------------------------------------- + +These chips have the basic 4 registers, but at unusual addresses. + +Additionally, they have: + + 1. pull-up/pull-down (pull_sel) + 2. a global pull enable, defaults to disabled (config) + 3. interrupt mask, interrupt status (int_mask, int_status) + +========== ===== ========= ===== ====== ====== ======== ========= ====== ======== ========== +compatible lines interrupt input invert config pull_sel direction output int_mask int_status +========== ===== ========= ===== ====== ====== ======== ========= ====== ======== ========== +pca9574 8 yes 00 01 02 03 04 05 06 07 +pca9575 16 yes 00 02 04 06 08 0A 0C 0E +========== ===== ========= ===== ====== ====== ======== ========= ====== ======== ========== + +Currently the driver supports none of the advanced features. + +XRA1202 +------- + +Basic 4 registers, plus advanced features: + + 1. interrupt mask, defaults to interrupts disabled + 2. interrupt status + 3. interrupt event selection, level, rising, falling, any edge + (int_mask, rising_mask, falling_mask) + 4. pull-up (no pull-down) + 5. tri-state + 6. debounce + +========== ===== ========= ===== ====== ====== ========= ========= +compatible lines interrupt input output invert direction pullup_en +========== ===== ========= ===== ====== ====== ========= ========= +xra1202 8 yes 00 01 02 03 04 +========== ===== ========= ===== ====== ====== ========= ========= + +========== ======== ======== ========== =========== ============ ======== +compatible int_mask tristate int_status rising_mask falling_mask debounce +========== ======== ======== ========== =========== ============ ======== +xra1202 05 06 07 08 09 0A +========== ======== ======== ========== =========== ============ ======== + +Overview of functions +===================== + +This section lists chip functions that are supported by the driver +already, or are at least common in multiple chips. + +Input, Output, Invert, Direction +-------------------------------- + +The basic 4 GPIO functions are present in all but one chip category, i.e. +`Chips with LED blink and intensity control`_ are missing the invert +register. + +3 different layouts are used for these registers: + + 1. banks 0, 1, 2, 3 with bank offsets of 2^n + - all other chips + + 2. banks 0, 1, 2, 3 with closely packed banks + - pcal6534 + + 3. banks 0, 5, 1, 4 with bank offsets of 2^n + - pca9574 + - pca9575 + +Interrupts +---------- + +Only an interrupt mask register +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The same layout is used for all of these: + + 1. bank 5 with bank offsets of 2^n + - pca9505 + - pca9506 + - pca9698 + +Interrupt mask and interrupt status registers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +These work the same way in all of the chips: mask and status have +one bit per line, 1 in the mask means interrupt enabled. + +Layouts: + + 1. base offset 0x40, bank 5 and bank 6, bank offsets of 2^n + - pcal6408 + - pcal6416 + - pcal9535 + - pcal9554b + - pcal9555a + - pcal6524 + + 2. base offset 0x30, bank 5 and 6, closely packed banks + - pcal6534 + + 3. bank 6 and 7, bank offsets of 2^n + - pca9574 + - pca9575 + + 4. bank 5 and 7, bank offsets of 2^n + - xra1202 + +Interrupt on specific edges +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +`PCAL chips with extended interrupt and output configuration functions`_ +have an int_edge register. This contains 2 bits per line, one of 4 events +can be selected for each line: + + 0: level, 1: rising edge, 2: falling edge, 3: any edge + +Layouts: + + 1. base offset 0x40, bank 7, bank offsets of 2^n + + - pcal6524 + + 2. base offset 0x30, bank 7 + offset 0x01, closely packed banks + (out_conf is 1 byte, not (lines/8) bytes, hence the 0x01 offset) + + - pcal6534 + +`XRA1202`_ chips have a different mechanism for the same thing: they have +a rising mask and a falling mask, with one bit per line. + +Layout: + + 1. bank 5, bank offsets of 2^n + +Input latch +----------- + +Only `Basic PCAL chips`_ and +`PCAL chips with extended interrupt and output configuration functions`_ +have this function. When the latch is enabled, the interrupt is not cleared +until the input port is read. When the latch is disabled, the interrupt +is cleared even if the input register is not read, if the input pin returns +to the logic value it had before generating the interrupt. Defaults to latch +disabled. + +Currently the driver enables the latch for each line with interrupt +enabled. + + 1. base offset 0x40, bank 2, bank offsets of 2^n + - pcal6408 + - pcal6416 + - pcal9535 + - pcal9554b + - pcal9555a + - pcal6524 + + 2. base offset 0x30, bank 2, closely packed banks + - pcal6534 + +Pull-up and pull-down +--------------------- + +`Basic PCAL chips`_ and +`PCAL chips with extended interrupt and output configuration functions`_ +use the same mechanism: their pull_en register enables the pull-up or pull-down +function, and their pull_sel register chooses the direction. They all use one +bit per line. + + 0: pull-down, 1: pull-up + +Layouts: + + 1. base offset 0x40, bank 3 (en) and 4 (sel), bank offsets of 2^n + - pcal6408 + - pcal6416 + - pcal9535 + - pcal9554b + - pcal9555a + - pcal6524 + + 2. base offset 0x30, bank 3 (en) and 4 (sel), closely packed banks + - pcal6534 + +`PCA957X chips with a completely different register layout`_ have a pull_sel +register with one bit per line, and a global pull_en bit in their config +register. + +Layout: + + 1. bank 2 (config), bank 3 (sel), bank offsets of 2^n + - pca9574 + - pca9575 + +`XRA1202`_ chips can only pull-up. They have a pullup_en register. + +Layout: + + 1. bank 4, bank offsets of 2^n + - xra1202 + +Push-pull and open-drain +------------------------ + +`Chips with additional int_mask and out_conf registers`_ have this function, +but only for select IO ports. Register has 1 bit per 2 lines. In pca9698, +only port0 and port1 have this function. + + 0: open-drain, 1: push-pull + +Layout: + + 1. base offset 5*bankoffset + - pca9698 + +`Basic PCAL chips`_ have 1 bit per port in one single out_conf register. +Only whole ports can be configured. + + 0: push-pull, 1: open-drain + +Layout: + + 1. base offset 0x4F + - pcal6408 + - pcal6416 + - pcal9535 + - pcal9554b + - pcal9555a + +`PCAL chips with extended interrupt and output configuration functions`_ +can set this for each line individually. They have the same per-port out_conf +register as `Basic PCAL chips`_, but they also have an indiv_out_conf register +with one bit per line, which inverts the effect of the port-wise setting. + + 0: push-pull, 1: open-drain + +Layouts: + + 1. base offset 0x40 + 7*bankoffset (out_conf), + base offset 0x60, bank 4 (indiv_out_conf) with bank offset of 2^n + + - pcal6524 + + 2. base offset 0x30 + 7*banksize (out_conf), + base offset 0x54, bank 4 (indiv_out_conf), closely packed banks + + - pcal6534 + +This function is currently not supported by the driver. + +Output drive strength +--------------------- + +Only PCAL chips have this function. 2 bits per line. + +==== ============== +bits drive strength +==== ============== + 00 0.25x + 01 0.50x + 10 0.75x + 11 1.00x +==== ============== + + 1. base offset 0x40, bank 0 and 1, bank offsets of 2^n + - pcal6408 + - pcal6416 + - pcal9535 + - pcal9554b + - pcal9555a + - pcal6524 + + 2. base offset 0x30, bank 0 and 1, closely packed banks + - pcal6534 + +Currently not supported by the driver. + +Datasheets +========== + +- MAX7310: https://datasheets.maximintegrated.com/en/ds/MAX7310.pdf +- MAX7312: https://datasheets.maximintegrated.com/en/ds/MAX7312.pdf +- MAX7313: https://datasheets.maximintegrated.com/en/ds/MAX7313.pdf +- MAX7315: https://datasheets.maximintegrated.com/en/ds/MAX7315.pdf +- MAX7318: https://datasheets.maximintegrated.com/en/ds/MAX7318.pdf +- PCA6107: https://pdf1.alldatasheet.com/datasheet-pdf/view/161780/TI/PCA6107.html +- PCA6408A: https://www.nxp.com/docs/en/data-sheet/PCA6408A.pdf +- PCA6416A: https://www.nxp.com/docs/en/data-sheet/PCA6416A.pdf +- PCA9505: https://www.nxp.com/docs/en/data-sheet/PCA9505_9506.pdf +- PCA9505: https://www.nxp.com/docs/en/data-sheet/PCA9505_9506.pdf +- PCA9534: https://www.nxp.com/docs/en/data-sheet/PCA9534.pdf +- PCA9535: https://www.nxp.com/docs/en/data-sheet/PCA9535_PCA9535C.pdf +- PCA9536: https://www.nxp.com/docs/en/data-sheet/PCA9536.pdf +- PCA9537: https://www.nxp.com/docs/en/data-sheet/PCA9537.pdf +- PCA9538: https://www.nxp.com/docs/en/data-sheet/PCA9538.pdf +- PCA9539: https://www.nxp.com/docs/en/data-sheet/PCA9539_PCA9539R.pdf +- PCA9554: https://www.nxp.com/docs/en/data-sheet/PCA9554_9554A.pdf +- PCA9555: https://www.nxp.com/docs/en/data-sheet/PCA9555.pdf +- PCA9556: https://www.nxp.com/docs/en/data-sheet/PCA9556.pdf +- PCA9557: https://www.nxp.com/docs/en/data-sheet/PCA9557.pdf +- PCA9574: https://www.nxp.com/docs/en/data-sheet/PCA9574.pdf +- PCA9575: https://www.nxp.com/docs/en/data-sheet/PCA9575.pdf +- PCA9698: https://www.nxp.com/docs/en/data-sheet/PCA9698.pdf +- PCAL6408A: https://www.nxp.com/docs/en/data-sheet/PCAL6408A.pdf +- PCAL6416A: https://www.nxp.com/docs/en/data-sheet/PCAL6416A.pdf +- PCAL6524: https://www.nxp.com/docs/en/data-sheet/PCAL6524.pdf +- PCAL6534: https://www.nxp.com/docs/en/data-sheet/PCAL6534.pdf +- PCAL9535A: https://www.nxp.com/docs/en/data-sheet/PCAL9535A.pdf +- PCAL9554B: https://www.nxp.com/docs/en/data-sheet/PCAL9554B_PCAL9554C.pdf +- PCAL9555A: https://www.nxp.com/docs/en/data-sheet/PCAL9555A.pdf +- TCA6408A: https://www.ti.com/lit/gpn/tca6408a +- TCA6416: https://www.ti.com/lit/gpn/tca6416 +- TCA6424: https://www.ti.com/lit/gpn/tca6424 +- TCA9539: https://www.ti.com/lit/gpn/tca9539 +- TCA9554: https://www.ti.com/lit/gpn/tca9554 +- XRA1202: https://assets.maxlinear.com/web/documents/xra1202_1202p_101_042213.pdf From a0c83150eea5807dbedf786f55cd49b14af118a8 Mon Sep 17 00:00:00 2001 From: Raag Jadav Date: Wed, 12 Nov 2025 09:10:10 +0530 Subject: [PATCH 43/80] platform/x86/intel: Introduce Intel Elkhart Lake PSE I/O MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Intel Elkhart Lake Programmable Service Engine (PSE) includes two PCI devices that expose two different capabilities of GPIO and Timed I/O as a single PCI function through shared MMIO with below layout. GPIO: 0x0000 - 0x1000 TIO: 0x1000 - 0x2000 This driver enumerates the PCI parent device and creates auxiliary child devices for these capabilities. The actual functionalities are provided by their respective auxiliary drivers. Signed-off-by: Raag Jadav Acked-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20251112034040.457801-2-raag.jadav@intel.com Signed-off-by: Bartosz Golaszewski --- MAINTAINERS | 7 ++ drivers/platform/x86/intel/Kconfig | 13 ++++ drivers/platform/x86/intel/Makefile | 1 + drivers/platform/x86/intel/ehl_pse_io.c | 86 +++++++++++++++++++++++++ include/linux/ehl_pse_io_aux.h | 24 +++++++ 5 files changed, 131 insertions(+) create mode 100644 drivers/platform/x86/intel/ehl_pse_io.c create mode 100644 include/linux/ehl_pse_io_aux.h diff --git a/MAINTAINERS b/MAINTAINERS index 3da2c26a796b..00e2cb65ddec 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12505,6 +12505,13 @@ F: drivers/gpu/drm/xe/ F: include/drm/intel/ F: include/uapi/drm/xe_drm.h +INTEL ELKHART LAKE PSE I/O DRIVER +M: Raag Jadav +L: platform-driver-x86@vger.kernel.org +S: Supported +F: drivers/platform/x86/intel/ehl_pse_io.c +F: include/linux/ehl_pse_io_aux.h + INTEL ETHERNET DRIVERS M: Tony Nguyen M: Przemek Kitszel diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 19a2246f2770..2900407d6095 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -41,6 +41,19 @@ config INTEL_VBTN To compile this driver as a module, choose M here: the module will be called intel_vbtn. +config INTEL_EHL_PSE_IO + tristate "Intel Elkhart Lake PSE I/O driver" + depends on PCI + select AUXILIARY_BUS + help + Select this option to enable Intel Elkhart Lake PSE GPIO and Timed + I/O support. This driver enumerates the PCI parent device and + creates auxiliary child devices for these capabilities. The actual + functionalities are provided by their respective auxiliary drivers. + + To compile this driver as a module, choose M here: the module will + be called intel_ehl_pse_io. + config INTEL_INT0002_VGPIO tristate "Intel ACPI INT0002 Virtual GPIO driver" depends on GPIOLIB && ACPI && PM_SLEEP diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index 78acb414e154..138b13756158 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -21,6 +21,7 @@ intel-target-$(CONFIG_INTEL_HID_EVENT) += hid.o intel-target-$(CONFIG_INTEL_VBTN) += vbtn.o # Intel miscellaneous drivers +intel-target-$(CONFIG_INTEL_EHL_PSE_IO) += ehl_pse_io.o intel-target-$(CONFIG_INTEL_INT0002_VGPIO) += int0002_vgpio.o intel-target-$(CONFIG_INTEL_ISHTP_ECLITE) += ishtp_eclite.o intel-target-$(CONFIG_INTEL_OAKTRAIL) += oaktrail.o diff --git a/drivers/platform/x86/intel/ehl_pse_io.c b/drivers/platform/x86/intel/ehl_pse_io.c new file mode 100644 index 000000000000..861e14808b35 --- /dev/null +++ b/drivers/platform/x86/intel/ehl_pse_io.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Intel Elkhart Lake Programmable Service Engine (PSE) I/O + * + * Copyright (c) 2025 Intel Corporation. + * + * Author: Raag Jadav + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define EHL_PSE_IO_DEV_SIZE SZ_4K + +static int ehl_pse_io_dev_create(struct pci_dev *pci, const char *name, int idx) +{ + struct device *dev = &pci->dev; + struct auxiliary_device *adev; + struct ehl_pse_io_data *data; + resource_size_t start, offset; + u32 id; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + id = (pci_domain_nr(pci->bus) << 16) | pci_dev_id(pci); + start = pci_resource_start(pci, 0); + offset = EHL_PSE_IO_DEV_SIZE * idx; + + data->mem = DEFINE_RES_MEM(start + offset, EHL_PSE_IO_DEV_SIZE); + data->irq = pci_irq_vector(pci, idx); + + adev = __devm_auxiliary_device_create(dev, EHL_PSE_IO_NAME, name, data, id); + + return adev ? 0 : -ENODEV; +} + +static int ehl_pse_io_probe(struct pci_dev *pci, const struct pci_device_id *id) +{ + int ret; + + ret = pcim_enable_device(pci); + if (ret) + return ret; + + pci_set_master(pci); + + ret = pci_alloc_irq_vectors(pci, 2, 2, PCI_IRQ_MSI); + if (ret < 0) + return ret; + + ret = ehl_pse_io_dev_create(pci, EHL_PSE_GPIO_NAME, 0); + if (ret) + return ret; + + return ehl_pse_io_dev_create(pci, EHL_PSE_TIO_NAME, 1); +} + +static const struct pci_device_id ehl_pse_io_ids[] = { + { PCI_VDEVICE(INTEL, 0x4b88) }, + { PCI_VDEVICE(INTEL, 0x4b89) }, + { } +}; +MODULE_DEVICE_TABLE(pci, ehl_pse_io_ids); + +static struct pci_driver ehl_pse_io_driver = { + .name = EHL_PSE_IO_NAME, + .id_table = ehl_pse_io_ids, + .probe = ehl_pse_io_probe, +}; +module_pci_driver(ehl_pse_io_driver); + +MODULE_AUTHOR("Raag Jadav "); +MODULE_DESCRIPTION("Intel Elkhart Lake PSE I/O driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/ehl_pse_io_aux.h b/include/linux/ehl_pse_io_aux.h new file mode 100644 index 000000000000..afb8587ee5fb --- /dev/null +++ b/include/linux/ehl_pse_io_aux.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Intel Elkhart Lake PSE I/O Auxiliary Device + * + * Copyright (c) 2025 Intel Corporation. + * + * Author: Raag Jadav + */ + +#ifndef _EHL_PSE_IO_AUX_H_ +#define _EHL_PSE_IO_AUX_H_ + +#include + +#define EHL_PSE_IO_NAME "ehl_pse_io" +#define EHL_PSE_GPIO_NAME "gpio" +#define EHL_PSE_TIO_NAME "pps_tio" + +struct ehl_pse_io_data { + struct resource mem; + int irq; +}; + +#endif /* _EHL_PSE_IO_AUX_H_ */ From 10c15296906952016a84e1e45d8dc361f35afbd8 Mon Sep 17 00:00:00 2001 From: Raag Jadav Date: Wed, 12 Nov 2025 09:10:11 +0530 Subject: [PATCH 44/80] gpio: elkhartlake: Convert to auxiliary driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since PCI device should not be abusing platform device, MFD parent to platform child path is no longer being pursued for this driver. Convert it to auxiliary driver, which will be used by EHL PSE auxiliary device. Signed-off-by: Raag Jadav Acked-by: Bartosz Golaszewski Reviewed-by: Andy Shevchenko Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20251112034040.457801-3-raag.jadav@intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 2 +- drivers/gpio/gpio-elkhartlake.c | 36 +++++++++++++++++---------------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index f910c20f0d5d..c74da29253e8 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1412,7 +1412,7 @@ config HTC_EGPIO config GPIO_ELKHARTLAKE tristate "Intel Elkhart Lake PSE GPIO support" - depends on X86 || COMPILE_TEST + depends on INTEL_EHL_PSE_IO select GPIO_TANGIER help Select this option to enable GPIO support for Intel Elkhart Lake diff --git a/drivers/gpio/gpio-elkhartlake.c b/drivers/gpio/gpio-elkhartlake.c index 95de52d2cc63..b96e7928b6e5 100644 --- a/drivers/gpio/gpio-elkhartlake.c +++ b/drivers/gpio/gpio-elkhartlake.c @@ -2,43 +2,46 @@ /* * Intel Elkhart Lake PSE GPIO driver * - * Copyright (c) 2023 Intel Corporation. + * Copyright (c) 2023, 2025 Intel Corporation. * * Authors: Pandith N * Raag Jadav */ +#include #include #include #include -#include #include +#include + #include "gpio-tangier.h" /* Each Intel EHL PSE GPIO Controller has 30 GPIO pins */ #define EHL_PSE_NGPIO 30 -static int ehl_gpio_probe(struct platform_device *pdev) +static int ehl_gpio_probe(struct auxiliary_device *adev, const struct auxiliary_device_id *id) { - struct device *dev = &pdev->dev; + struct device *dev = &adev->dev; + struct ehl_pse_io_data *data; struct tng_gpio *priv; - int irq, ret; + int ret; - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; + data = dev_get_platdata(dev); + if (!data) + return -ENODATA; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; - priv->reg_base = devm_platform_ioremap_resource(pdev, 0); + priv->reg_base = devm_ioremap_resource(dev, &data->mem); if (IS_ERR(priv->reg_base)) return PTR_ERR(priv->reg_base); priv->dev = dev; - priv->irq = irq; + priv->irq = data->irq; priv->info.base = -1; priv->info.ngpio = EHL_PSE_NGPIO; @@ -51,25 +54,24 @@ static int ehl_gpio_probe(struct platform_device *pdev) if (ret) return dev_err_probe(dev, ret, "tng_gpio_probe error\n"); - platform_set_drvdata(pdev, priv); + auxiliary_set_drvdata(adev, priv); return 0; } -static const struct platform_device_id ehl_gpio_ids[] = { - { "gpio-elkhartlake" }, +static const struct auxiliary_device_id ehl_gpio_ids[] = { + { EHL_PSE_IO_NAME "." EHL_PSE_GPIO_NAME }, { } }; -MODULE_DEVICE_TABLE(platform, ehl_gpio_ids); +MODULE_DEVICE_TABLE(auxiliary, ehl_gpio_ids); -static struct platform_driver ehl_gpio_driver = { +static struct auxiliary_driver ehl_gpio_driver = { .driver = { - .name = "gpio-elkhartlake", .pm = pm_sleep_ptr(&tng_gpio_pm_ops), }, .probe = ehl_gpio_probe, .id_table = ehl_gpio_ids, }; -module_platform_driver(ehl_gpio_driver); +module_auxiliary_driver(ehl_gpio_driver); MODULE_AUTHOR("Pandith N "); MODULE_AUTHOR("Raag Jadav "); From b2a186cced1199bb2777e229dc37a04d33507c6d Mon Sep 17 00:00:00 2001 From: David Laight Date: Wed, 19 Nov 2025 22:41:14 +0000 Subject: [PATCH 45/80] gpiolib: acpi: use min() instead of min_t() min_t(u16, a, b) casts an 'unsigned long' to 'u16'. Use min(a, b) instead as it promotes the both values to int and so cannot discard significant bits. In this case the values should be ok. Detected by an extra check added to min_t(). Signed-off-by: David Laight Signed-off-by: Andy Shevchenko --- drivers/gpio/gpiolib-acpi-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpiolib-acpi-core.c b/drivers/gpio/gpiolib-acpi-core.c index 284e762d92c4..bcb8324d617d 100644 --- a/drivers/gpio/gpiolib-acpi-core.c +++ b/drivers/gpio/gpiolib-acpi-core.c @@ -1096,7 +1096,7 @@ acpi_gpio_adr_space_handler(u32 function, acpi_physical_address address, return AE_BAD_PARAMETER; } - length = min_t(u16, agpio->pin_table_length, pin_index + bits); + length = min(agpio->pin_table_length, pin_index + bits); for (i = pin_index; i < length; ++i) { unsigned int pin = agpio->pin_table[i]; struct acpi_gpio_connection *conn; From 5dd9332c618473dee7c94946e482604cf095fbbc Mon Sep 17 00:00:00 2001 From: Jose Javier Rodriguez Barbarin Date: Tue, 18 Nov 2025 09:31:15 +0100 Subject: [PATCH 46/80] gpio: menz127: add support for 16Z034 and 16Z037 GPIO controllers The 16Z034 and 16Z037 are 8 bits GPIO controllers that share the same registers and features of the 16Z127 GPIO controller. Signed-off-by: Jose Javier Rodriguez Barbarin Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20251118083115.9545-1-dev-josejavier.rodriguez@duagon.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-menz127.c | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpio-menz127.c b/drivers/gpio/gpio-menz127.c index da2bf9381cc4..52b13c6ae496 100644 --- a/drivers/gpio/gpio-menz127.c +++ b/drivers/gpio/gpio-menz127.c @@ -24,6 +24,11 @@ #define MEN_Z127_ODER 0x1C #define GPIO_TO_DBCNT_REG(gpio) ((gpio * 4) + 0x80) +/* MEN Z127 supported model ids*/ +#define MEN_Z127_ID 0x7f +#define MEN_Z034_ID 0x22 +#define MEN_Z037_ID 0x25 + #define MEN_Z127_DB_MIN_US 50 /* 16 bit compare register. Each bit represents 50us */ #define MEN_Z127_DB_MAX_US (0xffff * MEN_Z127_DB_MIN_US) @@ -140,6 +145,7 @@ static int men_z127_probe(struct mcb_device *mdev, struct men_z127_gpio *men_z127_gpio; struct device *dev = &mdev->dev; int ret; + unsigned long sz; men_z127_gpio = devm_kzalloc(dev, sizeof(struct men_z127_gpio), GFP_KERNEL); @@ -163,9 +169,21 @@ static int men_z127_probe(struct mcb_device *mdev, mcb_set_drvdata(mdev, men_z127_gpio); + switch (mdev->id) { + case MEN_Z127_ID: + sz = 4; + break; + case MEN_Z034_ID: + case MEN_Z037_ID: + sz = 1; + break; + default: + return dev_err_probe(&mdev->dev, -EINVAL, "no size found for id %d", mdev->id); + } + config = (struct gpio_generic_chip_config) { .dev = &mdev->dev, - .sz = 4, + .sz = sz, .dat = men_z127_gpio->reg_base + MEN_Z127_PSR, .set = men_z127_gpio->reg_base + MEN_Z127_CTRL, .dirout = men_z127_gpio->reg_base + MEN_Z127_GPIODR, @@ -186,7 +204,9 @@ static int men_z127_probe(struct mcb_device *mdev, } static const struct mcb_device_id men_z127_ids[] = { - { .device = 0x7f }, + { .device = MEN_Z127_ID }, + { .device = MEN_Z034_ID }, + { .device = MEN_Z037_ID }, { } }; MODULE_DEVICE_TABLE(mcb, men_z127_ids); @@ -201,7 +221,7 @@ static struct mcb_driver men_z127_driver = { module_mcb_driver(men_z127_driver); MODULE_AUTHOR("Andreas Werner "); -MODULE_DESCRIPTION("MEN 16z127 GPIO Controller"); +MODULE_DESCRIPTION("MEN GPIO Controller"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("mcb:16z127"); MODULE_IMPORT_NS("MCB"); From 87100151e090217b9325d7fc007b2930f6a46f02 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Wed, 19 Nov 2025 22:04:55 +0800 Subject: [PATCH 47/80] gpio: fxl6408: Add suspend/resume support Currently, during suspend, do nothing; during resume, just sync the regmap cache to hw regs. Signed-off-by: Jisheng Zhang Link: https://lore.kernel.org/r/20251119140455.10096-1-jszhang@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-fxl6408.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/gpio/gpio-fxl6408.c b/drivers/gpio/gpio-fxl6408.c index 86ebc66b1104..afc1b8461dab 100644 --- a/drivers/gpio/gpio-fxl6408.c +++ b/drivers/gpio/gpio-fxl6408.c @@ -123,6 +123,8 @@ static int fxl6408_probe(struct i2c_client *client) if (ret) return ret; + i2c_set_clientdata(client, gpio_config.regmap); + /* Disable High-Z of outputs, so that our OUTPUT updates actually take effect. */ ret = regmap_write(gpio_config.regmap, FXL6408_REG_OUTPUT_HIGH_Z, 0); if (ret) @@ -131,6 +133,16 @@ static int fxl6408_probe(struct i2c_client *client) return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_config)); } +static int fxl6408_resume(struct device *dev) +{ + struct regmap *regmap = dev_get_drvdata(dev); + + regcache_mark_dirty(regmap); + return regcache_sync(regmap); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(fxl6408_pm_ops, NULL, fxl6408_resume); + static const __maybe_unused struct of_device_id fxl6408_dt_ids[] = { { .compatible = "fcs,fxl6408" }, { } @@ -146,6 +158,7 @@ MODULE_DEVICE_TABLE(i2c, fxl6408_id); static struct i2c_driver fxl6408_driver = { .driver = { .name = "fxl6408", + .pm = pm_sleep_ptr(&fxl6408_pm_ops), .of_match_table = fxl6408_dt_ids, }, .probe = fxl6408_probe, From 6f87b41303d3c4280a57b4f7360022a0951b43dd Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 18 Nov 2025 11:04:03 +0100 Subject: [PATCH 48/80] string: fix kerneldoc formatting in strends() strends() kernel doc should have used `@str:` format for arguments instead of `@str -`. Fixes: 197b3f3c70d6 ("string: provide strends()") Reported-by: Stephen Rothwell Closes: https://lore.kernel.org/all/20251118134748.40f03b9c@canb.auug.org.au/ Link: https://lore.kernel.org/r/20251118-strends-follow-up-v1-1-d3f8ef750f59@linaro.org Signed-off-by: Bartosz Golaszewski --- include/linux/string.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/string.h b/include/linux/string.h index 929d05d1247c..69e9256592f8 100644 --- a/include/linux/string.h +++ b/include/linux/string.h @@ -564,8 +564,8 @@ static inline bool strstarts(const char *str, const char *prefix) /** * strends - Check if a string ends with another string. - * @str - NULL-terminated string to check against @suffix - * @suffix - NULL-terminated string defining the suffix to look for in @str + * @str: NULL-terminated string to check against @suffix + * @suffix: NULL-terminated string defining the suffix to look for in @str * * Returns: * True if @str ends with @suffix. False in all other cases. From f11a8e996d5e27a85e8d7a05484db2942c267615 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 20 Nov 2025 14:23:56 +0100 Subject: [PATCH 49/80] software node: read the reference args via the fwnode API Once we allow software nodes to reference all kinds of firmware nodes, the refnode here will no longer necessarily be a software node so read its proprties going through its fwnode implementation. Acked-by: Linus Walleij Reviewed-by: Sakari Ailus Acked-by: Greg Kroah-Hartman Signed-off-by: Bartosz Golaszewski Reviewed-by: Andy Shevchenko Tested-by: Charles Keepax Reviewed-by: Charles Keepax Signed-off-by: Philipp Zabel --- drivers/base/swnode.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/base/swnode.c b/drivers/base/swnode.c index be1e9e61a7bf..016a6fd12864 100644 --- a/drivers/base/swnode.c +++ b/drivers/base/swnode.c @@ -540,9 +540,7 @@ software_node_get_reference_args(const struct fwnode_handle *fwnode, return -ENOENT; if (nargs_prop) { - error = property_entry_read_int_array(ref->node->properties, - nargs_prop, sizeof(u32), - &nargs_prop_val, 1); + error = fwnode_property_read_u32(refnode, nargs_prop, &nargs_prop_val); if (error) return error; From 0651933c117e778e6f71183a52a57f4216c56efb Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 20 Nov 2025 14:23:57 +0100 Subject: [PATCH 50/80] software node: increase the reference of the swnode by its fwnode Once we allow software nodes to reference other kinds of firmware nodes, the node in args will no longer necessarily be a software node so bump its reference count using its fwnode interface. Acked-by: Linus Walleij Reviewed-by: Sakari Ailus Acked-by: Greg Kroah-Hartman Signed-off-by: Bartosz Golaszewski Reviewed-by: Andy Shevchenko Reviewed-by: Charles Keepax Tested-by: Charles Keepax Signed-off-by: Philipp Zabel --- drivers/base/swnode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/base/swnode.c b/drivers/base/swnode.c index 016a6fd12864..6b1ee75a908f 100644 --- a/drivers/base/swnode.c +++ b/drivers/base/swnode.c @@ -553,7 +553,7 @@ software_node_get_reference_args(const struct fwnode_handle *fwnode, if (!args) return 0; - args->fwnode = software_node_get(refnode); + args->fwnode = fwnode_handle_get(refnode); args->nargs = nargs; for (i = 0; i < nargs; i++) From d7cdbbc93c564902169e854e78716a7b5e6cb241 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 20 Nov 2025 14:23:58 +0100 Subject: [PATCH 51/80] software node: allow referencing firmware nodes At the moment software nodes can only reference other software nodes. This is a limitation for devices created, for instance, on the auxiliary bus with a dynamic software node attached which cannot reference devices the firmware node of which is "real" (as an OF node or otherwise). Make it possible for a software node to reference all firmware nodes in addition to static software nodes. To that end: add a second pointer to struct software_node_ref_args of type struct fwnode_handle. The core swnode code will first check the swnode pointer and if it's NULL, it will assume the fwnode pointer should be set. Software node graphs remain the same, as in: the remote endpoints still have to be software nodes. Acked-by: Linus Walleij Reviewed-by: Sakari Ailus Reviewed-by: Andy Shevchenko Acked-by: Greg Kroah-Hartman Signed-off-by: Bartosz Golaszewski Reviewed-by: Charles Keepax Tested-by: Charles Keepax Signed-off-by: Philipp Zabel --- drivers/base/swnode.c | 24 ++++++++++++++++++++++-- include/linux/property.h | 13 ++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/drivers/base/swnode.c b/drivers/base/swnode.c index 6b1ee75a908f..16a8301c25d6 100644 --- a/drivers/base/swnode.c +++ b/drivers/base/swnode.c @@ -535,7 +535,24 @@ software_node_get_reference_args(const struct fwnode_handle *fwnode, ref_array = prop->pointer; ref = &ref_array[index]; - refnode = software_node_fwnode(ref->node); + /* + * A software node can reference other software nodes or firmware + * nodes (which are the abstraction layer sitting on top of them). + * This is done to ensure we can create references to static software + * nodes before they're registered with the firmware node framework. + * At the time the reference is being resolved, we expect the swnodes + * in question to already have been registered and to be backed by + * a firmware node. This is why we use the fwnode API below to read the + * relevant properties and bump the reference count. + */ + + if (ref->swnode) + refnode = software_node_fwnode(ref->swnode); + else if (ref->fwnode) + refnode = ref->fwnode; + else + return -EINVAL; + if (!refnode) return -ENOENT; @@ -633,7 +650,10 @@ software_node_graph_get_remote_endpoint(const struct fwnode_handle *fwnode) ref = prop->pointer; - return software_node_get(software_node_fwnode(ref[0].node)); + if (!ref->swnode) + return NULL; + + return software_node_get(software_node_fwnode(ref->swnode)); } static struct fwnode_handle * diff --git a/include/linux/property.h b/include/linux/property.h index 50b26589dd70..272bfbdea7bf 100644 --- a/include/linux/property.h +++ b/include/linux/property.h @@ -355,19 +355,26 @@ struct software_node; /** * struct software_node_ref_args - Reference property with additional arguments - * @node: Reference to a software node + * @swnode: Reference to a software node + * @fwnode: Alternative reference to a firmware node handle * @nargs: Number of elements in @args array * @args: Integer arguments */ struct software_node_ref_args { - const struct software_node *node; + const struct software_node *swnode; + struct fwnode_handle *fwnode; unsigned int nargs; u64 args[NR_FWNODE_REFERENCE_ARGS]; }; #define SOFTWARE_NODE_REFERENCE(_ref_, ...) \ (const struct software_node_ref_args) { \ - .node = _ref_, \ + .swnode = _Generic(_ref_, \ + const struct software_node *: _ref_, \ + default: NULL), \ + .fwnode = _Generic(_ref_, \ + struct fwnode_handle *: _ref_, \ + default: NULL), \ .nargs = COUNT_ARGS(__VA_ARGS__), \ .args = { __VA_ARGS__ }, \ } From d2a6cea44acc920fb18d7f29abfa0728b14d10c0 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Thu, 20 Nov 2025 14:23:59 +0100 Subject: [PATCH 52/80] spi: cs42l43: Use actual ACPI firmware node for chip selects On some systems the cs42l43 has amplifiers attached to its SPI controller that are not properly defined in ACPI. Currently software nodes are added to support this case, however, the chip selects for these devices are specified using a hack. A software node is added with the same name as the pinctrl driver, as the look up was name based, this allowed the GPIO look up to return the pinctrl driver even though the swnode was not owned by it. This was necessary as the swnodes did not support directly linking to real firmware nodes. Since commit e5d527be7e69 ("gpio: swnode: don't use the swnode's name as the key for GPIO lookup") changed the lookup to be fwnode based this hack will no longer find the pinctrl driver, resulting in the driver not probing. There is no pinctrl driver attached to the swnode itself. But other patches did add support for linking a swnode to a real fwnode node [1]. As such the hack is no longer needed, so switch over to just passing the real fwnode for the pinctrl property to avoid any issues. [Bartosz: - remove unneeded Fixes: tag, - use PROPERTY_ENTRY_REF_ARRAY() instead of PROPERTY_ENTRY_REF_ARRAY_LEN()] Link: https://lore.kernel.org/linux-gpio/20251106-reset-gpios-swnodes-v6-0-69aa852de9e4@linaro.org/ [1] Fixes: 439fbc97502a ("spi: cs42l43: Add bridged cs35l56 amplifiers") Cc: stable+noautosel@kernel.org # Don't backport, previous approach works, fix relies on swnode changes Signed-off-by: Charles Keepax Reviewed-by: Bartosz Golaszewski Signed-off-by: Bartosz Golaszewski Acked-by: Mark Brown Reviewed-by: Andy Shevchenko Signed-off-by: Philipp Zabel --- drivers/spi/spi-cs42l43.c | 40 ++++++++++----------------------------- 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/drivers/spi/spi-cs42l43.c b/drivers/spi/spi-cs42l43.c index 14307dd800b7..4b6b65f450a8 100644 --- a/drivers/spi/spi-cs42l43.c +++ b/drivers/spi/spi-cs42l43.c @@ -52,20 +52,6 @@ static struct spi_board_info amp_info_template = { .mode = SPI_MODE_0, }; -static const struct software_node cs42l43_gpiochip_swnode = { - .name = "cs42l43-pinctrl", -}; - -static const struct software_node_ref_args cs42l43_cs_refs[] = { - SOFTWARE_NODE_REFERENCE(&cs42l43_gpiochip_swnode, 0, GPIO_ACTIVE_LOW), - SOFTWARE_NODE_REFERENCE(&swnode_gpio_undefined), -}; - -static const struct property_entry cs42l43_cs_props[] = { - PROPERTY_ENTRY_REF_ARRAY("cs-gpios", cs42l43_cs_refs), - {} -}; - static int cs42l43_spi_tx(struct regmap *regmap, const u8 *buf, unsigned int len) { const u8 *end = buf + len; @@ -324,11 +310,6 @@ static void cs42l43_release_of_node(void *data) fwnode_handle_put(data); } -static void cs42l43_release_sw_node(void *data) -{ - software_node_unregister(&cs42l43_gpiochip_swnode); -} - static int cs42l43_spi_probe(struct platform_device *pdev) { struct cs42l43 *cs42l43 = dev_get_drvdata(pdev->dev.parent); @@ -391,6 +372,15 @@ static int cs42l43_spi_probe(struct platform_device *pdev) fwnode_property_read_u32(xu_fwnode, "01fa-sidecar-instances", &nsidecars); if (nsidecars) { + struct software_node_ref_args args[] = { + SOFTWARE_NODE_REFERENCE(fwnode, 0, GPIO_ACTIVE_LOW), + SOFTWARE_NODE_REFERENCE(&swnode_gpio_undefined), + }; + struct property_entry props[] = { + PROPERTY_ENTRY_REF_ARRAY("cs-gpios", args), + { } + }; + ret = fwnode_property_read_u32(xu_fwnode, "01fa-spk-id-val", &spkid); if (!ret) { dev_dbg(priv->dev, "01fa-spk-id-val = %d\n", spkid); @@ -403,17 +393,7 @@ static int cs42l43_spi_probe(struct platform_device *pdev) "Failed to get spk-id-gpios\n"); } - ret = software_node_register(&cs42l43_gpiochip_swnode); - if (ret) - return dev_err_probe(priv->dev, ret, - "Failed to register gpio swnode\n"); - - ret = devm_add_action_or_reset(priv->dev, cs42l43_release_sw_node, NULL); - if (ret) - return ret; - - ret = device_create_managed_software_node(&priv->ctlr->dev, - cs42l43_cs_props, NULL); + ret = device_create_managed_software_node(&priv->ctlr->dev, props, NULL); if (ret) return dev_err_probe(priv->dev, ret, "Failed to add swnode\n"); } else { From 216c1204757190666b0f6bd3e32590a1008a4343 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 20 Nov 2025 14:24:00 +0100 Subject: [PATCH 53/80] gpio: swnode: allow referencing GPIO chips by firmware nodes When doing a software node lookup, we require both the fwnode that references a GPIO chip as well as the node associated with that chip to be software nodes. However, we now allow referencing generic firmware nodes from software nodes in driver core so we should allow the same in GPIO core. Make the software node name check optional and dependent on whether the referenced firmware node is a software node. If it's not, just continue with the lookup. Acked-by: Linus Walleij Reviewed-by: Andy Shevchenko Acked-by: Greg Kroah-Hartman Signed-off-by: Bartosz Golaszewski Signed-off-by: Philipp Zabel --- drivers/gpio/gpiolib-swnode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpiolib-swnode.c b/drivers/gpio/gpiolib-swnode.c index e3806db1c0e0..b44f35d68459 100644 --- a/drivers/gpio/gpiolib-swnode.c +++ b/drivers/gpio/gpiolib-swnode.c @@ -31,7 +31,7 @@ static struct gpio_device *swnode_get_gpio_device(struct fwnode_handle *fwnode) gdev_node = to_software_node(fwnode); if (!gdev_node || !gdev_node->name) - return ERR_PTR(-EINVAL); + goto fwnode_lookup; /* * Check for a special node that identifies undefined GPIOs, this is @@ -41,6 +41,7 @@ static struct gpio_device *swnode_get_gpio_device(struct fwnode_handle *fwnode) !strcmp(gdev_node->name, GPIOLIB_SWNODE_UNDEFINED_NAME)) return ERR_PTR(-ENOENT); +fwnode_lookup: gdev = gpio_device_find_by_fwnode(fwnode); return gdev ?: ERR_PTR(-EPROBE_DEFER); } From 97d85328e3dcb2b3acb249a7c82d96c18b6b0d5b Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 20 Nov 2025 14:24:01 +0100 Subject: [PATCH 54/80] reset: order includes alphabetically in reset/core.c For better readability and easier maintenance order the includes alphabetically. Reviewed-by: Philipp Zabel Acked-by: Linus Walleij Acked-by: Greg Kroah-Hartman Signed-off-by: Bartosz Golaszewski Signed-off-by: Philipp Zabel --- drivers/reset/core.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/reset/core.c b/drivers/reset/core.c index 22f67fc77ae5..5a696e2dbcc2 100644 --- a/drivers/reset/core.c +++ b/drivers/reset/core.c @@ -4,19 +4,20 @@ * * Copyright 2013 Philipp Zabel, Pengutronix */ + +#include #include #include #include #include #include -#include -#include #include #include #include +#include +#include #include #include -#include #include #include #include From 46dae84a90f9845df661adb116560e33d47a82ee Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 20 Nov 2025 14:24:02 +0100 Subject: [PATCH 55/80] reset: make the provider of reset-gpios the parent of the reset device Auxiliary devices really do need a parent so ahead of converting the reset-gpios driver to registering on the auxiliary bus, make the GPIO device that provides the reset GPIO the parent of the reset-gpio device. To that end move the lookup of the GPIO device by fwnode to the beginning of __reset_add_reset_gpio_device() which has the added benefit of bailing out earlier, before allocating resources for the virtual device, if the chip is not up yet. Reviewed-by: Philipp Zabel Acked-by: Linus Walleij Acked-by: Greg Kroah-Hartman Signed-off-by: Bartosz Golaszewski Signed-off-by: Philipp Zabel --- drivers/reset/core.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/reset/core.c b/drivers/reset/core.c index 5a696e2dbcc2..13236ab69f10 100644 --- a/drivers/reset/core.c +++ b/drivers/reset/core.c @@ -849,11 +849,11 @@ static void __reset_control_put_internal(struct reset_control *rstc) kref_put(&rstc->refcnt, __reset_control_release); } -static int __reset_add_reset_gpio_lookup(int id, struct device_node *np, +static int __reset_add_reset_gpio_lookup(struct gpio_device *gdev, int id, + struct device_node *np, unsigned int gpio, unsigned int of_flags) { - const struct fwnode_handle *fwnode = of_fwnode_handle(np); unsigned int lookup_flags; const char *label_tmp; @@ -868,10 +868,6 @@ static int __reset_add_reset_gpio_lookup(int id, struct device_node *np, return -EINVAL; } - struct gpio_device *gdev __free(gpio_device_put) = gpio_device_find_by_fwnode(fwnode); - if (!gdev) - return -EPROBE_DEFER; - label_tmp = gpio_device_get_label(gdev); if (!label_tmp) return -EINVAL; @@ -926,6 +922,11 @@ static int __reset_add_reset_gpio_device(const struct of_phandle_args *args) */ lockdep_assert_not_held(&reset_list_mutex); + struct gpio_device *gdev __free(gpio_device_put) = + gpio_device_find_by_fwnode(of_fwnode_handle(args->np)); + if (!gdev) + return -EPROBE_DEFER; + guard(mutex)(&reset_gpio_lookup_mutex); list_for_each_entry(rgpio_dev, &reset_gpio_lookup_list, list) { @@ -946,7 +947,7 @@ static int __reset_add_reset_gpio_device(const struct of_phandle_args *args) goto err_ida_free; } - ret = __reset_add_reset_gpio_lookup(id, args->np, args->args[0], + ret = __reset_add_reset_gpio_lookup(gdev, id, args->np, args->args[0], args->args[1]); if (ret < 0) goto err_kfree; @@ -958,7 +959,8 @@ static int __reset_add_reset_gpio_device(const struct of_phandle_args *args) * Hold reference as long as rgpio_dev memory is valid. */ of_node_get(rgpio_dev->of_args.np); - pdev = platform_device_register_data(NULL, "reset-gpio", id, + pdev = platform_device_register_data(gpio_device_to_device(gdev), + "reset-gpio", id, &rgpio_dev->of_args, sizeof(rgpio_dev->of_args)); ret = PTR_ERR_OR_ZERO(pdev); From 109ce747ac22d22a88e5ebd76d606d60a834646c Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 20 Nov 2025 14:24:03 +0100 Subject: [PATCH 56/80] reset: gpio: convert the driver to using the auxiliary bus As the reset-gpio devices are purely virtual and never instantiated from real firmware nodes, let's convert the driver to using the - more fitting - auxiliary bus. Reviewed-by: Philipp Zabel Acked-by: Linus Walleij Acked-by: Greg Kroah-Hartman Signed-off-by: Bartosz Golaszewski Signed-off-by: Philipp Zabel --- drivers/reset/Kconfig | 1 + drivers/reset/core.c | 14 ++++++-------- drivers/reset/reset-gpio.c | 19 ++++++++++--------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index 78b7078478d4..30659115de55 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -89,6 +89,7 @@ config RESET_EYEQ config RESET_GPIO tristate "GPIO reset controller" depends on GPIOLIB + select AUXILIARY_BUS help This enables a generic reset controller for resets attached via GPIOs. Typically for OF platforms this driver expects "reset-gpios" diff --git a/drivers/reset/core.c b/drivers/reset/core.c index 13236ab69f10..e129c4c803ea 100644 --- a/drivers/reset/core.c +++ b/drivers/reset/core.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -18,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -882,7 +882,7 @@ static int __reset_add_reset_gpio_lookup(struct gpio_device *gdev, int id, if (!lookup) return -ENOMEM; - lookup->dev_id = kasprintf(GFP_KERNEL, "reset-gpio.%d", id); + lookup->dev_id = kasprintf(GFP_KERNEL, "reset.gpio.%d", id); if (!lookup->dev_id) return -ENOMEM; @@ -903,7 +903,7 @@ static int __reset_add_reset_gpio_lookup(struct gpio_device *gdev, int id, static int __reset_add_reset_gpio_device(const struct of_phandle_args *args) { struct reset_gpio_lookup *rgpio_dev; - struct platform_device *pdev; + struct auxiliary_device *adev; int id, ret; /* @@ -959,11 +959,9 @@ static int __reset_add_reset_gpio_device(const struct of_phandle_args *args) * Hold reference as long as rgpio_dev memory is valid. */ of_node_get(rgpio_dev->of_args.np); - pdev = platform_device_register_data(gpio_device_to_device(gdev), - "reset-gpio", id, - &rgpio_dev->of_args, - sizeof(rgpio_dev->of_args)); - ret = PTR_ERR_OR_ZERO(pdev); + adev = auxiliary_device_create(gpio_device_to_device(gdev), "reset", + "gpio", &rgpio_dev->of_args, id); + ret = PTR_ERR_OR_ZERO(adev); if (ret) goto err_put; diff --git a/drivers/reset/reset-gpio.c b/drivers/reset/reset-gpio.c index 2290b25b6703..e5512b3b596b 100644 --- a/drivers/reset/reset-gpio.c +++ b/drivers/reset/reset-gpio.c @@ -1,10 +1,10 @@ // SPDX-License-Identifier: GPL-2.0 +#include #include #include #include #include -#include #include struct reset_gpio_priv { @@ -61,9 +61,10 @@ static void reset_gpio_of_node_put(void *data) of_node_put(data); } -static int reset_gpio_probe(struct platform_device *pdev) +static int reset_gpio_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) { - struct device *dev = &pdev->dev; + struct device *dev = &adev->dev; struct of_phandle_args *platdata = dev_get_platdata(dev); struct reset_gpio_priv *priv; int ret; @@ -75,7 +76,7 @@ static int reset_gpio_probe(struct platform_device *pdev) if (!priv) return -ENOMEM; - platform_set_drvdata(pdev, &priv->rc); + auxiliary_set_drvdata(adev, &priv->rc); priv->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); if (IS_ERR(priv->reset)) @@ -99,20 +100,20 @@ static int reset_gpio_probe(struct platform_device *pdev) return devm_reset_controller_register(dev, &priv->rc); } -static const struct platform_device_id reset_gpio_ids[] = { - { .name = "reset-gpio", }, +static const struct auxiliary_device_id reset_gpio_ids[] = { + { .name = "reset.gpio" }, {} }; -MODULE_DEVICE_TABLE(platform, reset_gpio_ids); +MODULE_DEVICE_TABLE(auxiliary, reset_gpio_ids); -static struct platform_driver reset_gpio_driver = { +static struct auxiliary_driver reset_gpio_driver = { .probe = reset_gpio_probe, .id_table = reset_gpio_ids, .driver = { .name = "reset-gpio", }, }; -module_platform_driver(reset_gpio_driver); +module_auxiliary_driver(reset_gpio_driver); MODULE_AUTHOR("Krzysztof Kozlowski "); MODULE_DESCRIPTION("Generic GPIO reset driver"); From 5fc4e4cf7a2268b5f73700fd1e8d02159f2417d8 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 20 Nov 2025 14:24:04 +0100 Subject: [PATCH 57/80] reset: gpio: use software nodes to setup the GPIO lookup GPIO machine lookup is a nice mechanism for associating GPIOs with consumers if we don't know what kind of device the GPIO provider is or when it will become available. However in the case of the reset-gpio, we are already holding a reference to the device and so can reference its firmware node. Let's setup a software node that references the relevant GPIO and attach it to the auxiliary device we're creating. Reviewed-by: Philipp Zabel Acked-by: Linus Walleij Acked-by: Greg Kroah-Hartman Signed-off-by: Bartosz Golaszewski Signed-off-by: Philipp Zabel --- drivers/reset/core.c | 127 +++++++++++++++++++++++++------------------ 1 file changed, 74 insertions(+), 53 deletions(-) diff --git a/drivers/reset/core.c b/drivers/reset/core.c index e129c4c803ea..843cffc93909 100644 --- a/drivers/reset/core.c +++ b/drivers/reset/core.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -77,10 +78,12 @@ struct reset_control_array { /** * struct reset_gpio_lookup - lookup key for ad-hoc created reset-gpio devices * @of_args: phandle to the reset controller with all the args like GPIO number + * @swnode: Software node containing the reference to the GPIO provider * @list: list entry for the reset_gpio_lookup_list */ struct reset_gpio_lookup { struct of_phandle_args of_args; + struct fwnode_handle *swnode; struct list_head list; }; @@ -849,52 +852,45 @@ static void __reset_control_put_internal(struct reset_control *rstc) kref_put(&rstc->refcnt, __reset_control_release); } -static int __reset_add_reset_gpio_lookup(struct gpio_device *gdev, int id, - struct device_node *np, - unsigned int gpio, - unsigned int of_flags) +static void reset_gpio_aux_device_release(struct device *dev) { - unsigned int lookup_flags; - const char *label_tmp; + struct auxiliary_device *adev = to_auxiliary_dev(dev); - /* - * Later we map GPIO flags between OF and Linux, however not all - * constants from include/dt-bindings/gpio/gpio.h and - * include/linux/gpio/machine.h match each other. - */ - if (of_flags > GPIO_ACTIVE_LOW) { - pr_err("reset-gpio code does not support GPIO flags %u for GPIO %u\n", - of_flags, gpio); - return -EINVAL; + kfree(adev); +} + +static int reset_add_gpio_aux_device(struct device *parent, + struct fwnode_handle *swnode, + int id, void *pdata) +{ + struct auxiliary_device *adev; + int ret; + + adev = kzalloc(sizeof(*adev), GFP_KERNEL); + if (!adev) + return -ENOMEM; + + adev->id = id; + adev->name = "gpio"; + adev->dev.parent = parent; + adev->dev.platform_data = pdata; + adev->dev.release = reset_gpio_aux_device_release; + device_set_node(&adev->dev, swnode); + + ret = auxiliary_device_init(adev); + if (ret) { + kfree(adev); + return ret; } - label_tmp = gpio_device_get_label(gdev); - if (!label_tmp) - return -EINVAL; + ret = __auxiliary_device_add(adev, "reset"); + if (ret) { + auxiliary_device_uninit(adev); + kfree(adev); + return ret; + } - char *label __free(kfree) = kstrdup(label_tmp, GFP_KERNEL); - if (!label) - return -ENOMEM; - - /* Size: one lookup entry plus sentinel */ - struct gpiod_lookup_table *lookup __free(kfree) = kzalloc(struct_size(lookup, table, 2), - GFP_KERNEL); - if (!lookup) - return -ENOMEM; - - lookup->dev_id = kasprintf(GFP_KERNEL, "reset.gpio.%d", id); - if (!lookup->dev_id) - return -ENOMEM; - - lookup_flags = GPIO_PERSISTENT; - lookup_flags |= of_flags & GPIO_ACTIVE_LOW; - lookup->table[0] = GPIO_LOOKUP(no_free_ptr(label), gpio, "reset", - lookup_flags); - - /* Not freed on success, because it is persisent subsystem data. */ - gpiod_add_lookup_table(no_free_ptr(lookup)); - - return 0; + return ret; } /* @@ -902,8 +898,10 @@ static int __reset_add_reset_gpio_lookup(struct gpio_device *gdev, int id, */ static int __reset_add_reset_gpio_device(const struct of_phandle_args *args) { + struct property_entry properties[2] = { }; + unsigned int offset, of_flags, lflags; struct reset_gpio_lookup *rgpio_dev; - struct auxiliary_device *adev; + struct device *parent; int id, ret; /* @@ -922,6 +920,23 @@ static int __reset_add_reset_gpio_device(const struct of_phandle_args *args) */ lockdep_assert_not_held(&reset_list_mutex); + offset = args->args[0]; + of_flags = args->args[1]; + + /* + * Later we map GPIO flags between OF and Linux, however not all + * constants from include/dt-bindings/gpio/gpio.h and + * include/linux/gpio/machine.h match each other. + * + * FIXME: Find a better way of translating OF flags to GPIO lookup + * flags. + */ + if (of_flags > GPIO_ACTIVE_LOW) { + pr_err("reset-gpio code does not support GPIO flags %u for GPIO %u\n", + of_flags, offset); + return -EINVAL; + } + struct gpio_device *gdev __free(gpio_device_put) = gpio_device_find_by_fwnode(of_fwnode_handle(args->np)); if (!gdev) @@ -936,6 +951,10 @@ static int __reset_add_reset_gpio_device(const struct of_phandle_args *args) } } + lflags = GPIO_PERSISTENT | (of_flags & GPIO_ACTIVE_LOW); + parent = gpio_device_to_device(gdev); + properties[0] = PROPERTY_ENTRY_GPIO("reset-gpios", parent->fwnode, offset, lflags); + id = ida_alloc(&reset_gpio_ida, GFP_KERNEL); if (id < 0) return id; @@ -947,11 +966,6 @@ static int __reset_add_reset_gpio_device(const struct of_phandle_args *args) goto err_ida_free; } - ret = __reset_add_reset_gpio_lookup(gdev, id, args->np, args->args[0], - args->args[1]); - if (ret < 0) - goto err_kfree; - rgpio_dev->of_args = *args; /* * We keep the device_node reference, but of_args.np is put at the end @@ -959,19 +973,26 @@ static int __reset_add_reset_gpio_device(const struct of_phandle_args *args) * Hold reference as long as rgpio_dev memory is valid. */ of_node_get(rgpio_dev->of_args.np); - adev = auxiliary_device_create(gpio_device_to_device(gdev), "reset", - "gpio", &rgpio_dev->of_args, id); - ret = PTR_ERR_OR_ZERO(adev); + + rgpio_dev->swnode = fwnode_create_software_node(properties, NULL); + if (IS_ERR(rgpio_dev->swnode)) { + ret = PTR_ERR(rgpio_dev->swnode); + goto err_put_of_node; + } + + ret = reset_add_gpio_aux_device(parent, rgpio_dev->swnode, id, + &rgpio_dev->of_args); if (ret) - goto err_put; + goto err_del_swnode; list_add(&rgpio_dev->list, &reset_gpio_lookup_list); return 0; -err_put: +err_del_swnode: + fwnode_remove_software_node(rgpio_dev->swnode); +err_put_of_node: of_node_put(rgpio_dev->of_args.np); -err_kfree: kfree(rgpio_dev); err_ida_free: ida_free(&reset_gpio_ida, id); From 527250cd9092461f1beac3e4180a4481bffa01b5 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Fri, 21 Nov 2025 11:04:50 +0100 Subject: [PATCH 58/80] platform/x86: intel: chtwc_int33fe: don't dereference swnode args MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Members of struct software_node_ref_args should not be dereferenced directly but set using the provided macros. Commit d7cdbbc93c56 ("software node: allow referencing firmware nodes") changed the name of the software node member and caused a build failure. Remove all direct dereferences of the ref struct as a fix. However, this driver also seems to abuse the software node interface by waiting for a node with an arbitrary name "intel-xhci-usb-sw" to appear in the system before setting up the reference for the I2C device, while the actual software node already exists in the intel-xhci-usb-role-switch module and should be used to set up a static reference. Add a FIXME for a future improvement. Fixes: d7cdbbc93c56 ("software node: allow referencing firmware nodes") Fixes: 53c24c2932e5 ("platform/x86: intel_cht_int33fe: use inline reference properties") Cc: stable@vger.kernel.org Reported-by: Stephen Rothwell Closes: https://lore.kernel.org/all/20251121111534.7cdbfe5c@canb.auug.org.au/ Signed-off-by: Bartosz Golaszewski Reviewed-by: Hans de Goede Acked-by: Ilpo Järvinen Signed-off-by: Philipp Zabel --- drivers/platform/x86/intel/chtwc_int33fe.c | 29 +++++++++++++++------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/drivers/platform/x86/intel/chtwc_int33fe.c b/drivers/platform/x86/intel/chtwc_int33fe.c index 29e8b5432f4c..d183aa53c318 100644 --- a/drivers/platform/x86/intel/chtwc_int33fe.c +++ b/drivers/platform/x86/intel/chtwc_int33fe.c @@ -77,7 +77,7 @@ static const struct software_node max17047_node = { * software node. */ static struct software_node_ref_args fusb302_mux_refs[] = { - { .node = NULL }, + SOFTWARE_NODE_REFERENCE(NULL), }; static const struct property_entry fusb302_properties[] = { @@ -190,11 +190,6 @@ static void cht_int33fe_remove_nodes(struct cht_int33fe_data *data) { software_node_unregister_node_group(node_group); - if (fusb302_mux_refs[0].node) { - fwnode_handle_put(software_node_fwnode(fusb302_mux_refs[0].node)); - fusb302_mux_refs[0].node = NULL; - } - if (data->dp) { data->dp->secondary = NULL; fwnode_handle_put(data->dp); @@ -202,7 +197,15 @@ static void cht_int33fe_remove_nodes(struct cht_int33fe_data *data) } } -static int cht_int33fe_add_nodes(struct cht_int33fe_data *data) +static void cht_int33fe_put_swnode(void *data) +{ + struct fwnode_handle *fwnode = data; + + fwnode_handle_put(fwnode); + fusb302_mux_refs[0] = SOFTWARE_NODE_REFERENCE(NULL); +} + +static int cht_int33fe_add_nodes(struct device *dev, struct cht_int33fe_data *data) { const struct software_node *mux_ref_node; int ret; @@ -212,17 +215,25 @@ static int cht_int33fe_add_nodes(struct cht_int33fe_data *data) * until the mux driver has created software node for the mux device. * It means we depend on the mux driver. This function will return * -EPROBE_DEFER until the mux device is registered. + * + * FIXME: the relevant software node exists in intel-xhci-usb-role-switch + * and - if exported - could be used to set up a static reference. */ mux_ref_node = software_node_find_by_name(NULL, "intel-xhci-usb-sw"); if (!mux_ref_node) return -EPROBE_DEFER; + ret = devm_add_action_or_reset(dev, cht_int33fe_put_swnode, + software_node_fwnode(mux_ref_node)); + if (ret) + return ret; + /* * Update node used in "usb-role-switch" property. Note that we * rely on software_node_register_node_group() to use the original * instance of properties instead of copying them. */ - fusb302_mux_refs[0].node = mux_ref_node; + fusb302_mux_refs[0] = SOFTWARE_NODE_REFERENCE(mux_ref_node); ret = software_node_register_node_group(node_group); if (ret) @@ -345,7 +356,7 @@ static int cht_int33fe_typec_probe(struct platform_device *pdev) return fusb302_irq; } - ret = cht_int33fe_add_nodes(data); + ret = cht_int33fe_add_nodes(dev, data); if (ret) return ret; From 194832dcb13b0d02fce0df887235b7e6d1ef0121 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 18 Nov 2025 11:04:04 +0100 Subject: [PATCH 59/80] string: use __attribute__((nonnull())) in strends() The arguments of strends() must not be NULL so annotate the function with the nonnull attribute. Suggested-by: Kees Cook Link: https://lore.kernel.org/r/20251118-strends-follow-up-v1-2-d3f8ef750f59@linaro.org Signed-off-by: Bartosz Golaszewski --- include/linux/string.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/linux/string.h b/include/linux/string.h index 69e9256592f8..0266dbdaa4cd 100644 --- a/include/linux/string.h +++ b/include/linux/string.h @@ -570,7 +570,8 @@ static inline bool strstarts(const char *str, const char *prefix) * Returns: * True if @str ends with @suffix. False in all other cases. */ -static inline bool strends(const char *str, const char *suffix) +static inline bool __attribute__((nonnull(1, 2))) +strends(const char *str, const char *suffix) { unsigned int str_len = strlen(str), suffix_len = strlen(suffix); From 3f19e57cbfb55d743d60aeebf5d5c48cc7fd5d4e Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 24 Nov 2025 08:20:52 +0800 Subject: [PATCH 60/80] gpio: dwapb: Use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Acked-by: Linus Walleij Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20251124002105.25429-2-jszhang@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-dwapb.c | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/drivers/gpio/gpio-dwapb.c b/drivers/gpio/gpio-dwapb.c index b42ff46d292b..4986c465c9a8 100644 --- a/drivers/gpio/gpio-dwapb.c +++ b/drivers/gpio/gpio-dwapb.c @@ -79,7 +79,6 @@ struct dwapb_platform_data { unsigned int nports; }; -#ifdef CONFIG_PM_SLEEP /* Store GPIO context across system-wide suspend/resume transitions */ struct dwapb_context { u32 data; @@ -92,7 +91,6 @@ struct dwapb_context { u32 int_deb; u32 wake_en; }; -#endif struct dwapb_gpio_port_irqchip { unsigned int nr_irqs; @@ -103,9 +101,7 @@ struct dwapb_gpio_port { struct gpio_generic_chip chip; struct dwapb_gpio_port_irqchip *pirq; struct dwapb_gpio *gpio; -#ifdef CONFIG_PM_SLEEP struct dwapb_context *ctx; -#endif unsigned int idx; }; @@ -363,7 +359,6 @@ static int dwapb_irq_set_type(struct irq_data *d, u32 type) return 0; } -#ifdef CONFIG_PM_SLEEP static int dwapb_irq_set_wake(struct irq_data *d, unsigned int enable) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); @@ -378,9 +373,6 @@ static int dwapb_irq_set_wake(struct irq_data *d, unsigned int enable) return 0; } -#else -#define dwapb_irq_set_wake NULL -#endif static const struct irq_chip dwapb_irq_chip = { .name = DWAPB_DRIVER_NAME, @@ -390,7 +382,7 @@ static const struct irq_chip dwapb_irq_chip = { .irq_set_type = dwapb_irq_set_type, .irq_enable = dwapb_irq_enable, .irq_disable = dwapb_irq_disable, - .irq_set_wake = dwapb_irq_set_wake, + .irq_set_wake = pm_sleep_ptr(dwapb_irq_set_wake), .flags = IRQCHIP_IMMUTABLE, GPIOCHIP_IRQ_RESOURCE_HELPERS, }; @@ -759,7 +751,6 @@ static int dwapb_gpio_probe(struct platform_device *pdev) return 0; } -#ifdef CONFIG_PM_SLEEP static int dwapb_gpio_suspend(struct device *dev) { struct dwapb_gpio *gpio = dev_get_drvdata(dev); @@ -844,15 +835,14 @@ static int dwapb_gpio_resume(struct device *dev) return 0; } -#endif -static SIMPLE_DEV_PM_OPS(dwapb_gpio_pm_ops, dwapb_gpio_suspend, - dwapb_gpio_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(dwapb_gpio_pm_ops, + dwapb_gpio_suspend, dwapb_gpio_resume); static struct platform_driver dwapb_gpio_driver = { .driver = { .name = DWAPB_DRIVER_NAME, - .pm = &dwapb_gpio_pm_ops, + .pm = pm_sleep_ptr(&dwapb_gpio_pm_ops), .of_match_table = dwapb_of_match, .acpi_match_table = dwapb_acpi_match, }, From 56f3a6d7538d2e0dfb8d9df7871d2a9aec3115ac Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 24 Nov 2025 08:20:53 +0800 Subject: [PATCH 61/80] gpio: brcmstb: Use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Acked-by: Doug Berger Reviewed-by: Florian Fainelli Acked-by: Linus Walleij Link: https://lore.kernel.org/r/20251124002105.25429-3-jszhang@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-brcmstb.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/gpio/gpio-brcmstb.c b/drivers/gpio/gpio-brcmstb.c index f40c9472588b..af9287ff5dc4 100644 --- a/drivers/gpio/gpio-brcmstb.c +++ b/drivers/gpio/gpio-brcmstb.c @@ -533,7 +533,6 @@ static void brcmstb_gpio_shutdown(struct platform_device *pdev) brcmstb_gpio_quiesce(&pdev->dev, false); } -#ifdef CONFIG_PM_SLEEP static void brcmstb_gpio_bank_restore(struct brcmstb_gpio_priv *priv, struct brcmstb_gpio_bank *bank) { @@ -572,14 +571,9 @@ static int brcmstb_gpio_resume(struct device *dev) return 0; } -#else -#define brcmstb_gpio_suspend NULL -#define brcmstb_gpio_resume NULL -#endif /* CONFIG_PM_SLEEP */ - static const struct dev_pm_ops brcmstb_gpio_pm_ops = { - .suspend_noirq = brcmstb_gpio_suspend, - .resume_noirq = brcmstb_gpio_resume, + .suspend_noirq = pm_sleep_ptr(brcmstb_gpio_suspend), + .resume_noirq = pm_sleep_ptr(brcmstb_gpio_resume), }; static int brcmstb_gpio_probe(struct platform_device *pdev) @@ -755,7 +749,7 @@ static struct platform_driver brcmstb_gpio_driver = { .driver = { .name = "brcmstb-gpio", .of_match_table = brcmstb_gpio_of_match, - .pm = &brcmstb_gpio_pm_ops, + .pm = pm_sleep_ptr(&brcmstb_gpio_pm_ops), }, .probe = brcmstb_gpio_probe, .remove = brcmstb_gpio_remove, From 2557b1f4f21a75650a03c74a56ea30bd4214866e Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 24 Nov 2025 08:20:54 +0800 Subject: [PATCH 62/80] gpio: htc-egpio: Use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. Signed-off-by: Jisheng Zhang Reviewed-by: Florian Fainelli Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20251124002105.25429-4-jszhang@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-htc-egpio.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/drivers/gpio/gpio-htc-egpio.c b/drivers/gpio/gpio-htc-egpio.c index 2eaed83214d8..72935d6dbebf 100644 --- a/drivers/gpio/gpio-htc-egpio.c +++ b/drivers/gpio/gpio-htc-egpio.c @@ -364,21 +364,20 @@ static int __init egpio_probe(struct platform_device *pdev) return 0; } -#ifdef CONFIG_PM -static int egpio_suspend(struct platform_device *pdev, pm_message_t state) +static int egpio_suspend(struct device *dev) { - struct egpio_info *ei = platform_get_drvdata(pdev); + struct egpio_info *ei = dev_get_drvdata(dev); - if (ei->chained_irq && device_may_wakeup(&pdev->dev)) + if (ei->chained_irq && device_may_wakeup(dev)) enable_irq_wake(ei->chained_irq); return 0; } -static int egpio_resume(struct platform_device *pdev) +static int egpio_resume(struct device *dev) { - struct egpio_info *ei = platform_get_drvdata(pdev); + struct egpio_info *ei = dev_get_drvdata(dev); - if (ei->chained_irq && device_may_wakeup(&pdev->dev)) + if (ei->chained_irq && device_may_wakeup(dev)) disable_irq_wake(ei->chained_irq); /* Update registers from the cache, in case @@ -386,19 +385,15 @@ static int egpio_resume(struct platform_device *pdev) egpio_write_cache(ei); return 0; } -#else -#define egpio_suspend NULL -#define egpio_resume NULL -#endif +static DEFINE_SIMPLE_DEV_PM_OPS(egpio_pm_ops, egpio_suspend, egpio_resume); static struct platform_driver egpio_driver = { .driver = { .name = "htc-egpio", .suppress_bind_attrs = true, + .pm = pm_sleep_ptr(&egpio_pm_ops), }, - .suspend = egpio_suspend, - .resume = egpio_resume, }; static int __init egpio_init(void) From b40c4dacf48a42ddcd701552575945e90f5c8060 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 24 Nov 2025 08:20:55 +0800 Subject: [PATCH 63/80] gpio: pl061: Use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use #ifdef guards. This has the advantage of always compiling these functions in, independently of any Kconfig option. Thanks to that, bugs and other regressions are subsequently easier to catch. The pl061_context_save_regs structure is always embedded into struct pl061 to simplify code, so this brings a tiny 8 bytes memory overhead for !CONFIG_PM_SLEEP. Signed-off-by: Jisheng Zhang Reviewed-by: Linus Walleij Acked-by: Linus Walleij Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20251124002105.25429-5-jszhang@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-pl061.c | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/drivers/gpio/gpio-pl061.c b/drivers/gpio/gpio-pl061.c index 02e4ffcf5a6f..919cf86fd590 100644 --- a/drivers/gpio/gpio-pl061.c +++ b/drivers/gpio/gpio-pl061.c @@ -37,7 +37,6 @@ #define PL061_GPIO_NR 8 -#ifdef CONFIG_PM struct pl061_context_save_regs { u8 gpio_data; u8 gpio_dir; @@ -46,7 +45,6 @@ struct pl061_context_save_regs { u8 gpio_iev; u8 gpio_ie; }; -#endif struct pl061 { raw_spinlock_t lock; @@ -55,9 +53,7 @@ struct pl061 { struct gpio_chip gc; int parent_irq; -#ifdef CONFIG_PM struct pl061_context_save_regs csave_regs; -#endif }; static int pl061_get_direction(struct gpio_chip *gc, unsigned offset) @@ -367,7 +363,6 @@ static int pl061_probe(struct amba_device *adev, const struct amba_id *id) return 0; } -#ifdef CONFIG_PM static int pl061_suspend(struct device *dev) { struct pl061 *pl061 = dev_get_drvdata(dev); @@ -411,13 +406,7 @@ static int pl061_resume(struct device *dev) return 0; } -static const struct dev_pm_ops pl061_dev_pm_ops = { - .suspend = pl061_suspend, - .resume = pl061_resume, - .freeze = pl061_suspend, - .restore = pl061_resume, -}; -#endif +static DEFINE_SIMPLE_DEV_PM_OPS(pl061_dev_pm_ops, pl061_suspend, pl061_resume); static const struct amba_id pl061_ids[] = { { @@ -431,9 +420,7 @@ MODULE_DEVICE_TABLE(amba, pl061_ids); static struct amba_driver pl061_gpio_driver = { .drv = { .name = "pl061_gpio", -#ifdef CONFIG_PM - .pm = &pl061_dev_pm_ops, -#endif + .pm = pm_sleep_ptr(&pl061_dev_pm_ops), }, .id_table = pl061_ids, .probe = pl061_probe, From 1f37a9f7d1fa582833cc8e226d77e5b2397df9fa Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 24 Nov 2025 08:20:56 +0800 Subject: [PATCH 64/80] gpio: ml-ioh: Use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused Signed-off-by: Jisheng Zhang Acked-by: Andy Shevchenko Acked-by: Linus Walleij Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20251124002105.25429-6-jszhang@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-ml-ioh.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpio/gpio-ml-ioh.c b/drivers/gpio/gpio-ml-ioh.c index f6af81bf2b13..6576e5dcb0ee 100644 --- a/drivers/gpio/gpio-ml-ioh.c +++ b/drivers/gpio/gpio-ml-ioh.c @@ -160,7 +160,7 @@ static int ioh_gpio_direction_input(struct gpio_chip *gpio, unsigned nr) /* * Save register configuration and disable interrupts. */ -static void __maybe_unused ioh_gpio_save_reg_conf(struct ioh_gpio *chip) +static void ioh_gpio_save_reg_conf(struct ioh_gpio *chip) { int i; @@ -186,7 +186,7 @@ static void __maybe_unused ioh_gpio_save_reg_conf(struct ioh_gpio *chip) /* * This function restores the register configuration of the GPIO device. */ -static void __maybe_unused ioh_gpio_restore_reg_conf(struct ioh_gpio *chip) +static void ioh_gpio_restore_reg_conf(struct ioh_gpio *chip) { int i; @@ -479,7 +479,7 @@ static int ioh_gpio_probe(struct pci_dev *pdev, return 0; } -static int __maybe_unused ioh_gpio_suspend(struct device *dev) +static int ioh_gpio_suspend(struct device *dev) { struct ioh_gpio *chip = dev_get_drvdata(dev); unsigned long flags; @@ -491,7 +491,7 @@ static int __maybe_unused ioh_gpio_suspend(struct device *dev) return 0; } -static int __maybe_unused ioh_gpio_resume(struct device *dev) +static int ioh_gpio_resume(struct device *dev) { struct ioh_gpio *chip = dev_get_drvdata(dev); unsigned long flags; @@ -505,7 +505,7 @@ static int __maybe_unused ioh_gpio_resume(struct device *dev) return 0; } -static SIMPLE_DEV_PM_OPS(ioh_gpio_pm_ops, ioh_gpio_suspend, ioh_gpio_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(ioh_gpio_pm_ops, ioh_gpio_suspend, ioh_gpio_resume); static const struct pci_device_id ioh_gpio_pcidev_id[] = { { PCI_DEVICE(PCI_VENDOR_ID_ROHM, 0x802E) }, @@ -518,7 +518,7 @@ static struct pci_driver ioh_gpio_driver = { .id_table = ioh_gpio_pcidev_id, .probe = ioh_gpio_probe, .driver = { - .pm = &ioh_gpio_pm_ops, + .pm = pm_sleep_ptr(&ioh_gpio_pm_ops), }, }; From a92f492a1473eb2255be9b7b767d0720c5c3b2a9 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 24 Nov 2025 08:20:57 +0800 Subject: [PATCH 65/80] gpio: mlxbf2: Use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused Signed-off-by: Jisheng Zhang Acked-by: Linus Walleij Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20251124002105.25429-7-jszhang@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mlxbf2.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpio-mlxbf2.c b/drivers/gpio/gpio-mlxbf2.c index abffce3894fc..6668686a28ff 100644 --- a/drivers/gpio/gpio-mlxbf2.c +++ b/drivers/gpio/gpio-mlxbf2.c @@ -424,7 +424,7 @@ mlxbf2_gpio_probe(struct platform_device *pdev) return 0; } -static int __maybe_unused mlxbf2_gpio_suspend(struct device *dev) +static int mlxbf2_gpio_suspend(struct device *dev) { struct mlxbf2_gpio_context *gs = dev_get_drvdata(dev); @@ -436,7 +436,7 @@ static int __maybe_unused mlxbf2_gpio_suspend(struct device *dev) return 0; } -static int __maybe_unused mlxbf2_gpio_resume(struct device *dev) +static int mlxbf2_gpio_resume(struct device *dev) { struct mlxbf2_gpio_context *gs = dev_get_drvdata(dev); @@ -447,7 +447,7 @@ static int __maybe_unused mlxbf2_gpio_resume(struct device *dev) return 0; } -static SIMPLE_DEV_PM_OPS(mlxbf2_pm_ops, mlxbf2_gpio_suspend, mlxbf2_gpio_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(mlxbf2_pm_ops, mlxbf2_gpio_suspend, mlxbf2_gpio_resume); static const struct acpi_device_id __maybe_unused mlxbf2_gpio_acpi_match[] = { { "MLNXBF22", 0 }, @@ -459,7 +459,7 @@ static struct platform_driver mlxbf2_gpio_driver = { .driver = { .name = "mlxbf2_gpio", .acpi_match_table = mlxbf2_gpio_acpi_match, - .pm = &mlxbf2_pm_ops, + .pm = pm_sleep_ptr(&mlxbf2_pm_ops), }, .probe = mlxbf2_gpio_probe, }; From 07a251bfe3b690ebfaef7c46f6ce25ea9ccba8da Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 24 Nov 2025 08:20:58 +0800 Subject: [PATCH 66/80] gpio: msc313: Use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused Signed-off-by: Jisheng Zhang Acked-by: Linus Walleij Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20251124002105.25429-8-jszhang@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-msc313.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpio-msc313.c b/drivers/gpio/gpio-msc313.c index b0cccd856840..7345afdc78de 100644 --- a/drivers/gpio/gpio-msc313.c +++ b/drivers/gpio/gpio-msc313.c @@ -694,7 +694,7 @@ static const struct of_device_id msc313_gpio_of_match[] = { * SoC goes into suspend to memory mode so we need to save some * of the register bits before suspending and put it back when resuming */ -static int __maybe_unused msc313_gpio_suspend(struct device *dev) +static int msc313_gpio_suspend(struct device *dev) { struct msc313_gpio *gpio = dev_get_drvdata(dev); int i; @@ -705,7 +705,7 @@ static int __maybe_unused msc313_gpio_suspend(struct device *dev) return 0; } -static int __maybe_unused msc313_gpio_resume(struct device *dev) +static int msc313_gpio_resume(struct device *dev) { struct msc313_gpio *gpio = dev_get_drvdata(dev); int i; @@ -716,13 +716,13 @@ static int __maybe_unused msc313_gpio_resume(struct device *dev) return 0; } -static SIMPLE_DEV_PM_OPS(msc313_gpio_ops, msc313_gpio_suspend, msc313_gpio_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(msc313_gpio_ops, msc313_gpio_suspend, msc313_gpio_resume); static struct platform_driver msc313_gpio_driver = { .driver = { .name = DRIVER_NAME, .of_match_table = msc313_gpio_of_match, - .pm = &msc313_gpio_ops, + .pm = pm_sleep_ptr(&msc313_gpio_ops), }, .probe = msc313_gpio_probe, }; From 2b3c8bd8e13bd101fe8833b1f02ef5e5a6e9920b Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 24 Nov 2025 08:20:59 +0800 Subject: [PATCH 67/80] gpio: omap: Use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused Signed-off-by: Jisheng Zhang Acked-by: Linus Walleij Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20251124002105.25429-9-jszhang@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-omap.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/gpio/gpio-omap.c b/drivers/gpio/gpio-omap.c index a268c76bdca6..e136e81794df 100644 --- a/drivers/gpio/gpio-omap.c +++ b/drivers/gpio/gpio-omap.c @@ -1503,7 +1503,7 @@ static void omap_gpio_remove(struct platform_device *pdev) clk_unprepare(bank->dbck); } -static int __maybe_unused omap_gpio_runtime_suspend(struct device *dev) +static int omap_gpio_runtime_suspend(struct device *dev) { struct gpio_bank *bank = dev_get_drvdata(dev); unsigned long flags; @@ -1516,7 +1516,7 @@ static int __maybe_unused omap_gpio_runtime_suspend(struct device *dev) return 0; } -static int __maybe_unused omap_gpio_runtime_resume(struct device *dev) +static int omap_gpio_runtime_resume(struct device *dev) { struct gpio_bank *bank = dev_get_drvdata(dev); unsigned long flags; @@ -1529,7 +1529,7 @@ static int __maybe_unused omap_gpio_runtime_resume(struct device *dev) return 0; } -static int __maybe_unused omap_gpio_suspend(struct device *dev) +static int omap_gpio_suspend(struct device *dev) { struct gpio_bank *bank = dev_get_drvdata(dev); @@ -1541,7 +1541,7 @@ static int __maybe_unused omap_gpio_suspend(struct device *dev) return omap_gpio_runtime_suspend(dev); } -static int __maybe_unused omap_gpio_resume(struct device *dev) +static int omap_gpio_resume(struct device *dev) { struct gpio_bank *bank = dev_get_drvdata(dev); @@ -1554,9 +1554,8 @@ static int __maybe_unused omap_gpio_resume(struct device *dev) } static const struct dev_pm_ops gpio_pm_ops = { - SET_RUNTIME_PM_OPS(omap_gpio_runtime_suspend, omap_gpio_runtime_resume, - NULL) - SET_LATE_SYSTEM_SLEEP_PM_OPS(omap_gpio_suspend, omap_gpio_resume) + RUNTIME_PM_OPS(omap_gpio_runtime_suspend, omap_gpio_runtime_resume, NULL) + LATE_SYSTEM_SLEEP_PM_OPS(omap_gpio_suspend, omap_gpio_resume) }; static struct platform_driver omap_gpio_driver = { @@ -1564,7 +1563,7 @@ static struct platform_driver omap_gpio_driver = { .remove = omap_gpio_remove, .driver = { .name = "omap_gpio", - .pm = &gpio_pm_ops, + .pm = pm_ptr(&gpio_pm_ops), .of_match_table = omap_gpio_match, }, }; From 0ed358a87d6ef9782dca161ef3f1311d21f257d2 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 24 Nov 2025 08:21:00 +0800 Subject: [PATCH 68/80] gpio: pch: Use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused Signed-off-by: Jisheng Zhang Acked-by: Andy Shevchenko Acked-by: Linus Walleij Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20251124002105.25429-10-jszhang@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-pch.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpio/gpio-pch.c b/drivers/gpio/gpio-pch.c index 9925687e05fb..4ffa0955a9e3 100644 --- a/drivers/gpio/gpio-pch.c +++ b/drivers/gpio/gpio-pch.c @@ -171,7 +171,7 @@ static int pch_gpio_direction_input(struct gpio_chip *gpio, unsigned int nr) /* * Save register configuration and disable interrupts. */ -static void __maybe_unused pch_gpio_save_reg_conf(struct pch_gpio *chip) +static void pch_gpio_save_reg_conf(struct pch_gpio *chip) { chip->pch_gpio_reg.ien_reg = ioread32(&chip->reg->ien); chip->pch_gpio_reg.imask_reg = ioread32(&chip->reg->imask); @@ -187,7 +187,7 @@ static void __maybe_unused pch_gpio_save_reg_conf(struct pch_gpio *chip) /* * This function restores the register configuration of the GPIO device. */ -static void __maybe_unused pch_gpio_restore_reg_conf(struct pch_gpio *chip) +static void pch_gpio_restore_reg_conf(struct pch_gpio *chip) { iowrite32(chip->pch_gpio_reg.ien_reg, &chip->reg->ien); iowrite32(chip->pch_gpio_reg.imask_reg, &chip->reg->imask); @@ -402,7 +402,7 @@ static int pch_gpio_probe(struct pci_dev *pdev, return pch_gpio_alloc_generic_chip(chip, irq_base, gpio_pins[chip->ioh]); } -static int __maybe_unused pch_gpio_suspend(struct device *dev) +static int pch_gpio_suspend(struct device *dev) { struct pch_gpio *chip = dev_get_drvdata(dev); unsigned long flags; @@ -414,7 +414,7 @@ static int __maybe_unused pch_gpio_suspend(struct device *dev) return 0; } -static int __maybe_unused pch_gpio_resume(struct device *dev) +static int pch_gpio_resume(struct device *dev) { struct pch_gpio *chip = dev_get_drvdata(dev); unsigned long flags; @@ -428,7 +428,7 @@ static int __maybe_unused pch_gpio_resume(struct device *dev) return 0; } -static SIMPLE_DEV_PM_OPS(pch_gpio_pm_ops, pch_gpio_suspend, pch_gpio_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(pch_gpio_pm_ops, pch_gpio_suspend, pch_gpio_resume); static const struct pci_device_id pch_gpio_pcidev_id[] = { { PCI_DEVICE_DATA(INTEL, EG20T_PCH, INTEL_EG20T_PCH) }, @@ -444,7 +444,7 @@ static struct pci_driver pch_gpio_driver = { .id_table = pch_gpio_pcidev_id, .probe = pch_gpio_probe, .driver = { - .pm = &pch_gpio_pm_ops, + .pm = pm_sleep_ptr(&pch_gpio_pm_ops), }, }; From 75ff16234bf3af747b9c77b81d7ce3df5c09df8c Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 24 Nov 2025 08:21:01 +0800 Subject: [PATCH 69/80] gpio: tqmx86: Use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused Signed-off-by: Jisheng Zhang Acked-by: Linus Walleij Link: https://lore.kernel.org/r/20251124002105.25429-11-jszhang@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-tqmx86.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/gpio/gpio-tqmx86.c b/drivers/gpio/gpio-tqmx86.c index 27dd09273292..eedfc0e371e3 100644 --- a/drivers/gpio/gpio-tqmx86.c +++ b/drivers/gpio/gpio-tqmx86.c @@ -279,19 +279,18 @@ static void tqmx86_gpio_irq_handler(struct irq_desc *desc) } /* Minimal runtime PM is needed by the IRQ subsystem */ -static int __maybe_unused tqmx86_gpio_runtime_suspend(struct device *dev) +static int tqmx86_gpio_runtime_suspend(struct device *dev) { return 0; } -static int __maybe_unused tqmx86_gpio_runtime_resume(struct device *dev) +static int tqmx86_gpio_runtime_resume(struct device *dev) { return 0; } static const struct dev_pm_ops tqmx86_gpio_dev_pm_ops = { - SET_RUNTIME_PM_OPS(tqmx86_gpio_runtime_suspend, - tqmx86_gpio_runtime_resume, NULL) + RUNTIME_PM_OPS(tqmx86_gpio_runtime_suspend, tqmx86_gpio_runtime_resume, NULL) }; static void tqmx86_init_irq_valid_mask(struct gpio_chip *chip, @@ -425,7 +424,7 @@ static int tqmx86_gpio_probe(struct platform_device *pdev) static struct platform_driver tqmx86_gpio_driver = { .driver = { .name = "tqmx86-gpio", - .pm = &tqmx86_gpio_dev_pm_ops, + .pm = pm_ptr(&tqmx86_gpio_dev_pm_ops), }, .probe = tqmx86_gpio_probe, }; From 46e90d3924cb58b161c2dd57ba05f3a706c1c0e2 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 24 Nov 2025 08:21:02 +0800 Subject: [PATCH 70/80] gpio: uniphier: Use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused Signed-off-by: Jisheng Zhang Acked-by: Linus Walleij Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20251124002105.25429-12-jszhang@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-uniphier.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/gpio/gpio-uniphier.c b/drivers/gpio/gpio-uniphier.c index 197bb1d22b3c..0574dde5b5bb 100644 --- a/drivers/gpio/gpio-uniphier.c +++ b/drivers/gpio/gpio-uniphier.c @@ -426,7 +426,7 @@ static void uniphier_gpio_remove(struct platform_device *pdev) irq_domain_remove(priv->domain); } -static int __maybe_unused uniphier_gpio_suspend(struct device *dev) +static int uniphier_gpio_suspend(struct device *dev) { struct uniphier_gpio_priv *priv = dev_get_drvdata(dev); unsigned int nbanks = uniphier_gpio_get_nbanks(priv->chip.ngpio); @@ -448,7 +448,7 @@ static int __maybe_unused uniphier_gpio_suspend(struct device *dev) return 0; } -static int __maybe_unused uniphier_gpio_resume(struct device *dev) +static int uniphier_gpio_resume(struct device *dev) { struct uniphier_gpio_priv *priv = dev_get_drvdata(dev); unsigned int nbanks = uniphier_gpio_get_nbanks(priv->chip.ngpio); @@ -473,8 +473,7 @@ static int __maybe_unused uniphier_gpio_resume(struct device *dev) } static const struct dev_pm_ops uniphier_gpio_pm_ops = { - SET_LATE_SYSTEM_SLEEP_PM_OPS(uniphier_gpio_suspend, - uniphier_gpio_resume) + LATE_SYSTEM_SLEEP_PM_OPS(uniphier_gpio_suspend, uniphier_gpio_resume) }; static const struct of_device_id uniphier_gpio_match[] = { @@ -489,7 +488,7 @@ static struct platform_driver uniphier_gpio_driver = { .driver = { .name = "uniphier-gpio", .of_match_table = uniphier_gpio_match, - .pm = &uniphier_gpio_pm_ops, + .pm = pm_sleep_ptr(&uniphier_gpio_pm_ops), }, }; module_platform_driver(uniphier_gpio_driver); From 353fdaebdc6991f1cf03ae3aaec266ad0516859b Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 24 Nov 2025 08:21:03 +0800 Subject: [PATCH 71/80] gpio: xgene: Use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused Signed-off-by: Jisheng Zhang Acked-by: Linus Walleij Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20251124002105.25429-13-jszhang@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-xgene.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpio-xgene.c b/drivers/gpio/gpio-xgene.c index 4f627de3f56c..809668449dbe 100644 --- a/drivers/gpio/gpio-xgene.c +++ b/drivers/gpio/gpio-xgene.c @@ -130,7 +130,7 @@ static int xgene_gpio_dir_out(struct gpio_chip *gc, return 0; } -static __maybe_unused int xgene_gpio_suspend(struct device *dev) +static int xgene_gpio_suspend(struct device *dev) { struct xgene_gpio *gpio = dev_get_drvdata(dev); unsigned long bank_offset; @@ -143,7 +143,7 @@ static __maybe_unused int xgene_gpio_suspend(struct device *dev) return 0; } -static __maybe_unused int xgene_gpio_resume(struct device *dev) +static int xgene_gpio_resume(struct device *dev) { struct xgene_gpio *gpio = dev_get_drvdata(dev); unsigned long bank_offset; @@ -156,7 +156,7 @@ static __maybe_unused int xgene_gpio_resume(struct device *dev) return 0; } -static SIMPLE_DEV_PM_OPS(xgene_gpio_pm, xgene_gpio_suspend, xgene_gpio_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(xgene_gpio_pm, xgene_gpio_suspend, xgene_gpio_resume); static int xgene_gpio_probe(struct platform_device *pdev) { @@ -204,7 +204,7 @@ static struct platform_driver xgene_gpio_driver = { .name = "xgene-gpio", .of_match_table = xgene_gpio_of_match, .acpi_match_table = ACPI_PTR(xgene_gpio_acpi_match), - .pm = &xgene_gpio_pm, + .pm = pm_sleep_ptr(&xgene_gpio_pm), }, .probe = xgene_gpio_probe, }; From dbedf93d1082b4e755eb62338e5f6566f4e31fb8 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 24 Nov 2025 08:21:04 +0800 Subject: [PATCH 72/80] gpio: xilinx: Use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused Signed-off-by: Jisheng Zhang Acked-by: Linus Walleij Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20251124002105.25429-14-jszhang@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-xilinx.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/gpio/gpio-xilinx.c b/drivers/gpio/gpio-xilinx.c index 83675ac81077..be4b4d730547 100644 --- a/drivers/gpio/gpio-xilinx.c +++ b/drivers/gpio/gpio-xilinx.c @@ -286,7 +286,7 @@ static void xgpio_free(struct gpio_chip *chip, unsigned int offset) pm_runtime_put(chip->parent); } -static int __maybe_unused xgpio_suspend(struct device *dev) +static int xgpio_suspend(struct device *dev) { struct xgpio_instance *gpio = dev_get_drvdata(dev); struct irq_data *data = irq_get_irq_data(gpio->irq); @@ -327,7 +327,7 @@ static void xgpio_irq_ack(struct irq_data *irq_data) { } -static int __maybe_unused xgpio_resume(struct device *dev) +static int xgpio_resume(struct device *dev) { struct xgpio_instance *gpio = dev_get_drvdata(dev); struct irq_data *data = irq_get_irq_data(gpio->irq); @@ -343,7 +343,7 @@ static int __maybe_unused xgpio_resume(struct device *dev) return 0; } -static int __maybe_unused xgpio_runtime_suspend(struct device *dev) +static int xgpio_runtime_suspend(struct device *dev) { struct xgpio_instance *gpio = dev_get_drvdata(dev); @@ -352,7 +352,7 @@ static int __maybe_unused xgpio_runtime_suspend(struct device *dev) return 0; } -static int __maybe_unused xgpio_runtime_resume(struct device *dev) +static int xgpio_runtime_resume(struct device *dev) { struct xgpio_instance *gpio = dev_get_drvdata(dev); @@ -360,9 +360,8 @@ static int __maybe_unused xgpio_runtime_resume(struct device *dev) } static const struct dev_pm_ops xgpio_dev_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(xgpio_suspend, xgpio_resume) - SET_RUNTIME_PM_OPS(xgpio_runtime_suspend, - xgpio_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(xgpio_suspend, xgpio_resume) + RUNTIME_PM_OPS(xgpio_runtime_suspend, xgpio_runtime_resume, NULL) }; /** @@ -682,7 +681,7 @@ static struct platform_driver xgpio_plat_driver = { .driver = { .name = "gpio-xilinx", .of_match_table = xgpio_of_match, - .pm = &xgpio_dev_pm_ops, + .pm = pm_ptr(&xgpio_dev_pm_ops), }, }; From 23ac52a4a2dceb704d8dc1674abb8beefd93bf1a Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 24 Nov 2025 08:21:05 +0800 Subject: [PATCH 73/80] gpio: zynq: Use modern PM macros Use the modern PM macros for the suspend and resume functions to be automatically dropped by the compiler when CONFIG_PM or CONFIG_PM_SLEEP are disabled, without having to use __maybe_unused Signed-off-by: Jisheng Zhang Acked-by: Linus Walleij Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20251124002105.25429-15-jszhang@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-zynq.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/gpio/gpio-zynq.c b/drivers/gpio/gpio-zynq.c index 0ffd76e8951f..97780c57ab56 100644 --- a/drivers/gpio/gpio-zynq.c +++ b/drivers/gpio/gpio-zynq.c @@ -735,7 +735,7 @@ static void zynq_gpio_restore_context(struct zynq_gpio *gpio) } } -static int __maybe_unused zynq_gpio_suspend(struct device *dev) +static int zynq_gpio_suspend(struct device *dev) { struct zynq_gpio *gpio = dev_get_drvdata(dev); struct irq_data *data = irq_get_irq_data(gpio->irq); @@ -756,7 +756,7 @@ static int __maybe_unused zynq_gpio_suspend(struct device *dev) return 0; } -static int __maybe_unused zynq_gpio_resume(struct device *dev) +static int zynq_gpio_resume(struct device *dev) { struct zynq_gpio *gpio = dev_get_drvdata(dev); struct irq_data *data = irq_get_irq_data(gpio->irq); @@ -779,7 +779,7 @@ static int __maybe_unused zynq_gpio_resume(struct device *dev) return 0; } -static int __maybe_unused zynq_gpio_runtime_suspend(struct device *dev) +static int zynq_gpio_runtime_suspend(struct device *dev) { struct zynq_gpio *gpio = dev_get_drvdata(dev); @@ -788,7 +788,7 @@ static int __maybe_unused zynq_gpio_runtime_suspend(struct device *dev) return 0; } -static int __maybe_unused zynq_gpio_runtime_resume(struct device *dev) +static int zynq_gpio_runtime_resume(struct device *dev) { struct zynq_gpio *gpio = dev_get_drvdata(dev); @@ -814,9 +814,8 @@ static void zynq_gpio_free(struct gpio_chip *chip, unsigned int offset) } static const struct dev_pm_ops zynq_gpio_dev_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(zynq_gpio_suspend, zynq_gpio_resume) - SET_RUNTIME_PM_OPS(zynq_gpio_runtime_suspend, - zynq_gpio_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(zynq_gpio_suspend, zynq_gpio_resume) + RUNTIME_PM_OPS(zynq_gpio_runtime_suspend, zynq_gpio_runtime_resume, NULL) }; static const struct zynq_platform_data versal_gpio_def = { @@ -1022,7 +1021,7 @@ static void zynq_gpio_remove(struct platform_device *pdev) static struct platform_driver zynq_gpio_driver = { .driver = { .name = DRIVER_NAME, - .pm = &zynq_gpio_dev_pm_ops, + .pm = pm_ptr(&zynq_gpio_dev_pm_ops), .of_match_table = zynq_gpio_of_match, }, .probe = zynq_gpio_probe, From 7b78b26757e0d997b31635d76eaa46d5ef5e1431 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 25 Nov 2025 11:19:09 +0100 Subject: [PATCH 74/80] gpio: shared: handle the reset-gpios corner case There's an unexpected interaction between the reset-gpio driver and the shared GPIO support. The reset-gpio device is an auxiliary device that's created dynamically and fulfills a similar role to the gpio-shared-proxy driver but is limited in scope to just supporting the "reset-gpios" property. The shared GPIO core code does not take into account that the machine lookup entry we create when scanning the device-tree must connect the reset-gpio device - that is the actual consumer of the GPIO and not the consumer defined on the device tree, which in turn consumes the shared reset control exposed by the reset-gpio device - to the GPIO controller. We also must not skip the gpio-shared-proxy driver as it's possible that a shared GPIO may be used by one consumer as a reset-gpios going through the reset-gpio device and another that uses GPIOLIB. We need to make it a special case handled in gpiolib-shared.c. Add a new function - gpio_shared_dev_is_reset_gpio() - whose role it is to verify if a non-matching consumer of a shared pin is a reset-gpio device and make sure it's the right one for this pin. To that end make sure that its parent is the GPIO controller in question and that the fwnode we identified as sharing the pin references that controller via the "reset-gpios" property. Only include that code if the reset-gpio driver is enabled. Fixes: a060b8c511ab ("gpiolib: implement low-level, shared GPIO support") Reported-by: Val Packett Closes: https://lore.kernel.org/all/3b5d9df5-934d-4591-8827-6c9573a6f7ba@packett.cool/ Tested-by: Val Packett Tested-by: Abel Vesa Link: https://lore.kernel.org/r/20251125-gpiolib-shared-reset-gpio-fix-v2-1-4eb6fa41f1dd@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-shared.c | 81 ++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c index 3803b5c938f9..cc8646f563d2 100644 --- a/drivers/gpio/gpiolib-shared.c +++ b/drivers/gpio/gpiolib-shared.c @@ -253,6 +253,84 @@ static int gpio_shared_make_adev(struct gpio_device *gdev, return 0; } +#if IS_ENABLED(CONFIG_RESET_GPIO) +/* + * Special case: reset-gpio is an auxiliary device that's created dynamically + * and put in between the GPIO controller and consumers of shared GPIOs + * referred to by the "reset-gpios" property. + * + * If the supposed consumer of a shared GPIO didn't match any of the mappings + * we created when scanning the firmware nodes, it's still possible that it's + * the reset-gpio device which didn't exist at the time of the scan. + * + * This function verifies it an return true if it's the case. + */ +static bool gpio_shared_dev_is_reset_gpio(struct device *consumer, + struct gpio_shared_entry *entry, + struct gpio_shared_ref *ref) +{ + struct fwnode_handle *reset_fwnode = dev_fwnode(consumer); + struct fwnode_reference_args ref_args, aux_args; + struct device *parent = consumer->parent; + bool match; + int ret; + + /* The reset-gpio device must have a parent AND a firmware node. */ + if (!parent || !reset_fwnode) + return false; + + /* + * FIXME: use device_is_compatible() once the reset-gpio drivers gains + * a compatible string which it currently does not have. + */ + if (!strstarts(dev_name(consumer), "reset.gpio.")) + return false; + + /* + * Parent of the reset-gpio auxiliary device is the GPIO chip whose + * fwnode we stored in the entry structure. + */ + if (!device_match_fwnode(parent, entry->fwnode)) + return false; + + /* + * The device associated with the shared reference's firmware node is + * the consumer of the reset control exposed by the reset-gpio device. + * It must have a "reset-gpios" property that's referencing the entry's + * firmware node. + * + * The reference args must agree between the real consumer and the + * auxiliary reset-gpio device. + */ + ret = fwnode_property_get_reference_args(ref->fwnode, "reset-gpios", + NULL, 2, 0, &ref_args); + if (ret) + return false; + + ret = fwnode_property_get_reference_args(reset_fwnode, "reset-gpios", + NULL, 2, 0, &aux_args); + if (ret) { + fwnode_handle_put(ref_args.fwnode); + return false; + } + + match = ((ref_args.fwnode == entry->fwnode) && + (aux_args.fwnode == entry->fwnode) && + (ref_args.args[0] == aux_args.args[0])); + + fwnode_handle_put(ref_args.fwnode); + fwnode_handle_put(aux_args.fwnode); + return match; +} +#else +static bool gpio_shared_dev_is_reset_gpio(struct device *consumer, + struct gpio_shared_entry *entry, + struct gpio_shared_ref *ref) +{ + return false; +} +#endif /* CONFIG_RESET_GPIO */ + int gpio_shared_add_proxy_lookup(struct device *consumer, unsigned long lflags) { const char *dev_id = dev_name(consumer); @@ -268,7 +346,8 @@ int gpio_shared_add_proxy_lookup(struct device *consumer, unsigned long lflags) list_for_each_entry(entry, &gpio_shared_list, list) { list_for_each_entry(ref, &entry->refs, list) { - if (!device_match_fwnode(consumer, ref->fwnode)) + if (!device_match_fwnode(consumer, ref->fwnode) && + !gpio_shared_dev_is_reset_gpio(consumer, entry, ref)) continue; /* We've already done that on a previous request. */ From cfab6dc0700c3b9120dab726fc184c3b4500cc5b Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 26 Nov 2025 17:49:05 +0100 Subject: [PATCH 75/80] gpio: shared: ignore special __symbols__ node when traversing device tree The __symbols__ node is a special, internal node and its properties must not be considered when scanning the device-tree for shared GPIOs. Fixes: a060b8c511ab ("gpiolib: implement low-level, shared GPIO support") Reported-by: Jon Hunter Closes: https://lore.kernel.org/all/0829a21c-f97d-41b6-90bc-2acaec42caab@nvidia.com/ Tested-by: Jon Hunter Acked-by: Jon Hunter Link: https://lore.kernel.org/r/20251126-gpio-shared-fixes-v1-1-18309c0e87b5@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-shared.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c index cc8646f563d2..3a6d4cd54081 100644 --- a/drivers/gpio/gpiolib-shared.c +++ b/drivers/gpio/gpiolib-shared.c @@ -73,6 +73,19 @@ gpio_shared_find_entry(struct fwnode_handle *controller_node, return NULL; } +/* Handle all special nodes that we should ignore. */ +static bool gpio_shared_of_node_ignore(struct device_node *node) +{ + /* + * __symbols__ is a special, internal node and should not be considered + * when scanning for shared GPIOs. + */ + if (of_node_name_eq(node, "__symbols__")) + return true; + + return false; +} + static int gpio_shared_of_traverse(struct device_node *curr) { struct gpio_shared_entry *entry; @@ -84,6 +97,9 @@ static int gpio_shared_of_traverse(struct device_node *curr) const char *suffix; int ret, count, i; + if (gpio_shared_of_node_ignore(curr)) + return 0; + for_each_property_of_node(curr, prop) { /* * The standard name for a GPIO property is "foo-gpios" From 114e594e6cb791ce7c839ccfbe153ecfa3e7abce Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 26 Nov 2025 17:49:06 +0100 Subject: [PATCH 76/80] gpio: shared: ignore GPIO hogs when traversing the device tree GPIO hogs have a "gpios" property but it's not a phandle to a remote node - it references the parent GPIO controller. We must not try to parse it as a phandle. Fixes: a060b8c511ab ("gpiolib: implement low-level, shared GPIO support") Reported-by: Cosmin Tanislav Closes: https://lore.kernel.org/all/2d96e464-e17c-4ff5-9a08-b215b77da04f@gmail.com/ Link: https://lore.kernel.org/r/20251126-gpio-shared-fixes-v1-2-18309c0e87b5@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-shared.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c index 3a6d4cd54081..cd4dd6fc76ab 100644 --- a/drivers/gpio/gpiolib-shared.c +++ b/drivers/gpio/gpiolib-shared.c @@ -83,6 +83,13 @@ static bool gpio_shared_of_node_ignore(struct device_node *node) if (of_node_name_eq(node, "__symbols__")) return true; + /* + * GPIO hogs have a "gpios" property which is not a phandle and can't + * possibly refer to a shared GPIO. + */ + if (of_property_present(node, "gpio-hog")) + return true; + return false; } From 64309e40e357bead3a872db89512df6c071addc5 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 26 Nov 2025 20:17:30 +0100 Subject: [PATCH 77/80] gpio: shared-proxy: set suppress_bind_attrs User-space must not fiddle with shared-proxy auxiliary devices. Disable bind/unbind attributes in sysfs. Link: https://lore.kernel.org/r/20251126191730.66277-1-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-shared-proxy.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpio/gpio-shared-proxy.c b/drivers/gpio/gpio-shared-proxy.c index 3ef2c40ed152..29d7d2e4dfc0 100644 --- a/drivers/gpio/gpio-shared-proxy.c +++ b/drivers/gpio/gpio-shared-proxy.c @@ -322,6 +322,7 @@ MODULE_DEVICE_TABLE(auxiliary, gpio_shared_proxy_id_table); static struct auxiliary_driver gpio_shared_proxy_driver = { .driver = { .name = "gpio-shared-proxy", + .suppress_bind_attrs = true, }, .probe = gpio_shared_proxy_probe, .id_table = gpio_shared_proxy_id_table, From 54a2df5afa2382d6fd1168a3caf151f50c68dfea Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Fri, 28 Nov 2025 09:40:11 +0100 Subject: [PATCH 78/80] gpio: shared: fix a deadlock It's possible that the auxiliary proxy device we add when setting up the GPIO controller exposing shared pins, will get matched and probed immediately. The gpio-proxy-driver will then retrieve the shared descriptor structure. That will cause a recursive mutex locking and a deadlock because we're already holding the gpio_shared_lock in gpio_device_setup_shared() and try to take it again in devm_gpiod_shared_get() like this: [ 4.298346] gpiolib_shared: GPIO 130 owned by f100000.pinctrl is shared by multiple consumers [ 4.307157] gpiolib_shared: Setting up a shared GPIO entry for speaker@0,3 [ 4.314604] [ 4.316146] ============================================ [ 4.321600] WARNING: possible recursive locking detected [ 4.327054] 6.18.0-rc7-next-20251125-g3f300d0674f6-dirty #3887 Not tainted [ 4.334115] -------------------------------------------- [ 4.339566] kworker/u32:3/71 is trying to acquire lock: [ 4.344931] ffffda019ba71850 (gpio_shared_lock){+.+.}-{4:4}, at: devm_gpiod_shared_get+0x34/0x2e0 [ 4.354057] [ 4.354057] but task is already holding lock: [ 4.360041] ffffda019ba71850 (gpio_shared_lock){+.+.}-{4:4}, at: gpio_device_setup_shared+0x30/0x268 [ 4.369421] [ 4.369421] other info that might help us debug this: [ 4.376126] Possible unsafe locking scenario: [ 4.376126] [ 4.382198] CPU0 [ 4.384711] ---- [ 4.387223] lock(gpio_shared_lock); [ 4.390992] lock(gpio_shared_lock); [ 4.394761] [ 4.394761] *** DEADLOCK *** [ 4.394761] [ 4.400832] May be due to missing lock nesting notation [ 4.400832] [ 4.407802] 5 locks held by kworker/u32:3/71: [ 4.412279] #0: ffff000080020948 ((wq_completion)events_unbound){+.+.}-{0:0}, at: process_one_work+0x194/0x64c [ 4.422650] #1: ffff800080963d60 (deferred_probe_work){+.+.}-{0:0}, at: process_one_work+0x1bc/0x64c [ 4.432117] #2: ffff00008165c8f8 (&dev->mutex){....}-{4:4}, at: __device_attach+0x3c/0x198 [ 4.440700] #3: ffffda019ba71850 (gpio_shared_lock){+.+.}-{4:4}, at: gpio_device_setup_shared+0x30/0x268 [ 4.450523] #4: ffff0000810fe918 (&dev->mutex){....}-{4:4}, at: __device_attach+0x3c/0x198 [ 4.459103] [ 4.459103] stack backtrace: [ 4.463581] CPU: 6 UID: 0 PID: 71 Comm: kworker/u32:3 Not tainted 6.18.0-rc7-next-20251125-g3f300d0674f6-dirty #3887 PREEMPT [ 4.463589] Hardware name: Qualcomm Technologies, Inc. Robotics RB5 (DT) [ 4.463593] Workqueue: events_unbound deferred_probe_work_func [ 4.463602] Call trace: [ 4.463604] show_stack+0x18/0x24 (C) [ 4.463617] dump_stack_lvl+0x70/0x98 [ 4.463627] dump_stack+0x18/0x24 [ 4.463636] print_deadlock_bug+0x224/0x238 [ 4.463643] __lock_acquire+0xe4c/0x15f0 [ 4.463648] lock_acquire+0x1cc/0x344 [ 4.463653] __mutex_lock+0xb8/0x840 [ 4.463661] mutex_lock_nested+0x24/0x30 [ 4.463667] devm_gpiod_shared_get+0x34/0x2e0 [ 4.463674] gpio_shared_proxy_probe+0x18/0x138 [ 4.463682] auxiliary_bus_probe+0x40/0x78 [ 4.463688] really_probe+0xbc/0x2c0 [ 4.463694] __driver_probe_device+0x78/0x120 [ 4.463701] driver_probe_device+0x3c/0x160 [ 4.463708] __device_attach_driver+0xb8/0x140 [ 4.463716] bus_for_each_drv+0x88/0xe8 [ 4.463723] __device_attach+0xa0/0x198 [ 4.463729] device_initial_probe+0x14/0x20 [ 4.463737] bus_probe_device+0xb4/0xc0 [ 4.463743] device_add+0x578/0x76c [ 4.463747] __auxiliary_device_add+0x40/0xac [ 4.463752] gpio_device_setup_shared+0x1f8/0x268 [ 4.463758] gpiochip_add_data_with_key+0xdac/0x10ac [ 4.463763] devm_gpiochip_add_data_with_key+0x30/0x80 [ 4.463768] msm_pinctrl_probe+0x4b0/0x5e0 [ 4.463779] sm8250_pinctrl_probe+0x18/0x40 [ 4.463784] platform_probe+0x5c/0xa4 [ 4.463793] really_probe+0xbc/0x2c0 [ 4.463800] __driver_probe_device+0x78/0x120 [ 4.463807] driver_probe_device+0x3c/0x160 [ 4.463814] __device_attach_driver+0xb8/0x140 [ 4.463821] bus_for_each_drv+0x88/0xe8 [ 4.463827] __device_attach+0xa0/0x198 [ 4.463834] device_initial_probe+0x14/0x20 [ 4.463841] bus_probe_device+0xb4/0xc0 [ 4.463847] deferred_probe_work_func+0x90/0xcc [ 4.463854] process_one_work+0x214/0x64c [ 4.463860] worker_thread+0x1bc/0x360 [ 4.463866] kthread+0x14c/0x220 [ 4.463871] ret_from_fork+0x10/0x20 [ 77.265041] random: crng init done Fortunately, at the time of creating of the auxiliary device, we already know the correct entry so let's store it as the device's platform data. We don't need to hold gpio_shared_lock in devm_gpiod_shared_get() as we're not removing the entry or traversing the list anymore but we still need to protect it from concurrent modification of its fields so add a more fine-grained mutex. Fixes: a060b8c511ab ("gpiolib: implement low-level, shared GPIO support") Reported-by: Dmitry Baryshkov Closes: https://lore.kernel.org/all/fimuvblfy2cmn7o4wzcxjzrux5mwhvlvyxfsgeqs6ore2xg75i@ax46d3sfmdux/ Reviewed-by: Dmitry Baryshkov Tested-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20251128-gpio-shared-deadlock-v2-1-9f3ae8ddcb09@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-shared.c | 63 ++++++++++++++++------------------- 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c index cd4dd6fc76ab..8bdd107b1ad1 100644 --- a/drivers/gpio/gpiolib-shared.c +++ b/drivers/gpio/gpiolib-shared.c @@ -49,6 +49,7 @@ struct gpio_shared_entry { unsigned int offset; /* Index in the property value array. */ size_t index; + struct mutex lock; struct gpio_shared_desc *shared_desc; struct kref ref; struct list_head refs; @@ -170,6 +171,7 @@ static int gpio_shared_of_traverse(struct device_node *curr) entry->offset = offset; entry->index = count; INIT_LIST_HEAD(&entry->refs); + mutex_init(&entry->lock); list_add_tail(&entry->list, &gpio_shared_list); } @@ -246,6 +248,7 @@ static void gpio_shared_adev_release(struct device *dev) } static int gpio_shared_make_adev(struct gpio_device *gdev, + struct gpio_shared_entry *entry, struct gpio_shared_ref *ref) { struct auxiliary_device *adev = &ref->adev; @@ -258,6 +261,7 @@ static int gpio_shared_make_adev(struct gpio_device *gdev, adev->id = ref->dev_id; adev->name = "proxy"; adev->dev.parent = gdev->dev.parent; + adev->dev.platform_data = entry; adev->dev.release = gpio_shared_adev_release; ret = auxiliary_device_init(adev); @@ -461,7 +465,7 @@ int gpio_device_setup_shared(struct gpio_device *gdev) pr_debug("Setting up a shared GPIO entry for %s\n", fwnode_get_name(ref->fwnode)); - ret = gpio_shared_make_adev(gdev, ref); + ret = gpio_shared_make_adev(gdev, entry, ref); if (ret) return ret; } @@ -495,10 +499,11 @@ static void gpio_shared_release(struct kref *kref) { struct gpio_shared_entry *entry = container_of(kref, struct gpio_shared_entry, ref); - struct gpio_shared_desc *shared_desc = entry->shared_desc; + struct gpio_shared_desc *shared_desc; - guard(mutex)(&gpio_shared_lock); + guard(mutex)(&entry->lock); + shared_desc = entry->shared_desc; gpio_device_put(shared_desc->desc->gdev); if (shared_desc->can_sleep) mutex_destroy(&shared_desc->mutex); @@ -521,6 +526,8 @@ gpiod_shared_desc_create(struct gpio_shared_entry *entry) struct gpio_shared_desc *shared_desc; struct gpio_device *gdev; + lockdep_assert_held(&entry->lock); + shared_desc = kzalloc(sizeof(*shared_desc), GFP_KERNEL); if (!shared_desc) return ERR_PTR(-ENOMEM); @@ -541,57 +548,42 @@ gpiod_shared_desc_create(struct gpio_shared_entry *entry) return shared_desc; } -static struct gpio_shared_entry *gpiod_shared_find(struct auxiliary_device *adev) +struct gpio_shared_desc *devm_gpiod_shared_get(struct device *dev) { struct gpio_shared_desc *shared_desc; struct gpio_shared_entry *entry; - struct gpio_shared_ref *ref; + int ret; - guard(mutex)(&gpio_shared_lock); + lockdep_assert_not_held(&gpio_shared_lock); - list_for_each_entry(entry, &gpio_shared_list, list) { - list_for_each_entry(ref, &entry->refs, list) { - if (adev != &ref->adev) - continue; - - if (entry->shared_desc) { - kref_get(&entry->ref); - return entry; - } + entry = dev_get_platdata(dev); + if (WARN_ON(!entry)) + /* Programmer bug */ + return ERR_PTR(-ENOENT); + scoped_guard(mutex, &entry->lock) { + if (entry->shared_desc) { + kref_get(&entry->ref); + shared_desc = entry->shared_desc; + } else { shared_desc = gpiod_shared_desc_create(entry); if (IS_ERR(shared_desc)) return ERR_CAST(shared_desc); kref_init(&entry->ref); entry->shared_desc = shared_desc; - - pr_debug("Device %s acquired a reference to the shared GPIO %u owned by %s\n", - dev_name(&adev->dev), gpiod_hwgpio(shared_desc->desc), - gpio_device_get_label(shared_desc->desc->gdev)); - - - return entry; } + + pr_debug("Device %s acquired a reference to the shared GPIO %u owned by %s\n", + dev_name(dev), gpiod_hwgpio(shared_desc->desc), + gpio_device_get_label(shared_desc->desc->gdev)); } - return ERR_PTR(-ENOENT); -} - -struct gpio_shared_desc *devm_gpiod_shared_get(struct device *dev) -{ - struct gpio_shared_entry *entry; - int ret; - - entry = gpiod_shared_find(to_auxiliary_dev(dev)); - if (IS_ERR(entry)) - return ERR_CAST(entry); - ret = devm_add_action_or_reset(dev, gpiod_shared_put, entry); if (ret) return ERR_PTR(ret); - return entry->shared_desc; + return shared_desc; } EXPORT_SYMBOL_GPL(devm_gpiod_shared_get); @@ -607,6 +599,7 @@ static void gpio_shared_drop_ref(struct gpio_shared_ref *ref) static void gpio_shared_drop_entry(struct gpio_shared_entry *entry) { list_del(&entry->list); + mutex_destroy(&entry->lock); fwnode_handle_put(entry->fwnode); kfree(entry); } From f01c0f7ee59fce16e5bae92a2d388a8a6fdf3f0f Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 27 Nov 2025 22:27:39 -0800 Subject: [PATCH 79/80] gpio: regmap: fix kernel-doc notation Add a ':' to the end of struct member names to prevent kernel-doc warnings: Warning: include/linux/gpio/regmap.h:108 struct member 'regmap_irq_line' not described in 'gpio_regmap_config' Warning: include/linux/gpio/regmap.h:108 struct member 'regmap_irq_flags' not described in 'gpio_regmap_config' Fixes: 553b75d4bfe9 ("gpio: regmap: Allow to allocate regmap-irq device") Signed-off-by: Randy Dunlap Reviewed-by: Michael Walle Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20251128062739.845403-1-rdunlap@infradead.org Signed-off-by: Bartosz Golaszewski --- include/linux/gpio/regmap.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/gpio/regmap.h b/include/linux/gpio/regmap.h index 87983a5f3681..12d154732ca9 100644 --- a/include/linux/gpio/regmap.h +++ b/include/linux/gpio/regmap.h @@ -50,8 +50,8 @@ struct regmap; * @regmap_irq_chip: (Optional) Pointer on an regmap_irq_chip structure. If * set, a regmap-irq device will be created and the IRQ * domain will be set accordingly. - * @regmap_irq_line (Optional) The IRQ the device uses to signal interrupts. - * @regmap_irq_flags (Optional) The IRQF_ flags to use for the interrupt. + * @regmap_irq_line: (Optional) The IRQ the device uses to signal interrupts. + * @regmap_irq_flags: (Optional) The IRQF_ flags to use for the interrupt. * * The ->reg_mask_xlate translates a given base address and GPIO offset to * register and mask pair. The base address is one of the given register From dae9750105cf93ac1e156ef91f4beeb53bd64777 Mon Sep 17 00:00:00 2001 From: Xi Ruoyao Date: Fri, 28 Nov 2025 15:50:32 +0800 Subject: [PATCH 80/80] gpio: loongson: Switch 2K2000/3000 GPIO to BYTE_CTRL_MODE The manuals of 2K2000 says both BIT_CTRL_MODE and BYTE_CTRL_MODE are supported but the latter is recommended. Also on 2K3000, per the ACPI DSDT the GPIO controller is compatible with 2K2000, but it fails to operate GPIOs 62 and 63 (and maybe others) using BIT_CTRL_MODE. Using BYTE_CTRL_MODE also makes those 2K3000 GPIOs work. Fixes: 3feb70a61740 ("gpio: loongson: add more gpio chip support") Cc: stable@vger.kernel.org Signed-off-by: Xi Ruoyao Reviewed-by: Huacai Chen Link: https://lore.kernel.org/r/20251128075033.255821-1-xry111@xry111.site Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-loongson-64bit.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/gpio/gpio-loongson-64bit.c b/drivers/gpio/gpio-loongson-64bit.c index d4e291b275f0..77d07e31366f 100644 --- a/drivers/gpio/gpio-loongson-64bit.c +++ b/drivers/gpio/gpio-loongson-64bit.c @@ -408,11 +408,11 @@ static const struct loongson_gpio_chip_data loongson_gpio_ls2k2000_data0 = { static const struct loongson_gpio_chip_data loongson_gpio_ls2k2000_data1 = { .label = "ls2k2000_gpio", - .mode = BIT_CTRL_MODE, - .conf_offset = 0x0, - .in_offset = 0x20, - .out_offset = 0x10, - .inten_offset = 0x30, + .mode = BYTE_CTRL_MODE, + .conf_offset = 0x800, + .in_offset = 0xa00, + .out_offset = 0x900, + .inten_offset = 0xb00, }; static const struct loongson_gpio_chip_data loongson_gpio_ls2k2000_data2 = {