From 5711ae6de0646dcf5bf90cc9d4d7cacd34896472 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Wed, 4 Feb 2026 08:43:31 -0800 Subject: [PATCH 01/79] gpio: brcmstb: Utilize irqd_to_hwirq(d) instead of d->hwirq Consistently use irqd_to_hwirq(d) which is the recommended helper to fetch the hardware IRQ number from an irq_data structure. While at it, update the brcmstb_gpio_set_imask() function signature to use the proper type for the "hwirq" argument rather than "unsigned int". Reviewed-by: Linus Walleij Reviewed-by: Andy Shevchenko Signed-off-by: Florian Fainelli Link: https://patch.msgid.link/20260204164333.1146039-2-florian.fainelli@broadcom.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-brcmstb.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpio/gpio-brcmstb.c b/drivers/gpio/gpio-brcmstb.c index 2352d099709c..4c35ed664f65 100644 --- a/drivers/gpio/gpio-brcmstb.c +++ b/drivers/gpio/gpio-brcmstb.c @@ -96,7 +96,7 @@ static int brcmstb_gpio_hwirq_to_offset(irq_hw_number_t hwirq, } static void brcmstb_gpio_set_imask(struct brcmstb_gpio_bank *bank, - unsigned int hwirq, bool enable) + irq_hw_number_t hwirq, bool enable) { struct brcmstb_gpio_priv *priv = bank->parent_priv; u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(hwirq, bank)); @@ -132,7 +132,7 @@ static void brcmstb_gpio_irq_mask(struct irq_data *d) struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct brcmstb_gpio_bank *bank = gpiochip_get_data(gc); - brcmstb_gpio_set_imask(bank, d->hwirq, false); + brcmstb_gpio_set_imask(bank, irqd_to_hwirq(d), false); } static void brcmstb_gpio_irq_unmask(struct irq_data *d) @@ -140,7 +140,7 @@ static void brcmstb_gpio_irq_unmask(struct irq_data *d) struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct brcmstb_gpio_bank *bank = gpiochip_get_data(gc); - brcmstb_gpio_set_imask(bank, d->hwirq, true); + brcmstb_gpio_set_imask(bank, irqd_to_hwirq(d), true); } static void brcmstb_gpio_irq_ack(struct irq_data *d) @@ -148,7 +148,7 @@ static void brcmstb_gpio_irq_ack(struct irq_data *d) struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct brcmstb_gpio_bank *bank = gpiochip_get_data(gc); struct brcmstb_gpio_priv *priv = bank->parent_priv; - u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(d->hwirq, bank)); + u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(irqd_to_hwirq(d), bank)); gpio_generic_write_reg(&bank->chip, priv->reg_base + GIO_STAT(bank->id), mask); @@ -159,7 +159,7 @@ static int brcmstb_gpio_irq_set_type(struct irq_data *d, unsigned int type) struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct brcmstb_gpio_bank *bank = gpiochip_get_data(gc); struct brcmstb_gpio_priv *priv = bank->parent_priv; - u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(d->hwirq, bank)); + u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(irqd_to_hwirq(d), bank)); u32 edge_insensitive, iedge_insensitive; u32 edge_config, iedge_config; u32 level, ilevel; @@ -236,7 +236,7 @@ static int brcmstb_gpio_irq_set_wake(struct irq_data *d, unsigned int enable) struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct brcmstb_gpio_bank *bank = gpiochip_get_data(gc); struct brcmstb_gpio_priv *priv = bank->parent_priv; - u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(d->hwirq, bank)); + u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(irqd_to_hwirq(d), bank)); /* * Do not do anything specific for now, suspend/resume callbacks will From 66ff5094240e6e5cea743f5e656e28734de31f1e Mon Sep 17 00:00:00 2001 From: Doug Berger Date: Wed, 4 Feb 2026 08:43:32 -0800 Subject: [PATCH 02/79] gpio: brcmstb: implement .irq_mask_ack() The .irq_mask_ack() operation is slightly more efficient than doing .irq_mask() and .irq_ack() separately. More importantly for this driver it bypasses the check of irqd_irq_masked ensuring a previously masked but still active interrupt gets remasked if unmasked at the hardware level. This allows the driver to more efficiently unmask the wake capable interrupts when quiescing without needing to enable the irqs individually to clear the irqd_irq_masked state. Reviewed-by: Linus Walleij Reviewed-by: Andy Shevchenko Signed-off-by: Doug Berger Co-developed-by: Florian Fainelli Signed-off-by: Florian Fainelli Link: https://patch.msgid.link/20260204164333.1146039-3-florian.fainelli@broadcom.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-brcmstb.c | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/drivers/gpio/gpio-brcmstb.c b/drivers/gpio/gpio-brcmstb.c index 4c35ed664f65..7ebf1217f383 100644 --- a/drivers/gpio/gpio-brcmstb.c +++ b/drivers/gpio/gpio-brcmstb.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -// Copyright (C) 2015-2017 Broadcom +// Copyright (C) 2015-2017, 2026 Broadcom #include #include @@ -95,15 +95,13 @@ static int brcmstb_gpio_hwirq_to_offset(irq_hw_number_t hwirq, return hwirq - bank->chip.gc.offset; } -static void brcmstb_gpio_set_imask(struct brcmstb_gpio_bank *bank, - irq_hw_number_t hwirq, bool enable) +static void __brcmstb_gpio_set_imask(struct brcmstb_gpio_bank *bank, + irq_hw_number_t hwirq, bool enable) { struct brcmstb_gpio_priv *priv = bank->parent_priv; u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(hwirq, bank)); u32 imask; - guard(gpio_generic_lock_irqsave)(&bank->chip); - imask = gpio_generic_read_reg(&bank->chip, priv->reg_base + GIO_MASK(bank->id)); if (enable) @@ -114,6 +112,13 @@ static void brcmstb_gpio_set_imask(struct brcmstb_gpio_bank *bank, priv->reg_base + GIO_MASK(bank->id), imask); } +static void brcmstb_gpio_set_imask(struct brcmstb_gpio_bank *bank, + irq_hw_number_t hwirq, bool enable) +{ + guard(gpio_generic_lock_irqsave)(&bank->chip); + __brcmstb_gpio_set_imask(bank, hwirq, enable); +} + static int brcmstb_gpio_to_irq(struct gpio_chip *gc, unsigned offset) { struct brcmstb_gpio_priv *priv = brcmstb_gpio_gc_to_priv(gc); @@ -135,6 +140,20 @@ static void brcmstb_gpio_irq_mask(struct irq_data *d) brcmstb_gpio_set_imask(bank, irqd_to_hwirq(d), false); } +static void brcmstb_gpio_irq_mask_ack(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct brcmstb_gpio_bank *bank = gpiochip_get_data(gc); + struct brcmstb_gpio_priv *priv = bank->parent_priv; + irq_hw_number_t hwirq = irqd_to_hwirq(d); + u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(hwirq, bank)); + + guard(gpio_generic_lock_irqsave)(&bank->chip); + __brcmstb_gpio_set_imask(bank, hwirq, false); + gpio_generic_write_reg(&bank->chip, + priv->reg_base + GIO_STAT(bank->id), mask); +} + static void brcmstb_gpio_irq_unmask(struct irq_data *d) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); @@ -471,6 +490,7 @@ static int brcmstb_gpio_irq_setup(struct platform_device *pdev, priv->irq_chip.name = dev_name(dev); priv->irq_chip.irq_disable = brcmstb_gpio_irq_mask; priv->irq_chip.irq_mask = brcmstb_gpio_irq_mask; + priv->irq_chip.irq_mask_ack = brcmstb_gpio_irq_mask_ack; priv->irq_chip.irq_unmask = brcmstb_gpio_irq_unmask; priv->irq_chip.irq_ack = brcmstb_gpio_irq_ack; priv->irq_chip.irq_set_type = brcmstb_gpio_irq_set_type; From 2c46f19c9adf7634d1eb4ce89a715ca3b0374134 Mon Sep 17 00:00:00 2001 From: Doug Berger Date: Wed, 4 Feb 2026 08:43:33 -0800 Subject: [PATCH 03/79] gpio: brcmstb: allow parent_irq to wake The classic parent_wake_irq can only occur after the system has been placed into a hardware managed power management state. This prevents its use for waking from software managed suspend states like s2idle. By allowing the parent_irq to be enabled for wake enabled GPIO during suspend, these GPIO can now be used to wake from these states. The 'suspended' boolean is introduced to support wake event accounting. Signed-off-by: Doug Berger [florian: port changes after generic gpio chip conversion] Signed-off-by: Florian Fainelli Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260204164333.1146039-4-florian.fainelli@broadcom.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-brcmstb.c | 85 +++++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 28 deletions(-) diff --git a/drivers/gpio/gpio-brcmstb.c b/drivers/gpio/gpio-brcmstb.c index 7ebf1217f383..44ca798cf832 100644 --- a/drivers/gpio/gpio-brcmstb.c +++ b/drivers/gpio/gpio-brcmstb.c @@ -54,6 +54,7 @@ struct brcmstb_gpio_priv { int parent_irq; int num_gpios; int parent_wake_irq; + bool suspended; }; #define MAX_GPIO_PER_BANK 32 @@ -240,6 +241,9 @@ static int brcmstb_gpio_priv_set_wake(struct brcmstb_gpio_priv *priv, { int ret = 0; + if (priv->parent_wake_irq == priv->parent_irq) + return ret; + if (enable) ret = enable_irq_wake(priv->parent_wake_irq); else @@ -290,6 +294,11 @@ static void brcmstb_gpio_irq_bank_handler(struct brcmstb_gpio_bank *bank) while ((status = brcmstb_gpio_get_active_irqs(bank))) { unsigned int offset; + if (priv->suspended && bank->wake_active & status) { + priv->suspended = false; + pm_wakeup_event(&priv->pdev->dev, 0); + } + for_each_set_bit(offset, &status, 32) { if (offset >= bank->width) dev_warn(&priv->pdev->dev, @@ -463,18 +472,18 @@ static int brcmstb_gpio_irq_setup(struct platform_device *pdev, } if (of_property_read_bool(np, "wakeup-source")) { + /* + * Set wakeup capability so we can process boot-time + * "wakeups" (e.g., from S5 cold boot). + */ + device_set_wakeup_capable(dev, true); + device_wakeup_enable(dev); priv->parent_wake_irq = platform_get_irq(pdev, 1); if (priv->parent_wake_irq < 0) { - priv->parent_wake_irq = 0; + priv->parent_wake_irq = priv->parent_irq; dev_warn(dev, "Couldn't get wake IRQ - GPIOs will not be able to wake from sleep"); } else { - /* - * Set wakeup capability so we can process boot-time - * "wakeups" (e.g., from S5 cold boot) - */ - device_set_wakeup_capable(dev, true); - device_wakeup_enable(dev); err = devm_request_irq(dev, priv->parent_wake_irq, brcmstb_gpio_wake_irq_handler, IRQF_SHARED, @@ -485,6 +494,7 @@ static int brcmstb_gpio_irq_setup(struct platform_device *pdev, goto out_free_domain; } } + priv->irq_chip.irq_set_wake = brcmstb_gpio_irq_set_wake; } priv->irq_chip.name = dev_name(dev); @@ -495,9 +505,6 @@ static int brcmstb_gpio_irq_setup(struct platform_device *pdev, priv->irq_chip.irq_ack = brcmstb_gpio_irq_ack; priv->irq_chip.irq_set_type = brcmstb_gpio_irq_set_type; - if (priv->parent_wake_irq) - priv->irq_chip.irq_set_wake = brcmstb_gpio_irq_set_wake; - irq_set_chained_handler_and_data(priv->parent_irq, brcmstb_gpio_irq_handler, priv); irq_set_status_flags(priv->parent_irq, IRQ_DISABLE_UNLAZY); @@ -520,16 +527,11 @@ static void brcmstb_gpio_bank_save(struct brcmstb_gpio_priv *priv, priv->reg_base + GIO_BANK_OFF(bank->id, i)); } -static void brcmstb_gpio_quiesce(struct device *dev, bool save) +static void brcmstb_gpio_quiesce(struct brcmstb_gpio_priv *priv, bool save) { - struct brcmstb_gpio_priv *priv = dev_get_drvdata(dev); struct brcmstb_gpio_bank *bank; u32 imask; - /* disable non-wake interrupt */ - if (priv->parent_irq >= 0) - disable_irq(priv->parent_irq); - list_for_each_entry(bank, &priv->bank_list, node) { if (save) brcmstb_gpio_bank_save(priv, bank); @@ -547,8 +549,13 @@ static void brcmstb_gpio_quiesce(struct device *dev, bool save) static void brcmstb_gpio_shutdown(struct platform_device *pdev) { + struct brcmstb_gpio_priv *priv = dev_get_drvdata(&pdev->dev); + + if (priv->parent_irq > 0) + disable_irq(priv->parent_irq); + /* Enable GPIO for S5 cold boot */ - brcmstb_gpio_quiesce(&pdev->dev, false); + brcmstb_gpio_quiesce(priv, false); } static void brcmstb_gpio_bank_restore(struct brcmstb_gpio_priv *priv, @@ -564,7 +571,30 @@ static void brcmstb_gpio_bank_restore(struct brcmstb_gpio_priv *priv, static int brcmstb_gpio_suspend(struct device *dev) { - brcmstb_gpio_quiesce(dev, true); + struct brcmstb_gpio_priv *priv = dev_get_drvdata(dev); + + if (priv->parent_irq > 0) + priv->suspended = true; + + return 0; +} + +static int brcmstb_gpio_suspend_noirq(struct device *dev) +{ + struct brcmstb_gpio_priv *priv = dev_get_drvdata(dev); + + /* Catch any wakeup sources occurring between suspend and noirq */ + if (!priv->suspended) + return -EBUSY; + + if (priv->parent_irq > 0) + disable_irq(priv->parent_irq); + + brcmstb_gpio_quiesce(priv, true); + + if (priv->parent_wake_irq) + enable_irq(priv->parent_irq); + return 0; } @@ -572,25 +602,24 @@ static int brcmstb_gpio_resume(struct device *dev) { struct brcmstb_gpio_priv *priv = dev_get_drvdata(dev); struct brcmstb_gpio_bank *bank; - bool need_wakeup_event = false; - list_for_each_entry(bank, &priv->bank_list, node) { - need_wakeup_event |= !!__brcmstb_gpio_get_active_irqs(bank); + if (priv->parent_wake_irq) + disable_irq(priv->parent_irq); + + priv->suspended = false; + + list_for_each_entry(bank, &priv->bank_list, node) brcmstb_gpio_bank_restore(priv, bank); - } - if (priv->parent_wake_irq && need_wakeup_event) - pm_wakeup_event(dev, 0); - - /* enable non-wake interrupt */ - if (priv->parent_irq >= 0) + if (priv->parent_irq > 0) enable_irq(priv->parent_irq); return 0; } static const struct dev_pm_ops brcmstb_gpio_pm_ops = { - .suspend_noirq = pm_sleep_ptr(brcmstb_gpio_suspend), + .suspend = pm_sleep_ptr(brcmstb_gpio_suspend), + .suspend_noirq = pm_sleep_ptr(brcmstb_gpio_suspend_noirq), .resume_noirq = pm_sleep_ptr(brcmstb_gpio_resume), }; From 96c02c906a44bc280ef26e343126dd3349d8cae9 Mon Sep 17 00:00:00 2001 From: Kent Gibson Date: Mon, 16 Feb 2026 20:37:09 +0800 Subject: [PATCH 04/79] gpio: cdev: convert lineevent_create() and linereq_create() to FD_PREPARE() linehandle_create() was recently converted to use FD_PREPARE() to simplify fd creation and cleanup logic. Apply the equivalent conversion to lineevent_create() and linereq_create(). Signed-off-by: Kent Gibson Link: https://patch.msgid.link/20260216123709.281444-1-warthog618@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-cdev.c | 141 ++++++++++++------------------------ 1 file changed, 46 insertions(+), 95 deletions(-) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index 73ae77f0f213..c919ec0d0a66 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -1586,15 +1586,16 @@ static const struct file_operations line_fileops = { #endif }; +DEFINE_FREE(linereq_free, struct linereq *, if (!IS_ERR_OR_NULL(_T)) linereq_free(_T)) + static int linereq_create(struct gpio_device *gdev, void __user *ip) { struct gpio_v2_line_request ulr; struct gpio_v2_line_config *lc; - struct linereq *lr; - struct file *file; + struct linereq *lr __free(linereq_free) = NULL; u64 flags, edflags; unsigned int i; - int fd, ret; + int ret; if (copy_from_user(&ulr, ip, sizeof(ulr))) return -EFAULT; @@ -1627,10 +1628,8 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip) /* label is only initialized if consumer is set */ lr->label = kstrndup(ulr.consumer, sizeof(ulr.consumer) - 1, GFP_KERNEL); - if (!lr->label) { - ret = -ENOMEM; - goto out_free_linereq; - } + if (!lr->label) + return -ENOMEM; } mutex_init(&lr->config_mutex); @@ -1649,14 +1648,12 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip) u32 offset = ulr.offsets[i]; struct gpio_desc *desc = gpio_device_get_desc(gdev, offset); - if (IS_ERR(desc)) { - ret = PTR_ERR(desc); - goto out_free_linereq; - } + if (IS_ERR(desc)) + return PTR_ERR(desc); ret = gpiod_request_user(desc, lr->label); if (ret) - goto out_free_linereq; + return ret; lr->lines[i].desc = desc; flags = gpio_v2_line_config_flags(lc, i); @@ -1664,7 +1661,7 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip) ret = gpiod_set_transitory(desc, false); if (ret < 0) - goto out_free_linereq; + return ret; edflags = flags & GPIO_V2_LINE_EDGE_DETECTOR_FLAGS; /* @@ -1676,16 +1673,16 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip) ret = gpiod_direction_output_nonotify(desc, val); if (ret) - goto out_free_linereq; + return ret; } else if (flags & GPIO_V2_LINE_FLAG_INPUT) { ret = gpiod_direction_input_nonotify(desc); if (ret) - goto out_free_linereq; + return ret; ret = edge_detector_setup(&lr->lines[i], lc, i, edflags); if (ret) - goto out_free_linereq; + return ret; } lr->lines[i].edflags = edflags; @@ -1700,44 +1697,25 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip) ret = blocking_notifier_chain_register(&gdev->device_notifier, &lr->device_unregistered_nb); if (ret) - goto out_free_linereq; + return ret; - fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC); - if (fd < 0) { - ret = fd; - goto out_free_linereq; - } + FD_PREPARE(fdf, O_RDONLY | O_CLOEXEC, + anon_inode_getfile("gpio-line", &line_fileops, lr, + O_RDONLY | O_CLOEXEC)); + if (fdf.err) + return fdf.err; + retain_and_null_ptr(lr); - file = anon_inode_getfile("gpio-line", &line_fileops, lr, - O_RDONLY | O_CLOEXEC); - if (IS_ERR(file)) { - ret = PTR_ERR(file); - goto out_put_unused_fd; - } - - ulr.fd = fd; - if (copy_to_user(ip, &ulr, sizeof(ulr))) { - /* - * fput() will trigger the release() callback, so do not go onto - * the regular error cleanup path here. - */ - fput(file); - put_unused_fd(fd); + ulr.fd = fd_prepare_fd(fdf); + if (copy_to_user(ip, &ulr, sizeof(ulr))) return -EFAULT; - } - fd_install(fd, file); + fd_publish(fdf); dev_dbg(&gdev->dev, "registered chardev handle for %d lines\n", - lr->num_lines); + ulr.num_lines); return 0; - -out_put_unused_fd: - put_unused_fd(fd); -out_free_linereq: - linereq_free(lr); - return ret; } #ifdef CONFIG_GPIO_CDEV_V1 @@ -2010,16 +1988,16 @@ static irqreturn_t lineevent_irq_handler(int irq, void *p) return IRQ_WAKE_THREAD; } +DEFINE_FREE(lineevent_free, struct lineevent_state *, if (!IS_ERR_OR_NULL(_T)) lineevent_free(_T)) + static int lineevent_create(struct gpio_device *gdev, void __user *ip) { struct gpioevent_request eventreq; - struct lineevent_state *le; + struct lineevent_state *le __free(lineevent_free) = NULL; struct gpio_desc *desc; - struct file *file; u32 offset; u32 lflags; u32 eflags; - int fd; int ret; int irq, irqflags = 0; char *label; @@ -2064,15 +2042,13 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip) le->label = kstrndup(eventreq.consumer_label, sizeof(eventreq.consumer_label) - 1, GFP_KERNEL); - if (!le->label) { - ret = -ENOMEM; - goto out_free_le; - } + if (!le->label) + return -ENOMEM; } ret = gpiod_request_user(desc, le->label); if (ret) - goto out_free_le; + return ret; le->desc = desc; le->eflags = eflags; @@ -2080,15 +2056,13 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip) ret = gpiod_direction_input(desc); if (ret) - goto out_free_le; + return ret; gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_REQUESTED); irq = gpiod_to_irq(desc); - if (irq <= 0) { - ret = -ENODEV; - goto out_free_le; - } + if (irq <= 0) + return -ENODEV; if (eflags & GPIOEVENT_REQUEST_RISING_EDGE) irqflags |= test_bit(GPIOD_FLAG_ACTIVE_LOW, &desc->flags) ? @@ -2105,13 +2079,11 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip) ret = blocking_notifier_chain_register(&gdev->device_notifier, &le->device_unregistered_nb); if (ret) - goto out_free_le; + return ret; label = make_irq_label(le->label); - if (IS_ERR(label)) { - ret = PTR_ERR(label); - goto out_free_le; - } + if (IS_ERR(label)) + return PTR_ERR(label); /* Request a thread to read the events */ ret = request_threaded_irq(irq, @@ -2122,46 +2094,25 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip) le); if (ret) { free_irq_label(label); - goto out_free_le; + return ret; } le->irq = irq; - fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC); - if (fd < 0) { - ret = fd; - goto out_free_le; - } + FD_PREPARE(fdf, O_RDONLY | O_CLOEXEC, + anon_inode_getfile("gpio-event", &lineevent_fileops, le, + O_RDONLY | O_CLOEXEC)); + if (fdf.err) + return fdf.err; + retain_and_null_ptr(le); - file = anon_inode_getfile("gpio-event", - &lineevent_fileops, - le, - O_RDONLY | O_CLOEXEC); - if (IS_ERR(file)) { - ret = PTR_ERR(file); - goto out_put_unused_fd; - } - - eventreq.fd = fd; - if (copy_to_user(ip, &eventreq, sizeof(eventreq))) { - /* - * fput() will trigger the release() callback, so do not go onto - * the regular error cleanup path here. - */ - fput(file); - put_unused_fd(fd); + eventreq.fd = fd_prepare_fd(fdf); + if (copy_to_user(ip, &eventreq, sizeof(eventreq))) return -EFAULT; - } - fd_install(fd, file); + fd_publish(fdf); return 0; - -out_put_unused_fd: - put_unused_fd(fd); -out_free_le: - lineevent_free(le); - return ret; } static void gpio_v2_line_info_to_v1(struct gpio_v2_line_info *info_v2, From 2423e336d94868f0d2fcd81a87b90c5ea59736e0 Mon Sep 17 00:00:00 2001 From: Prathamesh Shete Date: Tue, 17 Feb 2026 08:14:30 +0000 Subject: [PATCH 05/79] gpio: tegra186: Simplify GPIO line name prefix handling Introduce TEGRA_GPIO_PREFIX() to define the Tegra SoC GPIO name prefix in one place. Use it for the Tegra410 COMPUTE and SYSTEM controllers so the prefix is "COMPUTE-" and "SYSTEM-" respectively. Signed-off-by: Prathamesh Shete Acked-by: Thierry Reding Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260217081431.1208351-1-pshete@nvidia.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-tegra186.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/gpio/gpio-tegra186.c b/drivers/gpio/gpio-tegra186.c index 9c874f07be75..f04cc240b5ec 100644 --- a/drivers/gpio/gpio-tegra186.c +++ b/drivers/gpio/gpio-tegra186.c @@ -942,12 +942,8 @@ static int tegra186_gpio_probe(struct platform_device *pdev) char *name; for (j = 0; j < port->pins; 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); + name = devm_kasprintf(gpio->gpio.parent, GFP_KERNEL, "%sP%s.%02x", + gpio->soc->prefix ?: "", port->name, j); if (!name) return -ENOMEM; @@ -1373,6 +1369,9 @@ static const struct tegra_gpio_soc tegra256_main_soc = { .has_vm_support = true, }; +/* Macro to define GPIO name prefix with separator */ +#define TEGRA_GPIO_PREFIX(_x) _x "-" + #define TEGRA410_COMPUTE_GPIO_PORT(_name, _bank, _port, _pins) \ TEGRA_GPIO_PORT(TEGRA410_COMPUTE, _name, _bank, _port, _pins) @@ -1388,7 +1387,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", + .prefix = TEGRA_GPIO_PREFIX("COMPUTE"), .num_irqs_per_bank = 8, .instance = 0, }; @@ -1418,7 +1417,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", + .prefix = TEGRA_GPIO_PREFIX("SYSTEM"), .num_irqs_per_bank = 8, .instance = 0, }; From 2c299030c6813eaa9ef95773c64d65c50fa706ac Mon Sep 17 00:00:00 2001 From: Prathamesh Shete Date: Tue, 17 Feb 2026 08:14:31 +0000 Subject: [PATCH 06/79] gpio: tegra186: Support multi-socket devices On Tegra platforms, multiple SoC instances may be present with each defining the same GPIO name. For such devices, this results in duplicate GPIO names. When the device has a valid NUMA node, prepend the NUMA node ID to the GPIO name prefix. The node ID identifies each socket, ensuring GPIO line names remain distinct across multiple sockets. Signed-off-by: Prathamesh Shete Acked-by: Thierry Reding Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260217081431.1208351-2-pshete@nvidia.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-tegra186.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpio-tegra186.c b/drivers/gpio/gpio-tegra186.c index f04cc240b5ec..fb26402b6c47 100644 --- a/drivers/gpio/gpio-tegra186.c +++ b/drivers/gpio/gpio-tegra186.c @@ -857,7 +857,7 @@ static int tegra186_gpio_probe(struct platform_device *pdev) struct device_node *np; struct resource *res; char **names; - int err; + int node, err; gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL); if (!gpio) @@ -937,13 +937,23 @@ static int tegra186_gpio_probe(struct platform_device *pdev) if (!names) return -ENOMEM; + node = dev_to_node(&pdev->dev); + for (i = 0, offset = 0; i < gpio->soc->num_ports; i++) { const struct tegra_gpio_port *port = &gpio->soc->ports[i]; char *name; for (j = 0; j < port->pins; j++) { - name = devm_kasprintf(gpio->gpio.parent, GFP_KERNEL, "%sP%s.%02x", - gpio->soc->prefix ?: "", port->name, j); + if (node >= 0) + name = devm_kasprintf(gpio->gpio.parent, GFP_KERNEL, + "%d-%sP%s.%02x", node, + gpio->soc->prefix ?: "", + port->name, j); + else + name = devm_kasprintf(gpio->gpio.parent, GFP_KERNEL, + "%sP%s.%02x", + gpio->soc->prefix ?: "", + port->name, j); if (!name) return -ENOMEM; From c43778680546dd379b3d8219c177b1a34ba87002 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Wed, 18 Feb 2026 11:06:50 -0800 Subject: [PATCH 07/79] gpio: bd9571mwv: normalize return value of gpio_get The GPIO get callback is expected to return 0 or 1 (or a negative error code). Ensure that the value returned by bd9571mwv_gpio_get() is normalized to the [0, 1] range. Signed-off-by: Dmitry Torokhov Link: https://patch.msgid.link/20260218190657.2974723-1-dmitry.torokhov@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-bd9571mwv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-bd9571mwv.c b/drivers/gpio/gpio-bd9571mwv.c index 7c95bb36511e..cc5b1746f2fe 100644 --- a/drivers/gpio/gpio-bd9571mwv.c +++ b/drivers/gpio/gpio-bd9571mwv.c @@ -69,7 +69,7 @@ static int bd9571mwv_gpio_get(struct gpio_chip *chip, unsigned int offset) if (ret < 0) return ret; - return val & BIT(offset); + return !!(val & BIT(offset)); } static int bd9571mwv_gpio_set(struct gpio_chip *chip, unsigned int offset, From 49621f1c97788216f2f10f1a9e903f216e289f5d Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Wed, 18 Feb 2026 11:06:51 -0800 Subject: [PATCH 08/79] gpio: cgbc: normalize return value of gpio_get The GPIO get callback is expected to return 0 or 1 (or a negative error code). Ensure that the value returned by cgbc_gpio_get() is normalized to the [0, 1] range. Signed-off-by: Dmitry Torokhov Link: https://patch.msgid.link/20260218190657.2974723-2-dmitry.torokhov@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-cgbc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-cgbc.c b/drivers/gpio/gpio-cgbc.c index 0efa1b61001a..84b5ed3c6e22 100644 --- a/drivers/gpio/gpio-cgbc.c +++ b/drivers/gpio/gpio-cgbc.c @@ -47,8 +47,8 @@ static int cgbc_gpio_get(struct gpio_chip *chip, unsigned int offset) if (ret) return ret; - else - return (int)(val & (u8)BIT(offset)); + + return !!(val & BIT(offset)); } static int __cgbc_gpio_set(struct gpio_chip *chip, unsigned int offset, From 4d720b0d68e9a251d60804eace42aac800d7a79f Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Wed, 18 Feb 2026 11:06:52 -0800 Subject: [PATCH 09/79] gpio: da9055: normalize return value of gpio_get The GPIO get callback is expected to return 0 or 1 (or a negative error code). Ensure that the value returned by da9055_gpio_get() is normalized to the [0, 1] range. Signed-off-by: Dmitry Torokhov Link: https://patch.msgid.link/20260218190657.2974723-3-dmitry.torokhov@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-da9055.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-da9055.c b/drivers/gpio/gpio-da9055.c index a09bd6eb93cf..1949a6ea8ec6 100644 --- a/drivers/gpio/gpio-da9055.c +++ b/drivers/gpio/gpio-da9055.c @@ -55,7 +55,7 @@ static int da9055_gpio_get(struct gpio_chip *gc, unsigned offset) return ret; } - return ret & (1 << offset); + return !!(ret & (1 << offset)); } From 5a32ebabb6819fafce99e7bc6575ca568af6d22a Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Wed, 18 Feb 2026 11:06:53 -0800 Subject: [PATCH 10/79] gpio: lp873x: normalize return value of gpio_get The GPIO get callback is expected to return 0 or 1 (or a negative error code). Ensure that the value returned by lp873x_gpio_get() is normalized to the [0, 1] range. Signed-off-by: Dmitry Torokhov Link: https://patch.msgid.link/20260218190657.2974723-4-dmitry.torokhov@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-lp873x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-lp873x.c b/drivers/gpio/gpio-lp873x.c index 5376708a81bf..f4413fa5a811 100644 --- a/drivers/gpio/gpio-lp873x.c +++ b/drivers/gpio/gpio-lp873x.c @@ -55,7 +55,7 @@ static int lp873x_gpio_get(struct gpio_chip *chip, unsigned int offset) if (ret < 0) return ret; - return val & BIT(offset * BITS_PER_GPO); + return !!(val & BIT(offset * BITS_PER_GPO)); } static int lp873x_gpio_set(struct gpio_chip *chip, unsigned int offset, From e62b94a690c8cd7050c3d308e01ee1b24ee9bb0b Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Wed, 18 Feb 2026 11:06:54 -0800 Subject: [PATCH 11/79] gpio: stp-xway: normalize return value of gpio_get The GPIO get callback is expected to return 0 or 1 (or a negative error code). Ensure that the value returned by xway_stp_get() is normalized to the [0, 1] range. Signed-off-by: Dmitry Torokhov Link: https://patch.msgid.link/20260218190657.2974723-5-dmitry.torokhov@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-stp-xway.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-stp-xway.c b/drivers/gpio/gpio-stp-xway.c index 493c027afdd6..78d6c78d4aab 100644 --- a/drivers/gpio/gpio-stp-xway.c +++ b/drivers/gpio/gpio-stp-xway.c @@ -102,7 +102,7 @@ static int xway_stp_get(struct gpio_chip *gc, unsigned int gpio) { struct xway_stp *chip = gpiochip_get_data(gc); - return (xway_stp_r32(chip->virt, XWAY_STP_CPU0) & BIT(gpio)); + return !!(xway_stp_r32(chip->virt, XWAY_STP_CPU0) & BIT(gpio)); } /** From 9eb7ecfd20f868421e44701274896ba9e136daae Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Wed, 18 Feb 2026 11:06:55 -0800 Subject: [PATCH 12/79] gpio: tps65086: normalize return value of gpio_get The GPIO get callback is expected to return 0 or 1 (or a negative error code). Ensure that the value returned by tps65086_gpio_get() is normalized to the [0, 1] range. Signed-off-by: Dmitry Torokhov Link: https://patch.msgid.link/20260218190657.2974723-6-dmitry.torokhov@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-tps65086.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-tps65086.c b/drivers/gpio/gpio-tps65086.c index 84b17b83476f..df770ecf28bc 100644 --- a/drivers/gpio/gpio-tps65086.c +++ b/drivers/gpio/gpio-tps65086.c @@ -50,7 +50,7 @@ static int tps65086_gpio_get(struct gpio_chip *chip, unsigned offset) if (ret < 0) return ret; - return val & BIT(4 + offset); + return !!(val & BIT(4 + offset)); } static int tps65086_gpio_set(struct gpio_chip *chip, unsigned int offset, From c08381ad56a9cc111f893b2b21400ceb468cc698 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Wed, 18 Feb 2026 11:06:56 -0800 Subject: [PATCH 13/79] gpio: viperboard: normalize return value of gpio_get The GPIO get callback is expected to return 0 or 1 (or a negative error code). Ensure that the value returned by vprbrd_gpiob_get() in the output case is normalized to the [0, 1] range. Signed-off-by: Dmitry Torokhov Link: https://patch.msgid.link/20260218190657.2974723-7-dmitry.torokhov@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-viperboard.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-viperboard.c b/drivers/gpio/gpio-viperboard.c index 15e495c109d2..89087fd48a81 100644 --- a/drivers/gpio/gpio-viperboard.c +++ b/drivers/gpio/gpio-viperboard.c @@ -288,7 +288,7 @@ static int vprbrd_gpiob_get(struct gpio_chip *chip, /* if io is set to output, just return the saved value */ if (gpio->gpiob_out & (1 << offset)) - return gpio->gpiob_val & (1 << offset); + return !!(gpio->gpiob_val & (1 << offset)); mutex_lock(&vb->lock); From 0acbe817c37344f10cb413663be494ad66bf40bc Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 19 Feb 2026 14:46:47 +0100 Subject: [PATCH 14/79] gpio: ts4800: Remove duplicate code to handle 'ngpios' property The gpio_generic_chip_init() handles the 'ngpios' property and assigns the respective field in struct gpio_chip either with the value of it, or, if not found, with the default based on the register size. There is no need to repeat this in the driver. Signed-off-by: Andy Shevchenko Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260219134647.2258593-1-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-ts4800.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/drivers/gpio/gpio-ts4800.c b/drivers/gpio/gpio-ts4800.c index 992ee231db9f..0207c2b813f4 100644 --- a/drivers/gpio/gpio-ts4800.c +++ b/drivers/gpio/gpio-ts4800.c @@ -11,7 +11,6 @@ #include #include -#define DEFAULT_PIN_NUMBER 16 #define INPUT_REG_OFFSET 0x00 #define OUTPUT_REG_OFFSET 0x02 #define DIRECTION_REG_OFFSET 0x04 @@ -23,7 +22,6 @@ static int ts4800_gpio_probe(struct platform_device *pdev) struct gpio_generic_chip *chip; void __iomem *base_addr; int retval; - u32 ngpios; chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); if (!chip) @@ -33,12 +31,6 @@ static int ts4800_gpio_probe(struct platform_device *pdev) if (IS_ERR(base_addr)) return PTR_ERR(base_addr); - retval = device_property_read_u32(dev, "ngpios", &ngpios); - if (retval == -EINVAL) - ngpios = DEFAULT_PIN_NUMBER; - else if (retval) - return retval; - config = (struct gpio_generic_chip_config) { .dev = dev, .sz = 2, @@ -52,8 +44,6 @@ static int ts4800_gpio_probe(struct platform_device *pdev) return dev_err_probe(dev, retval, "failed to initialize the generic GPIO chip\n"); - chip->gc.ngpio = ngpios; - return devm_gpiochip_add_data(dev, &chip->gc, NULL); } From e8fc8588d06cf46cd7df622886e5a4be57442b65 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sun, 22 Feb 2026 18:03:27 +0100 Subject: [PATCH 15/79] gpiolib: replace snprintf("%s") with strscpy Replace snprintf("%s", ...) with strscpy() for direct string copying. Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260222170327.281576-2-thorsten.blum@linux.dev Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index 3abb90385829..8d1a762f9d11 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -107,7 +108,7 @@ extern const char *const gpio_suffixes[]; if (con_id) \ snprintf(propname, sizeof(propname), "%s-%s", con_id, __gs); \ else \ - snprintf(propname, sizeof(propname), "%s", __gs); \ + strscpy(propname, __gs); \ 1; \ }); \ __suffixes++) From b2d51bc1601c762c63f19c119589a0a0c44bc8ec Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 26 Feb 2026 10:20:23 +0100 Subject: [PATCH 16/79] gpio: generic: Don't use 'proxy' headers Update header inclusions to follow IWYU (Include What You Use) principle. Signed-off-by: Andy Shevchenko Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260226092023.4096921-1-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mmio.c | 4 +--- include/linux/gpio/generic.h | 8 +++++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpio-mmio.c b/drivers/gpio/gpio-mmio.c index edbcaad57d00..0941d034a49c 100644 --- a/drivers/gpio/gpio-mmio.c +++ b/drivers/gpio/gpio-mmio.c @@ -42,18 +42,16 @@ o ` ~~~~\___/~~~~ ` controller in FPGA is ,.` #include #include -#include #include -#include #include #include +#include #include #include #include #include #include #include -#include #include #include diff --git a/include/linux/gpio/generic.h b/include/linux/gpio/generic.h index ff566dc9c3cb..de43c06c83ef 100644 --- a/include/linux/gpio/generic.h +++ b/include/linux/gpio/generic.h @@ -3,9 +3,15 @@ #ifndef __LINUX_GPIO_GENERIC_H #define __LINUX_GPIO_GENERIC_H +#include +#include #include -#include +#include +#include #include +#include + +#include struct device; From fa4a3a95139e7293c1333a33bd7b19e7261e3bd0 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 23 Feb 2026 18:20:06 +0100 Subject: [PATCH 17/79] gpio: introduce a header for symbols shared by suppliers and consumers GPIO_LINE_DIRECTION_IN/OUT definitions are used both in supplier (GPIO controller drivers) as well as consumer code. In order to not force the consumers to include gpio/driver.h or - even worse - to redefine these values, create a new header file - gpio/defs.h - and move them over there. Include this header from both gpio/consumer.h and gpio/driver.h. Reviewed-by: Linus Walleij Suggested-by: Andy Shevchenko Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260223172006.204268-1-bartosz.golaszewski@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- include/linux/gpio/consumer.h | 2 ++ include/linux/gpio/defs.h | 9 +++++++++ include/linux/gpio/driver.h | 5 ++--- 3 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 include/linux/gpio/defs.h diff --git a/include/linux/gpio/consumer.h b/include/linux/gpio/consumer.h index 0d8408582918..3efb5cb1e1d1 100644 --- a/include/linux/gpio/consumer.h +++ b/include/linux/gpio/consumer.h @@ -6,6 +6,8 @@ #include #include +#include "defs.h" + struct acpi_device; struct device; struct fwnode_handle; diff --git a/include/linux/gpio/defs.h b/include/linux/gpio/defs.h new file mode 100644 index 000000000000..b69fd7c041b2 --- /dev/null +++ b/include/linux/gpio/defs.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __LINUX_GPIO_DEFS_H +#define __LINUX_GPIO_DEFS_H + +#define GPIO_LINE_DIRECTION_IN 1 +#define GPIO_LINE_DIRECTION_OUT 0 + +#endif /* __LINUX_GPIO_DEFS_H */ diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h index fabe2baf7b50..5f5ddcbfa445 100644 --- a/include/linux/gpio/driver.h +++ b/include/linux/gpio/driver.h @@ -20,6 +20,8 @@ #include #endif +#include "defs.h" + struct device; struct irq_chip; struct irq_data; @@ -42,9 +44,6 @@ union gpio_irq_fwspec { #endif }; -#define GPIO_LINE_DIRECTION_IN 1 -#define GPIO_LINE_DIRECTION_OUT 0 - /** * struct gpio_irq_chip - GPIO interrupt controller */ From eb58f2b9bb0909ebce64e1a90b21b5cc2c9f17df Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 26 Feb 2026 10:56:36 +0100 Subject: [PATCH 18/79] gpiolib: match secondary fwnode too in gpio_device_find_by_fwnode() In GPIOLIB, during fwnode lookup, after having resolved the consumer's reference to a specific fwnode, we only match it against the primary node of the controllers. Let's extend that to also the secondary node by reworking gpio_chip_match_by_fwnode() Suggested-by: Dmitry Torokhov Reviewed-by: Danilo Krummrich Reviewed-by: Rafael J. Wysocki (Intel) Reviewed-by: Linus Walleij Reviewed-by: Sakari Ailus Link: https://patch.msgid.link/20260226-device-match-secondary-fwnode-v4-1-27bd0cfbd8c6@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 86a171e96b0e..d85c97bc158f 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -1394,7 +1395,16 @@ EXPORT_SYMBOL_GPL(gpio_device_find_by_label); static int gpio_chip_match_by_fwnode(struct gpio_chip *gc, const void *fwnode) { - return device_match_fwnode(&gc->gpiodev->dev, fwnode); + struct device *dev = &gc->gpiodev->dev; + struct fwnode_handle *node = dev_fwnode(dev); + + if (IS_ERR(fwnode)) + return 0; + + if (device_match_fwnode(dev, fwnode)) + return 1; + + return node && node->secondary == fwnode; } /** From cc11f4ef666fbca02c8a2f11d0184d57e6b75579 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Mon, 23 Feb 2026 14:17:21 +0800 Subject: [PATCH 19/79] gpio: Access `gpio_bus_type` in gpiochip_setup_dev() To make the intent clear, access `gpio_bus_type` only when it's ready in gpiochip_setup_dev(). Reviewed-by: Linus Walleij Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260223061726.82161-2-tzungbi@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 6a94ecc0ae12..f558211ad84b 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -902,6 +902,8 @@ static int gpiochip_setup_dev(struct gpio_device *gdev) struct fwnode_handle *fwnode = dev_fwnode(&gdev->dev); int ret; + gdev->dev.bus = &gpio_bus_type; + /* * If fwnode doesn't belong to another device, it's safe to clear its * initialized flag. @@ -1083,7 +1085,6 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, * then make sure they get free():ed there. */ gdev->dev.type = &gpio_dev_type; - gdev->dev.bus = &gpio_bus_type; gdev->dev.parent = gc->parent; device_set_node(&gdev->dev, gpiochip_choose_fwnode(gc)); @@ -1221,8 +1222,8 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, * we get a device node entry in sysfs under * /sys/bus/gpio/devices/gpiochipN/dev that can be used for * coldplug of device nodes and other udev business. - * We can do this only if gpiolib has been initialized. - * Otherwise, defer until later. + * We can do this only if gpiolib has been initialized + * (i.e., `gpio_bus_type` is ready). Otherwise, defer until later. */ if (gpiolib_initialized) { ret = gpiochip_setup_dev(gdev); From 049f71131734c47a6aaca2472273aef2cd17a6d8 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Mon, 23 Feb 2026 14:17:22 +0800 Subject: [PATCH 20/79] gpio: Remove redundant check for struct gpio_chip gpiolib_dbg_show() is only called by gpiolib_seq_show() which has ensured the struct gpio_chip. Remove the redundant check in gpiolib_dbg_show(). Reviewed-by: Linus Walleij Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260223061726.82161-3-tzungbi@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index f558211ad84b..9408ecbe7913 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -5322,23 +5322,14 @@ core_initcall(gpiolib_dev_init); #ifdef CONFIG_DEBUG_FS -static void gpiolib_dbg_show(struct seq_file *s, struct gpio_device *gdev) +static void gpiolib_dbg_show(struct seq_file *s, struct gpio_chip *gc) { bool active_low, is_irq, is_out; struct gpio_desc *desc; unsigned int gpio = 0; - struct gpio_chip *gc; unsigned long flags; int value; - guard(srcu)(&gdev->srcu); - - gc = srcu_dereference(gdev->chip, &gdev->srcu); - if (!gc) { - seq_puts(s, "Underlying GPIO chip is gone\n"); - return; - } - for_each_gpio_desc(gc, desc) { guard(srcu)(&desc->gdev->desc_srcu); flags = READ_ONCE(desc->flags); @@ -5451,7 +5442,7 @@ static int gpiolib_seq_show(struct seq_file *s, void *v) if (gc->dbg_show) gc->dbg_show(s, gc); else - gpiolib_dbg_show(s, gdev); + gpiolib_dbg_show(s, gc); return 0; } From 395b8e555dfcbab6b28f360e39bd048b2f3e362b Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Mon, 23 Feb 2026 14:17:23 +0800 Subject: [PATCH 21/79] gpio: sysfs: Remove redundant check for struct gpio_chip gpiochip_sysfs_unregister() is only called by gpiochip_remove() where the struct gpio_chip is ensured. Remove the redundant check. Reviewed-by: Linus Walleij Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260223061726.82161-4-tzungbi@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-sysfs.c | 11 +++-------- drivers/gpio/gpiolib-sysfs.h | 4 ++-- drivers/gpio/gpiolib.c | 2 +- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c index 270e8060e761..1c25a7dd3db4 100644 --- a/drivers/gpio/gpiolib-sysfs.c +++ b/drivers/gpio/gpiolib-sysfs.c @@ -1053,11 +1053,11 @@ int gpiochip_sysfs_register(struct gpio_device *gdev) return 0; } -void gpiochip_sysfs_unregister(struct gpio_device *gdev) +void gpiochip_sysfs_unregister(struct gpio_chip *gc) { + struct gpio_device *gdev = gc->gpiodev; struct gpiodev_data *data; struct gpio_desc *desc; - struct gpio_chip *chip; guard(mutex)(&sysfs_lock); @@ -1065,13 +1065,8 @@ void gpiochip_sysfs_unregister(struct gpio_device *gdev) if (!data) return; - guard(srcu)(&gdev->srcu); - chip = srcu_dereference(gdev->chip, &gdev->srcu); - if (!chip) - return; - /* unregister gpiod class devices owned by sysfs */ - for_each_gpio_desc_with_flag(chip, desc, GPIOD_FLAG_SYSFS) { + for_each_gpio_desc_with_flag(gc, desc, GPIOD_FLAG_SYSFS) { gpiod_unexport_unlocked(desc); gpiod_free(desc); } diff --git a/drivers/gpio/gpiolib-sysfs.h b/drivers/gpio/gpiolib-sysfs.h index b794b396d6a5..fd5db5384681 100644 --- a/drivers/gpio/gpiolib-sysfs.h +++ b/drivers/gpio/gpiolib-sysfs.h @@ -8,7 +8,7 @@ struct gpio_device; #ifdef CONFIG_GPIO_SYSFS int gpiochip_sysfs_register(struct gpio_device *gdev); -void gpiochip_sysfs_unregister(struct gpio_device *gdev); +void gpiochip_sysfs_unregister(struct gpio_chip *gc); #else @@ -17,7 +17,7 @@ static inline int gpiochip_sysfs_register(struct gpio_device *gdev) return 0; } -static inline void gpiochip_sysfs_unregister(struct gpio_device *gdev) +static inline void gpiochip_sysfs_unregister(struct gpio_chip *gc) { } diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 9408ecbe7913..8001ec1f9799 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -1286,7 +1286,7 @@ void gpiochip_remove(struct gpio_chip *gc) struct gpio_device *gdev = gc->gpiodev; /* FIXME: should the legacy sysfs handling be moved to gpio_device? */ - gpiochip_sysfs_unregister(gdev); + gpiochip_sysfs_unregister(gc); gpiochip_free_hogs(gc); gpiochip_free_remaining_irqs(gc); From cf674f1a0c9893ee1acef832679562007a94250a Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Mon, 23 Feb 2026 14:17:24 +0800 Subject: [PATCH 22/79] gpio: Ensure struct gpio_chip for gpiochip_setup_dev() Ensure struct gpio_chip for gpiochip_setup_dev(). This eliminates a few checks for struct gpio_chip. Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260223061726.82161-5-tzungbi@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-cdev.c | 14 ++++---------- drivers/gpio/gpiolib-cdev.h | 2 +- drivers/gpio/gpiolib-sysfs.c | 21 ++++++++------------- drivers/gpio/gpiolib-sysfs.h | 4 ++-- drivers/gpio/gpiolib.c | 24 +++++++++++++++++------- 5 files changed, 32 insertions(+), 33 deletions(-) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index c919ec0d0a66..58a6e9a6ec62 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -2733,11 +2733,13 @@ static const struct file_operations gpio_fileops = { #endif }; -int gpiolib_cdev_register(struct gpio_device *gdev, dev_t devt) +int gpiolib_cdev_register(struct gpio_chip *gc, dev_t devt) { - struct gpio_chip *gc; + struct gpio_device *gdev = gc->gpiodev; int ret; + lockdep_assert_held(&gdev->srcu); + cdev_init(&gdev->chrdev, &gpio_fileops); gdev->chrdev.owner = THIS_MODULE; gdev->dev.devt = MKDEV(MAJOR(devt), gdev->id); @@ -2753,14 +2755,6 @@ int gpiolib_cdev_register(struct gpio_device *gdev, dev_t devt) return ret; } - guard(srcu)(&gdev->srcu); - gc = srcu_dereference(gdev->chip, &gdev->srcu); - if (!gc) { - cdev_device_del(&gdev->chrdev, &gdev->dev); - destroy_workqueue(gdev->line_state_wq); - return -ENODEV; - } - gpiochip_dbg(gc, "added GPIO chardev (%d:%d)\n", MAJOR(devt), gdev->id); return 0; diff --git a/drivers/gpio/gpiolib-cdev.h b/drivers/gpio/gpiolib-cdev.h index b42644cbffb8..4a9cb3335d99 100644 --- a/drivers/gpio/gpiolib-cdev.h +++ b/drivers/gpio/gpiolib-cdev.h @@ -7,7 +7,7 @@ struct gpio_device; -int gpiolib_cdev_register(struct gpio_device *gdev, dev_t devt); +int gpiolib_cdev_register(struct gpio_chip *gc, dev_t devt); void gpiolib_cdev_unregister(struct gpio_device *gdev); #endif /* GPIOLIB_CDEV_H */ diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c index 1c25a7dd3db4..748a3eb1bf35 100644 --- a/drivers/gpio/gpiolib-sysfs.c +++ b/drivers/gpio/gpiolib-sysfs.c @@ -983,13 +983,15 @@ void gpiod_unexport(struct gpio_desc *desc) } EXPORT_SYMBOL_GPL(gpiod_unexport); -int gpiochip_sysfs_register(struct gpio_device *gdev) +int gpiochip_sysfs_register(struct gpio_chip *gc) { + struct gpio_device *gdev = gc->gpiodev; struct gpiodev_data *data; - struct gpio_chip *chip; struct device *parent; int err; + lockdep_assert_held(&gdev->srcu); + /* * Many systems add gpio chips for SOC support very early, * before driver model support is available. In those cases we @@ -999,18 +1001,12 @@ int gpiochip_sysfs_register(struct gpio_device *gdev) if (!class_is_registered(&gpio_class)) return 0; - guard(srcu)(&gdev->srcu); - - chip = srcu_dereference(gdev->chip, &gdev->srcu); - if (!chip) - return -ENODEV; - /* * For sysfs backward compatibility we need to preserve this * preferred parenting to the gpio_chip parent field, if set. */ - if (chip->parent) - parent = chip->parent; + if (gc->parent) + parent = gc->parent; else parent = &gdev->dev; @@ -1029,7 +1025,7 @@ int gpiochip_sysfs_register(struct gpio_device *gdev) MKDEV(0, 0), data, gpiochip_groups, GPIOCHIP_NAME "%d", - chip->base); + gc->base); if (IS_ERR(data->cdev_base)) { err = PTR_ERR(data->cdev_base); kfree(data); @@ -1085,10 +1081,9 @@ void gpiochip_sysfs_unregister(struct gpio_chip *gc) */ static int gpiofind_sysfs_register(struct gpio_chip *gc, const void *data) { - struct gpio_device *gdev = gc->gpiodev; int ret; - ret = gpiochip_sysfs_register(gdev); + ret = gpiochip_sysfs_register(gc); if (ret) gpiochip_err(gc, "failed to register the sysfs entry: %d\n", ret); diff --git a/drivers/gpio/gpiolib-sysfs.h b/drivers/gpio/gpiolib-sysfs.h index fd5db5384681..d0998de043a2 100644 --- a/drivers/gpio/gpiolib-sysfs.h +++ b/drivers/gpio/gpiolib-sysfs.h @@ -7,12 +7,12 @@ struct gpio_device; #ifdef CONFIG_GPIO_SYSFS -int gpiochip_sysfs_register(struct gpio_device *gdev); +int gpiochip_sysfs_register(struct gpio_chip *gc); void gpiochip_sysfs_unregister(struct gpio_chip *gc); #else -static inline int gpiochip_sysfs_register(struct gpio_device *gdev) +static inline int gpiochip_sysfs_register(struct gpio_chip *gc) { return 0; } diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 8001ec1f9799..bbb96e52197c 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -882,14 +882,14 @@ static const struct device_type gpio_dev_type = { }; #ifdef CONFIG_GPIO_CDEV -#define gcdev_register(gdev, devt) gpiolib_cdev_register((gdev), (devt)) +#define gcdev_register(gc, devt) gpiolib_cdev_register((gc), (devt)) #define gcdev_unregister(gdev) gpiolib_cdev_unregister((gdev)) #else /* * gpiolib_cdev_register() indirectly calls device_add(), which is still * required even when cdev is not selected. */ -#define gcdev_register(gdev, devt) device_add(&(gdev)->dev) +#define gcdev_register(gc, devt) device_add(&(gc)->gpiodev->dev) #define gcdev_unregister(gdev) device_del(&(gdev)->dev) #endif @@ -897,8 +897,9 @@ static const struct device_type gpio_dev_type = { * An initial reference count has been held in gpiochip_add_data_with_key(). * The caller should drop the reference via gpio_device_put() on errors. */ -static int gpiochip_setup_dev(struct gpio_device *gdev) +static int gpiochip_setup_dev(struct gpio_chip *gc) { + struct gpio_device *gdev = gc->gpiodev; struct fwnode_handle *fwnode = dev_fwnode(&gdev->dev); int ret; @@ -911,11 +912,11 @@ static int gpiochip_setup_dev(struct gpio_device *gdev) if (fwnode && !fwnode->dev) fwnode_dev_initialized(fwnode, false); - ret = gcdev_register(gdev, gpio_devt); + ret = gcdev_register(gc, gpio_devt); if (ret) return ret; - ret = gpiochip_sysfs_register(gdev); + ret = gpiochip_sysfs_register(gc); if (ret) goto err_remove_device; @@ -962,13 +963,22 @@ static void machine_gpiochip_add(struct gpio_chip *gc) static void gpiochip_setup_devs(void) { struct gpio_device *gdev; + struct gpio_chip *gc; int ret; guard(srcu)(&gpio_devices_srcu); list_for_each_entry_srcu(gdev, &gpio_devices, list, srcu_read_lock_held(&gpio_devices_srcu)) { - ret = gpiochip_setup_dev(gdev); + guard(srcu)(&gdev->srcu); + + gc = srcu_dereference(gdev->chip, &gdev->srcu); + if (!gc) { + dev_err(&gdev->dev, "Underlying GPIO chip is gone\n"); + continue; + } + + ret = gpiochip_setup_dev(gc); if (ret) { gpio_device_put(gdev); dev_err(&gdev->dev, @@ -1226,7 +1236,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, * (i.e., `gpio_bus_type` is ready). Otherwise, defer until later. */ if (gpiolib_initialized) { - ret = gpiochip_setup_dev(gdev); + ret = gpiochip_setup_dev(gc); if (ret) goto err_teardown_shared; } From ee68f18d1f0d943d93070eb8cb05598b8b7f0922 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Mon, 23 Feb 2026 14:17:25 +0800 Subject: [PATCH 23/79] gpio: cdev: Don't check struct gpio_chip in gpio_chrdev_open() It's harmless even if: chrdev_open() and cdev_device_del() run at the same time, and gpio_chrdev_open() gets called after the underlying GPIO chip has gone. The subsequent file operations check the availability of struct gpio_chip anyway. Don't check struct gpio_chip in gpio_chrdev_open(). Reviewed-by: Linus Walleij Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260223061726.82161-6-tzungbi@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-cdev.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index 58a6e9a6ec62..7ebdb4993a74 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -2640,12 +2640,6 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file) struct gpio_chardev_data *cdev; int ret = -ENOMEM; - guard(srcu)(&gdev->srcu); - - /* Fail on open if the backing gpiochip is gone */ - if (!rcu_access_pointer(gdev->chip)) - return -ENODEV; - cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); if (!cdev) return -ENOMEM; From c7f92042d3f3d4f084794f5314fa10366084179c Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Mon, 23 Feb 2026 14:17:26 +0800 Subject: [PATCH 24/79] selftests: gpio: Add gpio-cdev-uaf tests Add tests for gpiolib-cdev to make sure accessing to dangling resources via the opening file descriptor won't crash the system after the underlying resource providers have gone. Reviewed-by: Linus Walleij Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260223061726.82161-7-tzungbi@kernel.org Signed-off-by: Bartosz Golaszewski --- tools/testing/selftests/gpio/Makefile | 5 +- tools/testing/selftests/gpio/gpio-cdev-uaf.c | 292 ++++++++++++++++++ tools/testing/selftests/gpio/gpio-cdev-uaf.sh | 63 ++++ 3 files changed, 358 insertions(+), 2 deletions(-) create mode 100644 tools/testing/selftests/gpio/gpio-cdev-uaf.c create mode 100755 tools/testing/selftests/gpio/gpio-cdev-uaf.sh diff --git a/tools/testing/selftests/gpio/Makefile b/tools/testing/selftests/gpio/Makefile index 7bfe315f7001..741ab21e1260 100644 --- a/tools/testing/selftests/gpio/Makefile +++ b/tools/testing/selftests/gpio/Makefile @@ -1,8 +1,9 @@ # SPDX-License-Identifier: GPL-2.0 -TEST_PROGS := gpio-mockup.sh gpio-sim.sh gpio-aggregator.sh +TEST_PROGS := gpio-mockup.sh gpio-sim.sh gpio-aggregator.sh gpio-cdev-uaf.sh TEST_FILES := gpio-mockup-sysfs.sh -TEST_GEN_PROGS_EXTENDED := gpio-mockup-cdev gpio-chip-info gpio-line-name +TEST_GEN_PROGS_EXTENDED := gpio-mockup-cdev gpio-chip-info gpio-line-name \ + gpio-cdev-uaf CFLAGS += -O2 -g -Wall $(KHDR_INCLUDES) include ../lib.mk diff --git a/tools/testing/selftests/gpio/gpio-cdev-uaf.c b/tools/testing/selftests/gpio/gpio-cdev-uaf.c new file mode 100644 index 000000000000..765d3cc4f0ef --- /dev/null +++ b/tools/testing/selftests/gpio/gpio-cdev-uaf.c @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * GPIO character device helper for UAF tests. + * + * Copyright 2026 Google LLC + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CONFIGFS_DIR "/sys/kernel/config/gpio-sim" +#define PROCFS_DIR "/proc" + +static void print_usage(void) +{ + printf("usage:\n"); + printf(" gpio-cdev-uaf [chip|handle|event|req] [poll|read|ioctl]\n"); +} + +static int _create_chip(const char *name, int create) +{ + char path[64]; + + snprintf(path, sizeof(path), CONFIGFS_DIR "/%s", name); + + if (create) + return mkdir(path, 0755); + else + return rmdir(path); +} + +static int create_chip(const char *name) +{ + return _create_chip(name, 1); +} + +static void remove_chip(const char *name) +{ + _create_chip(name, 0); +} + +static int _create_bank(const char *chip_name, const char *name, int create) +{ + char path[64]; + + snprintf(path, sizeof(path), CONFIGFS_DIR "/%s/%s", chip_name, name); + + if (create) + return mkdir(path, 0755); + else + return rmdir(path); +} + +static int create_bank(const char *chip_name, const char *name) +{ + return _create_bank(chip_name, name, 1); +} + +static void remove_bank(const char *chip_name, const char *name) +{ + _create_bank(chip_name, name, 0); +} + +static int _enable_chip(const char *name, int enable) +{ + char path[64]; + int fd, ret; + + snprintf(path, sizeof(path), CONFIGFS_DIR "/%s/live", name); + + fd = open(path, O_WRONLY); + if (fd == -1) + return fd; + + if (enable) + ret = write(fd, "1", 1); + else + ret = write(fd, "0", 1); + + close(fd); + return ret == 1 ? 0 : -1; +} + +static int enable_chip(const char *name) +{ + return _enable_chip(name, 1); +} + +static void disable_chip(const char *name) +{ + _enable_chip(name, 0); +} + +static int open_chip(const char *chip_name, const char *bank_name) +{ + char path[64], dev_name[32]; + int ret, fd; + + ret = create_chip(chip_name); + if (ret) { + fprintf(stderr, "failed to create chip\n"); + return ret; + } + + ret = create_bank(chip_name, bank_name); + if (ret) { + fprintf(stderr, "failed to create bank\n"); + goto err_remove_chip; + } + + ret = enable_chip(chip_name); + if (ret) { + fprintf(stderr, "failed to enable chip\n"); + goto err_remove_bank; + } + + snprintf(path, sizeof(path), CONFIGFS_DIR "/%s/%s/chip_name", + chip_name, bank_name); + + fd = open(path, O_RDONLY); + if (fd == -1) { + ret = fd; + fprintf(stderr, "failed to open %s\n", path); + goto err_disable_chip; + } + + ret = read(fd, dev_name, sizeof(dev_name) - 1); + close(fd); + if (ret == -1) { + fprintf(stderr, "failed to read %s\n", path); + goto err_disable_chip; + } + dev_name[ret] = '\0'; + if (ret && dev_name[ret - 1] == '\n') + dev_name[ret - 1] = '\0'; + + snprintf(path, sizeof(path), "/dev/%s", dev_name); + + fd = open(path, O_RDWR); + if (fd == -1) { + ret = fd; + fprintf(stderr, "failed to open %s\n", path); + goto err_disable_chip; + } + + return fd; +err_disable_chip: + disable_chip(chip_name); +err_remove_bank: + remove_bank(chip_name, bank_name); +err_remove_chip: + remove_chip(chip_name); + return ret; +} + +static void close_chip(const char *chip_name, const char *bank_name) +{ + disable_chip(chip_name); + remove_bank(chip_name, bank_name); + remove_chip(chip_name); +} + +static int test_poll(int fd) +{ + struct pollfd pfds; + + pfds.fd = fd; + pfds.events = POLLIN; + pfds.revents = 0; + + if (poll(&pfds, 1, 0) == -1) + return -1; + + return (pfds.revents & ~(POLLHUP | POLLERR)) ? -1 : 0; +} + +static int test_read(int fd) +{ + char data; + + if (read(fd, &data, 1) == -1 && errno == ENODEV) + return 0; + return -1; +} + +static int test_ioctl(int fd) +{ + if (ioctl(fd, 0, NULL) == -1 && errno == ENODEV) + return 0; + return -1; +} + +int main(int argc, char **argv) +{ + int cfd, fd, ret; + int (*test_func)(int); + + if (argc != 3) { + print_usage(); + return EXIT_FAILURE; + } + + if (strcmp(argv[1], "chip") == 0 || + strcmp(argv[1], "event") == 0 || + strcmp(argv[1], "req") == 0) { + if (strcmp(argv[2], "poll") && + strcmp(argv[2], "read") && + strcmp(argv[2], "ioctl")) { + fprintf(stderr, "unknown command: %s\n", argv[2]); + return EXIT_FAILURE; + } + } else if (strcmp(argv[1], "handle") == 0) { + if (strcmp(argv[2], "ioctl")) { + fprintf(stderr, "unknown command: %s\n", argv[2]); + return EXIT_FAILURE; + } + } else { + fprintf(stderr, "unknown command: %s\n", argv[1]); + return EXIT_FAILURE; + } + + if (strcmp(argv[2], "poll") == 0) + test_func = test_poll; + else if (strcmp(argv[2], "read") == 0) + test_func = test_read; + else /* strcmp(argv[2], "ioctl") == 0 */ + test_func = test_ioctl; + + cfd = open_chip("chip", "bank"); + if (cfd == -1) { + fprintf(stderr, "failed to open chip\n"); + return EXIT_FAILURE; + } + + /* Step 1: Hold a FD to the test target. */ + if (strcmp(argv[1], "chip") == 0) { + fd = cfd; + } else if (strcmp(argv[1], "handle") == 0) { + struct gpiohandle_request req = {0}; + + req.lines = 1; + if (ioctl(cfd, GPIO_GET_LINEHANDLE_IOCTL, &req) == -1) { + fprintf(stderr, "failed to get handle FD\n"); + goto err_close_chip; + } + + close(cfd); + fd = req.fd; + } else if (strcmp(argv[1], "event") == 0) { + struct gpioevent_request req = {0}; + + if (ioctl(cfd, GPIO_GET_LINEEVENT_IOCTL, &req) == -1) { + fprintf(stderr, "failed to get event FD\n"); + goto err_close_chip; + } + + close(cfd); + fd = req.fd; + } else { /* strcmp(argv[1], "req") == 0 */ + struct gpio_v2_line_request req = {0}; + + req.num_lines = 1; + if (ioctl(cfd, GPIO_V2_GET_LINE_IOCTL, &req) == -1) { + fprintf(stderr, "failed to get req FD\n"); + goto err_close_chip; + } + + close(cfd); + fd = req.fd; + } + + /* Step 2: Free the chip. */ + close_chip("chip", "bank"); + + /* Step 3: Access the dangling FD to trigger UAF. */ + ret = test_func(fd); + close(fd); + return ret ? EXIT_FAILURE : EXIT_SUCCESS; +err_close_chip: + close(cfd); + close_chip("chip", "bank"); + return EXIT_FAILURE; +} diff --git a/tools/testing/selftests/gpio/gpio-cdev-uaf.sh b/tools/testing/selftests/gpio/gpio-cdev-uaf.sh new file mode 100755 index 000000000000..6e47533019cf --- /dev/null +++ b/tools/testing/selftests/gpio/gpio-cdev-uaf.sh @@ -0,0 +1,63 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# Copyright 2026 Google LLC + +BASE_DIR=`dirname $0` +MODULE="gpio-cdev-uaf" + +fail() { + echo "$*" >&2 + echo "GPIO $MODULE test FAIL" + exit 1 +} + +skip() { + echo "$*" >&2 + echo "GPIO $MODULE test SKIP" + exit 4 +} + +# Load the gpio-sim module. This will pull in configfs if needed too. +modprobe gpio-sim || skip "unable to load the gpio-sim module" +# Make sure configfs is mounted at /sys/kernel/config. Wait a bit if needed. +for _ in `seq 5`; do + mountpoint -q /sys/kernel/config && break + mount -t configfs none /sys/kernel/config + sleep 0.1 +done +mountpoint -q /sys/kernel/config || \ + skip "configfs not mounted at /sys/kernel/config" + +echo "1. GPIO" + +echo "1.1. poll" +$BASE_DIR/gpio-cdev-uaf chip poll || fail "failed to test chip poll" +echo "1.2. read" +$BASE_DIR/gpio-cdev-uaf chip read || fail "failed to test chip read" +echo "1.3. ioctl" +$BASE_DIR/gpio-cdev-uaf chip ioctl || fail "failed to test chip ioctl" + +echo "2. linehandle" + +echo "2.1. ioctl" +$BASE_DIR/gpio-cdev-uaf handle ioctl || fail "failed to test handle ioctl" + +echo "3. lineevent" + +echo "3.1. read" +$BASE_DIR/gpio-cdev-uaf event read || fail "failed to test event read" +echo "3.2. poll" +$BASE_DIR/gpio-cdev-uaf event poll || fail "failed to test event poll" +echo "3.3. ioctl" +$BASE_DIR/gpio-cdev-uaf event ioctl || fail "failed to test event ioctl" + +echo "4. linereq" + +echo "4.1. read" +$BASE_DIR/gpio-cdev-uaf req read || fail "failed to test req read" +echo "4.2. poll" +$BASE_DIR/gpio-cdev-uaf req poll || fail "failed to test req poll" +echo "4.3. ioctl" +$BASE_DIR/gpio-cdev-uaf req ioctl || fail "failed to test req ioctl" + +echo "GPIO $MODULE test PASS" From fdfe3e72a228b74da21939c47ebd6f5ad4969d5f Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sat, 28 Feb 2026 21:14:30 +0800 Subject: [PATCH 25/79] gpio: Fix lockdep warnings in gpiolib_{cdev,sysfs}_register() A lockdep warning is reported in gpiolib-cdev driver: WARNING: drivers/gpio/gpiolib-cdev.c:2735 at gpiolib_cdev_register+0x114/0x140, CPU#1: swapper/0/1 Modules linked in: CPU: 1 UID: 0 PID: 1 Comm: swapper/0 Not tainted 7.0.0-rc1-next-20260227-00065-g6af4b9cfeded #12259 PREEMPT Hardware name: Samsung Exynos (Flattened Device Tree) Call trace: unwind_backtrace from show_stack+0x10/0x14 show_stack from dump_stack_lvl+0x68/0x88 dump_stack_lvl from __warn+0x94/0x210 __warn from warn_slowpath_fmt+0x1b0/0x1bc warn_slowpath_fmt from gpiolib_cdev_register+0x114/0x140 gpiolib_cdev_register from gpiochip_setup_dev+0x4c/0xd0 gpiochip_setup_dev from gpiochip_add_data_with_key+0x960/0xad4 gpiochip_add_data_with_key from devm_gpiochip_add_data_with_key+0x20/0x5c This is because the SRCU wasn't held in gpiolib_cdev_register() when the caller is from gpiochip_add_data_with_key() instead of gpiochip_setup_devs(). gpiochip_sysfs_register() shares the similar concern. Given that both gpiolib_cdev_register() and gpiochip_sysfs_register() are only called from gpiolib but no external users. Remove the lockdep checks to fix the warnings. Reported-by: Marek Szyprowski Closes: https://lore.kernel.org/all/506ce9b3-d492-4fce-9d02-330e411911e2@samsung.com/ Fixes: cf674f1a0c98 ("gpio: Ensure struct gpio_chip for gpiochip_setup_dev()") Signed-off-by: Tzung-Bi Shih Tested-by: Marek Szyprowski Link: https://patch.msgid.link/20260228131430.102388-1-tzungbi@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-cdev.c | 2 -- drivers/gpio/gpiolib-sysfs.c | 2 -- 2 files changed, 4 deletions(-) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index 7ebdb4993a74..f36b7c06996d 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -2732,8 +2732,6 @@ int gpiolib_cdev_register(struct gpio_chip *gc, dev_t devt) struct gpio_device *gdev = gc->gpiodev; int ret; - lockdep_assert_held(&gdev->srcu); - cdev_init(&gdev->chrdev, &gpio_fileops); gdev->chrdev.owner = THIS_MODULE; gdev->dev.devt = MKDEV(MAJOR(devt), gdev->id); diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c index 748a3eb1bf35..fc06b0c2881b 100644 --- a/drivers/gpio/gpiolib-sysfs.c +++ b/drivers/gpio/gpiolib-sysfs.c @@ -990,8 +990,6 @@ int gpiochip_sysfs_register(struct gpio_chip *gc) struct device *parent; int err; - lockdep_assert_held(&gdev->srcu); - /* * Many systems add gpio chips for SOC support very early, * before driver model support is available. In those cases we From b678676b7a0ab65ad5b4278505d6bcf706e53230 Mon Sep 17 00:00:00 2001 From: Conor Dooley Date: Tue, 3 Mar 2026 16:33:12 +0000 Subject: [PATCH 26/79] dt-bindings: gpio: mpfs-gpio: permit resets Both CoreGPIO and the hardened versions of it on mpfs and pic64gx have a reset pin. For the former, usually this is wired to a common fabric reset not managed by software and for the latter two the platform firmware takes them out of reset on first-party boards (or those using modified versions of the vendor firmware), but not all boards may take this approach. Permit providing a reset in devicetree for Linux, or other devicetree-consuming software, to use. Signed-off-by: Conor Dooley Link: https://patch.msgid.link/20260303-irate-hungry-b54cda817e42@spud Signed-off-by: Bartosz Golaszewski --- .../devicetree/bindings/gpio/microchip,mpfs-gpio.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/gpio/microchip,mpfs-gpio.yaml b/Documentation/devicetree/bindings/gpio/microchip,mpfs-gpio.yaml index 184432d24ea1..eaa254a46806 100644 --- a/Documentation/devicetree/bindings/gpio/microchip,mpfs-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/microchip,mpfs-gpio.yaml @@ -33,6 +33,9 @@ properties: clocks: maxItems: 1 + resets: + maxItems: 1 + "#gpio-cells": const: 2 From 3671411e9073cb06d726dbf10835d158e18f0796 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 5 Mar 2026 13:49:45 +0100 Subject: [PATCH 27/79] gpio: mpsse: drop redundant device reference Driver core holds a reference to the USB interface and its parent USB device while the interface is bound to a driver and there is no need to take additional references unless the structures are needed after disconnect. Drop the redundant device reference to reduce cargo culting, make it easier to spot drivers where an extra reference is needed, and reduce the risk of memory leaks when drivers fail to release it. Signed-off-by: Johan Hovold Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260305124945.10781-1-johan@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mpsse.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/drivers/gpio/gpio-mpsse.c b/drivers/gpio/gpio-mpsse.c index 12191aeb6566..a859deab2bca 100644 --- a/drivers/gpio/gpio-mpsse.c +++ b/drivers/gpio/gpio-mpsse.c @@ -548,13 +548,6 @@ static void gpio_mpsse_ida_remove(void *data) ida_free(&gpio_mpsse_ida, priv->id); } -static void gpio_mpsse_usb_put_dev(void *data) -{ - struct mpsse_priv *priv = data; - - usb_put_dev(priv->udev); -} - static int mpsse_init_valid_mask(struct gpio_chip *chip, unsigned long *valid_mask, unsigned int ngpios) @@ -598,11 +591,7 @@ static int gpio_mpsse_probe(struct usb_interface *interface, INIT_LIST_HEAD(&priv->workers); - priv->udev = usb_get_dev(interface_to_usbdev(interface)); - err = devm_add_action_or_reset(dev, gpio_mpsse_usb_put_dev, priv); - if (err) - return err; - + priv->udev = interface_to_usbdev(interface); priv->intf = interface; priv->intf_id = interface->cur_altsetting->desc.bInterfaceNumber; From 5645f805927c9bd4443e6143e487ef3ffea34aaf Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 6 Mar 2026 14:22:00 +0100 Subject: [PATCH 28/79] gpio: Document line value semantics It is not clearly documented that the GPIO driver API expect the driver to get/set the physical level of the GPIO line and the consumer API will get/set the logic level. Document this in relevant places. Reported-by: David Jander Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260306-gpio-doc-levels-v1-1-19928739e400@kernel.org Signed-off-by: Bartosz Golaszewski --- Documentation/driver-api/gpio/driver.rst | 27 ++++++++++++++++++++++++ include/linux/gpio/driver.h | 10 +++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/Documentation/driver-api/gpio/driver.rst b/Documentation/driver-api/gpio/driver.rst index 85d86f92c41b..a4f160b95089 100644 --- a/Documentation/driver-api/gpio/driver.rst +++ b/Documentation/driver-api/gpio/driver.rst @@ -87,6 +87,33 @@ atomic context on realtime kernels (inside hard IRQ handlers and similar contexts). Normally this should not be required. +GPIO level semantics +-------------------- + +The gpip_chip .get/set[_multiple]() line values are clamped to the boolean +space [0, 1], low level or high level. + +Low and high values are defined as physical low on the line in/out to the +connector such as a physical pad, pin or rail. + +The GPIO library has internal logic to handle lines that are active low, such +as indicated by overstrike or #name in a schematic, and the driver should not +try to second-guess the logic value of a line. + +The way GPIO values are handled by the consumers is that the library present +the *logical* value to the consumer. A line is *asserted* if its *logical* +value is 1, and *de-asserted* if its logical value is 0. If inversion is +required, this is handled by gpiolib and configured using hardware descriptions +such as device tree or ACPI that can clearly indicate if a line is active +high or low. + +Since electronics commonly insert inverters as driving stages or protection +buffers in front of a GPIO line it is necessary that this semantic is part +of the hardware description, so that consumers such as kernel drivers need +not worry about this, and can for example assert a RESET line tied to a GPIO +pin by setting it to logic 1 even if it is physically active low. + + GPIO electrical configuration ----------------------------- diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h index 5f5ddcbfa445..17511434ed07 100644 --- a/include/linux/gpio/driver.h +++ b/include/linux/gpio/driver.h @@ -343,11 +343,17 @@ struct gpio_irq_chip { * @direction_output: configures signal "offset" as output, returns 0 on * success or a negative error number. This can be omitted on input-only * or output-only gpio chips. - * @get: returns value for signal "offset", 0=low, 1=high, or negative error + * @get: returns value for signal "offset", 0=low, 1=high, or negative error. + * the low and high values are defined as physical low on the line + * in/out to the connector such as a physical pad, pin or rail. The GPIO + * library has internal logic to handle lines that are active low, such + * as indicated by overstrike or #name in a schematic, and the driver + * should not try to second-guess the logic value of a line. * @get_multiple: reads values for multiple signals defined by "mask" and * stores them in "bits", returns 0 on success or negative error * @set: assigns output value for signal "offset", returns 0 on success or - * negative error value + * negative error value. The output value follows the same semantic + * rules as for @get. * @set_multiple: assigns output values for multiple signals defined by * "mask", returns 0 on success or negative error value * @set_config: optional hook for all kinds of settings. Uses the same From c452588f3cb6b5c2bb6448fc347465aa2174cd7a Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 12 Feb 2026 12:09:06 +0100 Subject: [PATCH 29/79] dt-bindings: gpio: gpio-delay: Use Alexander's email Group/anonymous mailboxes are not accepted for bindings maintainers, so switch from such linux @TQ mailbox to Alexander's email. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Alexander Stein Link: https://patch.msgid.link/20260212110905.52842-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- Documentation/devicetree/bindings/gpio/gpio-delay.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/gpio/gpio-delay.yaml b/Documentation/devicetree/bindings/gpio/gpio-delay.yaml index 1cebc4058e27..b99ceff6c5f6 100644 --- a/Documentation/devicetree/bindings/gpio/gpio-delay.yaml +++ b/Documentation/devicetree/bindings/gpio/gpio-delay.yaml @@ -7,7 +7,7 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: GPIO delay controller maintainers: - - Alexander Stein + - Alexander Stein description: | This binding describes an electrical setup where setting an GPIO output From b544927d75574330b0a8a33c113556b67df56f39 Mon Sep 17 00:00:00 2001 From: Jialu Xu Date: Sat, 7 Mar 2026 11:06:22 +0800 Subject: [PATCH 30/79] nfc: s3fwrn5: convert to gpio descriptors Replace the legacy of_get_named_gpio() / gpio_request_one() / gpio_set_value() API with the descriptor-based devm_gpiod_get() / gpiod_set_value() API from , removing the dependency on . This removes the s3fwrn5_i2c_parse_dt() and s3fwrn82_uart_parse_dt() functions since devm_gpiod_get() handles both DT lookup and resource management. The gpio_en and gpio_fw_wake fields in struct phy_common are changed from int to struct gpio_desc *. Add rename quirks in gpiolib-of.c for the deprecated "s3fwrn5,en-gpios" and "s3fwrn5,fw-gpios" properties to maintain backward compatibility with old device trees. Signed-off-by: Jialu Xu Reviewed-by: Linus Walleij Link: https://patch.msgid.link/94FF47746A92BD6B+20260307030623.3495092-2-xujialu@vimux.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-of.c | 4 +++ drivers/nfc/s3fwrn5/i2c.c | 54 +++++--------------------------- drivers/nfc/s3fwrn5/phy_common.c | 11 +++---- drivers/nfc/s3fwrn5/phy_common.h | 5 +-- drivers/nfc/s3fwrn5/uart.c | 43 ++++++------------------- 5 files changed, 29 insertions(+), 88 deletions(-) diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c index ef1ac68b94b7..3bdd9af67447 100644 --- a/drivers/gpio/gpiolib-of.c +++ b/drivers/gpio/gpiolib-of.c @@ -542,6 +542,10 @@ static struct gpio_desc *of_find_gpio_rename(struct device_node *np, { "reset", "reset-n-io", "marvell,nfc-uart" }, { "reset", "reset-n-io", "mrvl,nfc-uart" }, #endif +#if IS_ENABLED(CONFIG_NFC_S3FWRN5_I2C) + { "en", "s3fwrn5,en-gpios", "samsung,s3fwrn5-i2c" }, + { "wake", "s3fwrn5,fw-gpios", "samsung,s3fwrn5-i2c" }, +#endif #if IS_ENABLED(CONFIG_PCI_LANTIQ) /* MIPS Lantiq PCI */ { "reset", "gpio-reset", "lantiq,pci-xway" }, diff --git a/drivers/nfc/s3fwrn5/i2c.c b/drivers/nfc/s3fwrn5/i2c.c index 110d086cfe5b..91b8d1445efd 100644 --- a/drivers/nfc/s3fwrn5/i2c.c +++ b/drivers/nfc/s3fwrn5/i2c.c @@ -8,10 +8,8 @@ #include #include -#include +#include #include -#include -#include #include #include @@ -146,37 +144,6 @@ out: return IRQ_HANDLED; } -static int s3fwrn5_i2c_parse_dt(struct i2c_client *client) -{ - struct s3fwrn5_i2c_phy *phy = i2c_get_clientdata(client); - struct device_node *np = client->dev.of_node; - - if (!np) - return -ENODEV; - - phy->common.gpio_en = of_get_named_gpio(np, "en-gpios", 0); - if (!gpio_is_valid(phy->common.gpio_en)) { - /* Support also deprecated property */ - phy->common.gpio_en = of_get_named_gpio(np, - "s3fwrn5,en-gpios", - 0); - if (!gpio_is_valid(phy->common.gpio_en)) - return -ENODEV; - } - - phy->common.gpio_fw_wake = of_get_named_gpio(np, "wake-gpios", 0); - if (!gpio_is_valid(phy->common.gpio_fw_wake)) { - /* Support also deprecated property */ - phy->common.gpio_fw_wake = of_get_named_gpio(np, - "s3fwrn5,fw-gpios", - 0); - if (!gpio_is_valid(phy->common.gpio_fw_wake)) - return -ENODEV; - } - - return 0; -} - static int s3fwrn5_i2c_probe(struct i2c_client *client) { struct s3fwrn5_i2c_phy *phy; @@ -193,20 +160,13 @@ static int s3fwrn5_i2c_probe(struct i2c_client *client) phy->i2c_dev = client; i2c_set_clientdata(client, phy); - ret = s3fwrn5_i2c_parse_dt(client); - if (ret < 0) - return ret; + phy->common.gpio_en = devm_gpiod_get(&client->dev, "en", GPIOD_OUT_HIGH); + if (IS_ERR(phy->common.gpio_en)) + return PTR_ERR(phy->common.gpio_en); - ret = devm_gpio_request_one(&phy->i2c_dev->dev, phy->common.gpio_en, - GPIOF_OUT_INIT_HIGH, "s3fwrn5_en"); - if (ret < 0) - return ret; - - ret = devm_gpio_request_one(&phy->i2c_dev->dev, - phy->common.gpio_fw_wake, - GPIOF_OUT_INIT_LOW, "s3fwrn5_fw_wake"); - if (ret < 0) - return ret; + phy->common.gpio_fw_wake = devm_gpiod_get(&client->dev, "wake", GPIOD_OUT_LOW); + if (IS_ERR(phy->common.gpio_fw_wake)) + return PTR_ERR(phy->common.gpio_fw_wake); /* * S3FWRN5 depends on a clock input ("XI" pin) to function properly. diff --git a/drivers/nfc/s3fwrn5/phy_common.c b/drivers/nfc/s3fwrn5/phy_common.c index deb2c039f0fd..39a60e34136c 100644 --- a/drivers/nfc/s3fwrn5/phy_common.c +++ b/drivers/nfc/s3fwrn5/phy_common.c @@ -8,7 +8,6 @@ * Bongsu Jeon */ -#include #include #include @@ -19,7 +18,7 @@ void s3fwrn5_phy_set_wake(void *phy_id, bool wake) struct phy_common *phy = phy_id; mutex_lock(&phy->mutex); - gpio_set_value(phy->gpio_fw_wake, wake); + gpiod_set_value(phy->gpio_fw_wake, wake); if (wake) msleep(S3FWRN5_EN_WAIT_TIME); mutex_unlock(&phy->mutex); @@ -33,14 +32,14 @@ bool s3fwrn5_phy_power_ctrl(struct phy_common *phy, enum s3fwrn5_mode mode) phy->mode = mode; - gpio_set_value(phy->gpio_en, 1); - gpio_set_value(phy->gpio_fw_wake, 0); + gpiod_set_value(phy->gpio_en, 1); + gpiod_set_value(phy->gpio_fw_wake, 0); if (mode == S3FWRN5_MODE_FW) - gpio_set_value(phy->gpio_fw_wake, 1); + gpiod_set_value(phy->gpio_fw_wake, 1); if (mode != S3FWRN5_MODE_COLD) { msleep(S3FWRN5_EN_WAIT_TIME); - gpio_set_value(phy->gpio_en, 0); + gpiod_set_value(phy->gpio_en, 0); msleep(S3FWRN5_EN_WAIT_TIME); } diff --git a/drivers/nfc/s3fwrn5/phy_common.h b/drivers/nfc/s3fwrn5/phy_common.h index 9cef25436bf9..871bec53dd0a 100644 --- a/drivers/nfc/s3fwrn5/phy_common.h +++ b/drivers/nfc/s3fwrn5/phy_common.h @@ -12,6 +12,7 @@ #define __NFC_S3FWRN5_PHY_COMMON_H #include +#include #include #include "s3fwrn5.h" @@ -21,8 +22,8 @@ struct phy_common { struct nci_dev *ndev; - int gpio_en; - int gpio_fw_wake; + struct gpio_desc *gpio_en; + struct gpio_desc *gpio_fw_wake; struct mutex mutex; diff --git a/drivers/nfc/s3fwrn5/uart.c b/drivers/nfc/s3fwrn5/uart.c index 9c09c10c2a46..ccfcee06ee77 100644 --- a/drivers/nfc/s3fwrn5/uart.c +++ b/drivers/nfc/s3fwrn5/uart.c @@ -10,13 +10,12 @@ #include #include +#include #include #include #include -#include #include -#include -#include +#include #include "phy_common.h" @@ -88,25 +87,6 @@ static const struct of_device_id s3fwrn82_uart_of_match[] = { }; MODULE_DEVICE_TABLE(of, s3fwrn82_uart_of_match); -static int s3fwrn82_uart_parse_dt(struct serdev_device *serdev) -{ - struct s3fwrn82_uart_phy *phy = serdev_device_get_drvdata(serdev); - struct device_node *np = serdev->dev.of_node; - - if (!np) - return -ENODEV; - - phy->common.gpio_en = of_get_named_gpio(np, "en-gpios", 0); - if (!gpio_is_valid(phy->common.gpio_en)) - return -ENODEV; - - phy->common.gpio_fw_wake = of_get_named_gpio(np, "wake-gpios", 0); - if (!gpio_is_valid(phy->common.gpio_fw_wake)) - return -ENODEV; - - return 0; -} - static int s3fwrn82_uart_probe(struct serdev_device *serdev) { struct s3fwrn82_uart_phy *phy; @@ -140,20 +120,17 @@ static int s3fwrn82_uart_probe(struct serdev_device *serdev) serdev_device_set_flow_control(serdev, false); - ret = s3fwrn82_uart_parse_dt(serdev); - if (ret < 0) + phy->common.gpio_en = devm_gpiod_get(&serdev->dev, "en", GPIOD_OUT_HIGH); + if (IS_ERR(phy->common.gpio_en)) { + ret = PTR_ERR(phy->common.gpio_en); goto err_serdev; + } - ret = devm_gpio_request_one(&phy->ser_dev->dev, phy->common.gpio_en, - GPIOF_OUT_INIT_HIGH, "s3fwrn82_en"); - if (ret < 0) - goto err_serdev; - - ret = devm_gpio_request_one(&phy->ser_dev->dev, - phy->common.gpio_fw_wake, - GPIOF_OUT_INIT_LOW, "s3fwrn82_fw_wake"); - if (ret < 0) + phy->common.gpio_fw_wake = devm_gpiod_get(&serdev->dev, "wake", GPIOD_OUT_LOW); + if (IS_ERR(phy->common.gpio_fw_wake)) { + ret = PTR_ERR(phy->common.gpio_fw_wake); goto err_serdev; + } ret = s3fwrn5_probe(&phy->common.ndev, phy, &phy->ser_dev->dev, &uart_phy_ops); From 253350dbf3e7fbd136905c98bd9f800fddb4fead Mon Sep 17 00:00:00 2001 From: Jialu Xu Date: Sat, 7 Mar 2026 11:06:24 +0800 Subject: [PATCH 31/79] nfc: nfcmrvl: convert to gpio descriptors Replace the legacy of_get_named_gpio() / gpio_request_one() / gpio_set_value() API with the descriptor-based devm_gpiod_get_optional() / gpiod_set_value() API from , removing the dependency on . The "reset-n-io" property rename quirk already exists in gpiolib-of.c (added in commit 9c2cc7171e08), so no additional quirk is needed. Signed-off-by: Jialu Xu Reviewed-by: Linus Walleij Link: https://patch.msgid.link/DD684946FD7EE161+20260307030623.3495092-4-xujialu@vimux.org Signed-off-by: Bartosz Golaszewski --- drivers/nfc/nfcmrvl/main.c | 47 ++++++++++++----------------------- drivers/nfc/nfcmrvl/nfcmrvl.h | 4 ++- drivers/nfc/nfcmrvl/uart.c | 23 +++++++++++------ drivers/nfc/nfcmrvl/usb.c | 2 +- 4 files changed, 36 insertions(+), 40 deletions(-) diff --git a/drivers/nfc/nfcmrvl/main.c b/drivers/nfc/nfcmrvl/main.c index c51d22e4579c..6efa83219117 100644 --- a/drivers/nfc/nfcmrvl/main.c +++ b/drivers/nfc/nfcmrvl/main.c @@ -6,9 +6,9 @@ */ #include -#include +#include #include -#include +#include #include #include #include @@ -112,13 +112,12 @@ struct nfcmrvl_private *nfcmrvl_nci_register_dev(enum nfcmrvl_phy phy, memcpy(&priv->config, pdata, sizeof(*pdata)); - if (gpio_is_valid(priv->config.reset_n_io)) { - rc = gpio_request_one(priv->config.reset_n_io, - GPIOF_OUT_INIT_LOW, - "nfcmrvl_reset_n"); - if (rc < 0) { - priv->config.reset_n_io = -EINVAL; - nfc_err(dev, "failed to request reset_n io\n"); + if (!priv->config.reset_gpio) { + priv->config.reset_gpio = + devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(priv->config.reset_gpio)) { + priv->config.reset_gpio = NULL; + nfc_err(dev, "failed to get reset gpio\n"); } } @@ -144,7 +143,7 @@ struct nfcmrvl_private *nfcmrvl_nci_register_dev(enum nfcmrvl_phy phy, if (!priv->ndev) { nfc_err(dev, "nci_allocate_device failed\n"); rc = -ENOMEM; - goto error_free_gpio; + goto error_free; } rc = nfcmrvl_fw_dnld_init(priv); @@ -171,9 +170,7 @@ error_fw_dnld_deinit: nfcmrvl_fw_dnld_deinit(priv); error_free_dev: nci_free_device(priv->ndev); -error_free_gpio: - if (gpio_is_valid(priv->config.reset_n_io)) - gpio_free(priv->config.reset_n_io); +error_free: kfree(priv); return ERR_PTR(rc); } @@ -189,9 +186,6 @@ void nfcmrvl_nci_unregister_dev(struct nfcmrvl_private *priv) nfcmrvl_fw_dnld_deinit(priv); - if (gpio_is_valid(priv->config.reset_n_io)) - gpio_free(priv->config.reset_n_io); - nci_free_device(ndev); kfree(priv); } @@ -233,34 +227,25 @@ void nfcmrvl_chip_reset(struct nfcmrvl_private *priv) /* Reset possible fault of previous session */ clear_bit(NFCMRVL_PHY_ERROR, &priv->flags); - if (gpio_is_valid(priv->config.reset_n_io)) { + if (priv->config.reset_gpio) { nfc_info(priv->dev, "reset the chip\n"); - gpio_set_value(priv->config.reset_n_io, 0); + gpiod_set_value(priv->config.reset_gpio, 1); usleep_range(5000, 10000); - gpio_set_value(priv->config.reset_n_io, 1); + gpiod_set_value(priv->config.reset_gpio, 0); } else nfc_info(priv->dev, "no reset available on this interface\n"); } void nfcmrvl_chip_halt(struct nfcmrvl_private *priv) { - if (gpio_is_valid(priv->config.reset_n_io)) - gpio_set_value(priv->config.reset_n_io, 0); + if (priv->config.reset_gpio) + gpiod_set_value(priv->config.reset_gpio, 1); } int nfcmrvl_parse_dt(struct device_node *node, struct nfcmrvl_platform_data *pdata) { - int reset_n_io; - - reset_n_io = of_get_named_gpio(node, "reset-n-io", 0); - if (reset_n_io < 0) { - pr_info("no reset-n-io config\n"); - } else if (!gpio_is_valid(reset_n_io)) { - pr_err("invalid reset-n-io GPIO\n"); - return reset_n_io; - } - pdata->reset_n_io = reset_n_io; + pdata->reset_gpio = NULL; pdata->hci_muxed = of_property_read_bool(node, "hci-muxed"); return 0; diff --git a/drivers/nfc/nfcmrvl/nfcmrvl.h b/drivers/nfc/nfcmrvl/nfcmrvl.h index f61a99e553db..62fa77d587b2 100644 --- a/drivers/nfc/nfcmrvl/nfcmrvl.h +++ b/drivers/nfc/nfcmrvl/nfcmrvl.h @@ -10,6 +10,8 @@ #include "fw_dnld.h" +struct gpio_desc; + /* Define private flags: */ #define NFCMRVL_NCI_RUNNING 1 #define NFCMRVL_PHY_ERROR 2 @@ -54,7 +56,7 @@ struct nfcmrvl_platform_data { */ /* GPIO that is wired to RESET_N signal */ - int reset_n_io; + struct gpio_desc *reset_gpio; /* Tell if transport is muxed in HCI one */ bool hci_muxed; diff --git a/drivers/nfc/nfcmrvl/uart.c b/drivers/nfc/nfcmrvl/uart.c index 2037cd6d4f4f..9aedd168759c 100644 --- a/drivers/nfc/nfcmrvl/uart.c +++ b/drivers/nfc/nfcmrvl/uart.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -20,7 +21,6 @@ static unsigned int hci_muxed; static unsigned int flow_control; static unsigned int break_control; -static int reset_n_io = -EINVAL; /* * NFCMRVL NCI OPS @@ -62,9 +62,11 @@ static const struct nfcmrvl_if_ops uart_ops = { }; static int nfcmrvl_uart_parse_dt(struct device_node *node, - struct nfcmrvl_platform_data *pdata) + struct nfcmrvl_platform_data *pdata, + struct device *dev) { struct device_node *matched_node; + struct gpio_desc *reset_gpio; int ret; matched_node = of_get_compatible_child(node, "marvell,nfc-uart"); @@ -84,6 +86,16 @@ static int nfcmrvl_uart_parse_dt(struct device_node *node, pdata->flow_control = of_property_read_bool(matched_node, "flow-control"); pdata->break_control = of_property_read_bool(matched_node, "break-control"); + reset_gpio = devm_fwnode_gpiod_get_optional(dev, + of_fwnode_handle(matched_node), + "reset", GPIOD_OUT_HIGH, + "nfcmrvl_reset_n"); + if (IS_ERR(reset_gpio)) { + of_node_put(matched_node); + return PTR_ERR(reset_gpio); + } + pdata->reset_gpio = reset_gpio; + of_node_put(matched_node); return 0; @@ -107,13 +119,13 @@ static int nfcmrvl_nci_uart_open(struct nci_uart *nu) */ if (dev && dev->parent && dev->parent->of_node) - if (nfcmrvl_uart_parse_dt(dev->parent->of_node, &config) == 0) + if (nfcmrvl_uart_parse_dt(dev->parent->of_node, &config, dev) == 0) pdata = &config; if (!pdata) { pr_info("No platform data / DT -> fallback to module params\n"); config.hci_muxed = hci_muxed; - config.reset_n_io = reset_n_io; + config.reset_gpio = NULL; config.flow_control = flow_control; config.break_control = break_control; pdata = &config; @@ -201,6 +213,3 @@ MODULE_PARM_DESC(break_control, "Tell if UART driver must drive break signal."); module_param(hci_muxed, uint, 0); MODULE_PARM_DESC(hci_muxed, "Tell if transport is muxed in HCI one."); - -module_param(reset_n_io, int, 0); -MODULE_PARM_DESC(reset_n_io, "GPIO that is wired to RESET_N signal."); diff --git a/drivers/nfc/nfcmrvl/usb.c b/drivers/nfc/nfcmrvl/usb.c index ea7309453096..ac62358445bf 100644 --- a/drivers/nfc/nfcmrvl/usb.c +++ b/drivers/nfc/nfcmrvl/usb.c @@ -294,7 +294,7 @@ static int nfcmrvl_probe(struct usb_interface *intf, /* No configuration for USB */ memset(&config, 0, sizeof(config)); - config.reset_n_io = -EINVAL; + config.reset_gpio = NULL; nfc_info(&udev->dev, "intf %p id %p\n", intf, id); From b6420bd5aa0c374331bad6c0fa2eb5f0f87cf5a0 Mon Sep 17 00:00:00 2001 From: Jialu Xu Date: Sat, 7 Mar 2026 11:06:26 +0800 Subject: [PATCH 32/79] gpio: remove of_get_named_gpio() and All in-tree consumers have been converted to the descriptor-based API. Remove the deprecated of_get_named_gpio() helper, delete the header, and drop the corresponding entry from MAINTAINERS. Also remove the completed TODO item for this cleanup. Signed-off-by: Jialu Xu Reviewed-by: Linus Walleij Link: https://patch.msgid.link/02ABDA1F9E3FAF1F+20260307030623.3495092-6-xujialu@vimux.org Signed-off-by: Bartosz Golaszewski --- MAINTAINERS | 1 - drivers/gpio/TODO | 28 ---------------------------- drivers/gpio/gpiolib-of.c | 27 --------------------------- include/linux/of_gpio.h | 38 -------------------------------------- 4 files changed, 94 deletions(-) delete mode 100644 include/linux/of_gpio.h diff --git a/MAINTAINERS b/MAINTAINERS index 55af015174a5..24b3f8d2a64c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10959,7 +10959,6 @@ F: drivers/gpio/ F: include/dt-bindings/gpio/ F: include/linux/gpio.h F: include/linux/gpio/ -F: include/linux/of_gpio.h K: (devm_)?gpio_(request|free|direction|get|set) K: GPIOD_FLAGS_BIT_NONEXCLUSIVE K: devm_gpiod_unhinge diff --git a/drivers/gpio/TODO b/drivers/gpio/TODO index 5acaeab029ec..7ce80fde1f17 100644 --- a/drivers/gpio/TODO +++ b/drivers/gpio/TODO @@ -58,34 +58,6 @@ Work items: ------------------------------------------------------------------------------- -Get rid of - -This header and helpers appeared at one point when there was no proper -driver infrastructure for doing simpler MMIO GPIO devices and there was -no core support for parsing device tree GPIOs from the core library with -the [devm_]gpiod_get() calls we have today that will implicitly go into -the device tree back-end. It is legacy and should not be used in new code. - -Work items: - -- Change all consumer drivers that #include to - #include and stop doing custom parsing of the - GPIO lines from the device tree. This can be tricky and often involves - changing board files, etc. - -- Pull semantics for legacy device tree (OF) GPIO lookups into - gpiolib-of.c: in some cases subsystems are doing custom flags and - lookups for polarity inversion, open drain and what not. As we now - handle this with generic OF bindings, pull all legacy handling into - gpiolib so the library API becomes narrow and deep and handle all - legacy bindings internally. (See e.g. commits 6953c57ab172, - 6a537d48461d etc) - -- Delete when all the above is complete and everything - uses or instead. - -------------------------------------------------------------------------------- - 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 3bdd9af67447..c512d735e85f 100644 --- a/drivers/gpio/gpiolib-of.c +++ b/drivers/gpio/gpiolib-of.c @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -446,32 +445,6 @@ out: return desc; } -/** - * of_get_named_gpio() - Get a GPIO number to use with GPIO API - * @np: device node to get GPIO from - * @propname: Name of property containing gpio specifier(s) - * @index: index of the GPIO - * - * **DEPRECATED** This function is deprecated and must not be used in new code. - * - * Returns: - * GPIO number to use with Linux generic GPIO API, or one of the errno - * value on the error condition. - */ -int of_get_named_gpio(const struct device_node *np, const char *propname, - int index) -{ - struct gpio_desc *desc; - - desc = of_get_named_gpiod_flags(np, propname, index, NULL); - - if (IS_ERR(desc)) - return PTR_ERR(desc); - else - return desc_to_gpio(desc); -} -EXPORT_SYMBOL_GPL(of_get_named_gpio); - /* Converts gpio_lookup_flags into bitmask of GPIO_* values */ static unsigned long of_convert_gpio_flags(enum of_gpio_flags flags) { diff --git a/include/linux/of_gpio.h b/include/linux/of_gpio.h deleted file mode 100644 index d0f66a5e1b2a..000000000000 --- a/include/linux/of_gpio.h +++ /dev/null @@ -1,38 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * OF helpers for the GPIO API - * - * Copyright (c) 2007-2008 MontaVista Software, Inc. - * - * Author: Anton Vorontsov - */ - -#ifndef __LINUX_OF_GPIO_H -#define __LINUX_OF_GPIO_H - -#include -#include -#include /* FIXME: Shouldn't be here */ -#include - -struct device_node; - -#ifdef CONFIG_OF_GPIO - -extern int of_get_named_gpio(const struct device_node *np, - const char *list_name, int index); - -#else /* CONFIG_OF_GPIO */ - -#include - -/* Drivers may not strictly depend on the GPIO support, so let them link. */ -static inline int of_get_named_gpio(const struct device_node *np, - const char *propname, int index) -{ - return -ENOSYS; -} - -#endif /* CONFIG_OF_GPIO */ - -#endif /* __LINUX_OF_GPIO_H */ From 49944d6ab7eb951f2aefee69341c623e13434863 Mon Sep 17 00:00:00 2001 From: Rustam Adilov Date: Thu, 5 Mar 2026 21:11:05 +0500 Subject: [PATCH 33/79] dt-bindings: gpio: realtek-otto: add rtl9607 compatible Add the "realtek,rtl9607-gpio" compatible for GPIO nodes on the RTL9607C SoC series. Signed-off-by: Rustam Adilov Reviewed-by: Linus Walleij Reviewed-by: Sander Vanheule Link: https://patch.msgid.link/20260305161106.15999-2-adilov@disroot.org Signed-off-by: Bartosz Golaszewski --- Documentation/devicetree/bindings/gpio/realtek,otto-gpio.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/gpio/realtek,otto-gpio.yaml b/Documentation/devicetree/bindings/gpio/realtek,otto-gpio.yaml index 728099c65824..b18f8f0ca0ae 100644 --- a/Documentation/devicetree/bindings/gpio/realtek,otto-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/realtek,otto-gpio.yaml @@ -30,6 +30,7 @@ properties: - realtek,rtl8390-gpio - realtek,rtl9300-gpio - realtek,rtl9310-gpio + - realtek,rtl9607-gpio - const: realtek,otto-gpio reg: true From 8f0aecf2957e7dba78603544368846133bf6d22e Mon Sep 17 00:00:00 2001 From: Rustam Adilov Date: Thu, 5 Mar 2026 21:11:06 +0500 Subject: [PATCH 34/79] gpio: realtek-otto: add rtl9607 support The RTL9607C SoC has support for 3 GPIO banks with 32 GPIOs each and the port order is reversed just like in RTL930x. Signed-off-by: Rustam Adilov Reviewed-by: Sander Vanheule Link: https://patch.msgid.link/20260305161106.15999-3-adilov@disroot.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-realtek-otto.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpio/gpio-realtek-otto.c b/drivers/gpio/gpio-realtek-otto.c index 4cf91528f547..5e3152c2e51a 100644 --- a/drivers/gpio/gpio-realtek-otto.c +++ b/drivers/gpio/gpio-realtek-otto.c @@ -351,6 +351,10 @@ static const struct of_device_id realtek_gpio_of_match[] = { { .compatible = "realtek,rtl9310-gpio", }, + { + .compatible = "realtek,rtl9607-gpio", + .data = (void *)GPIO_PORTS_REVERSED, + }, {} }; MODULE_DEVICE_TABLE(of, realtek_gpio_of_match); From 0258fe8721f541bbd3949cac2f4971b98e1fe4ed Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Tue, 10 Mar 2026 17:34:31 -0700 Subject: [PATCH 35/79] gpio: bcm-kona: reduce the number of memory allocations Simplify allocation by using a flexible array member. Use __counted_by for extra runtime analysis. Shuffle some code as __counted_by requires the counting variable to be set right after allocation. Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260311003431.31881-1-rosenp@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-bcm-kona.c | 37 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/drivers/gpio/gpio-bcm-kona.c b/drivers/gpio/gpio-bcm-kona.c index 208b71c59d58..b1d32d590cf8 100644 --- a/drivers/gpio/gpio-bcm-kona.c +++ b/drivers/gpio/gpio-bcm-kona.c @@ -58,15 +58,6 @@ #define LOCK_CODE 0xffffffff #define UNLOCK_CODE 0x00000000 -struct bcm_kona_gpio { - void __iomem *reg_base; - int num_bank; - raw_spinlock_t lock; - struct gpio_chip gpio_chip; - struct irq_domain *irq_domain; - struct bcm_kona_gpio_bank *banks; -}; - struct bcm_kona_gpio_bank { int id; int irq; @@ -90,6 +81,15 @@ struct bcm_kona_gpio_bank { struct bcm_kona_gpio *kona_gpio; }; +struct bcm_kona_gpio { + void __iomem *reg_base; + int num_bank; + raw_spinlock_t lock; + struct gpio_chip gpio_chip; + struct irq_domain *irq_domain; + struct bcm_kona_gpio_bank banks[] __counted_by(num_bank); +}; + static inline void bcm_kona_gpio_write_lock_regs(void __iomem *reg_base, int bank_id, u32 lockcode) { @@ -584,12 +584,6 @@ static int bcm_kona_gpio_probe(struct platform_device *pdev) int ret; int i; - kona_gpio = devm_kzalloc(dev, sizeof(*kona_gpio), GFP_KERNEL); - if (!kona_gpio) - return -ENOMEM; - - kona_gpio->gpio_chip = template_chip; - chip = &kona_gpio->gpio_chip; ret = platform_irq_count(pdev); if (!ret) { dev_err(dev, "Couldn't determine # GPIO banks\n"); @@ -597,6 +591,11 @@ static int bcm_kona_gpio_probe(struct platform_device *pdev) } else if (ret < 0) { return dev_err_probe(dev, ret, "Couldn't determine GPIO banks\n"); } + + kona_gpio = devm_kzalloc(dev, struct_size(kona_gpio, banks, ret), GFP_KERNEL); + if (!kona_gpio) + return -ENOMEM; + kona_gpio->num_bank = ret; if (kona_gpio->num_bank > GPIO_MAX_BANK_NUM) { @@ -604,13 +603,9 @@ static int bcm_kona_gpio_probe(struct platform_device *pdev) GPIO_MAX_BANK_NUM); return -ENXIO; } - kona_gpio->banks = devm_kcalloc(dev, - kona_gpio->num_bank, - sizeof(*kona_gpio->banks), - GFP_KERNEL); - if (!kona_gpio->banks) - return -ENOMEM; + kona_gpio->gpio_chip = template_chip; + chip = &kona_gpio->gpio_chip; chip->parent = dev; chip->ngpio = kona_gpio->num_bank * GPIO_PER_BANK; From 223d9a310c7bd785f08320de8d2b66a5af6a25e6 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 9 Mar 2026 15:52:04 -0700 Subject: [PATCH 36/79] gpio: htc-egpio: allocate irq with the main struct Use a flexible array member to combinwe allocations. Add __counted_by for extra runtime analysis. Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260309225204.44789-1-rosenp@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-htc-egpio.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/drivers/gpio/gpio-htc-egpio.c b/drivers/gpio/gpio-htc-egpio.c index 72935d6dbebf..d15423c718d0 100644 --- a/drivers/gpio/gpio-htc-egpio.c +++ b/drivers/gpio/gpio-htc-egpio.c @@ -46,8 +46,8 @@ struct egpio_info { uint chained_irq; /* egpio info */ - struct egpio_chip *chip; int nchips; + struct egpio_chip chip[] __counted_by(nchips); }; static inline void egpio_writew(u16 value, struct egpio_info *ei, int reg) @@ -270,10 +270,12 @@ static int __init egpio_probe(struct platform_device *pdev) int i; /* Initialize ei data structure. */ - ei = devm_kzalloc(&pdev->dev, sizeof(*ei), GFP_KERNEL); + ei = devm_kzalloc(&pdev->dev, struct_size(ei, chip, pdata->num_chips), GFP_KERNEL); if (!ei) return -ENOMEM; + ei->nchips = pdata->num_chips; + spin_lock_init(&ei->lock); /* Find chained irq */ @@ -302,13 +304,6 @@ static int __init egpio_probe(struct platform_device *pdev) platform_set_drvdata(pdev, ei); - ei->nchips = pdata->num_chips; - ei->chip = devm_kcalloc(&pdev->dev, - ei->nchips, sizeof(struct egpio_chip), - GFP_KERNEL); - if (!ei->chip) - return -ENOMEM; - for (i = 0; i < ei->nchips; i++) { ei->chip[i].reg_start = pdata->chip[i].reg_start; ei->chip[i].cached_values = pdata->chip[i].initial_values; From b4784adfe3aab3e74b5f7556834d87e416b666d0 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 9 Mar 2026 16:28:04 -0700 Subject: [PATCH 37/79] gpio: tegra186: allocate irqs with the main struct Remove an extra kcalloc call by using a flexible array member. Add __counted_by for extra runtime analysis. Assign counting variable immediately after allocation as required by __counted_by. Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260309232804.331882-1-rosenp@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-tegra186.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/drivers/gpio/gpio-tegra186.c b/drivers/gpio/gpio-tegra186.c index fb26402b6c47..aa7c3e44234f 100644 --- a/drivers/gpio/gpio-tegra186.c +++ b/drivers/gpio/gpio-tegra186.c @@ -125,7 +125,6 @@ struct tegra_gpio_soc { struct tegra_gpio { struct gpio_chip gpio; unsigned int num_irq; - unsigned int *irq; const struct tegra_gpio_soc *soc; unsigned int num_irqs_per_bank; @@ -133,6 +132,8 @@ struct tegra_gpio { void __iomem *secure; void __iomem *base; + + unsigned int irq[] __counted_by(num_irq); }; static const struct tegra_gpio_port * @@ -859,10 +860,16 @@ static int tegra186_gpio_probe(struct platform_device *pdev) char **names; int node, err; - gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL); + err = platform_irq_count(pdev); + if (err < 0) + return err; + + gpio = devm_kzalloc(&pdev->dev, struct_size(gpio, irq, err), GFP_KERNEL); if (!gpio) return -ENOMEM; + gpio->num_irq = err; + gpio->soc = device_get_match_data(&pdev->dev); gpio->gpio.label = gpio->soc->name; gpio->gpio.parent = &pdev->dev; @@ -889,21 +896,10 @@ static int tegra186_gpio_probe(struct platform_device *pdev) if (IS_ERR(gpio->base)) return PTR_ERR(gpio->base); - err = platform_irq_count(pdev); - if (err < 0) - return err; - - gpio->num_irq = err; - err = tegra186_gpio_irqs_per_bank(gpio); if (err < 0) return err; - gpio->irq = devm_kcalloc(&pdev->dev, gpio->num_irq, sizeof(*gpio->irq), - GFP_KERNEL); - if (!gpio->irq) - return -ENOMEM; - for (i = 0; i < gpio->num_irq; i++) { err = platform_get_irq(pdev, i); if (err < 0) From d2cd20f7c2a4e4bf4fca844c01e925b112c5a2c5 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 4 Mar 2026 10:02:23 +0100 Subject: [PATCH 38/79] arc: axs10x: drop unneeded dependency on OF_GPIO OF_GPIO is automatically enabled on all OF systems. There's no need to select it explicitly. Acked-by: Vineet Gupta Link: https://patch.msgid.link/20260304-gpio-of-kconfig-v1-2-d597916e79e7@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- arch/arc/plat-axs10x/Kconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/arc/plat-axs10x/Kconfig b/arch/arc/plat-axs10x/Kconfig index b9652c69d1b9..40f2a74d404a 100644 --- a/arch/arc/plat-axs10x/Kconfig +++ b/arch/arc/plat-axs10x/Kconfig @@ -7,7 +7,6 @@ menuconfig ARC_PLAT_AXS10X bool "Synopsys ARC AXS10x Software Development Platforms" select DW_APB_ICTL select GPIO_DWAPB - select OF_GPIO select HAVE_PCI select GENERIC_IRQ_CHIP select GPIOLIB From dd1cdfb20e44e295512080dea508771b6a1f1c0a Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 4 Mar 2026 10:02:24 +0100 Subject: [PATCH 39/79] powerpc: drop unneeded dependency on OF_GPIO OF_GPIO is automatically enabled on all OF systems. There's no need to select it explicitly. Reviewed-by: Christophe Leroy (CS GROUP) Link: https://patch.msgid.link/20260304-gpio-of-kconfig-v1-3-d597916e79e7@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- arch/powerpc/platforms/85xx/Kconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/powerpc/platforms/85xx/Kconfig b/arch/powerpc/platforms/85xx/Kconfig index 604c1b4b6d45..6805c19ac8a9 100644 --- a/arch/powerpc/platforms/85xx/Kconfig +++ b/arch/powerpc/platforms/85xx/Kconfig @@ -217,7 +217,6 @@ config GE_IMP3A config SGY_CTS1000 tristate "Servergy CTS-1000 support" select GPIOLIB - select OF_GPIO depends on CORENET_GENERIC help Enable this to support functionality in Servergy's CTS-1000 systems. From bf017304fce10933f18fafe140bf749fb9672058 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 4 Mar 2026 10:02:26 +0100 Subject: [PATCH 40/79] regulator: drop unneeded dependencies on OF_GPIO OF_GPIO is selected automatically on all OF systems. Any symbols it controls also provide stubs so there's really no reason to select it explicitly. Acked-by: Mark Brown Link: https://patch.msgid.link/20260304-gpio-of-kconfig-v1-5-d597916e79e7@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/regulator/Kconfig | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index a708fc63f581..c1a5a516ce0a 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -1232,7 +1232,6 @@ config REGULATOR_RASPBERRYPI_TOUCHSCREEN_ATTINY depends on ARM || ARM64 || COMPILE_TEST depends on BACKLIGHT_CLASS_DEVICE depends on I2C - depends on OF_GPIO select REGMAP_I2C help This driver supports ATTINY regulator on the Raspberry Pi 7-inch @@ -1332,7 +1331,6 @@ config REGULATOR_RT5133 depends on I2C && GPIOLIB && OF select REGMAP select CRC8 - select OF_GPIO help This driver adds support for RT5133 PMIC regulators. RT5133 is an integrated chip. It includes 8 LDOs and 3 GPOs that From bbee90e750262bfb406d66dc65c46d616d2b6673 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 9 Mar 2026 13:42:37 +0100 Subject: [PATCH 41/79] gpio: of: clear OF_POPULATED on hog nodes in remove path The previously set OF_POPULATED flag should be cleared on the hog nodes when removing the chip. Cc: stable@vger.kernel.org Fixes: 63636d956c455 ("gpio: of: Add DT overlay support for GPIO hogs") Acked-by: Linus Walleij Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260309-gpio-hog-fwnode-v2-1-4e61f3dbf06a@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-of.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c index c512d735e85f..b5610a847e72 100644 --- a/drivers/gpio/gpiolib-of.c +++ b/drivers/gpio/gpiolib-of.c @@ -1187,7 +1187,14 @@ int of_gpiochip_add(struct gpio_chip *chip) void of_gpiochip_remove(struct gpio_chip *chip) { - of_node_put(dev_of_node(&chip->gpiodev->dev)); + struct device_node *np = dev_of_node(&chip->gpiodev->dev); + + for_each_child_of_node_scoped(np, child) { + if (of_property_present(child, "gpio-hog")) + of_node_clear_flag(child, OF_POPULATED); + } + + of_node_put(np); } bool of_gpiochip_instance_match(struct gpio_chip *gc, unsigned int index) From d1d564ec4992945db853303dc2978256bce8c0b4 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 9 Mar 2026 13:42:38 +0100 Subject: [PATCH 42/79] gpio: move hogs into GPIO core Refactor line hogging code by moving the parts duplicated in gpiolib-acpi-core.c and gpiolib-of.c into gpiolib.c, leaving just the OF-specific bits in the latter. This makes fwnode the primary API for setting up hogs and allows to use software nodes in addition to ACPI and OF nodes. Reviewed-by: Mika Westerberg Reviewed-by: Linus Walleij Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260309-gpio-hog-fwnode-v2-2-4e61f3dbf06a@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-acpi-core.c | 70 --------------- drivers/gpio/gpiolib-of.c | 143 ++++--------------------------- drivers/gpio/gpiolib-of.h | 10 +++ drivers/gpio/gpiolib.c | 98 ++++++++++++++++++++- drivers/gpio/gpiolib.h | 3 + 5 files changed, 125 insertions(+), 199 deletions(-) diff --git a/drivers/gpio/gpiolib-acpi-core.c b/drivers/gpio/gpiolib-acpi-core.c index ced6375d1bad..09f860200a05 100644 --- a/drivers/gpio/gpiolib-acpi-core.c +++ b/drivers/gpio/gpiolib-acpi-core.c @@ -1220,75 +1220,6 @@ static void acpi_gpiochip_free_regions(struct acpi_gpio_chip *achip) } } -static struct gpio_desc * -acpi_gpiochip_parse_own_gpio(struct acpi_gpio_chip *achip, - struct fwnode_handle *fwnode, - const char **name, - unsigned long *lflags, - enum gpiod_flags *dflags) -{ - struct gpio_chip *chip = achip->chip; - struct gpio_desc *desc; - u32 gpios[2]; - int ret; - - *lflags = GPIO_LOOKUP_FLAGS_DEFAULT; - *dflags = GPIOD_ASIS; - *name = NULL; - - ret = fwnode_property_read_u32_array(fwnode, "gpios", gpios, - ARRAY_SIZE(gpios)); - if (ret < 0) - return ERR_PTR(ret); - - desc = gpiochip_get_desc(chip, gpios[0]); - if (IS_ERR(desc)) - return desc; - - if (gpios[1]) - *lflags |= GPIO_ACTIVE_LOW; - - if (fwnode_property_present(fwnode, "input")) - *dflags |= GPIOD_IN; - else if (fwnode_property_present(fwnode, "output-low")) - *dflags |= GPIOD_OUT_LOW; - else if (fwnode_property_present(fwnode, "output-high")) - *dflags |= GPIOD_OUT_HIGH; - else - return ERR_PTR(-EINVAL); - - fwnode_property_read_string(fwnode, "line-name", name); - - return desc; -} - -static void acpi_gpiochip_scan_gpios(struct acpi_gpio_chip *achip) -{ - struct gpio_chip *chip = achip->chip; - - device_for_each_child_node_scoped(chip->parent, fwnode) { - unsigned long lflags; - enum gpiod_flags dflags; - struct gpio_desc *desc; - const char *name; - int ret; - - if (!fwnode_property_present(fwnode, "gpio-hog")) - continue; - - desc = acpi_gpiochip_parse_own_gpio(achip, fwnode, &name, - &lflags, &dflags); - if (IS_ERR(desc)) - continue; - - ret = gpiod_hog(desc, name, lflags, dflags); - if (ret) { - dev_err(chip->parent, "Failed to hog GPIO\n"); - return; - } - } -} - void acpi_gpiochip_add(struct gpio_chip *chip) { struct acpi_gpio_chip *acpi_gpio; @@ -1321,7 +1252,6 @@ void acpi_gpiochip_add(struct gpio_chip *chip) } acpi_gpiochip_request_regions(acpi_gpio); - acpi_gpiochip_scan_gpios(acpi_gpio); acpi_dev_clear_dependencies(adev); } diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c index b5610a847e72..2c923d17541f 100644 --- a/drivers/gpio/gpiolib-of.c +++ b/drivers/gpio/gpiolib-of.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -712,139 +713,26 @@ struct gpio_desc *of_find_gpio(struct device_node *np, const char *con_id, return desc; } -/** - * of_parse_own_gpio() - Get a GPIO hog descriptor, names and flags for GPIO API - * @np: device node to get GPIO from - * @chip: GPIO chip whose hog is parsed - * @idx: Index of the GPIO to parse - * @name: GPIO line name - * @lflags: bitmask of gpio_lookup_flags GPIO_* values - returned from - * of_find_gpio() or of_parse_own_gpio() - * @dflags: gpiod_flags - optional GPIO initialization flags - * - * Returns: - * GPIO descriptor to use with Linux GPIO API, or one of the errno - * value on the error condition. - */ -static struct gpio_desc *of_parse_own_gpio(struct device_node *np, - struct gpio_chip *chip, - unsigned int idx, const char **name, - unsigned long *lflags, - enum gpiod_flags *dflags) +int of_gpiochip_get_lflags(struct gpio_chip *chip, + struct fwnode_reference_args *gpiospec, + unsigned long *lflags) { - struct device_node *chip_np; enum of_gpio_flags xlate_flags; - struct of_phandle_args gpiospec; + struct of_phandle_args args; struct gpio_desc *desc; - unsigned int i; - u32 tmp; - int ret; - chip_np = dev_of_node(&chip->gpiodev->dev); - if (!chip_np) - return ERR_PTR(-EINVAL); + args.np = to_of_node(gpiospec->fwnode); + args.args_count = gpiospec->nargs; - xlate_flags = 0; - *lflags = GPIO_LOOKUP_FLAGS_DEFAULT; - *dflags = GPIOD_ASIS; + for (int i = 0; i < args.args_count; i++) + args.args[i] = gpiospec->args[i]; - ret = of_property_read_u32(chip_np, "#gpio-cells", &tmp); - if (ret) - return ERR_PTR(ret); - - gpiospec.np = chip_np; - gpiospec.args_count = tmp; - - for (i = 0; i < tmp; i++) { - ret = of_property_read_u32_index(np, "gpios", idx * tmp + i, - &gpiospec.args[i]); - if (ret) - return ERR_PTR(ret); - } - - desc = of_xlate_and_get_gpiod_flags(chip, &gpiospec, &xlate_flags); + desc = of_xlate_and_get_gpiod_flags(chip, &args, &xlate_flags); if (IS_ERR(desc)) - return desc; + return PTR_ERR(desc); *lflags = of_convert_gpio_flags(xlate_flags); - if (of_property_read_bool(np, "input")) - *dflags |= GPIOD_IN; - else if (of_property_read_bool(np, "output-low")) - *dflags |= GPIOD_OUT_LOW; - else if (of_property_read_bool(np, "output-high")) - *dflags |= GPIOD_OUT_HIGH; - else { - pr_warn("GPIO line %d (%pOFn): no hogging state specified, bailing out\n", - desc_to_gpio(desc), np); - return ERR_PTR(-EINVAL); - } - - if (name && of_property_read_string(np, "line-name", name)) - *name = np->name; - - return desc; -} - -/** - * of_gpiochip_add_hog - Add all hogs in a hog device node - * @chip: gpio chip to act on - * @hog: device node describing the hogs - * - * Returns: - * 0 on success, or negative errno on failure. - */ -static int of_gpiochip_add_hog(struct gpio_chip *chip, struct device_node *hog) -{ - enum gpiod_flags dflags; - struct gpio_desc *desc; - unsigned long lflags; - const char *name; - unsigned int i; - int ret; - - for (i = 0;; i++) { - desc = of_parse_own_gpio(hog, chip, i, &name, &lflags, &dflags); - if (IS_ERR(desc)) - break; - - ret = gpiod_hog(desc, name, lflags, dflags); - if (ret < 0) - return ret; - -#ifdef CONFIG_OF_DYNAMIC - WRITE_ONCE(desc->hog, hog); -#endif - } - - return 0; -} - -/** - * of_gpiochip_scan_gpios - Scan gpio-controller for gpio definitions - * @chip: gpio chip to act on - * - * This is only used by of_gpiochip_add to request/set GPIO initial - * configuration. - * - * Returns: - * 0 on success, or negative errno on failure. - */ -static int of_gpiochip_scan_gpios(struct gpio_chip *chip) -{ - int ret; - - for_each_available_child_of_node_scoped(dev_of_node(&chip->gpiodev->dev), np) { - if (!of_property_read_bool(np, "gpio-hog")) - continue; - - ret = of_gpiochip_add_hog(chip, np); - if (ret < 0) - return ret; - - of_node_set_flag(np, OF_POPULATED); - } - return 0; } @@ -899,7 +787,7 @@ static int of_gpio_notify(struct notifier_block *nb, unsigned long action, if (!gdev) return NOTIFY_DONE; /* not for us */ - ret = of_gpiochip_add_hog(gpio_device_get_chip(gdev), rd->dn); + ret = gpiochip_add_hog(gpio_device_get_chip(gdev), of_fwnode_handle(rd->dn)); if (ret < 0) { pr_err("%s: failed to add hogs for %pOF\n", __func__, rd->dn); @@ -1178,9 +1066,10 @@ int of_gpiochip_add(struct gpio_chip *chip) of_node_get(np); - ret = of_gpiochip_scan_gpios(chip); - if (ret) - of_node_put(np); + for_each_available_child_of_node_scoped(np, child) { + if (of_property_read_bool(child, "gpio-hog")) + of_node_set_flag(child, OF_POPULATED); + } return ret; } diff --git a/drivers/gpio/gpiolib-of.h b/drivers/gpio/gpiolib-of.h index 2257f7a498a1..218cfe5bc4ac 100644 --- a/drivers/gpio/gpiolib-of.h +++ b/drivers/gpio/gpiolib-of.h @@ -10,6 +10,7 @@ struct device_node; struct fwnode_handle; +struct fwnode_reference_args; struct gpio_chip; struct gpio_desc; @@ -24,6 +25,9 @@ int of_gpiochip_add(struct gpio_chip *gc); void of_gpiochip_remove(struct gpio_chip *gc); bool of_gpiochip_instance_match(struct gpio_chip *gc, unsigned int index); int of_gpio_count(const struct fwnode_handle *fwnode, const char *con_id); +int of_gpiochip_get_lflags(struct gpio_chip *chip, + struct fwnode_reference_args *gpiospec, + unsigned long *lflags); #else static inline struct gpio_desc *of_find_gpio(struct device_node *np, const char *con_id, @@ -44,6 +48,12 @@ static inline int of_gpio_count(const struct fwnode_handle *fwnode, { return 0; } +static inline int of_gpiochip_get_lflags(struct gpio_chip *chip, + struct fwnode_reference_args *gpiospec, + unsigned long *lflags) +{ + return -ENOENT; +} #endif /* CONFIG_OF_GPIO */ extern struct notifier_block gpio_of_notifier; diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index bbb96e52197c..4a57d9882600 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -948,7 +948,7 @@ static void gpiochip_machine_hog(struct gpio_chip *gc, struct gpiod_hog *hog) __func__, gc->label, hog->chip_hwnum, rv); } -static void machine_gpiochip_add(struct gpio_chip *gc) +static void gpiochip_machine_hog_lines(struct gpio_chip *gc) { struct gpiod_hog *hog; @@ -960,6 +960,98 @@ static void machine_gpiochip_add(struct gpio_chip *gc) } } +int gpiochip_add_hog(struct gpio_chip *gc, struct fwnode_handle *fwnode) +{ + struct fwnode_handle *gc_node = dev_fwnode(&gc->gpiodev->dev); + struct fwnode_reference_args gpiospec; + enum gpiod_flags dflags; + struct gpio_desc *desc; + unsigned long lflags; + const char *name; + int ret, argc; + u32 gpios[3]; /* We support up to three-cell bindings. */ + u32 cells; + + lflags = GPIO_LOOKUP_FLAGS_DEFAULT; + dflags = GPIOD_ASIS; + name = NULL; + + argc = fwnode_property_count_u32(fwnode, "gpios"); + if (argc < 0) + return argc; + if (argc > 3) + return -EINVAL; + + ret = fwnode_property_read_u32_array(fwnode, "gpios", gpios, argc); + if (ret < 0) + return ret; + + if (is_of_node(fwnode)) { + /* + * OF-nodes need some additional special handling for + * translating of devicetree flags. + */ + ret = fwnode_property_read_u32(gc_node, "#gpio-cells", &cells); + if (ret) + return ret; + if (!ret && argc != cells) + return -EINVAL; + + memset(&gpiospec, 0, sizeof(gpiospec)); + gpiospec.fwnode = fwnode; + gpiospec.nargs = argc; + + for (int i = 0; i < argc; i++) + gpiospec.args[i] = gpios[i]; + + ret = of_gpiochip_get_lflags(gc, &gpiospec, &lflags); + if (ret) + return ret; + } else { + /* + * GPIO_ACTIVE_LOW is currently the only lookup flag + * supported for non-OF firmware nodes. + */ + if (gpios[1]) + lflags |= GPIO_ACTIVE_LOW; + } + + if (fwnode_property_present(fwnode, "input")) + dflags |= GPIOD_IN; + else if (fwnode_property_present(fwnode, "output-low")) + dflags |= GPIOD_OUT_LOW; + else if (fwnode_property_present(fwnode, "output-high")) + dflags |= GPIOD_OUT_HIGH; + else + return -EINVAL; + + fwnode_property_read_string(fwnode, "line-name", &name); + + desc = gpiochip_get_desc(gc, gpios[0]); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + return gpiod_hog(desc, name, lflags, dflags); +} + +static int gpiochip_hog_lines(struct gpio_chip *gc) +{ + int ret; + + device_for_each_child_node_scoped(&gc->gpiodev->dev, fwnode) { + if (!fwnode_property_present(fwnode, "gpio-hog")) + continue; + + ret = gpiochip_add_hog(gc, fwnode); + if (ret) + return ret; + } + + gpiochip_machine_hog_lines(gc); + + return 0; +} + static void gpiochip_setup_devs(void) { struct gpio_device *gdev; @@ -1209,7 +1301,9 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, acpi_gpiochip_add(gc); - machine_gpiochip_add(gc); + ret = gpiochip_hog_lines(gc); + if (ret) + goto err_remove_of_chip; ret = gpiochip_irqchip_init_valid_mask(gc); if (ret) diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index 8d1a762f9d11..dc4cb61a9318 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -23,6 +23,8 @@ #define GPIOCHIP_NAME "gpiochip" +struct fwnode_handle; + /** * struct gpio_device - internal state container for GPIO devices * @dev: the GPIO device struct @@ -274,6 +276,7 @@ int gpiod_configure_flags(struct gpio_desc *desc, const char *con_id, int gpio_set_debounce_timeout(struct gpio_desc *desc, unsigned int debounce); int gpiod_hog(struct gpio_desc *desc, const char *name, unsigned long lflags, enum gpiod_flags dflags); +int gpiochip_add_hog(struct gpio_chip *gc, struct fwnode_handle *fwnode); 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); From 5cfbd0eb784f19436b5d5a9a7e0dca862619739a Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 9 Mar 2026 13:42:39 +0100 Subject: [PATCH 43/79] gpio: sim: use fwnode-based GPIO hogs Convert gpio-sim to using software nodes for setting up simulated hogs instead of legacy machine hogs. Reviewed-by: Linus Walleij Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260309-gpio-hog-fwnode-v2-3-4e61f3dbf06a@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-sim.c | 162 ++++++++++++++-------------------------- 1 file changed, 56 insertions(+), 106 deletions(-) diff --git a/drivers/gpio/gpio-sim.c b/drivers/gpio/gpio-sim.c index 13b87c8e6d0c..51bcbdd91b4b 100644 --- a/drivers/gpio/gpio-sim.c +++ b/drivers/gpio/gpio-sim.c @@ -40,6 +40,7 @@ #define GPIO_SIM_NGPIO_MAX 1024 #define GPIO_SIM_PROP_MAX 5 /* Max 4 properties + sentinel. */ +#define GPIO_SIM_HOG_PROP_MAX 5 #define GPIO_SIM_NUM_ATTRS 3 /* value, pull and sentinel */ static DEFINE_IDA(gpio_sim_ida); @@ -561,8 +562,6 @@ struct gpio_sim_device { */ struct mutex lock; - struct gpiod_hog *hogs; - struct list_head bank_list; }; @@ -774,102 +773,6 @@ static void gpio_sim_set_reserved_ranges(struct gpio_sim_bank *bank, } } -static void gpio_sim_remove_hogs(struct gpio_sim_device *dev) -{ - struct gpiod_hog *hog; - - if (!dev->hogs) - return; - - gpiod_remove_hogs(dev->hogs); - - for (hog = dev->hogs; hog->chip_label; hog++) { - kfree(hog->chip_label); - kfree(hog->line_name); - } - - kfree(dev->hogs); - dev->hogs = NULL; -} - -static int gpio_sim_add_hogs(struct gpio_sim_device *dev) -{ - unsigned int num_hogs = 0, idx = 0; - struct gpio_sim_bank *bank; - struct gpio_sim_line *line; - struct gpiod_hog *hog; - - list_for_each_entry(bank, &dev->bank_list, siblings) { - list_for_each_entry(line, &bank->line_list, siblings) { - if (line->offset >= bank->num_lines) - continue; - - if (line->hog) - num_hogs++; - } - } - - if (!num_hogs) - return 0; - - /* Allocate one more for the sentinel. */ - dev->hogs = kzalloc_objs(*dev->hogs, num_hogs + 1); - if (!dev->hogs) - return -ENOMEM; - - list_for_each_entry(bank, &dev->bank_list, siblings) { - list_for_each_entry(line, &bank->line_list, siblings) { - if (line->offset >= bank->num_lines) - continue; - - if (!line->hog) - continue; - - hog = &dev->hogs[idx++]; - - /* - * We need to make this string manually because at this - * point the device doesn't exist yet and so dev_name() - * is not available. - */ - if (gpio_sim_bank_has_label(bank)) - hog->chip_label = kstrdup(bank->label, - GFP_KERNEL); - else - hog->chip_label = kasprintf(GFP_KERNEL, - "gpio-sim.%u:%pfwP", - dev->id, - bank->swnode); - if (!hog->chip_label) { - gpio_sim_remove_hogs(dev); - return -ENOMEM; - } - - /* - * We need to duplicate this because the hog config - * item can be removed at any time (and we can't block - * it) and gpiolib doesn't make a deep copy of the hog - * data. - */ - if (line->hog->name) { - hog->line_name = kstrdup(line->hog->name, - GFP_KERNEL); - if (!hog->line_name) { - gpio_sim_remove_hogs(dev); - return -ENOMEM; - } - } - - hog->chip_hwnum = line->offset; - hog->dflags = line->hog->dir; - } - } - - gpiod_add_hogs(dev->hogs); - - return 0; -} - static struct fwnode_handle * gpio_sim_make_bank_swnode(struct gpio_sim_bank *bank, struct fwnode_handle *parent) @@ -917,12 +820,61 @@ gpio_sim_make_bank_swnode(struct gpio_sim_bank *bank, return fwnode_create_software_node(properties, parent); } +static int gpio_sim_bank_add_hogs(struct gpio_sim_bank *bank) +{ + struct property_entry properties[GPIO_SIM_HOG_PROP_MAX]; + struct fwnode_handle *swnode; + struct gpio_sim_line *line; + struct gpio_sim_hog *hog; + unsigned int idx; + u32 gpios[2]; + + list_for_each_entry(line, &bank->line_list, siblings) { + if (!line->hog) + continue; + + hog = line->hog; + + gpios[0] = line->offset; + gpios[1] = 0; + + memset(properties, 0, sizeof(properties)); + + idx = 0; + properties[idx++] = PROPERTY_ENTRY_BOOL("gpio-hog"); + properties[idx++] = PROPERTY_ENTRY_U32_ARRAY("gpios", gpios); + properties[idx++] = PROPERTY_ENTRY_STRING("line-name", hog->name); + + switch (hog->dir) { + case GPIOD_IN: + properties[idx++] = PROPERTY_ENTRY_BOOL("input"); + break; + case GPIOD_OUT_HIGH: + properties[idx++] = PROPERTY_ENTRY_BOOL("output-high"); + break; + case GPIOD_OUT_LOW: + properties[idx++] = PROPERTY_ENTRY_BOOL("output-low"); + break; + default: + /* Would have been validated at configfs store. */ + WARN(1, "Unexpected hog direction value: %d", hog->dir); + return -EINVAL; + } + + swnode = fwnode_create_software_node(properties, bank->swnode); + if (IS_ERR(swnode)) + return PTR_ERR(swnode); + } + + return 0; +} + static void gpio_sim_remove_swnode_recursive(struct fwnode_handle *swnode) { struct fwnode_handle *child; fwnode_for_each_child_node(swnode, child) - fwnode_remove_software_node(child); + gpio_sim_remove_swnode_recursive(child); fwnode_remove_software_node(swnode); } @@ -977,12 +929,12 @@ static int gpio_sim_device_activate(struct gpio_sim_device *dev) gpio_sim_remove_swnode_recursive(swnode); return ret; } - } - ret = gpio_sim_add_hogs(dev); - if (ret) { - gpio_sim_remove_swnode_recursive(swnode); - return ret; + ret = gpio_sim_bank_add_hogs(bank); + if (ret) { + gpio_sim_remove_swnode_recursive(swnode); + return ret; + } } pdevinfo.name = "gpio-sim"; @@ -991,7 +943,6 @@ static int gpio_sim_device_activate(struct gpio_sim_device *dev) ret = dev_sync_probe_register(&dev->probe_data, &pdevinfo); if (ret) { - gpio_sim_remove_hogs(dev); gpio_sim_remove_swnode_recursive(swnode); return ret; } @@ -1007,7 +958,6 @@ static void gpio_sim_device_deactivate(struct gpio_sim_device *dev) swnode = dev_fwnode(&dev->probe_data.pdev->dev); dev_sync_probe_unregister(&dev->probe_data); - gpio_sim_remove_hogs(dev); gpio_sim_remove_swnode_recursive(swnode); } From e627fc9fad93d59765dd16adac1b2a9bf68d7523 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 9 Mar 2026 13:42:40 +0100 Subject: [PATCH 44/79] ARM: omap1: ams-delta: convert GPIO hogs to using firmware nodes Setup a software node hierarchy for the latch2 GPIO controller defining the required hog and stop using legacy machine hog API. Acked-by: Kevin Hilman Reviewed-by: Linus Walleij Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260309-gpio-hog-fwnode-v2-4-4e61f3dbf06a@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- arch/arm/mach-omap1/board-ams-delta.c | 32 ++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/arch/arm/mach-omap1/board-ams-delta.c b/arch/arm/mach-omap1/board-ams-delta.c index 16392720296c..1bec4fa0bd5e 100644 --- a/arch/arm/mach-omap1/board-ams-delta.c +++ b/arch/arm/mach-omap1/board-ams-delta.c @@ -556,10 +556,30 @@ static struct gpiod_lookup_table *ams_delta_gpio_tables[] __initdata = { &ams_delta_nand_gpio_table, }; -static struct gpiod_hog ams_delta_gpio_hogs[] = { - GPIO_HOG(LATCH2_LABEL, LATCH2_PIN_KEYBRD_DATAOUT, "keybrd_dataout", - GPIO_ACTIVE_HIGH, GPIOD_OUT_LOW), - {}, +static const struct software_node latch2_gpio_swnode = { + .name = LATCH2_LABEL, +}; + +static const u32 latch2_hog_gpios[] = { LATCH2_PIN_KEYBRD_DATAOUT, 0 }; + +static const struct property_entry latch2_gpio_hog_props[] = { + PROPERTY_ENTRY_BOOL("gpio-hog"), + PROPERTY_ENTRY_U32_ARRAY("gpios", latch2_hog_gpios), + PROPERTY_ENTRY_STRING("line-name", "keybrd_dataout"), + PROPERTY_ENTRY_BOOL("output-low"), + { } +}; + +static const struct software_node latch2_gpio_hog_swnode = { + .parent = &latch2_gpio_swnode, + .name = "latch2-hog", + .properties = latch2_gpio_hog_props, +}; + +static const struct software_node *const latch2_gpio_swnodes[] = { + &latch2_gpio_swnode, + &latch2_gpio_hog_swnode, + NULL }; static struct plat_serial8250_port ams_delta_modem_ports[]; @@ -684,7 +704,6 @@ static void __init ams_delta_init(void) omap_gpio_deps_init(); ams_delta_latch2_init(); - gpiod_add_hogs(ams_delta_gpio_hogs); omap_serial_init(); omap_register_i2c_bus(1, 100, NULL, 0); @@ -693,6 +712,9 @@ static void __init ams_delta_init(void) platform_add_devices(ams_delta_devices, ARRAY_SIZE(ams_delta_devices)); platform_device_register_full(&latch1_gpio_devinfo); + + software_node_register_node_group(latch2_gpio_swnodes); + latch2_gpio_devinfo.fwnode = software_node_fwnode(&latch2_gpio_swnode); platform_device_register_full(&latch2_gpio_devinfo); /* From dea046e7f46f2357124a465e058c92cac3e351c5 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 9 Mar 2026 13:42:41 +0100 Subject: [PATCH 45/79] gpio: remove machine hogs With no more users, remove legacy machine hog API from the kernel. Reviewed-by: Linus Walleij Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260309-gpio-hog-fwnode-v2-5-4e61f3dbf06a@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- Documentation/driver-api/gpio/board.rst | 16 ------ drivers/gpio/gpiolib.c | 71 ------------------------- include/linux/gpio/machine.h | 33 ------------ 3 files changed, 120 deletions(-) diff --git a/Documentation/driver-api/gpio/board.rst b/Documentation/driver-api/gpio/board.rst index 069b54d8591b..0993cac891fb 100644 --- a/Documentation/driver-api/gpio/board.rst +++ b/Documentation/driver-api/gpio/board.rst @@ -239,22 +239,6 @@ mapping and is thus transparent to GPIO consumers. A set of functions such as gpiod_set_value() is available to work with the new descriptor-oriented interface. -Boards using platform data can also hog GPIO lines by defining GPIO hog tables. - -.. code-block:: c - - struct gpiod_hog gpio_hog_table[] = { - GPIO_HOG("gpio.0", 10, "foo", GPIO_ACTIVE_LOW, GPIOD_OUT_HIGH), - { } - }; - -And the table can be added to the board code as follows:: - - gpiod_add_hogs(gpio_hog_table); - -The line will be hogged as soon as the gpiochip is created or - in case the -chip was created earlier - when the hog table is registered. - Arrays of pins -------------- In addition to requesting pins belonging to a function one by one, a device may diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 4a57d9882600..56fda7891d55 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -103,9 +103,6 @@ static DEFINE_MUTEX(gpio_devices_lock); /* Ensures coherence during read-only accesses to the list of GPIO devices. */ DEFINE_STATIC_SRCU(gpio_devices_srcu); -static DEFINE_MUTEX(gpio_machine_hogs_mutex); -static LIST_HEAD(gpio_machine_hogs); - const char *const gpio_suffixes[] = { "gpios", "gpio", NULL }; static void gpiochip_free_hogs(struct gpio_chip *gc); @@ -930,36 +927,6 @@ err_remove_device: return ret; } -static void gpiochip_machine_hog(struct gpio_chip *gc, struct gpiod_hog *hog) -{ - struct gpio_desc *desc; - int rv; - - desc = gpiochip_get_desc(gc, hog->chip_hwnum); - if (IS_ERR(desc)) { - gpiochip_err(gc, "%s: unable to get GPIO desc: %ld\n", - __func__, PTR_ERR(desc)); - return; - } - - rv = gpiod_hog(desc, hog->line_name, hog->lflags, hog->dflags); - if (rv) - gpiod_err(desc, "%s: unable to hog GPIO line (%s:%u): %d\n", - __func__, gc->label, hog->chip_hwnum, rv); -} - -static void gpiochip_machine_hog_lines(struct gpio_chip *gc) -{ - struct gpiod_hog *hog; - - guard(mutex)(&gpio_machine_hogs_mutex); - - list_for_each_entry(hog, &gpio_machine_hogs, list) { - if (!strcmp(gc->label, hog->chip_label)) - gpiochip_machine_hog(gc, hog); - } -} - int gpiochip_add_hog(struct gpio_chip *gc, struct fwnode_handle *fwnode) { struct fwnode_handle *gc_node = dev_fwnode(&gc->gpiodev->dev); @@ -1047,8 +1014,6 @@ static int gpiochip_hog_lines(struct gpio_chip *gc) return ret; } - gpiochip_machine_hog_lines(gc); - return 0; } @@ -4578,42 +4543,6 @@ void gpiod_remove_lookup_table(struct gpiod_lookup_table *table) } EXPORT_SYMBOL_GPL(gpiod_remove_lookup_table); -/** - * gpiod_add_hogs() - register a set of GPIO hogs from machine code - * @hogs: table of gpio hog entries with a zeroed sentinel at the end - */ -void gpiod_add_hogs(struct gpiod_hog *hogs) -{ - struct gpiod_hog *hog; - - guard(mutex)(&gpio_machine_hogs_mutex); - - for (hog = &hogs[0]; hog->chip_label; hog++) { - list_add_tail(&hog->list, &gpio_machine_hogs); - - /* - * The chip may have been registered earlier, so check if it - * exists and, if so, try to hog the line now. - */ - struct gpio_device *gdev __free(gpio_device_put) = - gpio_device_find_by_label(hog->chip_label); - if (gdev) - gpiochip_machine_hog(gpio_device_get_chip(gdev), hog); - } -} -EXPORT_SYMBOL_GPL(gpiod_add_hogs); - -void gpiod_remove_hogs(struct gpiod_hog *hogs) -{ - struct gpiod_hog *hog; - - guard(mutex)(&gpio_machine_hogs_mutex); - - for (hog = &hogs[0]; hog->chip_label; hog++) - list_del(&hog->list); -} -EXPORT_SYMBOL_GPL(gpiod_remove_hogs); - static bool gpiod_match_lookup_table(struct device *dev, const struct gpiod_lookup_table *table) { diff --git a/include/linux/gpio/machine.h b/include/linux/gpio/machine.h index 44e5f162973e..5eb88f5d0630 100644 --- a/include/linux/gpio/machine.h +++ b/include/linux/gpio/machine.h @@ -46,23 +46,6 @@ struct gpiod_lookup_table { struct gpiod_lookup table[]; }; -/** - * struct gpiod_hog - GPIO line hog table - * @chip_label: name of the chip the GPIO belongs to - * @chip_hwnum: hardware number (i.e. relative to the chip) of the GPIO - * @line_name: consumer name for the hogged line - * @lflags: bitmask of gpio_lookup_flags GPIO_* values - * @dflags: GPIO flags used to specify the direction and value - */ -struct gpiod_hog { - struct list_head list; - const char *chip_label; - u16 chip_hwnum; - const char *line_name; - unsigned long lflags; - int dflags; -}; - /* * Helper for lookup tables with just one single lookup for a device. */ @@ -95,24 +78,10 @@ static struct gpiod_lookup_table _name = { \ .flags = _flags, \ } -/* - * Simple definition of a single GPIO hog in an array. - */ -#define GPIO_HOG(_chip_label, _chip_hwnum, _line_name, _lflags, _dflags) \ -(struct gpiod_hog) { \ - .chip_label = _chip_label, \ - .chip_hwnum = _chip_hwnum, \ - .line_name = _line_name, \ - .lflags = _lflags, \ - .dflags = _dflags, \ -} - #ifdef CONFIG_GPIOLIB void gpiod_add_lookup_table(struct gpiod_lookup_table *table); void gpiod_add_lookup_tables(struct gpiod_lookup_table **tables, size_t n); void gpiod_remove_lookup_table(struct gpiod_lookup_table *table); -void gpiod_add_hogs(struct gpiod_hog *hogs); -void gpiod_remove_hogs(struct gpiod_hog *hogs); #else /* ! CONFIG_GPIOLIB */ static inline void gpiod_add_lookup_table(struct gpiod_lookup_table *table) {} @@ -120,8 +89,6 @@ static inline void gpiod_add_lookup_tables(struct gpiod_lookup_table **tables, size_t n) {} static inline void gpiod_remove_lookup_table(struct gpiod_lookup_table *table) {} -static inline void gpiod_add_hogs(struct gpiod_hog *hogs) {} -static inline void gpiod_remove_hogs(struct gpiod_hog *hogs) {} #endif /* CONFIG_GPIOLIB */ #endif /* __LINUX_GPIO_MACHINE_H */ From 696e9ba9a3da3d919d08a1abf05c9288311858f1 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 9 Mar 2026 13:42:42 +0100 Subject: [PATCH 46/79] gpio: sim: allow to define the active-low setting of a simulated hog Add a new configfs attribute to the hog group allowing to configure the active-low lookup flag for hogged lines. This will allow us to extend tests to also cover the line config of hogs set up using software nodes. Reviewed-by: Linus Walleij Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260309-gpio-hog-fwnode-v2-6-4e61f3dbf06a@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-sim.c | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-sim.c b/drivers/gpio/gpio-sim.c index 51bcbdd91b4b..f32674230237 100644 --- a/drivers/gpio/gpio-sim.c +++ b/drivers/gpio/gpio-sim.c @@ -654,6 +654,7 @@ struct gpio_sim_hog { char *name; int dir; + bool active_low; }; static struct gpio_sim_hog *to_gpio_sim_hog(struct config_item *item) @@ -836,7 +837,7 @@ static int gpio_sim_bank_add_hogs(struct gpio_sim_bank *bank) hog = line->hog; gpios[0] = line->offset; - gpios[1] = 0; + gpios[1] = hog->active_low ? 1 : 0; memset(properties, 0, sizeof(properties)); @@ -1315,9 +1316,46 @@ gpio_sim_hog_config_direction_store(struct config_item *item, CONFIGFS_ATTR(gpio_sim_hog_config_, direction); +static ssize_t gpio_sim_hog_config_active_low_show(struct config_item *item, + char *page) +{ + struct gpio_sim_hog *hog = to_gpio_sim_hog(item); + struct gpio_sim_device *dev = gpio_sim_hog_get_device(hog); + + guard(mutex)(&dev->lock); + + return sprintf(page, "%c\n", hog->active_low ? '1' : '0'); +} + +static ssize_t +gpio_sim_hog_config_active_low_store(struct config_item *item, + const char *page, size_t count) +{ + struct gpio_sim_hog *hog = to_gpio_sim_hog(item); + struct gpio_sim_device *dev = gpio_sim_hog_get_device(hog); + bool active_low; + int ret; + + guard(mutex)(&dev->lock); + + if (gpio_sim_device_is_live(dev)) + return -EBUSY; + + ret = kstrtobool(page, &active_low); + if (ret) + return ret; + + hog->active_low = active_low; + + return count; +} + +CONFIGFS_ATTR(gpio_sim_hog_config_, active_low); + static struct configfs_attribute *gpio_sim_hog_config_attrs[] = { &gpio_sim_hog_config_attr_name, &gpio_sim_hog_config_attr_direction, + &gpio_sim_hog_config_attr_active_low, NULL }; From 8a3613898ff3b7eb9eed252f41aebcc1d7af4a31 Mon Sep 17 00:00:00 2001 From: Richard Lyu Date: Wed, 11 Mar 2026 16:59:26 +0800 Subject: [PATCH 47/79] gpio: max732x: use guard(mutex) to simplify locking Convert the max732x driver to use the RAII-based guard(mutex) macro from . This change replaces manual mutex_lock() and mutex_unlock() calls, allowing the chip lock to be managed automatically based on function scope. Refactor max732x_gpio_set_mask() and max732x_irq_update_mask() to improve code readability. This allows for direct returns and removes the redundant 'out' label in the set_mask function, resulting in cleaner and more maintainable code. While at it: order includes alphabetically and add missing ones. Signed-off-by: Richard Lyu Link: https://patch.msgid.link/20260311085924.191288-1-richard.lyu@suse.com [Bartosz: tweak commit message, add err.h and device.h to includes] Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-max732x.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/gpio/gpio-max732x.c b/drivers/gpio/gpio-max732x.c index a61d670ceeda..281ba1740a6a 100644 --- a/drivers/gpio/gpio-max732x.c +++ b/drivers/gpio/gpio-max732x.c @@ -10,14 +10,18 @@ * Derived from drivers/gpio/pca953x.c */ -#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include #include -#include -#include -#include -#include /* * Each port of MAX732x (including MAX7319) falls into one of the @@ -207,22 +211,20 @@ static void max732x_gpio_set_mask(struct gpio_chip *gc, unsigned off, int mask, uint8_t reg_out; int ret; - mutex_lock(&chip->lock); + guard(mutex)(&chip->lock); reg_out = (off > 7) ? chip->reg_out[1] : chip->reg_out[0]; reg_out = (reg_out & ~mask) | (val & mask); ret = max732x_writeb(chip, is_group_a(chip, off), reg_out); if (ret < 0) - goto out; + return; /* update the shadow register then */ if (off > 7) chip->reg_out[1] = reg_out; else chip->reg_out[0] = reg_out; -out: - mutex_unlock(&chip->lock); } static int max732x_gpio_set_value(struct gpio_chip *gc, unsigned int off, @@ -329,7 +331,7 @@ static void max732x_irq_update_mask(struct max732x_chip *chip) if (chip->irq_features == INT_NO_MASK) return; - mutex_lock(&chip->lock); + guard(mutex)(&chip->lock); switch (chip->irq_features) { case INT_INDEP_MASK: @@ -342,8 +344,6 @@ static void max732x_irq_update_mask(struct max732x_chip *chip) max732x_writeb(chip, 1, (uint8_t)msg); break; } - - mutex_unlock(&chip->lock); } static void max732x_irq_mask(struct irq_data *d) From 803e822b0089211367d8d368a163b1dea077159d Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 11 Mar 2026 10:42:14 +0100 Subject: [PATCH 48/79] gpiolib: Update gpiochip_find_base_unlocked() kerneldoc This function albeit static was lacking a kerneldoc, and the function returns a dangerous number for internal use so make that clear in the kerneldoc. Reported-by: Matthijs Kooijman Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260311-gpio-discourage-dynamic-v1-1-c8b68fc84203@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 56fda7891d55..e287ab0d21fc 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -338,7 +338,15 @@ struct gpio_chip *gpio_device_get_chip(struct gpio_device *gdev) } EXPORT_SYMBOL_GPL(gpio_device_get_chip); -/* dynamic allocation of GPIOs, e.g. on a hotplugged device */ +/** + * gpiochip_find_base_unlocked() - Find a global GPIO number base + * @ngpio: Number of consecutive GPIOs to number + * + * Finds and allocates a consecutive range of unsigned integers representing + * the GPIOs on the system. Using this numberspace outside of gpiolibs + * internals is STRONGLY DISCOURAGED, drivers and consumers should NOT concern + * themselves with this numberspace. + */ static int gpiochip_find_base_unlocked(u16 ngpio) { unsigned int base = GPIO_DYNAMIC_BASE; From 4071437cd2aac6b9d48f160d46cfb35ecbb11136 Mon Sep 17 00:00:00 2001 From: Alban Bedel Date: Wed, 11 Mar 2026 15:31:17 +0100 Subject: [PATCH 49/79] gpio: kempld: Simplify the bit level register accesses The hardware uses 8 bit registers but supports configurations with up to 16 GPIO, so all GPIO registers come in pairs. Most accesses to single bits is done using the kempld_gpio_bitop() and kempld_gpio_get_bit() functions, which take a register index and bit offset as parameter. These functions apply a modulo on the bit offset but leave the register index as is, so callers have to use an additional macro to fix the register index before the call. Simplify things by also handling the register index offsetting in the bitop functions. Signed-off-by: Alban Bedel Link: https://patch.msgid.link/20260311143120.2179347-2-alban.bedel@lht.dlh.de Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-kempld.c | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/drivers/gpio/gpio-kempld.c b/drivers/gpio/gpio-kempld.c index 923aad3ab4d4..532e4000879a 100644 --- a/drivers/gpio/gpio-kempld.c +++ b/drivers/gpio/gpio-kempld.c @@ -17,8 +17,8 @@ #define KEMPLD_GPIO_MAX_NUM 16 #define KEMPLD_GPIO_MASK(x) (BIT((x) % 8)) -#define KEMPLD_GPIO_DIR_NUM(x) (0x40 + (x) / 8) -#define KEMPLD_GPIO_LVL_NUM(x) (0x42 + (x) / 8) +#define KEMPLD_GPIO_DIR 0x40 +#define KEMPLD_GPIO_LVL 0x42 #define KEMPLD_GPIO_EVT_LVL_EDGE 0x46 #define KEMPLD_GPIO_IEN 0x4A @@ -32,24 +32,25 @@ struct kempld_gpio_data { * kempld_get_mutex must be called prior to calling this function. */ static void kempld_gpio_bitop(struct kempld_device_data *pld, - u8 reg, u8 bit, u8 val) + u8 reg, unsigned int bit, bool val) { u8 status; - status = kempld_read8(pld, reg); + status = kempld_read8(pld, reg + (bit / 8)); if (val) status |= KEMPLD_GPIO_MASK(bit); else status &= ~KEMPLD_GPIO_MASK(bit); - kempld_write8(pld, reg, status); + kempld_write8(pld, reg + (bit / 8), status); } -static int kempld_gpio_get_bit(struct kempld_device_data *pld, u8 reg, u8 bit) +static int kempld_gpio_get_bit(struct kempld_device_data *pld, + u8 reg, unsigned int bit) { u8 status; kempld_get_mutex(pld); - status = kempld_read8(pld, reg); + status = kempld_read8(pld, reg + (bit / 8)); kempld_release_mutex(pld); return !!(status & KEMPLD_GPIO_MASK(bit)); @@ -60,7 +61,7 @@ static int kempld_gpio_get(struct gpio_chip *chip, unsigned offset) struct kempld_gpio_data *gpio = gpiochip_get_data(chip); struct kempld_device_data *pld = gpio->pld; - return !!kempld_gpio_get_bit(pld, KEMPLD_GPIO_LVL_NUM(offset), offset); + return !!kempld_gpio_get_bit(pld, KEMPLD_GPIO_LVL, offset); } static int kempld_gpio_set(struct gpio_chip *chip, unsigned int offset, @@ -70,7 +71,7 @@ static int kempld_gpio_set(struct gpio_chip *chip, unsigned int offset, struct kempld_device_data *pld = gpio->pld; kempld_get_mutex(pld); - kempld_gpio_bitop(pld, KEMPLD_GPIO_LVL_NUM(offset), offset, value); + kempld_gpio_bitop(pld, KEMPLD_GPIO_LVL, offset, value); kempld_release_mutex(pld); return 0; @@ -82,7 +83,7 @@ static int kempld_gpio_direction_input(struct gpio_chip *chip, unsigned offset) struct kempld_device_data *pld = gpio->pld; kempld_get_mutex(pld); - kempld_gpio_bitop(pld, KEMPLD_GPIO_DIR_NUM(offset), offset, 0); + kempld_gpio_bitop(pld, KEMPLD_GPIO_DIR, offset, 0); kempld_release_mutex(pld); return 0; @@ -95,8 +96,8 @@ static int kempld_gpio_direction_output(struct gpio_chip *chip, unsigned offset, struct kempld_device_data *pld = gpio->pld; kempld_get_mutex(pld); - kempld_gpio_bitop(pld, KEMPLD_GPIO_LVL_NUM(offset), offset, value); - kempld_gpio_bitop(pld, KEMPLD_GPIO_DIR_NUM(offset), offset, 1); + kempld_gpio_bitop(pld, KEMPLD_GPIO_LVL, offset, value); + kempld_gpio_bitop(pld, KEMPLD_GPIO_DIR, offset, 1); kempld_release_mutex(pld); return 0; @@ -107,7 +108,7 @@ static int kempld_gpio_get_direction(struct gpio_chip *chip, unsigned offset) struct kempld_gpio_data *gpio = gpiochip_get_data(chip); struct kempld_device_data *pld = gpio->pld; - if (kempld_gpio_get_bit(pld, KEMPLD_GPIO_DIR_NUM(offset), offset)) + if (kempld_gpio_get_bit(pld, KEMPLD_GPIO_DIR, offset)) return GPIO_LINE_DIRECTION_OUT; return GPIO_LINE_DIRECTION_IN; From 84cb463d2f6597c7951da6fb795f12119af8130d Mon Sep 17 00:00:00 2001 From: Alban Bedel Date: Wed, 11 Mar 2026 15:31:18 +0100 Subject: [PATCH 50/79] gpio: kempld: Add support for PLD version >= 2.8 Starting with version 2.8 there is a dedicated register to configure the output level. Read the PLD version in the probe and select the correct register to use for the set operations. Signed-off-by: Alban Bedel Link: https://patch.msgid.link/20260311143120.2179347-3-alban.bedel@lht.dlh.de Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-kempld.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-kempld.c b/drivers/gpio/gpio-kempld.c index 532e4000879a..2263de77d40e 100644 --- a/drivers/gpio/gpio-kempld.c +++ b/drivers/gpio/gpio-kempld.c @@ -25,6 +25,7 @@ struct kempld_gpio_data { struct gpio_chip chip; struct kempld_device_data *pld; + u8 out_lvl_reg; }; /* @@ -71,7 +72,7 @@ static int kempld_gpio_set(struct gpio_chip *chip, unsigned int offset, struct kempld_device_data *pld = gpio->pld; kempld_get_mutex(pld); - kempld_gpio_bitop(pld, KEMPLD_GPIO_LVL, offset, value); + kempld_gpio_bitop(pld, gpio->out_lvl_reg, offset, value); kempld_release_mutex(pld); return 0; @@ -96,7 +97,7 @@ static int kempld_gpio_direction_output(struct gpio_chip *chip, unsigned offset, struct kempld_device_data *pld = gpio->pld; kempld_get_mutex(pld); - kempld_gpio_bitop(pld, KEMPLD_GPIO_LVL, offset, value); + kempld_gpio_bitop(pld, gpio->out_lvl_reg, offset, value); kempld_gpio_bitop(pld, KEMPLD_GPIO_DIR, offset, 1); kempld_release_mutex(pld); @@ -153,6 +154,15 @@ static int kempld_gpio_probe(struct platform_device *pdev) if (!gpio) return -ENOMEM; + /* Starting with version 2.8 there is a dedicated register for the + * output state, earlier versions share the register used to read + * the line level. + */ + if (pld->info.spec_major > 2 || pld->info.spec_minor >= 8) + gpio->out_lvl_reg = KEMPLD_GPIO_OUT_LVL; + else + gpio->out_lvl_reg = KEMPLD_GPIO_LVL; + gpio->pld = pld; platform_set_drvdata(pdev, gpio); From 2443c2e1223bda4dcef5563d0154df6a72969922 Mon Sep 17 00:00:00 2001 From: Alban Bedel Date: Wed, 11 Mar 2026 15:31:19 +0100 Subject: [PATCH 51/79] gpio: kempld: Add support for get/set multiple As the bus accesses are quiet slow with this device, supporting the get/set multiple API can help with performences. The implementation tries to keep the number of bus access to a minimum by checking the mask to only read or write the needed bytes. Signed-off-by: Alban Bedel Link: https://patch.msgid.link/20260311143120.2179347-4-alban.bedel@lht.dlh.de Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-kempld.c | 60 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/drivers/gpio/gpio-kempld.c b/drivers/gpio/gpio-kempld.c index 2263de77d40e..7dd94ff6f2df 100644 --- a/drivers/gpio/gpio-kempld.c +++ b/drivers/gpio/gpio-kempld.c @@ -65,6 +65,33 @@ static int kempld_gpio_get(struct gpio_chip *chip, unsigned offset) return !!kempld_gpio_get_bit(pld, KEMPLD_GPIO_LVL, offset); } +static int kempld_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask, + unsigned long *bits) +{ + struct kempld_gpio_data *gpio = gpiochip_get_data(chip); + struct kempld_device_data *pld = gpio->pld; + u8 reg = KEMPLD_GPIO_LVL; + unsigned int shift; + + bits[0] &= ~mask[0]; + + kempld_get_mutex(pld); + + /* Try to reduce to a single 8 bits access if possible */ + for (shift = 0; shift < gpio->chip.ngpio; shift += 8, reg++) { + unsigned long msk = (mask[0] >> shift) & 0xff; + + if (!msk) + continue; + + bits[0] |= (kempld_read8(pld, reg) & msk) << shift; + } + + kempld_release_mutex(pld); + + return 0; +} + static int kempld_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) { @@ -78,6 +105,37 @@ static int kempld_gpio_set(struct gpio_chip *chip, unsigned int offset, return 0; } +static int kempld_gpio_set_multiple(struct gpio_chip *chip, + unsigned long *mask, unsigned long *bits) +{ + struct kempld_gpio_data *gpio = gpiochip_get_data(chip); + struct kempld_device_data *pld = gpio->pld; + u8 reg = gpio->out_lvl_reg; + unsigned int shift; + + kempld_get_mutex(pld); + + /* Try to reduce to a single 8 bits access if possible */ + for (shift = 0; shift < gpio->chip.ngpio; shift += 8, reg++) { + u8 val, msk = mask[0] >> shift; + + if (!msk) + continue; + + if (msk != 0xFF) + val = kempld_read8(pld, reg) & ~msk; + else + val = 0; + + val |= (bits[0] >> shift) & msk; + kempld_write8(pld, reg, val); + } + + kempld_release_mutex(pld); + + return 0; +} + static int kempld_gpio_direction_input(struct gpio_chip *chip, unsigned offset) { struct kempld_gpio_data *gpio = gpiochip_get_data(chip); @@ -180,7 +238,9 @@ static int kempld_gpio_probe(struct platform_device *pdev) chip->direction_output = kempld_gpio_direction_output; chip->get_direction = kempld_gpio_get_direction; chip->get = kempld_gpio_get; + chip->get_multiple = kempld_gpio_get_multiple; chip->set = kempld_gpio_set; + chip->set_multiple = kempld_gpio_set_multiple; chip->ngpio = kempld_gpio_pincount(pld); if (chip->ngpio == 0) { dev_err(dev, "No GPIO pins detected\n"); From a25f48fd920b557e6ad02f692f690520c82f5914 Mon Sep 17 00:00:00 2001 From: Alban Bedel Date: Wed, 11 Mar 2026 15:31:20 +0100 Subject: [PATCH 52/79] gpio: kempld: Implement the interrupt controller Add a GPIO IRQ chip implementation for the kempld GPIO controller. Of note is only how the parent IRQ is obtained. The IRQ for the GPIO controller can be configured in the BIOS, along with the IRQ for the I2C controller. These IRQ are returned by ACPI but this information is only usable if both IRQ are configured. When only one is configured, only one is returned making it impossible to know which one it is. Luckily the BIOS will set the configured IRQ in the PLD registers, so it can be read from there instead, and that also work on platforms without ACPI. The vendor driver allowed to override the IRQ using a module parameters, so there are boards in field which used this parameter instead of properly configuring the BIOS. This implementation provides this as well for compatibility. Signed-off-by: Alban Bedel Link: https://patch.msgid.link/20260311143120.2179347-5-alban.bedel@lht.dlh.de Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 1 + drivers/gpio/gpio-kempld.c | 192 +++++++++++++++++++++++++++++++++++++ include/linux/mfd/kempld.h | 1 + 3 files changed, 194 insertions(+) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index b45fb799e36c..d665afe19709 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1440,6 +1440,7 @@ config GPIO_JANZ_TTL config GPIO_KEMPLD tristate "Kontron ETX / COMexpress GPIO" depends on MFD_KEMPLD + select GPIOLIB_IRQCHIP help This enables support for the PLD GPIO interface on some Kontron ETX and COMexpress (ETXexpress) modules. diff --git a/drivers/gpio/gpio-kempld.c b/drivers/gpio/gpio-kempld.c index 7dd94ff6f2df..5a63df3ea5fa 100644 --- a/drivers/gpio/gpio-kempld.c +++ b/drivers/gpio/gpio-kempld.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -19,13 +20,26 @@ #define KEMPLD_GPIO_MASK(x) (BIT((x) % 8)) #define KEMPLD_GPIO_DIR 0x40 #define KEMPLD_GPIO_LVL 0x42 +#define KEMPLD_GPIO_STS 0x44 #define KEMPLD_GPIO_EVT_LVL_EDGE 0x46 +#define KEMPLD_GPIO_EVT_LOW_HIGH 0x48 #define KEMPLD_GPIO_IEN 0x4A +#define KEMPLD_GPIO_OUT_LVL 0x4E + +/* The IRQ to use if none was configured in the BIOS */ +static unsigned int gpio_irq; +module_param_hw(gpio_irq, uint, irq, 0444); +MODULE_PARM_DESC(gpio_irq, "Set legacy GPIO IRQ (1-15)"); struct kempld_gpio_data { struct gpio_chip chip; struct kempld_device_data *pld; u8 out_lvl_reg; + + struct mutex irq_lock; + u16 ien; + u16 evt_low_high; + u16 evt_lvl_edge; }; /* @@ -193,6 +207,180 @@ static int kempld_gpio_pincount(struct kempld_device_data *pld) return evt ? __ffs(evt) : 16; } +static void kempld_irq_mask(struct irq_data *data) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(data); + struct kempld_gpio_data *gpio = gpiochip_get_data(chip); + + gpio->ien &= ~BIT(irqd_to_hwirq(data)); + gpiochip_disable_irq(chip, irqd_to_hwirq(data)); +} + +static void kempld_irq_unmask(struct irq_data *data) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(data); + struct kempld_gpio_data *gpio = gpiochip_get_data(chip); + + gpiochip_enable_irq(chip, irqd_to_hwirq(data)); + gpio->ien |= BIT(irqd_to_hwirq(data)); +} + +static int kempld_irq_set_type(struct irq_data *data, unsigned int type) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(data); + struct kempld_gpio_data *gpio = gpiochip_get_data(chip); + + switch (type) { + case IRQ_TYPE_EDGE_RISING: + gpio->evt_low_high |= BIT(data->hwirq); + gpio->evt_lvl_edge |= BIT(data->hwirq); + break; + case IRQ_TYPE_EDGE_FALLING: + gpio->evt_low_high &= ~BIT(data->hwirq); + gpio->evt_lvl_edge |= BIT(data->hwirq); + break; + case IRQ_TYPE_LEVEL_HIGH: + gpio->evt_low_high |= BIT(data->hwirq); + gpio->evt_lvl_edge &= ~BIT(data->hwirq); + break; + case IRQ_TYPE_LEVEL_LOW: + gpio->evt_low_high &= ~BIT(data->hwirq); + gpio->evt_lvl_edge &= ~BIT(data->hwirq); + break; + default: + return -EINVAL; + } + + return 0; +} + +static void kempld_irq_bus_lock(struct irq_data *data) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(data); + struct kempld_gpio_data *gpio = gpiochip_get_data(chip); + + mutex_lock(&gpio->irq_lock); +} + +static void kempld_irq_bus_sync_unlock(struct irq_data *data) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(data); + struct kempld_gpio_data *gpio = gpiochip_get_data(chip); + struct kempld_device_data *pld = gpio->pld; + + kempld_get_mutex(pld); + kempld_write16(pld, KEMPLD_GPIO_EVT_LVL_EDGE, gpio->evt_lvl_edge); + kempld_write16(pld, KEMPLD_GPIO_EVT_LOW_HIGH, gpio->evt_low_high); + kempld_write16(pld, KEMPLD_GPIO_IEN, gpio->ien); + kempld_release_mutex(pld); + + mutex_unlock(&gpio->irq_lock); +} + +static const struct irq_chip kempld_irqchip = { + .name = "kempld-gpio", + .irq_mask = kempld_irq_mask, + .irq_unmask = kempld_irq_unmask, + .irq_set_type = kempld_irq_set_type, + .irq_bus_lock = kempld_irq_bus_lock, + .irq_bus_sync_unlock = kempld_irq_bus_sync_unlock, + .flags = IRQCHIP_IMMUTABLE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, +}; + +static irqreturn_t kempld_gpio_irq_handler(int irq, void *data) +{ + struct kempld_gpio_data *gpio = data; + struct gpio_chip *chip = &gpio->chip; + unsigned int pin, child_irq; + unsigned long status; + + kempld_get_mutex(gpio->pld); + + status = kempld_read16(gpio->pld, KEMPLD_GPIO_STS); + if (status) + kempld_write16(gpio->pld, KEMPLD_GPIO_STS, status); + + kempld_release_mutex(gpio->pld); + + status &= gpio->ien; + if (!status) + return IRQ_NONE; + + for_each_set_bit(pin, &status, chip->ngpio) { + child_irq = irq_find_mapping(chip->irq.domain, pin); + handle_nested_irq(child_irq); + } + + return IRQ_HANDLED; +} + +static int kempld_gpio_irq_init(struct device *dev, + struct kempld_gpio_data *gpio) +{ + struct kempld_device_data *pld = gpio->pld; + struct gpio_chip *chip = &gpio->chip; + struct gpio_irq_chip *girq; + unsigned int irq; + int ret; + + /* Get the IRQ configured by the BIOS in the PLD */ + kempld_get_mutex(pld); + irq = kempld_read8(pld, KEMPLD_IRQ_GPIO); + kempld_release_mutex(pld); + + if (irq == 0xff) { + dev_info(dev, "GPIO controller has no IRQ support\n"); + return 0; + } + + /* Allow overriding the IRQ with the module parameter */ + if (gpio_irq > 0) { + dev_warn(dev, "Forcing IRQ to %d\n", gpio_irq); + irq &= ~KEMPLD_IRQ_GPIO_MASK; + irq |= gpio_irq & KEMPLD_IRQ_GPIO_MASK; + } + + if (!(irq & KEMPLD_IRQ_GPIO_MASK)) { + dev_warn(dev, "No IRQ configured\n"); + return 0; + } + + /* Get the current config, disable all child interrupts, clear them + * and set the parent IRQ + */ + kempld_get_mutex(pld); + gpio->evt_low_high = kempld_read16(pld, KEMPLD_GPIO_EVT_LOW_HIGH); + gpio->evt_lvl_edge = kempld_read16(pld, KEMPLD_GPIO_EVT_LVL_EDGE); + kempld_write16(pld, KEMPLD_GPIO_IEN, 0); + kempld_write16(pld, KEMPLD_GPIO_STS, 0xFFFF); + kempld_write16(pld, KEMPLD_IRQ_GPIO, irq); + kempld_release_mutex(pld); + + girq = &chip->irq; + gpio_irq_chip_set_chip(girq, &kempld_irqchip); + + girq->parent_handler = NULL; + girq->num_parents = 0; + girq->parents = NULL; + girq->default_type = IRQ_TYPE_NONE; + girq->handler = handle_simple_irq; + girq->threaded = true; + + mutex_init(&gpio->irq_lock); + + ret = devm_request_threaded_irq(dev, irq & KEMPLD_IRQ_GPIO_MASK, + NULL, kempld_gpio_irq_handler, + IRQF_ONESHOT, chip->label, + gpio); + if (ret) { + dev_err(dev, "failed to request irq %d\n", irq); + return ret; + } + + return 0; +} + static int kempld_gpio_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -247,6 +435,10 @@ static int kempld_gpio_probe(struct platform_device *pdev) return -ENODEV; } + ret = kempld_gpio_irq_init(dev, gpio); + if (ret) + return ret; + ret = devm_gpiochip_add_data(dev, chip, gpio); if (ret) { dev_err(dev, "Could not register GPIO chip\n"); diff --git a/include/linux/mfd/kempld.h b/include/linux/mfd/kempld.h index 643c096b93ac..2dbd80abfd1d 100644 --- a/include/linux/mfd/kempld.h +++ b/include/linux/mfd/kempld.h @@ -37,6 +37,7 @@ #define KEMPLD_SPEC_GET_MINOR(x) (x & 0x0f) #define KEMPLD_SPEC_GET_MAJOR(x) ((x >> 4) & 0x0f) #define KEMPLD_IRQ_GPIO 0x35 +#define KEMPLD_IRQ_GPIO_MASK 0x0f #define KEMPLD_IRQ_I2C 0x36 #define KEMPLD_CFG 0x37 #define KEMPLD_CFG_GPIO_I2C_MUX (1 << 0) From 7673e4c7f7f99bfc9f30294ac8ab769dbb386866 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 14 Mar 2026 00:07:50 +0100 Subject: [PATCH 53/79] Do not enable the v1 uAPI by default It's been five years since we introduced the v2 uAPI and the major consumer libgpiod is at v2.2.3. Let's discourage the old ABI. Signed-off-by: Linus Walleij Reviewed-by: Kent Gibson Link: https://patch.msgid.link/20260314-no-y-uapi1-default-v2-1-578f09c91b8f@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index d665afe19709..6d39c8712513 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -95,7 +95,6 @@ config GPIO_CDEV config GPIO_CDEV_V1 bool "Support GPIO ABI Version 1" - default y depends on GPIO_CDEV help Say Y here to support version 1 of the GPIO CDEV ABI. @@ -103,8 +102,6 @@ config GPIO_CDEV_V1 This ABI version is deprecated. Please use the latest ABI for new developments. - If unsure, say Y. - config GPIO_GENERIC depends on HAS_IOMEM # Only for IOMEM drivers tristate From 3518fd4c780d4fa4c0e2cedd95c0f8bc5d8b457f Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Fri, 13 Mar 2026 05:41:12 +0000 Subject: [PATCH 54/79] Revert "gpio: Access `gpio_bus_type` in gpiochip_setup_dev()" This reverts commit cc11f4ef666fbca02c8a2f11d0184d57e6b75579. Commit cc11f4ef666f ("gpio: Access `gpio_bus_type` in gpiochip_setup_dev()") moved the bus type assignment from gpiochip_add_data_with_key() to gpiochip_setup_dev(). This change introduced a bug where dev_printk() and friends might access the bus name after gpiochip_add_data_with_key() but before gpiochip_setup_dev() has run. In this window, the bus type is not yet initialized, leading to empty bus names in logs. Move the bus type assignment back to gpiochip_add_data_with_key() to ensure the bus name is available before any potential users like dev_printk(). Reported-by: Geert Uytterhoeven Closes: https://lore.kernel.org/all/CAMuHMdU0Xb=Moca5LUex+VxyHQa2-uYJgYf4hzHiSEjDCQQT=Q@mail.gmail.com/ Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260313054112.1248074-1-tzungbi@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index e287ab0d21fc..9130f2e001ac 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -908,8 +908,6 @@ static int gpiochip_setup_dev(struct gpio_chip *gc) struct fwnode_handle *fwnode = dev_fwnode(&gdev->dev); int ret; - gdev->dev.bus = &gpio_bus_type; - /* * If fwnode doesn't belong to another device, it's safe to clear its * initialized flag. @@ -1160,6 +1158,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, * then make sure they get free():ed there. */ gdev->dev.type = &gpio_dev_type; + gdev->dev.bus = &gpio_bus_type; gdev->dev.parent = gc->parent; device_set_node(&gdev->dev, gpiochip_choose_fwnode(gc)); @@ -1299,8 +1298,8 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, * we get a device node entry in sysfs under * /sys/bus/gpio/devices/gpiochipN/dev that can be used for * coldplug of device nodes and other udev business. - * We can do this only if gpiolib has been initialized - * (i.e., `gpio_bus_type` is ready). Otherwise, defer until later. + * We can do this only if gpiolib has been initialized. + * Otherwise, defer until later. */ if (gpiolib_initialized) { ret = gpiochip_setup_dev(gc); From a6e53d05ab849779d55ca985566a1da7f22435b9 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Thu, 12 Mar 2026 17:12:09 -0700 Subject: [PATCH 55/79] gpio: cs5535: use dynamically allocated priv struct Static allocation is deprecated. Remove the FIXME as gpiochip_add_data allows using gpiod_get_data. No need for container_of. Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260313001209.117823-1-rosenp@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-cs5535.c | 48 +++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/drivers/gpio/gpio-cs5535.c b/drivers/gpio/gpio-cs5535.c index 8affe4e9f90e..5f5373d86397 100644 --- a/drivers/gpio/gpio-cs5535.c +++ b/drivers/gpio/gpio-cs5535.c @@ -39,10 +39,6 @@ static ulong mask = GPIO_DEFAULT_MASK; module_param_named(mask, mask, ulong, 0444); MODULE_PARM_DESC(mask, "GPIO channel mask."); -/* - * FIXME: convert this singleton driver to use the state container - * design pattern, see Documentation/driver-api/driver-model/design-patterns.rst - */ static struct cs5535_gpio_chip { struct gpio_chip chip; resource_size_t base; @@ -285,30 +281,29 @@ static const char * const cs5535_gpio_names[] = { "GPIO28", NULL, NULL, NULL, }; -static struct cs5535_gpio_chip cs5535_gpio_chip = { - .chip = { - .owner = THIS_MODULE, - .label = DRV_NAME, - - .base = 0, - .ngpio = 32, - .names = cs5535_gpio_names, - .request = chip_gpio_request, - - .get = chip_gpio_get, - .set = chip_gpio_set, - - .direction_input = chip_direction_input, - .direction_output = chip_direction_output, - }, -}; - static int cs5535_gpio_probe(struct platform_device *pdev) { + struct cs5535_gpio_chip *priv; + struct gpio_chip *gc; struct resource *res; int err = -EIO; ulong mask_orig = mask; + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + gc = &priv->chip; + gc->owner = THIS_MODULE; + gc->label = DRV_NAME; + gc->ngpio = 32; + gc->names = cs5535_gpio_names; + gc->request = chip_gpio_request; + gc->get = chip_gpio_get; + gc->set = chip_gpio_set; + gc->direction_input = chip_direction_input; + gc->direction_output = chip_direction_output; + /* There are two ways to get the GPIO base address; one is by * fetching it from MSR_LBAR_GPIO, the other is by reading the * PCI BAR info. The latter method is easier (especially across @@ -329,9 +324,9 @@ static int cs5535_gpio_probe(struct platform_device *pdev) } /* set up the driver-specific struct */ - cs5535_gpio_chip.base = res->start; - cs5535_gpio_chip.pdev = pdev; - spin_lock_init(&cs5535_gpio_chip.lock); + priv->base = res->start; + priv->pdev = pdev; + spin_lock_init(&priv->lock); dev_info(&pdev->dev, "reserved resource region %pR\n", res); @@ -347,8 +342,7 @@ static int cs5535_gpio_probe(struct platform_device *pdev) mask_orig, mask); /* finally, register with the generic GPIO API */ - return devm_gpiochip_add_data(&pdev->dev, &cs5535_gpio_chip.chip, - &cs5535_gpio_chip); + return devm_gpiochip_add_data(&pdev->dev, gc, priv); } static struct platform_driver cs5535_gpio_driver = { From 9a5bf2f53b76b1619c602f9e751fe4c0e64713ca Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Thu, 19 Mar 2026 17:53:38 -0700 Subject: [PATCH 56/79] gpio: dwapb: reduce allocation to single kzalloc Instead of kzalloc + kcalloc, Combine the two using a flexible array member. Allows using __counted_by for extra runtime analysis. Move counting variable to right after allocation as required by __counted_by. Signed-off-by: Rosen Penev Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260320005338.30355-1-rosenp@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-dwapb.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/drivers/gpio/gpio-dwapb.c b/drivers/gpio/gpio-dwapb.c index 4986c465c9a8..15cebc8b5d66 100644 --- a/drivers/gpio/gpio-dwapb.c +++ b/drivers/gpio/gpio-dwapb.c @@ -75,8 +75,8 @@ struct dwapb_port_property { }; struct dwapb_platform_data { - struct dwapb_port_property *properties; unsigned int nports; + struct dwapb_port_property properties[] __counted_by(nports); }; /* Store GPIO context across system-wide suspend/resume transitions */ @@ -114,11 +114,11 @@ static inline struct dwapb_gpio *to_dwapb_gpio(struct gpio_chip *gc) struct dwapb_gpio { struct device *dev; void __iomem *regs; - struct dwapb_gpio_port *ports; unsigned int nr_ports; unsigned int flags; struct reset_control *rst; struct clk_bulk_data clks[DWAPB_NR_CLOCKS]; + struct dwapb_gpio_port ports[] __counted_by(nr_ports); }; static inline u32 gpio_reg_v2_convert(unsigned int offset) @@ -585,14 +585,10 @@ static struct dwapb_platform_data *dwapb_gpio_get_pdata(struct device *dev) if (nports == 0) return ERR_PTR(-ENODEV); - pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + pdata = devm_kzalloc(dev, struct_size(pdata, properties, nports), GFP_KERNEL); if (!pdata) return ERR_PTR(-ENOMEM); - pdata->properties = devm_kcalloc(dev, nports, sizeof(*pp), GFP_KERNEL); - if (!pdata->properties) - return ERR_PTR(-ENOMEM); - pdata->nports = nports; i = 0; @@ -714,22 +710,17 @@ static int dwapb_gpio_probe(struct platform_device *pdev) if (IS_ERR(pdata)) return PTR_ERR(pdata); - gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL); + gpio = devm_kzalloc(&pdev->dev, struct_size(gpio, ports, pdata->nports), GFP_KERNEL); if (!gpio) return -ENOMEM; - gpio->dev = &pdev->dev; gpio->nr_ports = pdata->nports; + gpio->dev = &pdev->dev; err = dwapb_get_reset(gpio); if (err) return err; - gpio->ports = devm_kcalloc(&pdev->dev, gpio->nr_ports, - sizeof(*gpio->ports), GFP_KERNEL); - if (!gpio->ports) - return -ENOMEM; - gpio->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(gpio->regs)) return PTR_ERR(gpio->regs); From ececb46fc947705f22cc8c1f9182224e7ec4bb97 Mon Sep 17 00:00:00 2001 From: Conor Dooley Date: Wed, 18 Mar 2026 11:04:32 +0000 Subject: [PATCH 57/79] dt-bindings: gpio: fix microchip,mpfs-gpio interrupt documentation The microchip,mpfs-gpio binding suffered greatly due to being written with a narrow minded view of the controller, and the interrupt bits ended up incorrect. It was mistakenly assumed that the interrupt configuration was set by platform firmware, based on the FPGA configuration, and that the GPIO DT nodes were the only way to really communicate interrupt configuration to software. Instead, the mux should be a device in its own right, and the GPIO controllers should be connected to it, rather than to the PLIC. Now that a binding exists for that mux, try to fix the misconceptions in the GPIO controller binding. Firstly, it's not possible for this controller to have fewer than 14 GPIOs, and thus 14 interrupts also. There are three controllers, with 14, 24 & 32 GPIOs each. The fabric core, CoreGPIO, can of course have a customisable number of GPIOs. The example is wacky too - it follows from the incorrect understanding that the GPIO controllers are connected to the PLIC directly. They are not however, with a mux sitting in between. Update the example to use the mux as a parent, and the interrupt numbers at the mux for GPIO2 as the example - rather than the strange looking, repeated <53>. Signed-off-by: Conor Dooley Acked-by: Rob Herring (Arm) Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260318-fondly-tradition-90b8241f0cc8@spud Signed-off-by: Bartosz Golaszewski --- .../bindings/gpio/microchip,mpfs-gpio.yaml | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Documentation/devicetree/bindings/gpio/microchip,mpfs-gpio.yaml b/Documentation/devicetree/bindings/gpio/microchip,mpfs-gpio.yaml index eaa254a46806..6a0c5341d8a4 100644 --- a/Documentation/devicetree/bindings/gpio/microchip,mpfs-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/microchip,mpfs-gpio.yaml @@ -65,6 +65,11 @@ allOf: contains: const: microchip,mpfs-gpio then: + properties: + ngpios: + enum: [14, 24, 32] + interrupts: + minItems: 14 required: - interrupts - "#interrupt-cells" @@ -85,18 +90,19 @@ examples: compatible = "microchip,mpfs-gpio"; reg = <0x20122000 0x1000>; clocks = <&clkcfg 25>; - interrupt-parent = <&plic>; + interrupt-parent = <&irqmux>; gpio-controller; #gpio-cells = <2>; + ngpios = <32>; interrupt-controller; #interrupt-cells = <1>; - interrupts = <53>, <53>, <53>, <53>, - <53>, <53>, <53>, <53>, - <53>, <53>, <53>, <53>, - <53>, <53>, <53>, <53>, - <53>, <53>, <53>, <53>, - <53>, <53>, <53>, <53>, - <53>, <53>, <53>, <53>, - <53>, <53>, <53>, <53>; + interrupts = <64>, <65>, <66>, <67>, + <68>, <69>, <70>, <71>, + <72>, <73>, <74>, <75>, + <76>, <77>, <78>, <79>, + <80>, <81>, <82>, <83>, + <84>, <85>, <86>, <87>, + <88>, <89>, <90>, <91>, + <92>, <93>, <94>, <95>; }; ... From 7803501e5754dc4b295ab22b20562e2b965358ba Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 16 Mar 2026 10:45:23 +0100 Subject: [PATCH 58/79] gpio: drop unneeded Kconfig dependencies on OF_GPIO OF_GPIO is selected automatically on all OF systems. Any symbols it controls also provide stubs so there's really no reason to select it explicitly. Remove all Kconfig dependencies/selects for GPIO drivers. For those that have no other dependencies: convert it to requiring CONFIG_OF instead to avoid new symbols popping up in make config. Link: https://patch.msgid.link/20260316-gpio-of-kconfig-v2-3-de2f4b00a0e4@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 53 ++++++++++++++------------------------------ 1 file changed, 17 insertions(+), 36 deletions(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 6d39c8712513..4294d6cbccf4 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -142,7 +142,7 @@ menu "Memory mapped GPIO drivers" config GPIO_74XX_MMIO tristate "GPIO driver for 74xx-ICs with MMIO access" - depends on OF_GPIO + depends on OF select GPIO_GENERIC help Say yes here to support GPIO functionality for 74xx-compatible ICs @@ -172,14 +172,14 @@ config GPIO_AMDPT config GPIO_ASPEED tristate "Aspeed GPIO support" - depends on (ARCH_ASPEED || COMPILE_TEST) && OF_GPIO + depends on ARCH_ASPEED || COMPILE_TEST select GPIOLIB_IRQCHIP help Say Y here to support Aspeed AST2400 and AST2500 GPIO controllers. config GPIO_ASPEED_SGPIO bool "Aspeed SGPIO support" - depends on (ARCH_ASPEED || COMPILE_TEST) && OF_GPIO + depends on ARCH_ASPEED || COMPILE_TEST select GPIO_GENERIC select GPIOLIB_IRQCHIP help @@ -198,7 +198,6 @@ config GPIO_ATH79 config GPIO_RASPBERRYPI_EXP tristate "Raspberry Pi 3 GPIO Expander" default RASPBERRYPI_FIRMWARE - depends on OF_GPIO # Make sure not 'y' when RASPBERRYPI_FIRMWARE is 'm'. This can only # happen when COMPILE_TEST=y, hence the added !RASPBERRYPI_FIRMWARE. depends on (ARCH_BCM2835 && RASPBERRYPI_FIRMWARE) || (COMPILE_TEST && !RASPBERRYPI_FIRMWARE) @@ -215,7 +214,7 @@ config GPIO_BCM_KONA config GPIO_BCM_XGS_IPROC tristate "BRCM XGS iProc GPIO support" - depends on OF_GPIO && (ARCH_BCM_IPROC || COMPILE_TEST) + depends on ARCH_BCM_IPROC || COMPILE_TEST select GPIO_GENERIC select GPIOLIB_IRQCHIP default ARCH_BCM_IPROC @@ -226,7 +225,6 @@ config GPIO_BLZP1600 tristate "Blaize BLZP1600 GPIO support" default y if ARCH_BLAIZE depends on ARCH_BLAIZE || COMPILE_TEST - depends on OF_GPIO select GPIO_GENERIC select GPIOLIB_IRQCHIP help @@ -237,7 +235,7 @@ config GPIO_BLZP1600 config GPIO_BRCMSTB tristate "BRCMSTB GPIO support" default y if (ARCH_BRCMSTB || BMIPS_GENERIC) - depends on OF_GPIO && (ARCH_BRCMSTB || ARCH_BCM2835 || BMIPS_GENERIC || COMPILE_TEST) + depends on ARCH_BRCMSTB || ARCH_BCM2835 || BMIPS_GENERIC || COMPILE_TEST select GPIO_GENERIC select IRQ_DOMAIN help @@ -245,7 +243,7 @@ config GPIO_BRCMSTB config GPIO_CADENCE tristate "Cadence GPIO support" - depends on OF_GPIO + depends on OF select GPIO_GENERIC select GPIOLIB_IRQCHIP help @@ -277,14 +275,13 @@ config GPIO_DWAPB config GPIO_EIC_SPRD tristate "Spreadtrum EIC support" depends on ARCH_SPRD || COMPILE_TEST - depends on OF_GPIO select GPIOLIB_IRQCHIP help Say yes here to support Spreadtrum EIC device. config GPIO_EM tristate "Emma Mobile GPIO" - depends on (ARCH_EMEV2 || COMPILE_TEST) && OF_GPIO + depends on ARCH_EMEV2 || COMPILE_TEST help Say yes here to support GPIO on Renesas Emma Mobile SoCs. @@ -326,7 +323,7 @@ config GPIO_GE_FPGA config GPIO_FTGPIO010 bool "Faraday FTGPIO010 GPIO" - depends on OF_GPIO + depends on OF select GPIO_GENERIC select GPIOLIB_IRQCHIP default (ARCH_GEMINI || ARCH_MOXART) @@ -380,7 +377,7 @@ config GPIO_HISI config GPIO_HLWD tristate "Nintendo Wii (Hollywood) GPIO" - depends on OF_GPIO + depends on OF select GPIO_GENERIC select GPIOLIB_IRQCHIP help @@ -431,7 +428,6 @@ config GPIO_LOONGSON config GPIO_LOONGSON_64BIT tristate "Loongson 64 bit GPIO support" depends on LOONGARCH || COMPILE_TEST - depends on OF_GPIO select GPIO_GENERIC select GPIOLIB_IRQCHIP help @@ -444,7 +440,7 @@ config GPIO_LOONGSON_64BIT config GPIO_LPC18XX tristate "NXP LPC18XX/43XX GPIO support" default y if ARCH_LPC18XX - depends on OF_GPIO && (ARCH_LPC18XX || COMPILE_TEST) + depends on ARCH_LPC18XX || COMPILE_TEST select IRQ_DOMAIN_HIERARCHY select GPIOLIB_IRQCHIP help @@ -453,7 +449,7 @@ config GPIO_LPC18XX config GPIO_LPC32XX tristate "NXP LPC32XX GPIO support" - depends on OF_GPIO && (ARCH_LPC32XX || COMPILE_TEST) + depends on ARCH_LPC32XX || COMPILE_TEST help Select this option to enable GPIO driver for NXP LPC32XX devices. @@ -496,7 +492,6 @@ config GPIO_MPC8XXX config GPIO_MT7621 bool "Mediatek MT7621 GPIO Support" depends on SOC_MT7620 || SOC_MT7621 || COMPILE_TEST - depends on OF_GPIO select GPIO_GENERIC select GPIOLIB_IRQCHIP help @@ -589,7 +584,6 @@ config GPIO_RCAR config GPIO_RDA bool "RDA Micro GPIO controller support" depends on ARCH_RDA || COMPILE_TEST - depends on OF_GPIO select GPIO_GENERIC select GPIOLIB_IRQCHIP help @@ -639,7 +633,6 @@ config GPIO_RTD config GPIO_SAMA5D2_PIOBU tristate "SAMA5D2 PIOBU GPIO support" depends on MFD_SYSCON - depends on OF_GPIO depends on ARCH_AT91 || COMPILE_TEST select GPIO_SYSCON help @@ -651,7 +644,7 @@ config GPIO_SAMA5D2_PIOBU config GPIO_SIFIVE tristate "SiFive GPIO support" - depends on OF_GPIO + depends on OF select IRQ_DOMAIN_HIERARCHY select GPIO_GENERIC select GPIOLIB_IRQCHIP @@ -670,7 +663,6 @@ config GPIO_SIOX config GPIO_SNPS_CREG bool "Synopsys GPIO via CREG (Control REGisters) driver" depends on ARC || COMPILE_TEST - depends on OF_GPIO help This driver supports GPIOs via CREG on various Synopsys SoCs. This is a single-register MMIO GPIO driver for complex cases @@ -680,7 +672,6 @@ config GPIO_SNPS_CREG config GPIO_SPACEMIT_K1 tristate "SPACEMIT K1 GPIO support" depends on ARCH_SPACEMIT || COMPILE_TEST - depends on OF_GPIO select GPIO_GENERIC select GPIOLIB_IRQCHIP help @@ -696,7 +687,6 @@ config GPIO_SPEAR_SPICS config GPIO_SPRD tristate "Spreadtrum GPIO support" depends on ARCH_SPRD || COMPILE_TEST - depends on OF_GPIO select GPIOLIB_IRQCHIP help Say yes here to support Spreadtrum GPIO device. @@ -704,7 +694,6 @@ config GPIO_SPRD config GPIO_STP_XWAY bool "XWAY STP GPIOs" depends on SOC_XWAY || COMPILE_TEST - depends on OF_GPIO help This enables support for the Serial To Parallel (STP) unit found on XWAY SoC. The STP allows the SoC to drive a shift registers cascade, @@ -739,7 +728,6 @@ config GPIO_TEGRA tristate "NVIDIA Tegra GPIO support" default ARCH_TEGRA depends on ARCH_TEGRA || COMPILE_TEST - depends on OF_GPIO select GPIOLIB_IRQCHIP select IRQ_DOMAIN_HIERARCHY help @@ -749,7 +737,6 @@ config GPIO_TEGRA186 tristate "NVIDIA Tegra186 GPIO support" default ARCH_TEGRA_186_SOC || ARCH_TEGRA_194_SOC || ARCH_TEGRA_234_SOC depends on ARCH_TEGRA_186_SOC || ARCH_TEGRA_194_SOC || ARCH_TEGRA_234_SOC || COMPILE_TEST - depends on OF_GPIO select GPIOLIB_IRQCHIP select IRQ_DOMAIN_HIERARCHY help @@ -757,7 +744,6 @@ config GPIO_TEGRA186 config GPIO_TS4800 tristate "TS-4800 DIO blocks and compatibles" - depends on OF_GPIO depends on SOC_IMX51 || COMPILE_TEST select GPIO_GENERIC help @@ -777,7 +763,6 @@ config GPIO_THUNDERX config GPIO_UNIPHIER tristate "UniPhier GPIO support" depends on ARCH_UNIPHIER || COMPILE_TEST - depends on OF_GPIO select IRQ_DOMAIN_HIERARCHY help Say yes here to support UniPhier GPIOs. @@ -794,7 +779,6 @@ config GPIO_VF610 config GPIO_VISCONTI tristate "Toshiba Visconti GPIO support" depends on ARCH_VISCONTI || COMPILE_TEST - depends on OF_GPIO select GPIOLIB_IRQCHIP select GPIO_GENERIC select IRQ_DOMAIN_HIERARCHY @@ -803,14 +787,14 @@ config GPIO_VISCONTI config GPIO_WCD934X tristate "Qualcomm Technologies Inc WCD9340/WCD9341 GPIO controller driver" - depends on MFD_WCD934X && OF_GPIO + depends on MFD_WCD934X help This driver is to support GPIO block found on the Qualcomm Technologies Inc WCD9340/WCD9341 Audio Codec. config GPIO_XGENE bool "APM X-Gene GPIO controller support" - depends on ARM64 && OF_GPIO + depends on ARM64 help This driver is to support the GPIO block within the APM X-Gene SoC platform's generic flash controller. The GPIO pins are muxed with @@ -1108,7 +1092,7 @@ menu "I2C GPIO expanders" config GPIO_ADNP tristate "Avionic Design N-bit GPIO expander" - depends on OF_GPIO + depends on OF select GPIOLIB_IRQCHIP help This option enables support for N GPIOs found on Avionic Design @@ -1141,7 +1125,7 @@ config GPIO_DS4520 config GPIO_GW_PLD tristate "Gateworks PLD GPIO Expander" - depends on OF_GPIO + depends on OF help Say yes here to provide access to the Gateworks I2C PLD GPIO Expander. This is used at least on the Cambria GW2358-4. @@ -1565,7 +1549,6 @@ config GPIO_PALMAS config GPIO_PMIC_EIC_SPRD tristate "Spreadtrum PMIC EIC support" depends on MFD_SC27XX_PMIC || COMPILE_TEST - depends on OF_GPIO select GPIOLIB_IRQCHIP help Say yes here to support Spreadtrum PMIC EIC device. @@ -1604,7 +1587,6 @@ config GPIO_SL28CPLD config GPIO_STMPE tristate "STMPE GPIOs" depends on MFD_STMPE - depends on OF_GPIO select GPIOLIB_IRQCHIP help This enables support for the GPIOs found on the STMPE I/O @@ -1613,7 +1595,6 @@ config GPIO_STMPE config GPIO_TC3589X bool "TC3589X GPIOs" depends on MFD_TC3589X - depends on OF_GPIO select GPIOLIB_IRQCHIP help This enables support for the GPIOs found on the TC3589X @@ -2003,7 +1984,7 @@ config GPIO_LATCH config GPIO_LINE_MUX tristate "GPIO line mux driver" - depends on OF_GPIO + depends on OF select MULTIPLEXER help Say Y here to support the GPIO line mux, which can provide virtual From 96b76f7bc575ac6c69090f4642e424b04fb6784c Mon Sep 17 00:00:00 2001 From: AKASHI Takahiro Date: Mon, 23 Mar 2026 22:01:10 +0300 Subject: [PATCH 59/79] pinctrl: introduce pinctrl_gpio_get_config() This is a counterpart of pinctrl_gpio_set_config(), which will be used to implement the ->get() interface in a GPIO driver for SCMI. This also requires that we create a stub so pin_config_get_for_pin() can build when CONFIG_PINCONF is disabled. Signed-off-by: AKASHI Takahiro Signed-off-by: Dan Carpenter Reviewed-by: Linus Walleij Reviewed-by: Andy Shevchenko Signed-off-by: Linus Walleij --- drivers/pinctrl/core.c | 31 +++++++++++++++++++++++++++++++ drivers/pinctrl/pinconf.h | 6 ++++++ include/linux/pinctrl/consumer.h | 9 +++++++++ 3 files changed, 46 insertions(+) diff --git a/drivers/pinctrl/core.c b/drivers/pinctrl/core.c index b5e97689589f..da0a07742460 100644 --- a/drivers/pinctrl/core.c +++ b/drivers/pinctrl/core.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include "core.h" @@ -938,6 +939,36 @@ int pinctrl_gpio_set_config(struct gpio_chip *gc, unsigned int offset, } EXPORT_SYMBOL_GPL(pinctrl_gpio_set_config); +/** + * pinctrl_gpio_get_config() - Get the config for a given GPIO pin + * @gc: GPIO chip structure from the GPIO subsystem + * @offset: hardware offset of the GPIO relative to the controller + * @config: the configuration to query. On success it holds the result + * Return: 0 on success, negative errno otherwise + */ +int pinctrl_gpio_get_config(struct gpio_chip *gc, unsigned int offset, unsigned long *config) +{ + struct pinctrl_gpio_range *range; + struct pinctrl_dev *pctldev; + int ret, pin; + + ret = pinctrl_get_device_gpio_range(gc, offset, &pctldev, &range); + if (ret) + return ret; + + mutex_lock(&pctldev->mutex); + pin = gpio_to_pin(range, gc, offset); + ret = pin_config_get_for_pin(pctldev, pin, config); + mutex_unlock(&pctldev->mutex); + + if (ret) + return ret; + + *config = pinconf_to_config_argument(*config); + return 0; +} +EXPORT_SYMBOL_GPL(pinctrl_gpio_get_config); + static struct pinctrl_state *find_state(struct pinctrl *p, const char *name) { diff --git a/drivers/pinctrl/pinconf.h b/drivers/pinctrl/pinconf.h index 2880adef476e..659a781e2091 100644 --- a/drivers/pinctrl/pinconf.h +++ b/drivers/pinctrl/pinconf.h @@ -74,6 +74,12 @@ static inline int pinconf_set_config(struct pinctrl_dev *pctldev, unsigned int p return -ENOTSUPP; } +static inline int pin_config_get_for_pin(struct pinctrl_dev *pctldev, unsigned int pin, + unsigned long *config) +{ + return -ENOTSUPP; +} + #endif #if defined(CONFIG_PINCONF) && defined(CONFIG_DEBUG_FS) diff --git a/include/linux/pinctrl/consumer.h b/include/linux/pinctrl/consumer.h index 63ce16191eb9..11b8f0b8da0c 100644 --- a/include/linux/pinctrl/consumer.h +++ b/include/linux/pinctrl/consumer.h @@ -35,6 +35,8 @@ int pinctrl_gpio_direction_output(struct gpio_chip *gc, unsigned int offset); int pinctrl_gpio_set_config(struct gpio_chip *gc, unsigned int offset, unsigned long config); +int pinctrl_gpio_get_config(struct gpio_chip *gc, unsigned int offset, + unsigned long *config); struct pinctrl * __must_check pinctrl_get(struct device *dev); void pinctrl_put(struct pinctrl *p); @@ -101,6 +103,13 @@ pinctrl_gpio_direction_output(struct gpio_chip *gc, unsigned int offset) return 0; } +static inline int +pinctrl_gpio_get_config(struct gpio_chip *gc, unsigned int offset, + unsigned long *config) +{ + return 0; +} + static inline int pinctrl_gpio_set_config(struct gpio_chip *gc, unsigned int offset, unsigned long config) From 9ea2647b0089ce8d7e2723eda4c77cbc55f8b7c5 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 23 Mar 2026 22:01:15 +0300 Subject: [PATCH 60/79] pinctrl: scmi: Add SCMI_PIN_INPUT_VALUE The PIN_CONFIG_LEVEL parameter represents the value of the pin, whether reading or writing to the pin. In SCMI, the parameter is represented by two different values SCMI_PIN_OUTPUT_VALUE for writing to a pin and SCMI_PIN_INPUT_VALUE for reading. The current code translates PIN_CONFIG_LEVEL as SCMI_PIN_OUTPUT_VALUE (writing). Add a function to translate it to either INPUT or OUTPUT depending on whether it is called from a _get or _set() operation. Signed-off-by: Dan Carpenter Reviewed-by: Linus Walleij Acked-by: Sudeep Holla Signed-off-by: Linus Walleij --- drivers/pinctrl/pinctrl-scmi.c | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/drivers/pinctrl/pinctrl-scmi.c b/drivers/pinctrl/pinctrl-scmi.c index f4f296e07be5..5d347e6b2e4c 100644 --- a/drivers/pinctrl/pinctrl-scmi.c +++ b/drivers/pinctrl/pinctrl-scmi.c @@ -251,9 +251,6 @@ static int pinctrl_scmi_map_pinconf_type(enum pin_config_param param, case PIN_CONFIG_MODE_LOW_POWER: *type = SCMI_PIN_LOW_POWER_MODE; break; - case PIN_CONFIG_LEVEL: - *type = SCMI_PIN_OUTPUT_VALUE; - break; case PIN_CONFIG_OUTPUT_ENABLE: *type = SCMI_PIN_OUTPUT_MODE; break; @@ -276,6 +273,28 @@ static int pinctrl_scmi_map_pinconf_type(enum pin_config_param param, return 0; } +static int pinctrl_scmi_map_pinconf_type_get(enum pin_config_param param, + enum scmi_pinctrl_conf_type *type) +{ + if (param == PIN_CONFIG_LEVEL) { + *type = SCMI_PIN_INPUT_VALUE; + return 0; + } + + return pinctrl_scmi_map_pinconf_type(param, type); +} + +static int pinctrl_scmi_map_pinconf_type_set(enum pin_config_param param, + enum scmi_pinctrl_conf_type *type) +{ + if (param == PIN_CONFIG_LEVEL) { + *type = SCMI_PIN_OUTPUT_VALUE; + return 0; + } + + return pinctrl_scmi_map_pinconf_type(param, type); +} + static int pinctrl_scmi_pinconf_get(struct pinctrl_dev *pctldev, unsigned int pin, unsigned long *config) { @@ -290,7 +309,7 @@ static int pinctrl_scmi_pinconf_get(struct pinctrl_dev *pctldev, config_type = pinconf_to_config_param(*config); - ret = pinctrl_scmi_map_pinconf_type(config_type, &type); + ret = pinctrl_scmi_map_pinconf_type_get(config_type, &type); if (ret) return ret; @@ -363,7 +382,7 @@ static int pinctrl_scmi_pinconf_set(struct pinctrl_dev *pctldev, for (i = 0; i < num_configs; i++) { param = pinconf_to_config_param(configs[i]); - ret = pinctrl_scmi_map_pinconf_type(param, &p_config_type[i]); + ret = pinctrl_scmi_map_pinconf_type_set(param, &p_config_type[i]); if (ret) { dev_err(pmx->dev, "Error map pinconf_type %d\n", ret); goto free_config; @@ -405,7 +424,7 @@ static int pinctrl_scmi_pinconf_group_set(struct pinctrl_dev *pctldev, for (i = 0; i < num_configs; i++) { param = pinconf_to_config_param(configs[i]); - ret = pinctrl_scmi_map_pinconf_type(param, &p_config_type[i]); + ret = pinctrl_scmi_map_pinconf_type_set(param, &p_config_type[i]); if (ret) { dev_err(pmx->dev, "Error map pinconf_type %d\n", ret); goto free_config; @@ -440,7 +459,7 @@ static int pinctrl_scmi_pinconf_group_get(struct pinctrl_dev *pctldev, return -EINVAL; config_type = pinconf_to_config_param(*config); - ret = pinctrl_scmi_map_pinconf_type(config_type, &type); + ret = pinctrl_scmi_map_pinconf_type_get(config_type, &type); if (ret) { dev_err(pmx->dev, "Error map pinconf_type %d\n", ret); return ret; From 37a584414d9ceca48f4367dcb829bf9dc6015988 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 23 Mar 2026 22:01:32 +0300 Subject: [PATCH 61/79] pinctrl: scmi: Delete PIN_CONFIG_OUTPUT_IMPEDANCE_OHMS support The argument for PIN_CONFIG_OUTPUT_IMPEDANCE_OHMS is supposed to be expressed in terms of ohms. But the pinctrl-scmi driver was implementing it the same as PIN_CONFIG_OUTPUT and writing either a zero or one to the pin. The SCMI protocol doesn't have an support configuration type so just delete this code instead of replacing it. Cc: Peng Fan Signed-off-by: Dan Carpenter Reviewed-by: Linus Walleij Signed-off-by: Linus Walleij --- drivers/pinctrl/pinctrl-scmi.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/pinctrl/pinctrl-scmi.c b/drivers/pinctrl/pinctrl-scmi.c index 5d347e6b2e4c..de8c113bc61d 100644 --- a/drivers/pinctrl/pinctrl-scmi.c +++ b/drivers/pinctrl/pinctrl-scmi.c @@ -254,9 +254,6 @@ static int pinctrl_scmi_map_pinconf_type(enum pin_config_param param, case PIN_CONFIG_OUTPUT_ENABLE: *type = SCMI_PIN_OUTPUT_MODE; break; - case PIN_CONFIG_OUTPUT_IMPEDANCE_OHMS: - *type = SCMI_PIN_OUTPUT_VALUE; - break; case PIN_CONFIG_POWER_SOURCE: *type = SCMI_PIN_POWER_SOURCE; break; From f20e81322f3a071db248f050c32713b503ae1fa4 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 23 Mar 2026 22:01:22 +0300 Subject: [PATCH 62/79] pinctrl: scmi: ignore PIN_CONFIG_PERSIST_STATE The PIN_CONFIG_PERSIST_STATE setting ensures that the pin state persists across a sleep or controller reset. The SCMI spec does not have an equivalent command to this so just ignore it. Signed-off-by: Dan Carpenter Reviewed-by: Linus Walleij Signed-off-by: Linus Walleij --- drivers/pinctrl/pinctrl-scmi.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/pinctrl/pinctrl-scmi.c b/drivers/pinctrl/pinctrl-scmi.c index de8c113bc61d..f22be6b7b82a 100644 --- a/drivers/pinctrl/pinctrl-scmi.c +++ b/drivers/pinctrl/pinctrl-scmi.c @@ -361,7 +361,7 @@ static int pinctrl_scmi_pinconf_set(struct pinctrl_dev *pctldev, unsigned long *configs, unsigned int num_configs) { - int i, ret; + int i, cnt, ret; struct scmi_pinctrl *pmx = pinctrl_dev_get_drvdata(pctldev); enum scmi_pinctrl_conf_type config_type[SCMI_NUM_CONFIGS]; u32 config_value[SCMI_NUM_CONFIGS]; @@ -377,17 +377,21 @@ static int pinctrl_scmi_pinconf_set(struct pinctrl_dev *pctldev, if (ret) return ret; + cnt = 0; for (i = 0; i < num_configs; i++) { param = pinconf_to_config_param(configs[i]); - ret = pinctrl_scmi_map_pinconf_type_set(param, &p_config_type[i]); + if (param == PIN_CONFIG_PERSIST_STATE) + continue; + ret = pinctrl_scmi_map_pinconf_type_set(param, &p_config_type[cnt]); if (ret) { dev_err(pmx->dev, "Error map pinconf_type %d\n", ret); goto free_config; } - p_config_value[i] = pinconf_to_config_argument(configs[i]); + p_config_value[cnt] = pinconf_to_config_argument(configs[i]); + cnt++; } - ret = pinctrl_ops->settings_conf(pmx->ph, pin, PIN_TYPE, num_configs, + ret = pinctrl_ops->settings_conf(pmx->ph, pin, PIN_TYPE, cnt, p_config_type, p_config_value); if (ret) dev_err(pmx->dev, "Error parsing config %d\n", ret); From bf1fbd189d45216dec1f02f6e12fffde9f3b4ea6 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 23 Mar 2026 22:01:37 +0300 Subject: [PATCH 63/79] firmware: arm_scmi: Allow PINCTRL_REQUEST to return EOPNOTSUPP The SCMI protocol specification says that the PINCTRL_REQUEST and PINCTRL_RELEASE commands are optional. So if the SCMI server returns -EOPNOTSUPP, then treat that as success and continue. Signed-off-by: Dan Carpenter Reviewed-by: Linus Walleij Reviewed-by: Sudeep Holla Signed-off-by: Linus Walleij --- drivers/firmware/arm_scmi/pinctrl.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/firmware/arm_scmi/pinctrl.c b/drivers/firmware/arm_scmi/pinctrl.c index a020e23d7c49..42cb1aef1fe1 100644 --- a/drivers/firmware/arm_scmi/pinctrl.c +++ b/drivers/firmware/arm_scmi/pinctrl.c @@ -578,6 +578,8 @@ static int scmi_pinctrl_request_free(const struct scmi_protocol_handle *ph, tx->flags = cpu_to_le32(type); ret = ph->xops->do_xfer(ph, t); + if (ret == -EOPNOTSUPP) + ret = 0; ph->xops->xfer_put(ph, t); return ret; From 05a8a80efaacc42013d78fc3fe41159b7be4333c Mon Sep 17 00:00:00 2001 From: AKASHI Takahiro Date: Mon, 23 Mar 2026 22:01:42 +0300 Subject: [PATCH 64/79] gpio: dt-bindings: Add GPIO on top of generic pin control Traditionally, firmware will provide a GPIO interface or a pin control interface. However, the SCMI protocol provides a generic pin control interface and the GPIO support is built on top of that using the normal pin control interfaces. Potentially, other firmware will adopt a similar generic approach in the future. Document how to configure the GPIO device. Signed-off-by: AKASHI Takahiro Signed-off-by: Dan Carpenter Reviewed-by: Linus Walleij Reviewed-by: Krzysztof Kozlowski Signed-off-by: Linus Walleij --- .../bindings/gpio/pin-control-gpio.yaml | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 Documentation/devicetree/bindings/gpio/pin-control-gpio.yaml diff --git a/Documentation/devicetree/bindings/gpio/pin-control-gpio.yaml b/Documentation/devicetree/bindings/gpio/pin-control-gpio.yaml new file mode 100644 index 000000000000..a05cd339253a --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/pin-control-gpio.yaml @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/gpio/pin-control-gpio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Pin control based generic GPIO controller + +description: + The pin control-based GPIO will facilitate a pin controller's ability + to drive electric lines high/low and other generic properties of a + pin controller to perform general-purpose one-bit binary I/O. + +maintainers: + - Dan Carpenter + +properties: + compatible: + const: scmi-pinctrl-gpio + + gpio-controller: true + + "#gpio-cells": + const: 2 + + gpio-line-names: true + + gpio-ranges: true + + ngpios: true + +patternProperties: + "^.+-hog(-[0-9]+)?$": + type: object + + required: + - gpio-hog + +required: + - compatible + - gpio-controller + - "#gpio-cells" + - gpio-ranges + - ngpios + +additionalProperties: false + +examples: + - | + gpio { + compatible = "scmi-pinctrl-gpio"; + gpio-controller; + #gpio-cells = <2>; + ngpios = <4>; + gpio-line-names = "gpio_5_17", "gpio_5_20", "gpio_5_22", "gpio_2_1"; + gpio-ranges = <&scmi_pinctrl 0 30 4>; + pinctrl-names = "default"; + pinctrl-0 = <&keys_pins>; + }; From 7671f4949a6c9111234fdbcd577b227ace799f16 Mon Sep 17 00:00:00 2001 From: AKASHI Takahiro Date: Mon, 23 Mar 2026 22:01:47 +0300 Subject: [PATCH 65/79] gpio: gpio-by-pinctrl: add pinctrl based generic GPIO driver The ARM SCMI pinctrl protocol allows GPIO access. Instead of creating a new SCMI GPIO driver, this driver is a generic GPIO driver that uses standard pinctrl interfaces. Signed-off-by: AKASHI Takahiro Signed-off-by: Dan Carpenter Reviewed-by: Linus Walleij Acked-by: Bartosz Golaszewski Reviewed-by: Andy Shevchenko Signed-off-by: Linus Walleij --- drivers/gpio/Kconfig | 13 +++++ drivers/gpio/Makefile | 1 + drivers/gpio/gpio-by-pinctrl.c | 101 +++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 drivers/gpio/gpio-by-pinctrl.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index b45fb799e36c..c631ecb01e07 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -246,6 +246,19 @@ config GPIO_BRCMSTB help Say yes here to enable GPIO support for Broadcom STB (BCM7XXX) SoCs. +config GPIO_BY_PINCTRL + tristate "GPIO support based on a pure pin control backend" + depends on GPIOLIB + help + Support for generic GPIO handling based on top of pin control. + Traditionally, firmware creates a GPIO interface or a pin + controller interface and we have a driver to support it. But + in SCMI, the pin control interface is generic and we can + create a simple GPIO device based on the pin control interface + without doing anything custom. + + This driver used to do GPIO over the ARM SCMI protocol. + config GPIO_CADENCE tristate "Cadence GPIO support" depends on OF_GPIO diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index c05f7d795c43..20d4a57afdaa 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -51,6 +51,7 @@ obj-$(CONFIG_GPIO_BD9571MWV) += gpio-bd9571mwv.o obj-$(CONFIG_GPIO_BLZP1600) += gpio-blzp1600.o obj-$(CONFIG_GPIO_BRCMSTB) += gpio-brcmstb.o obj-$(CONFIG_GPIO_BT8XX) += gpio-bt8xx.o +obj-$(CONFIG_GPIO_BY_PINCTRL) += gpio-by-pinctrl.o obj-$(CONFIG_GPIO_CADENCE) += gpio-cadence.o obj-$(CONFIG_GPIO_CGBC) += gpio-cgbc.o obj-$(CONFIG_GPIO_CLPS711X) += gpio-clps711x.o diff --git a/drivers/gpio/gpio-by-pinctrl.c b/drivers/gpio/gpio-by-pinctrl.c new file mode 100644 index 000000000000..ddfdc479d38a --- /dev/null +++ b/drivers/gpio/gpio-by-pinctrl.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (C) 2026 Linaro Inc. +// Author: AKASHI takahiro + +#include +#include +#include +#include +#include +#include +#include + +#include "gpiolib.h" + +static int pin_control_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + unsigned long config; + int ret; + + config = PIN_CONFIG_OUTPUT_ENABLE; + ret = pinctrl_gpio_get_config(gc, offset, &config); + if (ret) + return ret; + if (config) + return GPIO_LINE_DIRECTION_OUT; + + return GPIO_LINE_DIRECTION_IN; +} + +static int pin_control_gpio_direction_output(struct gpio_chip *chip, + unsigned int offset, int val) +{ + return pinctrl_gpio_direction_output(chip, offset); +} + +static int pin_control_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + unsigned long config; + int ret; + + config = PIN_CONFIG_LEVEL; + ret = pinctrl_gpio_get_config(chip, offset, &config); + if (ret) + return ret; + + return !!config; +} + +static int pin_control_gpio_set(struct gpio_chip *chip, unsigned int offset, + int val) +{ + unsigned long config; + + config = pinconf_to_config_packed(PIN_CONFIG_LEVEL, val); + return pinctrl_gpio_set_config(chip, offset, config); +} + +static int pin_control_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct gpio_chip *chip; + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->label = dev_name(dev); + chip->parent = dev; + chip->base = -1; + + chip->request = gpiochip_generic_request; + chip->free = gpiochip_generic_free; + chip->get_direction = pin_control_gpio_get_direction; + chip->direction_input = pinctrl_gpio_direction_input; + chip->direction_output = pin_control_gpio_direction_output; + chip->get = pin_control_gpio_get; + chip->set = pin_control_gpio_set; + chip->set_config = gpiochip_generic_config; + + return devm_gpiochip_add_data(dev, chip, NULL); +} + +static const struct of_device_id pin_control_gpio_match[] = { + { .compatible = "scmi-pinctrl-gpio" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, pin_control_gpio_match); + +static struct platform_driver pin_control_gpio_driver = { + .probe = pin_control_gpio_probe, + .driver = { + .name = "pin-control-gpio", + .of_match_table = pin_control_gpio_match, + }, +}; +module_platform_driver(pin_control_gpio_driver); + +MODULE_AUTHOR("AKASHI Takahiro "); +MODULE_DESCRIPTION("Pinctrl based GPIO driver"); +MODULE_LICENSE("GPL"); From af475c16bc02a08ed6af6ca0c920f98a45611fe6 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 25 Mar 2026 11:01:14 +0100 Subject: [PATCH 66/79] gpio: fix up CONFIG_OF dependencies A number of GPIO drivers that used to have a CONFIG_OF_GPIO dependency now fail to build on targets without CONFIG_OF: WARNING: unmet direct dependencies detected for GPIO_SYSCON Depends on [n]: GPIOLIB [=y] && HAS_IOMEM [=y] && MFD_SYSCON [=y] && OF [=n] Selected by [y]: - GPIO_SAMA5D2_PIOBU [=y] && GPIOLIB [=y] && HAS_IOMEM [=y] && MFD_SYSCON [=y] && (ARCH_AT91 || COMPILE_TEST [=y]) drivers/gpio/gpio-mt7621.c: In function 'mediatek_gpio_bank_probe': drivers/gpio/gpio-mt7621.c:254:20: error: 'struct gpio_chip' has no member named 'of_gpio_n_cells' 254 | rg->chip.gc.of_gpio_n_cells = 2; | ^ drivers/gpio/gpio-tegra186.c: In function 'tegra186_gpio_of_xlate': drivers/gpio/gpio-tegra186.c:502:25: error: 'struct gpio_chip' has no member named 'of_gpio_n_cells' 502 | if (WARN_ON(chip->of_gpio_n_cells < 2)) | ^~ drivers/gpio/gpio-lpc32xx.c: In function 'lpc32xx_gpio_probe': drivers/gpio/gpio-lpc32xx.c:523:49: error: 'struct gpio_chip' has no member named 'of_xlate' 523 | lpc32xx_gpiochip[i].chip.of_xlate = lpc32xx_of_xlate; | ^ drivers/gpio/gpio-spacemit-k1.c: In function 'spacemit_gpio_add_bank': drivers/gpio/gpio-spacemit-k1.c:234:11: error: 'struct gpio_chip' has no member named 'of_gpio_n_cells' 234 | gc->of_gpio_n_cells = 3; | ^~ Bring that back as a dependency. Fixes: 7803501e5754 ("gpio: drop unneeded Kconfig dependencies on OF_GPIO") Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260325100144.1696731-1-arnd@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 13b36f103e5e..4c3f6ec336c1 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -236,6 +236,7 @@ config GPIO_BRCMSTB tristate "BRCMSTB GPIO support" default y if (ARCH_BRCMSTB || BMIPS_GENERIC) depends on ARCH_BRCMSTB || ARCH_BCM2835 || BMIPS_GENERIC || COMPILE_TEST + depends on OF select GPIO_GENERIC select IRQ_DOMAIN help @@ -463,6 +464,7 @@ config GPIO_LPC18XX config GPIO_LPC32XX tristate "NXP LPC32XX GPIO support" depends on ARCH_LPC32XX || COMPILE_TEST + depends on OF help Select this option to enable GPIO driver for NXP LPC32XX devices. @@ -505,6 +507,7 @@ config GPIO_MPC8XXX config GPIO_MT7621 bool "Mediatek MT7621 GPIO Support" depends on SOC_MT7620 || SOC_MT7621 || COMPILE_TEST + depends on OF select GPIO_GENERIC select GPIOLIB_IRQCHIP help @@ -645,6 +648,7 @@ config GPIO_RTD config GPIO_SAMA5D2_PIOBU tristate "SAMA5D2 PIOBU GPIO support" + depends on OF depends on MFD_SYSCON depends on ARCH_AT91 || COMPILE_TEST select GPIO_SYSCON @@ -685,6 +689,7 @@ config GPIO_SNPS_CREG config GPIO_SPACEMIT_K1 tristate "SPACEMIT K1 GPIO support" depends on ARCH_SPACEMIT || COMPILE_TEST + depends on OF select GPIO_GENERIC select GPIOLIB_IRQCHIP help @@ -750,6 +755,7 @@ config GPIO_TEGRA186 tristate "NVIDIA Tegra186 GPIO support" default ARCH_TEGRA_186_SOC || ARCH_TEGRA_194_SOC || ARCH_TEGRA_234_SOC depends on ARCH_TEGRA_186_SOC || ARCH_TEGRA_194_SOC || ARCH_TEGRA_234_SOC || COMPILE_TEST + depends on OF select GPIOLIB_IRQCHIP select IRQ_DOMAIN_HIERARCHY help From 802c51a83e9a0617d1e97ecd383471f4c6fd5437 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 30 Mar 2026 10:36:03 +0200 Subject: [PATCH 67/79] gpiolib: fix hogs with multiple lines After moving GPIO hog handling into GPIOLIB core, we accidentally stopped supporting devicetree hog definitions with multiple lines like so: hog { gpio-hog; gpios = <3 0>, <4 GPIO_ACTIVE_LOW>; output-high; line-name = "foo"; }; Restore this functionality to fix reported regressions. Fixes: d1d564ec4992 ("gpio: move hogs into GPIO core") Reported-by: Geert Uytterhoeven Closes: https://lore.kernel.org/all/CAMuHMdX6RuZXAozrF5m625ZepJTVVr4pcyKczSk12MedWvoejw@mail.gmail.com/ Tested-by: Geert Uytterhoeven Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260330-gpio-hogs-multiple-v3-1-175c3839ad9f@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 92 +++++++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 38 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 9130f2e001ac..076b7cfe2a1c 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -938,12 +938,18 @@ int gpiochip_add_hog(struct gpio_chip *gc, struct fwnode_handle *fwnode) struct fwnode_handle *gc_node = dev_fwnode(&gc->gpiodev->dev); struct fwnode_reference_args gpiospec; enum gpiod_flags dflags; + const char *name = NULL; struct gpio_desc *desc; + unsigned int num_hogs; unsigned long lflags; - const char *name; int ret, argc; - u32 gpios[3]; /* We support up to three-cell bindings. */ - u32 cells; + /* + * For devicetree-based systems, this needs to be defined in bindings + * and there's no real default value. For other firmware descriptions + * it makes the most sense to use 2 cells for the GPIO offset and + * request flags. + */ + u32 cells = 2; lflags = GPIO_LOOKUP_FLAGS_DEFAULT; dflags = GPIOD_ASIS; @@ -952,43 +958,23 @@ int gpiochip_add_hog(struct gpio_chip *gc, struct fwnode_handle *fwnode) argc = fwnode_property_count_u32(fwnode, "gpios"); if (argc < 0) return argc; - if (argc > 3) + + ret = fwnode_property_read_u32(gc_node, "#gpio-cells", &cells); + if (ret && is_of_node(fwnode)) + return ret; + if (argc % cells) return -EINVAL; + num_hogs = argc / cells; + + u32 *gpios __free(kfree) = kzalloc_objs(*gpios, argc); + if (!gpios) + return -ENOMEM; + ret = fwnode_property_read_u32_array(fwnode, "gpios", gpios, argc); if (ret < 0) return ret; - if (is_of_node(fwnode)) { - /* - * OF-nodes need some additional special handling for - * translating of devicetree flags. - */ - ret = fwnode_property_read_u32(gc_node, "#gpio-cells", &cells); - if (ret) - return ret; - if (!ret && argc != cells) - return -EINVAL; - - memset(&gpiospec, 0, sizeof(gpiospec)); - gpiospec.fwnode = fwnode; - gpiospec.nargs = argc; - - for (int i = 0; i < argc; i++) - gpiospec.args[i] = gpios[i]; - - ret = of_gpiochip_get_lflags(gc, &gpiospec, &lflags); - if (ret) - return ret; - } else { - /* - * GPIO_ACTIVE_LOW is currently the only lookup flag - * supported for non-OF firmware nodes. - */ - if (gpios[1]) - lflags |= GPIO_ACTIVE_LOW; - } - if (fwnode_property_present(fwnode, "input")) dflags |= GPIOD_IN; else if (fwnode_property_present(fwnode, "output-low")) @@ -1000,11 +986,41 @@ int gpiochip_add_hog(struct gpio_chip *gc, struct fwnode_handle *fwnode) fwnode_property_read_string(fwnode, "line-name", &name); - desc = gpiochip_get_desc(gc, gpios[0]); - if (IS_ERR(desc)) - return PTR_ERR(desc); + for (unsigned int i = 0; i < num_hogs; i++) { + if (is_of_node(fwnode)) { + /* + * OF-nodes need some additional special handling for + * translating of devicetree flags. + */ + memset(&gpiospec, 0, sizeof(gpiospec)); + gpiospec.fwnode = fwnode; + gpiospec.nargs = cells; - return gpiod_hog(desc, name, lflags, dflags); + for (unsigned int j = 0; j < cells; j++) + gpiospec.args[j] = gpios[i * cells + j]; + + ret = of_gpiochip_get_lflags(gc, &gpiospec, &lflags); + if (ret) + return ret; + } else { + /* + * GPIO_ACTIVE_LOW is currently the only lookup flag + * supported for non-OF firmware nodes. + */ + if (gpios[i * cells + 1]) + lflags |= GPIO_ACTIVE_LOW; + } + + desc = gpiochip_get_desc(gc, gpios[i * cells]); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + ret = gpiod_hog(desc, name, lflags, dflags); + if (ret) + return ret; + } + + return 0; } static int gpiochip_hog_lines(struct gpio_chip *gc) From 779ae2232cd1fd80661327e503606df004b4cda4 Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Wed, 1 Apr 2026 14:34:41 +0100 Subject: [PATCH 68/79] gpiolib: Make deferral warnings debug messages With the recent addition of the shared GPIO support, warning messages such as the following are being observed ... reg-fixed-voltage regulator-vdd-3v3-pcie: cannot find GPIO chip gpiolib_shared.proxy.6, deferring These are seen even with GPIO_SHARED_PROXY=y. Given that the GPIOs are successfully found a bit later during boot and the code is intentionally returning -EPROBE_DEFER when they are not found, downgrade these messages to debug prints to avoid unnecessary warnings being observed. Note that although the 'cannot find GPIO line' warning has not been observed in this case, it seems reasonable to make this print a debug print for consistency too. Signed-off-by: Jon Hunter Link: https://patch.msgid.link/20260401133441.47641-1-jonathanh@nvidia.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 076b7cfe2a1c..55e5f37dba9a 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -4618,8 +4618,8 @@ static struct gpio_desc *gpio_desc_table_match(struct device *dev, const char *c return desc; } - dev_warn(dev, "cannot find GPIO line %s, deferring\n", - p->key); + dev_dbg(dev, "cannot find GPIO line %s, deferring\n", + p->key); return ERR_PTR(-EPROBE_DEFER); } @@ -4633,8 +4633,8 @@ static struct gpio_desc *gpio_desc_table_match(struct device *dev, const char *c * consumer be probed again or let the Deferred * Probe infrastructure handle the error. */ - dev_warn(dev, "cannot find GPIO chip %s, deferring\n", - p->key); + dev_dbg(dev, "cannot find GPIO chip %s, deferring\n", + p->key); return ERR_PTR(-EPROBE_DEFER); } From 15cbd66b69a9ecdc5c3638aa400ca9b872b84509 Mon Sep 17 00:00:00 2001 From: Alan Borzeszkowski Date: Wed, 1 Apr 2026 19:45:26 +0200 Subject: [PATCH 69/79] gpio: Add Intel Nova Lake ACPI GPIO events driver This driver provides support for new way of handling platform events, through the use of GPIO-signaled ACPI events. This mechanism is used on Intel client platforms released in 2026 and later, starting with Intel Nova Lake. Signed-off-by: Alan Borzeszkowski Reviewed-by: Mika Westerberg Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260401174526.60881-1-alan.borzeszkowski@linux.intel.com Signed-off-by: Bartosz Golaszewski --- MAINTAINERS | 7 + drivers/gpio/Kconfig | 26 +++ drivers/gpio/Makefile | 1 + drivers/gpio/gpio-novalake-events.c | 323 ++++++++++++++++++++++++++++ 4 files changed, 357 insertions(+) create mode 100644 drivers/gpio/gpio-novalake-events.c diff --git a/MAINTAINERS b/MAINTAINERS index 24b3f8d2a64c..dbe2cad0c947 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12851,6 +12851,13 @@ F: drivers/gpio/gpio-sodaville.c F: drivers/gpio/gpio-tangier.c F: drivers/gpio/gpio-tangier.h +INTEL GPIO GPE DRIVER +M: Alan Borzeszkowski +R: Mika Westerberg +L: linux-gpio@vger.kernel.org +S: Supported +F: drivers/gpio/gpio-novalake-events.c + INTEL GVT-g DRIVERS (Intel GPU Virtualization) R: Zhenyu Wang R: Zhi Wang diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 4c3f6ec336c1..5c62a0063351 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1057,6 +1057,32 @@ config GPIO_SCH The Intel Quark X1000 SoC has 2 GPIOs powered by the core power well and 6 from the suspend power well. +config GPIO_NOVALAKE + tristate "Intel Nova Lake GPIO-signaled ACPI events support" + depends on (X86 || COMPILE_TEST) && ACPI + select GPIOLIB_IRQCHIP + help + Select this to enable GPIO-signaled ACPI events support on platforms + with the following SoCs: + + - Intel Nova Lake + + This driver adds support for new mode of handling platform events, + through the use of GPIO-signaled ACPI events. Main purpose is to + handle platform IRQs that originate in PCH components, for example + interrupt triggered by Power Management Event (PME). + + This driver, at this time, is not required to handle platform events. + Listed platform(s) will stay in legacy mode, handling ACPI events as + in previous generations. However, future platforms will eventually + switch to new handling mode, requiring this driver to run events + properly. + + Driver supports up to 128 GPIO pins per GPE block. + + To compile this driver as a module, choose M here: the module will + be called gpio-novalake-events. + config GPIO_SCH311X tristate "SMSC SCH311x SuperI/O GPIO" help diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 20d4a57afdaa..13ff2a129354 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -135,6 +135,7 @@ obj-$(CONFIG_GPIO_MXC) += gpio-mxc.o obj-$(CONFIG_GPIO_MXS) += gpio-mxs.o obj-$(CONFIG_GPIO_NCT6694) += gpio-nct6694.o obj-$(CONFIG_GPIO_NOMADIK) += gpio-nomadik.o +obj-$(CONFIG_GPIO_NOVALAKE) += gpio-novalake-events.o obj-$(CONFIG_GPIO_NPCM_SGPIO) += gpio-npcm-sgpio.o obj-$(CONFIG_GPIO_OCTEON) += gpio-octeon.o obj-$(CONFIG_GPIO_OMAP) += gpio-omap.o diff --git a/drivers/gpio/gpio-novalake-events.c b/drivers/gpio/gpio-novalake-events.c new file mode 100644 index 000000000000..b3bf0038f84a --- /dev/null +++ b/drivers/gpio/gpio-novalake-events.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Intel Nova Lake GPIO-signaled ACPI events driver + * + * Copyright (c) 2026, Intel Corporation. + * + * Author: Alan Borzeszkowski + * + * Intel client platforms released in 2026 and later (starting with Intel Nova + * Lake) support two modes of handling ACPI General Purpose Events (GPE): + * exposed GPIO interrupt mode and legacy mode. + * + * By default, the platform uses legacy mode, handling GPEs as usual. If this + * driver is installed, it signals to the platform (on every boot) that exposed + * GPIO interrupt mode is supported. The platform then switches to exposed + * mode, which takes effect on next boot. From the user perspective, this + * change is transparent. + * + * However, if driver is uninstalled while in exposed interrupt mode, GPEs will + * _not_ be handled until platform falls back to legacy mode. This means that + * USB keyboard, mouse might not function properly for the fallback duration. + * Fallback requires two reboots to take effect: on first reboot, platform no + * longer receives signal from this driver and switches to legacy mode, which + * takes effect on second boot. + * + * Example ACPI event: Power Management Event coming from motherboard PCH, + * waking system from sleep following USB mouse hotplug. + * + * This driver supports up to 128 GPIO pins in each GPE block, per ACPI + * specification v6.6 section 5.6.4. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* + * GPE block has two registers, each register takes half the block size. + * Convert size to bits to get total GPIO pin count. + */ +#define GPE_BLK_REG_SIZE(block_size) ((block_size) / 2) +#define GPE_REG_PIN_COUNT(block_size) BYTES_TO_BITS(GPE_BLK_REG_SIZE(block_size)) +#define GPE_STS_REG_OFFSET 0 +#define GPE_EN_REG_OFFSET(block_size) GPE_BLK_REG_SIZE(block_size) + +/** + * struct nvl_gpio - Intel Nova Lake GPIO driver state + * @gc: GPIO controller interface + * @reg_base: Base address of the GPE registers + * @lock: Guard register access + * @blk_size: GPE block length + */ +struct nvl_gpio { + struct gpio_chip gc; + void __iomem *reg_base; + raw_spinlock_t lock; + size_t blk_size; +}; + +static void __iomem *nvl_gpio_get_byte_addr(struct nvl_gpio *priv, + unsigned int reg_offset, + unsigned long gpio) +{ + return priv->reg_base + reg_offset + gpio; +} + +static int nvl_gpio_get(struct gpio_chip *gc, unsigned int gpio) +{ + struct nvl_gpio *priv = gpiochip_get_data(gc); + unsigned int byte_idx = gpio / BITS_PER_BYTE; + unsigned int bit_idx = gpio % BITS_PER_BYTE; + void __iomem *addr; + u8 reg; + + addr = nvl_gpio_get_byte_addr(priv, GPE_STS_REG_OFFSET, byte_idx); + + guard(raw_spinlock_irqsave)(&priv->lock); + + reg = ioread8(addr); + + return !!(reg & BIT(bit_idx)); +} + +static const struct gpio_chip nvl_gpio_chip = { + .owner = THIS_MODULE, + .get = nvl_gpio_get, +}; + +static int nvl_gpio_irq_set_type(struct irq_data *d, unsigned int type) +{ + if (type & IRQ_TYPE_EDGE_BOTH) + irq_set_handler_locked(d, handle_edge_irq); + else if (type & IRQ_TYPE_LEVEL_MASK) + irq_set_handler_locked(d, handle_level_irq); + + return 0; +} + +static void nvl_gpio_irq_mask_unmask(struct gpio_chip *gc, unsigned long hwirq, + bool mask) +{ + struct nvl_gpio *priv = gpiochip_get_data(gc); + unsigned int byte_idx = hwirq / BITS_PER_BYTE; + unsigned int bit_idx = hwirq % BITS_PER_BYTE; + void __iomem *addr; + u8 reg; + + addr = nvl_gpio_get_byte_addr(priv, GPE_EN_REG_OFFSET(priv->blk_size), byte_idx); + + guard(raw_spinlock_irqsave)(&priv->lock); + + reg = ioread8(addr); + if (mask) + reg &= ~BIT(bit_idx); + else + reg |= BIT(bit_idx); + iowrite8(reg, addr); +} + +static void nvl_gpio_irq_unmask(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + + gpiochip_enable_irq(gc, hwirq); + nvl_gpio_irq_mask_unmask(gc, hwirq, false); +} + +static void nvl_gpio_irq_mask(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + + nvl_gpio_irq_mask_unmask(gc, hwirq, true); + gpiochip_disable_irq(gc, hwirq); +} + +static void nvl_gpio_irq_ack(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct nvl_gpio *priv = gpiochip_get_data(gc); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + unsigned int byte_idx = hwirq / BITS_PER_BYTE; + unsigned int bit_idx = hwirq % BITS_PER_BYTE; + void __iomem *addr; + u8 reg; + + addr = nvl_gpio_get_byte_addr(priv, GPE_STS_REG_OFFSET, byte_idx); + + guard(raw_spinlock_irqsave)(&priv->lock); + + reg = ioread8(addr); + reg |= BIT(bit_idx); + iowrite8(reg, addr); +} + +static const struct irq_chip nvl_gpio_irq_chip = { + .name = "gpio-novalake", + .irq_ack = nvl_gpio_irq_ack, + .irq_mask = nvl_gpio_irq_mask, + .irq_unmask = nvl_gpio_irq_unmask, + .irq_set_type = nvl_gpio_irq_set_type, + .flags = IRQCHIP_IMMUTABLE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, +}; + +static irqreturn_t nvl_gpio_irq(int irq, void *data) +{ + struct nvl_gpio *priv = data; + const size_t block_size = priv->blk_size; + unsigned int handled = 0; + + for (unsigned int i = 0; i < block_size; i++) { + const void __iomem *reg = priv->reg_base + i; + unsigned long pending; + unsigned long enabled; + unsigned int bit_idx; + + scoped_guard(raw_spinlock, &priv->lock) { + pending = ioread8(reg + GPE_STS_REG_OFFSET); + enabled = ioread8(reg + GPE_EN_REG_OFFSET(block_size)); + } + pending &= enabled; + + for_each_set_bit(bit_idx, &pending, BITS_PER_BYTE) { + unsigned int hwirq = i * BITS_PER_BYTE + bit_idx; + + generic_handle_domain_irq(priv->gc.irq.domain, hwirq); + } + + handled += pending ? 1 : 0; + } + + return IRQ_RETVAL(handled); +} + +/* UUID for GPE device _DSM: 079406e6-bdea-49cf-8563-03e2811901cb */ +static const guid_t nvl_gpe_dsm_guid = + GUID_INIT(0x079406e6, 0xbdea, 0x49cf, + 0x85, 0x63, 0x03, 0xe2, 0x81, 0x19, 0x01, 0xcb); + +#define DSM_GPE_MODE_REV 1 +#define DSM_GPE_MODE_FN_INDEX 1 +#define DSM_ENABLE_GPE_MODE 1 + +static int nvl_acpi_enable_gpe_mode(struct device *dev) +{ + union acpi_object argv4[2]; + union acpi_object *obj; + + argv4[0].type = ACPI_TYPE_PACKAGE; + argv4[0].package.count = 1; + argv4[0].package.elements = &argv4[1]; + argv4[1].integer.type = ACPI_TYPE_INTEGER; + argv4[1].integer.value = DSM_ENABLE_GPE_MODE; + + obj = acpi_evaluate_dsm_typed(ACPI_HANDLE(dev), &nvl_gpe_dsm_guid, + DSM_GPE_MODE_REV, DSM_GPE_MODE_FN_INDEX, + argv4, ACPI_TYPE_BUFFER); + if (!obj) + return -EIO; + ACPI_FREE(obj); + + return 0; +} + +static int nvl_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + resource_size_t ioresource_size; + struct gpio_irq_chip *girq; + struct nvl_gpio *priv; + struct resource *res; + void __iomem *regs; + int ret, irq; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!res) + return -ENXIO; + + /* + * GPE block length should be non-negative multiple of two and allow up + * to 128 pins. ACPI v6.6 section 5.2.9 and 5.6.4. + */ + ioresource_size = resource_size(res); + if (!ioresource_size || ioresource_size % 2 || ioresource_size > 0x20) + return dev_err_probe(dev, -EINVAL, + "invalid GPE block length, resource: %pR\n", + res); + + regs = devm_ioport_map(dev, res->start, ioresource_size); + if (!regs) + return -ENOMEM; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + raw_spin_lock_init(&priv->lock); + + priv->reg_base = regs; + priv->blk_size = ioresource_size; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, nvl_gpio_irq, IRQF_SHARED, dev_name(dev), priv); + if (ret) + return ret; + + priv->gc = nvl_gpio_chip; + priv->gc.label = dev_name(dev); + priv->gc.parent = dev; + priv->gc.ngpio = GPE_REG_PIN_COUNT(priv->blk_size); + priv->gc.base = -1; + + girq = &priv->gc.irq; + gpio_irq_chip_set_chip(girq, &nvl_gpio_irq_chip); + girq->parent_handler = NULL; + girq->num_parents = 0; + girq->parents = NULL; + girq->default_type = IRQ_TYPE_NONE; + girq->handler = handle_bad_irq; + + ret = devm_gpiochip_add_data(dev, &priv->gc, priv); + if (ret) + return ret; + + return nvl_acpi_enable_gpe_mode(dev); +} + +static const struct acpi_device_id nvl_gpio_acpi_match[] = { + { "INTC1114" }, + {} +}; +MODULE_DEVICE_TABLE(acpi, nvl_gpio_acpi_match); + +static struct platform_driver nvl_gpio_driver = { + .driver = { + .name = "gpio-novalake-events", + .acpi_match_table = nvl_gpio_acpi_match, + }, + .probe = nvl_gpio_probe, +}; +module_platform_driver(nvl_gpio_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Alan Borzeszkowski "); +MODULE_DESCRIPTION("Intel Nova Lake ACPI GPIO events driver"); From 7fb3287946f937a32adad35c9bec4bbc71e25bb8 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Fri, 27 Mar 2026 11:31:11 +0100 Subject: [PATCH 70/79] gpio: sim: stop using dev-sync-probe dev-err-probe is an overengineered solution to a simple problem. Use a combination of wait_for_probe() and device_is_bound() to synchronously wait for the platform device to probe. Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260327-gpio-kill-dev-sync-probe-v1-1-efac254f1a1d@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 1 - drivers/gpio/gpio-sim.c | 49 ++++++++++++++++++++++++----------------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 5c62a0063351..0a16aa7a3a96 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -2062,7 +2062,6 @@ config GPIO_SIM tristate "GPIO Simulator Module" select IRQ_SIM select CONFIGFS_FS - select DEV_SYNC_PROBE help This enables the GPIO simulator - a configfs-based GPIO testing driver. diff --git a/drivers/gpio/gpio-sim.c b/drivers/gpio/gpio-sim.c index f32674230237..e19701c2ed67 100644 --- a/drivers/gpio/gpio-sim.c +++ b/drivers/gpio/gpio-sim.c @@ -36,8 +36,6 @@ #include #include -#include "dev-sync-probe.h" - #define GPIO_SIM_NGPIO_MAX 1024 #define GPIO_SIM_PROP_MAX 5 /* Max 4 properties + sentinel. */ #define GPIO_SIM_HOG_PROP_MAX 5 @@ -546,7 +544,7 @@ static struct platform_driver gpio_sim_driver = { }; struct gpio_sim_device { - struct dev_sync_probe_data probe_data; + struct platform_device *pdev; struct config_group group; int id; @@ -673,7 +671,7 @@ static bool gpio_sim_device_is_live(struct gpio_sim_device *dev) { lockdep_assert_held(&dev->lock); - return !!dev->probe_data.pdev; + return !!dev->pdev; } static char *gpio_sim_strdup_trimmed(const char *str, size_t count) @@ -695,7 +693,7 @@ static ssize_t gpio_sim_device_config_dev_name_show(struct config_item *item, guard(mutex)(&dev->lock); - pdev = dev->probe_data.pdev; + pdev = dev->pdev; if (pdev) return sprintf(page, "%s\n", dev_name(&pdev->dev)); @@ -900,6 +898,7 @@ static bool gpio_sim_bank_labels_non_unique(struct gpio_sim_device *dev) static int gpio_sim_device_activate(struct gpio_sim_device *dev) { struct platform_device_info pdevinfo; + struct platform_device *pdev; struct fwnode_handle *swnode; struct gpio_sim_bank *bank; int ret; @@ -927,28 +926,39 @@ static int gpio_sim_device_activate(struct gpio_sim_device *dev) bank->swnode = gpio_sim_make_bank_swnode(bank, swnode); if (IS_ERR(bank->swnode)) { ret = PTR_ERR(bank->swnode); - gpio_sim_remove_swnode_recursive(swnode); - return ret; + goto err_remove_swnode; } ret = gpio_sim_bank_add_hogs(bank); - if (ret) { - gpio_sim_remove_swnode_recursive(swnode); - return ret; - } + if (ret) + goto err_remove_swnode; } pdevinfo.name = "gpio-sim"; pdevinfo.fwnode = swnode; pdevinfo.id = dev->id; - ret = dev_sync_probe_register(&dev->probe_data, &pdevinfo); - if (ret) { - gpio_sim_remove_swnode_recursive(swnode); - return ret; + pdev = platform_device_register_full(&pdevinfo); + if (IS_ERR(pdev)) { + ret = PTR_ERR(pdev); + goto err_remove_swnode; } + wait_for_device_probe(); + if (!device_is_bound(&pdev->dev)) { + ret = -ENXIO; + goto err_unregister_pdev; + } + + dev->pdev = pdev; return 0; + +err_unregister_pdev: + platform_device_unregister(pdev); +err_remove_swnode: + gpio_sim_remove_swnode_recursive(swnode); + + return ret; } static void gpio_sim_device_deactivate(struct gpio_sim_device *dev) @@ -957,8 +967,9 @@ static void gpio_sim_device_deactivate(struct gpio_sim_device *dev) lockdep_assert_held(&dev->lock); - swnode = dev_fwnode(&dev->probe_data.pdev->dev); - dev_sync_probe_unregister(&dev->probe_data); + swnode = dev_fwnode(&dev->pdev->dev); + platform_device_unregister(dev->pdev); + dev->pdev = NULL; gpio_sim_remove_swnode_recursive(swnode); } @@ -1060,7 +1071,7 @@ static ssize_t gpio_sim_bank_config_chip_name_show(struct config_item *item, guard(mutex)(&dev->lock); if (gpio_sim_device_is_live(dev)) - return device_for_each_child(&dev->probe_data.pdev->dev, &ctx, + return device_for_each_child(&dev->pdev->dev, &ctx, gpio_sim_emit_chip_name); return sprintf(page, "none\n"); @@ -1571,8 +1582,6 @@ gpio_sim_config_make_device_group(struct config_group *group, const char *name) mutex_init(&dev->lock); INIT_LIST_HEAD(&dev->bank_list); - dev_sync_probe_init(&dev->probe_data); - return &no_free_ptr(dev)->group; } From 3a27f40b457053e6112a63d14590e4a3ff553b44 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Fri, 27 Mar 2026 11:31:12 +0100 Subject: [PATCH 71/79] gpio: aggregator: stop using dev-sync-probe dev-err-probe is an overengineered solution to a simple problem. Use a combination of wait_for_probe() and device_is_bound() to synchronously wait for the platform device to probe. Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260327-gpio-kill-dev-sync-probe-v1-2-efac254f1a1d@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 1 - drivers/gpio/gpio-aggregator.c | 38 +++++++++++++++++++--------------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 0a16aa7a3a96..ab7ac3d62ea9 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -2010,7 +2010,6 @@ menu "Virtual GPIO drivers" config GPIO_AGGREGATOR tristate "GPIO Aggregator" select CONFIGFS_FS - select DEV_SYNC_PROBE help Say yes here to enable the GPIO Aggregator, which provides a way to aggregate existing GPIO lines into a new virtual GPIO chip. diff --git a/drivers/gpio/gpio-aggregator.c b/drivers/gpio/gpio-aggregator.c index 9adf3228c12a..5915209e1e21 100644 --- a/drivers/gpio/gpio-aggregator.c +++ b/drivers/gpio/gpio-aggregator.c @@ -32,8 +32,6 @@ #include #include -#include "dev-sync-probe.h" - #define AGGREGATOR_MAX_GPIOS 512 #define AGGREGATOR_LEGACY_PREFIX "_sysfs" @@ -42,7 +40,7 @@ */ struct gpio_aggregator { - struct dev_sync_probe_data probe_data; + struct platform_device *pdev; struct config_group group; struct gpiod_lookup_table *lookups; struct mutex lock; @@ -135,7 +133,7 @@ static bool gpio_aggregator_is_active(struct gpio_aggregator *aggr) { lockdep_assert_held(&aggr->lock); - return aggr->probe_data.pdev && platform_get_drvdata(aggr->probe_data.pdev); + return aggr->pdev && platform_get_drvdata(aggr->pdev); } /* Only aggregators created via legacy sysfs can be "activating". */ @@ -143,7 +141,7 @@ static bool gpio_aggregator_is_activating(struct gpio_aggregator *aggr) { lockdep_assert_held(&aggr->lock); - return aggr->probe_data.pdev && !platform_get_drvdata(aggr->probe_data.pdev); + return aggr->pdev && !platform_get_drvdata(aggr->pdev); } static size_t gpio_aggregator_count_lines(struct gpio_aggregator *aggr) @@ -909,6 +907,7 @@ static int gpio_aggregator_activate(struct gpio_aggregator *aggr) { struct platform_device_info pdevinfo; struct gpio_aggregator_line *line; + struct platform_device *pdev; struct fwnode_handle *swnode; unsigned int n = 0; int ret = 0; @@ -962,12 +961,23 @@ static int gpio_aggregator_activate(struct gpio_aggregator *aggr) gpiod_add_lookup_table(aggr->lookups); - ret = dev_sync_probe_register(&aggr->probe_data, &pdevinfo); - if (ret) + pdev = platform_device_register_full(&pdevinfo); + if (IS_ERR(pdev)) { + ret = PTR_ERR(pdev); goto err_remove_lookup_table; + } + wait_for_device_probe(); + if (!device_is_bound(&pdev->dev)) { + ret = -ENXIO; + goto err_unregister_pdev; + } + + aggr->pdev = pdev; return 0; +err_unregister_pdev: + platform_device_unregister(pdev); err_remove_lookup_table: kfree(aggr->lookups->dev_id); gpiod_remove_lookup_table(aggr->lookups); @@ -981,7 +991,8 @@ err_remove_lookups: static void gpio_aggregator_deactivate(struct gpio_aggregator *aggr) { - dev_sync_probe_unregister(&aggr->probe_data); + platform_device_unregister(aggr->pdev); + aggr->pdev = NULL; gpiod_remove_lookup_table(aggr->lookups); kfree(aggr->lookups->dev_id); kfree(aggr->lookups); @@ -1145,7 +1156,7 @@ gpio_aggregator_device_dev_name_show(struct config_item *item, char *page) guard(mutex)(&aggr->lock); - pdev = aggr->probe_data.pdev; + pdev = aggr->pdev; if (pdev) return sysfs_emit(page, "%s\n", dev_name(&pdev->dev)); @@ -1322,7 +1333,6 @@ gpio_aggregator_make_group(struct config_group *group, const char *name) return ERR_PTR(ret); config_group_init_type_name(&aggr->group, name, &gpio_aggregator_device_type); - dev_sync_probe_init(&aggr->probe_data); return &aggr->group; } @@ -1471,12 +1481,6 @@ static ssize_t gpio_aggregator_new_device_store(struct device_driver *driver, scnprintf(name, sizeof(name), "%s.%d", AGGREGATOR_LEGACY_PREFIX, aggr->id); config_group_init_type_name(&aggr->group, name, &gpio_aggregator_device_type); - /* - * Since the device created by sysfs might be toggled via configfs - * 'live' attribute later, this initialization is needed. - */ - dev_sync_probe_init(&aggr->probe_data); - /* Expose to configfs */ res = configfs_register_group(&gpio_aggregator_subsys.su_group, &aggr->group); @@ -1495,7 +1499,7 @@ static ssize_t gpio_aggregator_new_device_store(struct device_driver *driver, goto remove_table; } - aggr->probe_data.pdev = pdev; + aggr->pdev = pdev; module_put(THIS_MODULE); return count; From c3e2a8aef28c48a94c0cf0525ca96aa308bcf961 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Fri, 27 Mar 2026 11:31:13 +0100 Subject: [PATCH 72/79] gpio: virtuser: stop using dev-sync-probe dev-err-probe is an overengineered solution to a simple problem. Use a combination of wait_for_probe() and device_is_bound() to synchronously wait for the platform device to probe. Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260327-gpio-kill-dev-sync-probe-v1-3-efac254f1a1d@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 1 - drivers/gpio/gpio-virtuser.c | 30 ++++++++++++++++++++---------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index ab7ac3d62ea9..6dd2e08b9be6 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -2098,7 +2098,6 @@ config GPIO_VIRTUSER select DEBUG_FS select CONFIGFS_FS select IRQ_WORK - select DEV_SYNC_PROBE help Say Y here to enable the configurable, configfs-based virtual GPIO consumer testing driver. diff --git a/drivers/gpio/gpio-virtuser.c b/drivers/gpio/gpio-virtuser.c index 955b5efc283e..fe0eac920ced 100644 --- a/drivers/gpio/gpio-virtuser.c +++ b/drivers/gpio/gpio-virtuser.c @@ -36,8 +36,6 @@ #include #include -#include "dev-sync-probe.h" - #define GPIO_VIRTUSER_NAME_BUF_LEN 32 static DEFINE_IDA(gpio_virtuser_ida); @@ -978,7 +976,7 @@ static struct platform_driver gpio_virtuser_driver = { }; struct gpio_virtuser_device { - struct dev_sync_probe_data probe_data; + struct platform_device *pdev; struct config_group group; int id; @@ -1002,7 +1000,7 @@ gpio_virtuser_device_is_live(struct gpio_virtuser_device *dev) { lockdep_assert_held(&dev->lock); - return !!dev->probe_data.pdev; + return !!dev->pdev; } struct gpio_virtuser_lookup { @@ -1342,7 +1340,7 @@ gpio_virtuser_device_config_dev_name_show(struct config_item *item, guard(mutex)(&dev->lock); - pdev = dev->probe_data.pdev; + pdev = dev->pdev; if (pdev) return sprintf(page, "%s\n", dev_name(&pdev->dev)); @@ -1450,6 +1448,7 @@ static int gpio_virtuser_device_activate(struct gpio_virtuser_device *dev) { struct platform_device_info pdevinfo; + struct platform_device *pdev; struct fwnode_handle *swnode; int ret; @@ -1471,12 +1470,23 @@ gpio_virtuser_device_activate(struct gpio_virtuser_device *dev) if (ret) goto err_remove_swnode; - ret = dev_sync_probe_register(&dev->probe_data, &pdevinfo); - if (ret) + pdev = platform_device_register_full(&pdevinfo); + if (IS_ERR(pdev)) { + ret = PTR_ERR(pdev); goto err_remove_lookup_table; + } + wait_for_device_probe(); + if (!device_is_bound(&pdev->dev)) { + ret = -ENXIO; + goto err_unregister_pdev; + } + + dev->pdev = pdev; return 0; +err_unregister_pdev: + platform_device_unregister(pdev); err_remove_lookup_table: gpio_virtuser_remove_lookup_table(dev); err_remove_swnode: @@ -1492,8 +1502,9 @@ gpio_virtuser_device_deactivate(struct gpio_virtuser_device *dev) lockdep_assert_held(&dev->lock); - swnode = dev_fwnode(&dev->probe_data.pdev->dev); - dev_sync_probe_unregister(&dev->probe_data); + swnode = dev_fwnode(&dev->pdev->dev); + platform_device_unregister(dev->pdev); + dev->pdev = NULL; gpio_virtuser_remove_lookup_table(dev); fwnode_remove_software_node(swnode); } @@ -1723,7 +1734,6 @@ gpio_virtuser_config_make_device_group(struct config_group *group, &gpio_virtuser_device_config_group_type); mutex_init(&dev->lock); INIT_LIST_HEAD(&dev->lookup_list); - dev_sync_probe_init(&dev->probe_data); return &no_free_ptr(dev)->group; } From dd84f7ce6fd12fb5d6648d6b5d8ea4bf6f834273 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Fri, 27 Mar 2026 11:31:14 +0100 Subject: [PATCH 73/79] gpio: remove dev-sync-probe There are no more users. Remove the dev-sync-probe module. Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260327-gpio-kill-dev-sync-probe-v1-4-efac254f1a1d@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 3 -- drivers/gpio/Makefile | 3 -- drivers/gpio/dev-sync-probe.c | 97 ----------------------------------- drivers/gpio/dev-sync-probe.h | 25 --------- 4 files changed, 128 deletions(-) delete mode 100644 drivers/gpio/dev-sync-probe.c delete mode 100644 drivers/gpio/dev-sync-probe.h diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 6dd2e08b9be6..536af328b56e 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -2108,6 +2108,3 @@ config GPIO_VIRTUSER endmenu endif - -config DEV_SYNC_PROBE - tristate diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 13ff2a129354..b267598b517d 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -21,9 +21,6 @@ obj-$(CONFIG_GPIO_GENERIC) += gpio-generic.o # directly supported by gpio-generic gpio-generic-$(CONFIG_GPIO_GENERIC) += gpio-mmio.o -# Utilities for drivers that need synchronous fake device creation -obj-$(CONFIG_DEV_SYNC_PROBE) += dev-sync-probe.o - obj-$(CONFIG_GPIO_104_DIO_48E) += gpio-104-dio-48e.o obj-$(CONFIG_GPIO_104_IDI_48) += gpio-104-idi-48.o obj-$(CONFIG_GPIO_104_IDIO_16) += gpio-104-idio-16.o diff --git a/drivers/gpio/dev-sync-probe.c b/drivers/gpio/dev-sync-probe.c deleted file mode 100644 index 9ea733b863b2..000000000000 --- a/drivers/gpio/dev-sync-probe.c +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Common code for drivers creating fake platform devices. - * - * Provides synchronous device creation: waits for probe completion and - * returns the probe success or error status to the device creator. - * - * Copyright (C) 2021 Bartosz Golaszewski - * Copyright (C) 2025 Koichiro Den - */ - -#include -#include - -#include "dev-sync-probe.h" - -static int dev_sync_probe_notifier_call(struct notifier_block *nb, - unsigned long action, void *data) -{ - struct dev_sync_probe_data *pdata; - struct device *dev = data; - - pdata = container_of(nb, struct dev_sync_probe_data, bus_notifier); - if (!device_match_name(dev, pdata->name)) - return NOTIFY_DONE; - - switch (action) { - case BUS_NOTIFY_BOUND_DRIVER: - pdata->driver_bound = true; - break; - case BUS_NOTIFY_DRIVER_NOT_BOUND: - pdata->driver_bound = false; - break; - default: - return NOTIFY_DONE; - } - - complete(&pdata->probe_completion); - return NOTIFY_OK; -} - -void dev_sync_probe_init(struct dev_sync_probe_data *data) -{ - memset(data, 0, sizeof(*data)); - init_completion(&data->probe_completion); - data->bus_notifier.notifier_call = dev_sync_probe_notifier_call; -} -EXPORT_SYMBOL_GPL(dev_sync_probe_init); - -int dev_sync_probe_register(struct dev_sync_probe_data *data, - struct platform_device_info *pdevinfo) -{ - struct platform_device *pdev; - char *name; - - name = kasprintf(GFP_KERNEL, "%s.%d", pdevinfo->name, pdevinfo->id); - if (!name) - return -ENOMEM; - - data->driver_bound = false; - data->name = name; - reinit_completion(&data->probe_completion); - bus_register_notifier(&platform_bus_type, &data->bus_notifier); - - pdev = platform_device_register_full(pdevinfo); - if (IS_ERR(pdev)) { - bus_unregister_notifier(&platform_bus_type, &data->bus_notifier); - kfree(data->name); - return PTR_ERR(pdev); - } - - wait_for_completion(&data->probe_completion); - bus_unregister_notifier(&platform_bus_type, &data->bus_notifier); - - if (!data->driver_bound) { - platform_device_unregister(pdev); - kfree(data->name); - return -ENXIO; - } - - data->pdev = pdev; - return 0; -} -EXPORT_SYMBOL_GPL(dev_sync_probe_register); - -void dev_sync_probe_unregister(struct dev_sync_probe_data *data) -{ - platform_device_unregister(data->pdev); - kfree(data->name); - data->pdev = NULL; -} -EXPORT_SYMBOL_GPL(dev_sync_probe_unregister); - -MODULE_AUTHOR("Bartosz Golaszewski "); -MODULE_AUTHOR("Koichiro Den "); -MODULE_DESCRIPTION("Utilities for synchronous fake device creation"); -MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/dev-sync-probe.h b/drivers/gpio/dev-sync-probe.h deleted file mode 100644 index 4b3d52b70519..000000000000 --- a/drivers/gpio/dev-sync-probe.h +++ /dev/null @@ -1,25 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -#ifndef DEV_SYNC_PROBE_H -#define DEV_SYNC_PROBE_H - -#include -#include -#include - -struct dev_sync_probe_data { - struct platform_device *pdev; - const char *name; - - /* Synchronize with probe */ - struct notifier_block bus_notifier; - struct completion probe_completion; - bool driver_bound; -}; - -void dev_sync_probe_init(struct dev_sync_probe_data *data); -int dev_sync_probe_register(struct dev_sync_probe_data *data, - struct platform_device_info *pdevinfo); -void dev_sync_probe_unregister(struct dev_sync_probe_data *data); - -#endif /* DEV_SYNC_PROBE_H */ From c8079f83e0bf312645050c17d9c87deb707369c1 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Mon, 30 Mar 2026 17:53:21 +0800 Subject: [PATCH 74/79] gpio: rockchip: convert to dynamic GPIO base allocation This driver is used on device tree based platform. Use dynamic GPIO numberspace base to suppress the warning: gpio gpiochip0: Static allocation of GPIO base is deprecated, use dynamic allocation. gpio gpiochip1: Static allocation of GPIO base is deprecated, use dynamic allocation. gpio gpiochip2: Static allocation of GPIO base is deprecated, use dynamic allocation. gpio gpiochip3: Static allocation of GPIO base is deprecated, use dynamic allocation. gpio gpiochip4: Static allocation of GPIO base is deprecated, use dynamic allocation. Signed-off-by: Shawn Lin Reviewed-by: Linus Walleij Link: https://patch.msgid.link/1774864401-177149-1-git-send-email-shawn.lin@rock-chips.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-rockchip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-rockchip.c b/drivers/gpio/gpio-rockchip.c index 0fff4a699f12..50f3733a455d 100644 --- a/drivers/gpio/gpio-rockchip.c +++ b/drivers/gpio/gpio-rockchip.c @@ -582,7 +582,7 @@ static int rockchip_gpiolib_register(struct rockchip_pin_bank *bank) bank->gpio_chip = rockchip_gpiolib_chip; gc = &bank->gpio_chip; - gc->base = bank->pin_base; + gc->base = -1; gc->ngpio = bank->nr_pins; gc->label = bank->name; gc->parent = bank->dev; From 50f1c48b155b74528b0b251b8c4e097fddd5ab46 Mon Sep 17 00:00:00 2001 From: Chen Jung Ku Date: Sun, 5 Apr 2026 22:48:03 +0800 Subject: [PATCH 75/79] gpio: aspeed: fix unsigned long int declaration Replace "unsigned long int" with "unsigned long" to follow Linux kernel coding style. No functional change intended. Signed-off-by: Chen Jung Ku Link: https://patch.msgid.link/20260405144803.31358-1-ku.loong@gapp.nthu.edu.tw Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-aspeed.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-aspeed.c b/drivers/gpio/gpio-aspeed.c index 9115e56a1626..e6af7f3fba5e 100644 --- a/drivers/gpio/gpio-aspeed.c +++ b/drivers/gpio/gpio-aspeed.c @@ -655,7 +655,7 @@ static void aspeed_init_irq_valid_mask(struct gpio_chip *gc, while (!is_bank_props_sentinel(props)) { unsigned int offset; - const unsigned long int input = props->input; + const unsigned long input = props->input; /* Pretty crummy approach, but similar to GPIO core */ for_each_clear_bit(offset, &input, 32) { From 4a0fc189859bb564fddded12752e1893ad318263 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Tue, 7 Apr 2026 12:11:01 +0200 Subject: [PATCH 76/79] gpio: gpio-by-pinctrl: s/used to do/is used to do/ Add missing "is" to the driver's help text ("used to do" has a completely different meaning). Fixes: 7671f4949a6c9111 ("gpio: gpio-by-pinctrl: add pinctrl based generic GPIO driver") Signed-off-by: Geert Uytterhoeven Reviewed-by: Linus Walleij Link: https://patch.msgid.link/b1ecb31a37f8e35447122554a38985cb6240eb11.1775556619.git.geert+renesas@glider.be [Bartosz: tweak the help text even more] Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 536af328b56e..c5ede0e4a32a 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -253,7 +253,7 @@ config GPIO_BY_PINCTRL create a simple GPIO device based on the pin control interface without doing anything custom. - This driver used to do GPIO over the ARM SCMI protocol. + This driver is used to access GPIOs over the ARM SCMI protocol. config GPIO_CADENCE tristate "Cadence GPIO support" From d129779da5e3f8878e105fb3ca8519d9ff759a91 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Fri, 3 Apr 2026 15:04:55 +0200 Subject: [PATCH 77/79] Documentation: gpio: update the preferred method for using software node lookup In its current version, the manual for converting of board files from using GPIO lookup tables to software nodes recommends leaving the software nodes representing GPIO controllers as "free-floating", not attached objects and relying on the matching of their names against the GPIO controller's name. This is an abuse of the software node API and makes it impossible to create fw_devlinks between GPIO suppliers and consumers in this case. We want to remove this behavior from GPIOLIB and to this end, work on converting all existing drivers to using "attached" software nodes. Except for a few corner-cases where board files define consumers depending on GPIO controllers described in firmware - where we need to reference a real firmware node from a software node - which requires a more complex approach, most board files can easily be converted to using propert firmware node lookup. Update the documentation to recommend attaching the GPIO chip's software nodes to the actual platform devices and show how to do it. Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260403-doc-gpio-swnodes-v2-1-c705f5897b80@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- Documentation/driver-api/gpio/board.rst | 23 +++++++++--- .../driver-api/gpio/legacy-boards.rst | 36 ++++++++++++++----- 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/Documentation/driver-api/gpio/board.rst b/Documentation/driver-api/gpio/board.rst index 0993cac891fb..4ac1e12cf872 100644 --- a/Documentation/driver-api/gpio/board.rst +++ b/Documentation/driver-api/gpio/board.rst @@ -108,9 +108,8 @@ macro, which ties a software node representing the GPIO controller with consumer device. It allows consumers to use regular gpiolib APIs, such as gpiod_get(), gpiod_get_optional(). -The software node representing a GPIO controller need not be attached to the -GPIO controller device. The only requirement is that the node must be -registered and its name must match the GPIO controller's label. +The software node representing a GPIO controller must be attached to the +GPIO controller device - either as the primary or the secondary firmware node. For example, here is how to describe a single GPIO-connected LED. This is an alternative to using platform_data on legacy systems. @@ -122,8 +121,7 @@ alternative to using platform_data on legacy systems. #include /* - * 1. Define a node for the GPIO controller. Its .name must match the - * controller's label. + * 1. Define a node for the GPIO controller. */ static const struct software_node gpio_controller_node = { .name = "gpio-foo", @@ -153,6 +151,21 @@ alternative to using platform_data on legacy systems. }; software_node_register_node_group(swnodes); + /* + * 5. Attach the GPIO controller's software node to the device and + * register it. + */ + static void gpio_foo_register(void) + { + struct platform_device_info pdev_info = { + .name = "gpio-foo", + .id = PLATFORM_DEVID_NONE, + .swnode = &gpio_controller_node + }; + + platform_device_register_full(&pdev_info); + } + // Then register a platform_device for "leds-gpio" and associate // it with &led_device_swnode via .fwnode. diff --git a/Documentation/driver-api/gpio/legacy-boards.rst b/Documentation/driver-api/gpio/legacy-boards.rst index 46e3a26dba77..a9d33bcbb176 100644 --- a/Documentation/driver-api/gpio/legacy-boards.rst +++ b/Documentation/driver-api/gpio/legacy-boards.rst @@ -36,12 +36,10 @@ Requirements for GPIO Properties When using software nodes to describe GPIO connections, the following requirements must be met for the GPIO core to correctly resolve the reference: -1. **The GPIO controller's software node "name" must match the controller's - "label".** The gpiolib core uses this name to find the corresponding - struct gpio_chip at runtime. - This software node has to be registered, but need not be attached to the - device representing the GPIO controller that is providing the GPIO in - question. It may be left as a "free floating" node. +1. **The GPIO controller's software node must be registered and attached to + the controller's ``struct device`` either as its primary or secondary + firmware node.** The gpiolib core uses the address of the firmware node to + find the corresponding ``struct gpio_chip`` at runtime. 2. **The GPIO property must be a reference.** The ``PROPERTY_ENTRY_GPIO()`` macro handles this as it is an alias for ``PROPERTY_ENTRY_REF()``. @@ -121,13 +119,21 @@ A typical legacy board file might look like this: /* Device registration */ static int __init myboard_init(void) { + struct platform_device_info pdev_info = { + .name = MYBOARD_GPIO_CONTROLLER, + .id = PLATFORM_DEVID_NONE, + .swnode = &gpio_controller_node + }; + gpiod_add_lookup_table(&myboard_leds_gpios); gpiod_add_lookup_table(&myboard_buttons_gpios); + platform_device_register_full(&pdev_info); platform_device_register_data(NULL, "leds-gpio", -1, &myboard_leds_pdata, sizeof(myboard_leds_pdata)); platform_device_register_data(NULL, "gpio-keys", -1, - &myboard_buttons_pdata, sizeof(myboard_buttons_pdata)); + &myboard_buttons_pdata, + sizeof(myboard_buttons_pdata)); return 0; } @@ -141,8 +147,7 @@ Step 1: Define the GPIO Controller Node *************************************** First, define a software node that represents the GPIO controller that the -LEDs and buttons are connected to. The ``name`` of this node must match the -name of the driver for the GPIO controller (e.g., "gpio-foo"). +LEDs and buttons are connected to. The ``name`` of this node is optional. .. code-block:: c @@ -257,6 +262,16 @@ software nodes using the ``fwnode`` field in struct platform_device_info. if (error) return error; + memset(&pdev_info, 0, sizeof(pdev_info)); + pdev_info.name = MYBOARD_GPIO_CONTROLLER; + pdev_info.id = PLATFORM_DEVID_NONE; + pdev_info.swnode = &myboard_gpio_controller_node; + gpio_pdev = platform_device_register_full(&pdev_info); + if (IS_ERR(gpio_pdev)) { + error = PTR_ERR(gpio_pdev); + goto err_unregister_nodes; + } + memset(&pdev_info, 0, sizeof(pdev_info)); pdev_info.name = "leds-gpio"; pdev_info.id = PLATFORM_DEVID_NONE; @@ -264,6 +279,7 @@ software nodes using the ``fwnode`` field in struct platform_device_info. leds_pdev = platform_device_register_full(&pdev_info); if (IS_ERR(leds_pdev)) { error = PTR_ERR(leds_pdev); + platform_device_unregister(gpio_pdev); goto err_unregister_nodes; } @@ -274,6 +290,7 @@ software nodes using the ``fwnode`` field in struct platform_device_info. keys_pdev = platform_device_register_full(&pdev_info); if (IS_ERR(keys_pdev)) { error = PTR_ERR(keys_pdev); + platform_device_unregister(gpio_pdev); platform_device_unregister(leds_pdev); goto err_unregister_nodes; } @@ -289,6 +306,7 @@ software nodes using the ``fwnode`` field in struct platform_device_info. { platform_device_unregister(keys_pdev); platform_device_unregister(leds_pdev); + platform_device_unregister(gpio_pdev); software_node_unregister_node_group(myboard_swnodes); } From 5bcd451286176202f4ba84b89fd98c7ea74f33a2 Mon Sep 17 00:00:00 2001 From: Shi Hao Date: Wed, 8 Apr 2026 15:03:13 +0530 Subject: [PATCH 78/79] dt-bindings: gpio: cavium,thunder-8890: Remove DT binding Remove the cavium,thunder-8890 GPIO binding as there are no active use cases. The binding is unused as the corresponding kernel driver binds via PCI and not the compatible. Signed-off-by: Shi Hao Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260408093313.17025-1-i.shihao.999@gmail.com [Bartosz: tweaked the commit message] Signed-off-by: Bartosz Golaszewski --- .../bindings/gpio/gpio-thunderx.txt | 27 ------------------- 1 file changed, 27 deletions(-) delete mode 100644 Documentation/devicetree/bindings/gpio/gpio-thunderx.txt diff --git a/Documentation/devicetree/bindings/gpio/gpio-thunderx.txt b/Documentation/devicetree/bindings/gpio/gpio-thunderx.txt deleted file mode 100644 index 3f883ae29d11..000000000000 --- a/Documentation/devicetree/bindings/gpio/gpio-thunderx.txt +++ /dev/null @@ -1,27 +0,0 @@ -Cavium ThunderX/OCTEON-TX GPIO controller bindings - -Required Properties: -- reg: The controller bus address. -- gpio-controller: Marks the device node as a GPIO controller. -- #gpio-cells: Must be 2. - - First cell is the GPIO pin number relative to the controller. - - Second cell is a standard generic flag bitfield as described in gpio.txt. - -Optional Properties: -- compatible: "cavium,thunder-8890-gpio", unused as PCI driver binding is used. -- interrupt-controller: Marks the device node as an interrupt controller. -- #interrupt-cells: Must be present and have value of 2 if - "interrupt-controller" is present. - - First cell is the GPIO pin number relative to the controller. - - Second cell is triggering flags as defined in interrupts.txt. - -Example: - -gpio_6_0: gpio@6,0 { - compatible = "cavium,thunder-8890-gpio"; - reg = <0x3000 0 0 0 0>; /* DEVFN = 0x30 (6:0) */ - gpio-controller; - #gpio-cells = <2>; - interrupt-controller; - #interrupt-cells = <2>; -}; From ca13ab654064fee86d6e7c9e87d0af7789561509 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 7 Apr 2026 15:27:58 +0200 Subject: [PATCH 79/79] gpio: swnode: defer probe on references to unregistered software nodes fwnode_property_get_reference_args() now returns -ENOTCONN when called on a software node referencing another software node which has not yet been registered as a firmware node. It makes sense to defer probe in this situation as the node will most likely be registered later on and we'll be able to resolve the reference eventually. Change the behavior of swnode_find_gpio() to return -EPROBE_DEFER if the software node reference resolution returns -ENOTCONN. Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260407-swnode-unreg-retcode-v4-2-1b2f0725eb9c@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-swnode.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/gpio/gpiolib-swnode.c b/drivers/gpio/gpiolib-swnode.c index 0d7f3f09a0b4..4374067f621e 100644 --- a/drivers/gpio/gpiolib-swnode.c +++ b/drivers/gpio/gpiolib-swnode.c @@ -93,6 +93,14 @@ struct gpio_desc *swnode_find_gpio(struct fwnode_handle *fwnode, ret = swnode_gpio_get_reference(fwnode, propname, idx, &args); if (ret == 0) break; + if (ret == -ENOTCONN) + /* + * -ENOTCONN for a software node reference lookup means + * that a remote struct software_node exists but has + * not yet been registered as a firmware node. Defer + * until this happens. + */ + return ERR_PTR(-EPROBE_DEFER); } if (ret) { pr_debug("%s: can't parse '%s' property of node '%pfwP[%d]'\n",