From 78c0baf85cd0ab61939b6d4670158d5c53dada69 Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Tue, 25 Aug 2026 11:41:57 +0200 Subject: [PATCH 01/63] dt-bindings: gpio: fsl,qoriq-gpio: allow gpio-hog child nodes QorIQ GPIO controllers may have hogs in device nodes, so let's expose it. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Vincent Jardin Link: https://patch.msgid.link/20260825-for-upstream-dt-qoriq-gpio-hog-v3-1-b99bda4f44d7@free.fr Signed-off-by: Bartosz Golaszewski --- Documentation/devicetree/bindings/gpio/fsl,qoriq-gpio.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Documentation/devicetree/bindings/gpio/fsl,qoriq-gpio.yaml b/Documentation/devicetree/bindings/gpio/fsl,qoriq-gpio.yaml index 4cb2a6b9fabf..1e61e17b0ee3 100644 --- a/Documentation/devicetree/bindings/gpio/fsl,qoriq-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/fsl,qoriq-gpio.yaml @@ -63,6 +63,13 @@ properties: GPIO registers are used as little endian. If not present registers are used as big endian by default. +patternProperties: + "^.+-hog(-[0-9]+)?$": + type: object + + required: + - gpio-hog + required: - compatible - reg From ed14364ae8c310cc3f3f649ffcbc7f82befe1579 Mon Sep 17 00:00:00 2001 From: Tapio Reijonen Date: Tue, 25 Aug 2026 04:15:57 +0000 Subject: [PATCH 02/63] gpio: pcf857x: implement get_direction() The GPIO core warns (and taints the kernel) when a gpiochip lacks .get_direction() and a consumer queries a line's direction, for example via /sys/kernel/debug/gpio. pcf857x provided direction_input/output but no get_direction. These quasi-bidirectional expanders cannot report direction in hardware, and the 'out' software latch alone is ambiguous - a released (input) line and an output driven high both read back as a set bit. Track the direction explicitly in a 'dir' latch updated by the direction_input(), direction_output() and set_multiple() paths, and return it from get_direction(). Initialise it from the same reset state as 'out': released lines are inputs, lines flagged in the power-on latch are driven-low outputs. Fixes: 15fae37d9f5f ("gpiolib: pcf857x i2c gpio expander support") Signed-off-by: Tapio Reijonen Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260825-b4-gpio-pcf857x-get-direction-v1-1-de15996ae354@vaisala.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-pcf857x.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-pcf857x.c b/drivers/gpio/gpio-pcf857x.c index c1f5e10a3c20..1ba6a09542f3 100644 --- a/drivers/gpio/gpio-pcf857x.c +++ b/drivers/gpio/gpio-pcf857x.c @@ -71,8 +71,9 @@ MODULE_DEVICE_TABLE(of, pcf857x_of_table); struct pcf857x { struct gpio_chip chip; struct i2c_client *client; - struct mutex lock; /* protect 'out' */ + struct mutex lock; /* protect 'out' and 'dir' */ unsigned int out; /* software latch */ + unsigned int dir; /* direction latch (1 = input) */ unsigned int status; /* current status */ unsigned int irq_enabled; /* enabled irqs */ @@ -126,12 +127,21 @@ static int pcf857x_input(struct gpio_chip *chip, unsigned int offset) mutex_lock(&gpio->lock); gpio->out |= (1 << offset); + gpio->dir |= (1 << offset); status = gpio->write(gpio->client, gpio->out); mutex_unlock(&gpio->lock); return status; } +static int pcf857x_get_direction(struct gpio_chip *chip, unsigned int offset) +{ + struct pcf857x *gpio = gpiochip_get_data(chip); + + return (gpio->dir & (1 << offset)) ? GPIO_LINE_DIRECTION_IN + : GPIO_LINE_DIRECTION_OUT; +} + static int pcf857x_get(struct gpio_chip *chip, unsigned int offset) { struct pcf857x *gpio = gpiochip_get_data(chip); @@ -167,6 +177,7 @@ static int pcf857x_output(struct gpio_chip *chip, unsigned int offset, int value gpio->out |= bit; else gpio->out &= ~bit; + gpio->dir &= ~bit; status = gpio->write(gpio->client, gpio->out); mutex_unlock(&gpio->lock); @@ -187,6 +198,7 @@ static int pcf857x_set_multiple(struct gpio_chip *chip, unsigned long *mask, mutex_lock(&gpio->lock); gpio->out &= ~*mask; gpio->out |= *bits & *mask; + gpio->dir &= ~*mask; status = gpio->write(gpio->client, gpio->out); mutex_unlock(&gpio->lock); @@ -301,6 +313,7 @@ static int pcf857x_probe(struct i2c_client *client) gpio->chip.set_multiple = pcf857x_set_multiple; gpio->chip.direction_input = pcf857x_input; gpio->chip.direction_output = pcf857x_output; + gpio->chip.get_direction = pcf857x_get_direction; gpio->chip.ngpio = (uintptr_t)i2c_get_match_data(client); reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", GPIOD_OUT_HIGH); @@ -396,6 +409,7 @@ static int pcf857x_probe(struct i2c_client *client) * reset state. Otherwise it flags pins to be driven low. */ gpio->out = ~n_latch; + gpio->dir = ~n_latch; gpio->status = gpio->read(gpio->client); /* Enable irqchip if we have an interrupt */ From 870f46a42afdc65d39587d2c56cf35eca294d201 Mon Sep 17 00:00:00 2001 From: Eliav Farber Date: Sun, 16 Aug 2026 06:06:04 +0000 Subject: [PATCH 03/63] gpio: adp5585: use devm_blocking_notifier_chain_register() Replace the blocking_notifier_chain_register() + devm_add_action_or_reset() pattern with a single call to devm_blocking_notifier_chain_register(), removing the adp5585_gpio_unreg_notifier() callback. Signed-off-by: Eliav Farber Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260816060648.4030-6-farbere@amazon.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-adp5585.c | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/drivers/gpio/gpio-adp5585.c b/drivers/gpio/gpio-adp5585.c index 6f10fc646008..7c04a7e86c8f 100644 --- a/drivers/gpio/gpio-adp5585.c +++ b/drivers/gpio/gpio-adp5585.c @@ -390,16 +390,6 @@ static const struct irq_chip adp5585_irq_chip = { GPIOCHIP_IRQ_RESOURCE_HELPERS, }; -static void adp5585_gpio_unreg_notifier(void *data) -{ - struct adp5585_gpio_dev *adp5585_gpio = data; - struct device *dev = adp5585_gpio->gpio_chip.parent; - struct adp5585_dev *adp5585 = dev_get_drvdata(dev->parent); - - blocking_notifier_chain_unregister(&adp5585->event_notifier, - &adp5585_gpio->nb); -} - static int adp5585_gpio_probe(struct platform_device *pdev) { struct adp5585_dev *adp5585 = dev_get_drvdata(pdev->dev.parent); @@ -450,13 +440,9 @@ static int adp5585_gpio_probe(struct platform_device *pdev) girq->threaded = true; adp5585_gpio->nb.notifier_call = adp5585_gpio_key_event; - ret = blocking_notifier_chain_register(&adp5585->event_notifier, - &adp5585_gpio->nb); - if (ret) - return ret; - - ret = devm_add_action_or_reset(dev, adp5585_gpio_unreg_notifier, - adp5585_gpio); + ret = devm_blocking_notifier_chain_register(dev, + &adp5585->event_notifier, + &adp5585_gpio->nb); if (ret) return ret; } From 32390690610190f63a31f92ca555077f7861657c Mon Sep 17 00:00:00 2001 From: Eliav Farber Date: Sun, 16 Aug 2026 06:06:10 +0000 Subject: [PATCH 04/63] gpio: eic-sprd: use devm_atomic_notifier_chain_register() Replace the atomic_notifier_chain_register() + devm_add_action_or_reset() pattern with a single call to devm_atomic_notifier_chain_register(), removing the sprd_eic_unregister_notifier() callback. Signed-off-by: Eliav Farber Reviewed-by: Baolin Wang Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260816060648.4030-12-farbere@amazon.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-eic-sprd.c | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/drivers/gpio/gpio-eic-sprd.c b/drivers/gpio/gpio-eic-sprd.c index 3b7ebcf12fe7..86eb9624d38a 100644 --- a/drivers/gpio/gpio-eic-sprd.c +++ b/drivers/gpio/gpio-eic-sprd.c @@ -601,13 +601,6 @@ static const struct irq_chip sprd_eic_irq = { GPIOCHIP_IRQ_RESOURCE_HELPERS, }; -static void sprd_eic_unregister_notifier(void *data) -{ - struct notifier_block *nb = data; - - atomic_notifier_chain_unregister(&sprd_eic_irq_notifier, nb); -} - static int sprd_eic_probe(struct platform_device *pdev) { const struct sprd_eic_variant_data *pdata; @@ -690,14 +683,8 @@ static int sprd_eic_probe(struct platform_device *pdev) } sprd_eic->irq_nb.notifier_call = sprd_eic_irq_notify; - ret = atomic_notifier_chain_register(&sprd_eic_irq_notifier, - &sprd_eic->irq_nb); - if (ret) - return dev_err_probe(dev, ret, - "Failed to register with the interrupt notifier"); - - return devm_add_action_or_reset(dev, sprd_eic_unregister_notifier, - &sprd_eic->irq_nb); + return devm_atomic_notifier_chain_register(dev, &sprd_eic_irq_notifier, + &sprd_eic->irq_nb); } static const struct of_device_id sprd_eic_of_match[] = { From df6501bf6fc4b16c9f502a7ab8e785ec91f3f7e3 Mon Sep 17 00:00:00 2001 From: Eliav Farber Date: Sun, 16 Aug 2026 06:06:11 +0000 Subject: [PATCH 05/63] gpio: gpiolib-kunit: use devm_blocking_notifier_chain_register() Replace the blocking_notifier_chain_register() + devm_add_action_or_reset() pattern with a single call to devm_blocking_notifier_chain_register(), removing the gpio_unbind_unregister_notifier() callback. Signed-off-by: Eliav Farber Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260816060648.4030-13-farbere@amazon.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-kunit.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/drivers/gpio/gpiolib-kunit.c b/drivers/gpio/gpiolib-kunit.c index e6cb43a8df5b..90b7e77ef0b5 100644 --- a/drivers/gpio/gpiolib-kunit.c +++ b/drivers/gpio/gpiolib-kunit.c @@ -564,13 +564,6 @@ static int gpio_unbind_notify(struct notifier_block *nb, unsigned long action, return NOTIFY_OK; } -static void gpio_unbind_unregister_notifier(void *data) -{ - struct notifier_block *nb = data; - - blocking_notifier_chain_unregister(&gpio_unbind_notifier, nb); -} - static int gpio_unbind_consumer_probe(struct platform_device *pdev) { struct gpio_unbind_consumer_drvdata *data; @@ -588,11 +581,8 @@ static int gpio_unbind_consumer_probe(struct platform_device *pdev) return PTR_ERR(data->desc); data->nb.notifier_call = gpio_unbind_notify; - ret = blocking_notifier_chain_register(&gpio_unbind_notifier, &data->nb); - if (ret) - return ret; - - ret = devm_add_action_or_reset(dev, gpio_unbind_unregister_notifier, &data->nb); + ret = devm_blocking_notifier_chain_register(dev, &gpio_unbind_notifier, + &data->nb); if (ret) return ret; From a38ad4bbd1f2862a7db1cb0a4ec27af166882e95 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 20 Aug 2026 19:10:05 +0200 Subject: [PATCH 06/63] gpio: generic: Avoid pinctrl calls when ranges are missing Most calls to the pinctrl core is done through the generic helper that go into gpiolib, where the library checks if there is a range associated with the chip and just bails out silently if ther isn't. gpio_mmio_dir_return() misses this check before calling pinctrl_gpio_direction_output/input(). Fix this up with an explicit range check. Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/linux-devicetree/20260820073920.909F71F000E9@smtp.kernel.org/ Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260820-fix-gpio-mmio-pinctrl-v1-1-62c2d7dd4903@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mmio.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpio/gpio-mmio.c b/drivers/gpio/gpio-mmio.c index e9c531eef452..7e4b3e8d609f 100644 --- a/drivers/gpio/gpio-mmio.c +++ b/drivers/gpio/gpio-mmio.c @@ -354,6 +354,11 @@ static int gpio_mmio_dir_return(struct gpio_chip *gc, unsigned int gpio, if (!chip->pinctrl) return 0; +#ifdef CONFIG_PINCTRL + if (list_empty(&gc->gpiodev->pin_ranges)) + return 0; +#endif + if (dir_out) return pinctrl_gpio_direction_output(gc, gpio); else From f60178ca784aeeac3552383253e463d7e9d91620 Mon Sep 17 00:00:00 2001 From: Yu-Chun Lin Date: Sun, 16 Aug 2026 23:17:30 +0800 Subject: [PATCH 07/63] gpio: rtd1625: Remove unnecessary blank line Remove an unnecessary blank line in rtd1625_gpio_setup_irq() to clean up the coding style. Link: https://lore.kernel.org/lkml/anUcSPcJjJkLh0Z-@ashevche-desk.local/ Suggested-by: Andy Shevchenko Signed-off-by: Yu-Chun Lin Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260816-gpio-cleanup-v3-1-f298cdd65fca@realtek.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-rtd1625.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpio/gpio-rtd1625.c b/drivers/gpio/gpio-rtd1625.c index 483e44cf5abc..469d4e94e4c7 100644 --- a/drivers/gpio/gpio-rtd1625.c +++ b/drivers/gpio/gpio-rtd1625.c @@ -486,7 +486,6 @@ static int rtd1625_gpio_setup_irq(struct platform_device *pdev, struct rtd1625_g return irq; num_irqs = (data->info->irq_type_support & IRQ_TYPE_LEVEL_MASK) ? 3 : 2; - for (unsigned int i = 0; i < num_irqs; i++) { irq = platform_get_irq(pdev, i); if (irq < 0) From 097d950bb33edb708f3ea40453ade989cbd8c283 Mon Sep 17 00:00:00 2001 From: Yu-Chun Lin Date: Sun, 16 Aug 2026 23:17:31 +0800 Subject: [PATCH 08/63] gpio: rtd1625: Drop unneeded initializations Drop unneeded assignments. While at it, place correctly the comment to be just above the referenced code. Link: https://lore.kernel.org/lkml/anUcSPcJjJkLh0Z-@ashevche-desk.local/ Suggested-by: Andy Shevchenko Signed-off-by: Yu-Chun Lin Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260816-gpio-cleanup-v3-2-f298cdd65fca@realtek.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-rtd1625.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-rtd1625.c b/drivers/gpio/gpio-rtd1625.c index 469d4e94e4c7..910004c423df 100644 --- a/drivers/gpio/gpio-rtd1625.c +++ b/drivers/gpio/gpio-rtd1625.c @@ -107,10 +107,10 @@ static int rtd1625_reg_mask_xlate(struct gpio_regmap *gpio, enum gpio_regmap_ope unsigned int base, unsigned int offset, unsigned int *reg, unsigned int *mask) { - /* Each GPIO has its own dedicated 32-bit register */ struct rtd1625_gpio *data = gpio_regmap_get_drvdata(gpio); - int val = 0, ret = 0; + /* Each GPIO has its own dedicated 32-bit register */ *reg = base + offset * 4; + int val, ret; switch (op) { case GPIO_REGMAP_SET_OP: From d54bcfc3402fff60f38028a3ddf4e21cb0700fe2 Mon Sep 17 00:00:00 2001 From: Yu-Chun Lin Date: Sun, 16 Aug 2026 23:17:32 +0800 Subject: [PATCH 09/63] gpio: rtd1625: Fix variable type for regmap_read() Change the type of 'val' from int to unsigned int. This properly matches the expected parameter type of regmap_read(). Reviewed-by: Andy Shevchenko Signed-off-by: Yu-Chun Lin Link: https://patch.msgid.link/20260816-gpio-cleanup-v3-3-f298cdd65fca@realtek.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-rtd1625.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-rtd1625.c b/drivers/gpio/gpio-rtd1625.c index 910004c423df..0308d7a052e7 100644 --- a/drivers/gpio/gpio-rtd1625.c +++ b/drivers/gpio/gpio-rtd1625.c @@ -110,7 +110,8 @@ static int rtd1625_reg_mask_xlate(struct gpio_regmap *gpio, enum gpio_regmap_ope struct rtd1625_gpio *data = gpio_regmap_get_drvdata(gpio); /* Each GPIO has its own dedicated 32-bit register */ *reg = base + offset * 4; - int val, ret; + unsigned int val; + int ret; switch (op) { case GPIO_REGMAP_SET_OP: From f5440a157b062e0bc67196614b6a24649533d495 Mon Sep 17 00:00:00 2001 From: Yu-Chun Lin Date: Sun, 16 Aug 2026 23:17:33 +0800 Subject: [PATCH 10/63] gpio: rtd1625: Narrow 'i' variable scope in loop Declare 'i' inside the for-loop header to reduce variable visibility. Link: https://lore.kernel.org/lkml/anUcSPcJjJkLh0Z-@ashevche-desk.local/ Suggested-by: Andy Shevchenko Signed-off-by: Yu-Chun Lin Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260816-gpio-cleanup-v3-4-f298cdd65fca@realtek.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-rtd1625.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-rtd1625.c b/drivers/gpio/gpio-rtd1625.c index 0308d7a052e7..2652bc6cb523 100644 --- a/drivers/gpio/gpio-rtd1625.c +++ b/drivers/gpio/gpio-rtd1625.c @@ -224,7 +224,7 @@ static void rtd1625_gpio_irq_handle(struct irq_desc *desc) struct irq_chip *chip = irq_desc_get_chip(desc); unsigned int irq = irq_desc_get_irq(desc); struct irq_domain *domain = data->domain; - unsigned int reg_offset, i, j, val; + unsigned int reg_offset, j, val; irq_hw_number_t hwirq; unsigned long status; u32 irq_type; @@ -241,7 +241,7 @@ static void rtd1625_gpio_irq_handle(struct irq_desc *desc) chained_irq_enter(chip, desc); - for (i = 0; i < data->info->num_gpios; i += 32) { + for (unsigned int i = 0; i < data->info->num_gpios; i += 32) { reg_offset = get_reg_offset(data, i); ret = regmap_read(data->regmap, reg_offset, &val); if (ret) { From 7e9d11c9abf5e856208c0a3f3f32671f2861f4a8 Mon Sep 17 00:00:00 2001 From: Yu-Chun Lin Date: Sun, 16 Aug 2026 23:17:34 +0800 Subject: [PATCH 11/63] gpio: rtd1625: Narrow 'hwirq' variable scope in loop Declare 'hwirq' within the inner block where it's used to reduce variable visibility. Link: https://lore.kernel.org/lkml/anUcSPcJjJkLh0Z-@ashevche-desk.local/ Suggested-by: Andy Shevchenko Signed-off-by: Yu-Chun Lin Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260816-gpio-cleanup-v3-5-f298cdd65fca@realtek.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-rtd1625.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-rtd1625.c b/drivers/gpio/gpio-rtd1625.c index 2652bc6cb523..8f584287c2e6 100644 --- a/drivers/gpio/gpio-rtd1625.c +++ b/drivers/gpio/gpio-rtd1625.c @@ -225,7 +225,6 @@ static void rtd1625_gpio_irq_handle(struct irq_desc *desc) unsigned int irq = irq_desc_get_irq(desc); struct irq_domain *domain = data->domain; unsigned int reg_offset, j, val; - irq_hw_number_t hwirq; unsigned long status; u32 irq_type; int ret; @@ -267,7 +266,8 @@ static void rtd1625_gpio_irq_handle(struct irq_desc *desc) } for_each_set_bit(j, &status, 32) { - hwirq = i + j; + irq_hw_number_t hwirq = i + j; + irq_type = irq_get_trigger_type(irq_find_mapping(domain, hwirq)); /* From a3902d376dd2e384931aa10c077005547b9c2ba8 Mon Sep 17 00:00:00 2001 From: Yu-Chun Lin Date: Sun, 16 Aug 2026 23:17:35 +0800 Subject: [PATCH 12/63] gpio: rtd1625: Replace pr_err_ratelimited() with dev_err_ratelimited() Using dev_* logging macros is preferred over pr_* in device drivers as it provides proper device context in the kernel log output. Link: https://lore.kernel.org/lkml/anUcSPcJjJkLh0Z-@ashevche-desk.local/ Suggested-by: Andy Shevchenko Reviewed-by: Andy Shevchenko Signed-off-by: Yu-Chun Lin Link: https://patch.msgid.link/20260816-gpio-cleanup-v3-6-f298cdd65fca@realtek.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-rtd1625.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpio-rtd1625.c b/drivers/gpio/gpio-rtd1625.c index 8f584287c2e6..613462614210 100644 --- a/drivers/gpio/gpio-rtd1625.c +++ b/drivers/gpio/gpio-rtd1625.c @@ -221,6 +221,7 @@ static void rtd1625_gpio_irq_handle(struct irq_desc *desc) { unsigned int (*get_reg_offset)(struct rtd1625_gpio *gpio, unsigned int offset); struct rtd1625_gpio *data = irq_desc_get_handler_data(desc); + struct device *dev = regmap_get_device(data->regmap); struct irq_chip *chip = irq_desc_get_chip(desc); unsigned int irq = irq_desc_get_irq(desc); struct irq_domain *domain = data->domain; @@ -244,7 +245,8 @@ static void rtd1625_gpio_irq_handle(struct irq_desc *desc) reg_offset = get_reg_offset(data, i); ret = regmap_read(data->regmap, reg_offset, &val); if (ret) { - pr_err_ratelimited("Failed to read IRQ status for GPIO %u: %d\n", i, ret); + dev_err_ratelimited(dev, "Failed to read IRQ status for GPIO %u: %d\n", + i, ret); continue; } @@ -261,8 +263,9 @@ static void rtd1625_gpio_irq_handle(struct irq_desc *desc) if (irq != data->irqs[RTD1625_IRQ_LEVEL]) { ret = regmap_write(data->regmap, reg_offset, status); if (ret) - pr_err_ratelimited("Failed to clear edge IRQ for GPIO %u: %d\n", - i, ret); + dev_err_ratelimited(dev, + "Failed to clear edge IRQ for GPIO %u: %d\n", + i, ret); } for_each_set_bit(j, &status, 32) { From d42c1df90df49b19c063d029c7d67e91751edc96 Mon Sep 17 00:00:00 2001 From: Yu-Chun Lin Date: Sun, 16 Aug 2026 23:17:36 +0800 Subject: [PATCH 13/63] gpio: rtd1625: Use IRQ_TYPE_DEFAULT macro Use the standard IRQ_TYPE_DEFAULT macro instead of explicitly listing all supported edge and level IRQ type flags. Link: https://lore.kernel.org/lkml/anUcSPcJjJkLh0Z-@ashevche-desk.local/ Suggested-by: Andy Shevchenko Reviewed-by: Andy Shevchenko Signed-off-by: Yu-Chun Lin Link: https://patch.msgid.link/20260816-gpio-cleanup-v3-7-f298cdd65fca@realtek.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-rtd1625.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-rtd1625.c b/drivers/gpio/gpio-rtd1625.c index 613462614210..bebd1b8ede48 100644 --- a/drivers/gpio/gpio-rtd1625.c +++ b/drivers/gpio/gpio-rtd1625.c @@ -615,8 +615,7 @@ static const struct rtd1625_gpio_info rtd1625_iso_gpio_info = { static const struct rtd1625_gpio_info rtd1625_isom_gpio_info = { .num_gpios = 4, - .irq_type_support = IRQ_TYPE_EDGE_BOTH | IRQ_TYPE_LEVEL_LOW | - IRQ_TYPE_LEVEL_HIGH, + .irq_type_support = IRQ_TYPE_DEFAULT, .base_offset = 0x20, .gpa_offset = 0x00, .gpda_offset = 0x04, From beb0556bccecfea7cfe54d7263dad95950b577ed Mon Sep 17 00:00:00 2001 From: Cengiz Can Date: Fri, 31 Jul 2026 01:02:58 +0300 Subject: [PATCH 14/63] gpio: sloppy-logic-analyzer: use debugfs_create_file() for buf_size and capture The "buf_size" and "capture" debugfs files are created with debugfs_create_file_unsafe() and DEFINE_DEBUGFS_ATTRIBUTE() fops. That is safe on its own: debugfs_attr_read()/write() take a debugfs reference themselves, so debugfs_remove_recursive() drains them on removal. Now that "trigger" uses debugfs_create_file(), switch these two to the same call so all three debugfs files in this driver are created uniformly. This is a cosmetic change; the extra debugfs_file_get()/put() added by the full_proxy wrapper is negligible. Suggested-by: Wolfram Sang Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Cengiz Can Reviewed-by: Wolfram Sang Link: https://patch.msgid.link/20260730220258.358169-3-cengiz.can@canonical.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-sloppy-logic-analyzer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-sloppy-logic-analyzer.c b/drivers/gpio/gpio-sloppy-logic-analyzer.c index 044d81e7cafb..0717649a6820 100644 --- a/drivers/gpio/gpio-sloppy-logic-analyzer.c +++ b/drivers/gpio/gpio-sloppy-logic-analyzer.c @@ -299,8 +299,8 @@ static int gpio_la_poll_probe(struct platform_device *pdev) debugfs_create_blob("meta_data", 0400, priv->debug_dir, &priv->meta); debugfs_create_ulong("delay_ns", 0600, priv->debug_dir, &priv->delay_ns); debugfs_create_ulong("delay_ns_acquisition", 0400, priv->debug_dir, &priv->acq_delay); - debugfs_create_file_unsafe("buf_size", 0600, priv->debug_dir, priv, &fops_buf_size); - debugfs_create_file_unsafe("capture", 0200, priv->debug_dir, priv, &fops_capture); + debugfs_create_file("buf_size", 0600, priv->debug_dir, priv, &fops_buf_size); + debugfs_create_file("capture", 0200, priv->debug_dir, priv, &fops_capture); debugfs_create_file("trigger", 0200, priv->debug_dir, priv, &fops_trigger); return 0; From 3d12d02dda0917f50fcc4cdf4b54bb417f609d78 Mon Sep 17 00:00:00 2001 From: Richard Weinberger Date: Tue, 11 Aug 2026 15:03:10 +0200 Subject: [PATCH 15/63] gpio: omap: Use device name as chip label Since commit 92bf78b33b0b4 ("gpio: omap: use dynamic allocation of base"), the driver no longer statically allocates the gpio base. Having chip labels with the old static allocation makes no sense anymore. Use the device name to have a unique name such that gpiod users can cleary distinguish between gpio chips. Signed-off-by: Richard Weinberger Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260811130310.3867697-1-richard@nod.at Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-omap.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/drivers/gpio/gpio-omap.c b/drivers/gpio/gpio-omap.c index 005420baecf4..a9b9a3d9631e 100644 --- a/drivers/gpio/gpio-omap.c +++ b/drivers/gpio/gpio-omap.c @@ -1032,8 +1032,6 @@ static void omap_gpio_mod_init(struct gpio_bank *bank) static int omap_gpio_chip_init(struct gpio_bank *bank, struct device *pm_dev) { struct gpio_irq_chip *irq; - static int gpio; - const char *label; int ret; /* @@ -1055,11 +1053,7 @@ static int omap_gpio_chip_init(struct gpio_bank *bank, struct device *pm_dev) if (bank->regs->wkup_en) bank->chip.parent = &omap_mpuio_device.dev; } else { - label = devm_kasprintf(bank->chip.parent, GFP_KERNEL, "gpio-%d-%d", - gpio, gpio + bank->width - 1); - if (!label) - return -ENOMEM; - bank->chip.label = label; + bank->chip.label = dev_name(bank->chip.parent); } bank->chip.base = -1; bank->chip.ngpio = bank->width; @@ -1086,9 +1080,6 @@ static int omap_gpio_chip_init(struct gpio_bank *bank, struct device *pm_dev) if (ret) gpiochip_remove(&bank->chip); - if (!bank->is_mpuio) - gpio += bank->width; - return ret; } From ef0d275786a3c98d1e52f1d4d89183c802af58db Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 14 Aug 2026 11:30:25 +0700 Subject: [PATCH 16/63] gpio: realtek-otto: Handle errors from optional IRQ lookup platform_get_irq_optional() returns a positive IRQ number on success or a negative error code on failure. For an optional IRQ, -ENXIO indicates that no optional IRQ is available. Other errors, such as -EPROBE_DEFER and -EINVAL, should be propagated so that the caller can handle them appropriately. Propagate negative errors other than -ENXIO. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260814043028.12548-1-phucduc.bui@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-realtek-otto.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpio/gpio-realtek-otto.c b/drivers/gpio/gpio-realtek-otto.c index 4a606bad5848..5d7d28a26544 100644 --- a/drivers/gpio/gpio-realtek-otto.c +++ b/drivers/gpio/gpio-realtek-otto.c @@ -423,6 +423,8 @@ static int realtek_gpio_probe(struct platform_device *pdev) ctrl->chip.gc.owner = THIS_MODULE; irq = platform_get_irq_optional(pdev, 0); + if (irq < 0 && irq != -ENXIO) + return irq; if (!(dev_flags & GPIO_INTERRUPTS_DISABLED) && irq > 0) { girq = &ctrl->chip.gc.irq; gpio_irq_chip_set_chip(girq, &realtek_gpio_irq_chip); From 53ffd779a094d5752551dea2ba7bc787aab29229 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 14 Aug 2026 11:30:26 +0700 Subject: [PATCH 17/63] gpio: mlxbf3: Handle errors from optional IRQ lookup platform_get_irq_optional() returns a positive IRQ number on success or a negative error code on failure. For an optional IRQ, -ENXIO indicates that no optional IRQ is available. Other errors, such as -EPROBE_DEFER and -EINVAL, should be propagated so that the caller can handle them appropriately. Propagate negative errors other than -ENXIO. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260814043028.12548-2-phucduc.bui@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mlxbf3.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-mlxbf3.c b/drivers/gpio/gpio-mlxbf3.c index 566326644a2c..929f64c5140a 100644 --- a/drivers/gpio/gpio-mlxbf3.c +++ b/drivers/gpio/gpio-mlxbf3.c @@ -231,7 +231,9 @@ static int mlxbf3_gpio_probe(struct platform_device *pdev) gc->add_pin_ranges = mlxbf3_gpio_add_pin_ranges; irq = platform_get_irq_optional(pdev, 0); - if (irq >= 0) { + if (irq < 0 && irq != -ENXIO) + return irq; + if (irq > 0) { girq = &gs->chip.gc.irq; gpio_irq_chip_set_chip(girq, &gpio_mlxbf3_irqchip); girq->default_type = IRQ_TYPE_NONE; From d8aaf6b0fdc835d10c7def7775803b28ebfd4d81 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 14 Aug 2026 11:30:27 +0700 Subject: [PATCH 18/63] gpio: mlxbf2: Handle errors from optional IRQ lookup platform_get_irq_optional() returns a positive IRQ number on success or a negative error code on failure. For an optional IRQ, -ENXIO indicates that no optional IRQ is available. Other errors, such as -EPROBE_DEFER and -EINVAL, should be propagated so that the caller can handle them appropriately. Propagate negative errors other than -ENXIO. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260814043028.12548-3-phucduc.bui@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mlxbf2.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-mlxbf2.c b/drivers/gpio/gpio-mlxbf2.c index 4e2f3381d82b..4e5c5d196a50 100644 --- a/drivers/gpio/gpio-mlxbf2.c +++ b/drivers/gpio/gpio-mlxbf2.c @@ -394,7 +394,9 @@ mlxbf2_gpio_probe(struct platform_device *pdev) gc->owner = THIS_MODULE; irq = platform_get_irq_optional(pdev, 0); - if (irq >= 0) { + if (irq < 0 && irq != -ENXIO) + return irq; + if (irq > 0) { girq = &gs->chip.gc.irq; gpio_irq_chip_set_chip(girq, &mlxbf2_gpio_irq_chip); girq->handler = handle_simple_irq; From 6e6e585b603e4f679c2a33c8f0954ec88b52fd7a Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 14 Aug 2026 11:30:28 +0700 Subject: [PATCH 19/63] gpio: altera: Handle errors from optional IRQ lookup platform_get_irq_optional() returns a positive IRQ number on success or a negative error code on failure. For an optional IRQ, -ENXIO indicates that no optional IRQ is available. Other errors, such as -EPROBE_DEFER and -EINVAL, should be propagated so that the caller can handle them appropriately. Propagate negative errors other than -ENXIO. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260814043028.12548-4-phucduc.bui@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-altera.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-altera.c b/drivers/gpio/gpio-altera.c index 532e3360b70e..d22f5be00745 100644 --- a/drivers/gpio/gpio-altera.c +++ b/drivers/gpio/gpio-altera.c @@ -220,8 +220,11 @@ static int altera_gpio_probe(struct platform_device *pdev) return -ENOMEM; mapped_irq = platform_get_irq_optional(pdev, 0); - if (mapped_irq < 0) + if (mapped_irq < 0) { + if (irq != -ENXIO) + return irq; goto skip_irq; + } if (device_property_read_u32(dev, "altr,interrupt-type", ®)) { dev_err(&pdev->dev, From 61ef599b782e776437886f2be0623b3c9055e5d3 Mon Sep 17 00:00:00 2001 From: Rustam Adilov Date: Sat, 15 Aug 2026 14:44:50 +0500 Subject: [PATCH 20/63] gpio: realtek-otto: use __raw_readl/writel in realtek_gpio_update_line_imr() In preparation for upcoming changes to how bank reads and writes are defined in this driver, change the ioread32 and iowrite32 to their __raw variants. The realtek_gpio_update_line_imr() function is used by all devices regardless of GPIO_PORTS_REVERSED flag and thus this is the only place where there shouldn't be any byte swapping whether SWAP_IO_SPACE config is enabled or not and that is only possible with __raw_readl and __raw_writel. Reviewed-by: Linus Walleij Signed-off-by: Rustam Adilov Link: https://patch.msgid.link/20260815094451.31792-2-adilov@disroot.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-realtek-otto.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-realtek-otto.c b/drivers/gpio/gpio-realtek-otto.c index 5d7d28a26544..778cb4a913fd 100644 --- a/drivers/gpio/gpio-realtek-otto.c +++ b/drivers/gpio/gpio-realtek-otto.c @@ -176,10 +176,10 @@ static void realtek_gpio_update_line_imr(struct realtek_gpio_ctrl *ctrl, unsigne u32 reg_val; reg += 4 * (line_shift / 32); - reg_val = ioread32(reg); + reg_val = __raw_readl(reg); reg_val &= ~(REALTEK_GPIO_IMR_LINE_MASK << shift); reg_val |= (irq_type & irq_mask & REALTEK_GPIO_IMR_LINE_MASK) << shift; - iowrite32(reg_val, reg); + __raw_writel(reg_val, reg); } static void realtek_gpio_irq_ack(struct irq_data *data) From 785ec26c37e56ee5f142b6684f7c15c7ca7430a3 Mon Sep 17 00:00:00 2001 From: Rustam Adilov Date: Sat, 15 Aug 2026 14:44:51 +0500 Subject: [PATCH 21/63] gpio: realtek-otto: make bank_read/write overridable by endian property In order to have a working gpio controller with SWAP_IO_SPACE, the bank_read/bank_write functions and the gen_gc_flag used for gpio_generic_chip_config must be able to be changed to their respective counterparts. To do that, introduce checks for the presence of endian property under the device tree node and if so, override what has been set by GPIO_PORTS_REVERSED quirk flag. This way, existing device trees are kept working as they were originally. Both big-endian and little-endian properties must be checked because there are devices that have GPIO_PORTS_REVERSED flag and the others that don't. Signed-off-by: Rustam Adilov Link: https://patch.msgid.link/20260815094451.31792-3-adilov@disroot.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-realtek-otto.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/gpio/gpio-realtek-otto.c b/drivers/gpio/gpio-realtek-otto.c index 778cb4a913fd..be743a2eaa42 100644 --- a/drivers/gpio/gpio-realtek-otto.c +++ b/drivers/gpio/gpio-realtek-otto.c @@ -405,6 +405,16 @@ static int realtek_gpio_probe(struct platform_device *pdev) ctrl->line_imr_pos = realtek_gpio_line_imr_pos_swapped; } + if (device_property_read_bool(dev, "little-endian")) { + gen_gc_flags = 0; + ctrl->bank_read = realtek_gpio_bank_read; + ctrl->bank_write = realtek_gpio_bank_write; + } else if (device_is_big_endian(dev)) { + gen_gc_flags = GPIO_GENERIC_BIG_ENDIAN_BYTE_ORDER; + ctrl->bank_read = realtek_gpio_bank_read_swapped; + ctrl->bank_write = realtek_gpio_bank_write_swapped; + } + config = (struct gpio_generic_chip_config) { .dev = dev, .sz = 4, From 1900b5a41493e7050c68c1e62780d6fb9a209457 Mon Sep 17 00:00:00 2001 From: Kivan Date: Fri, 21 Aug 2026 10:06:50 +0300 Subject: [PATCH 22/63] gpiolib: acpi: Add ignore_wake quirk for ASUS ZenBook UX425UA_UM425UA The ASUS ZenBook UX425UA_UM425UA (with AMD Ryzen 7 5700U SoC) fails to stay suspended in s2idle (Modern Standby), prematurely waking after 1-3 seconds with 0% hardware sleep achieved. In the laptop's ACPI SSDT tables (SSDT15), GPIO Pin 24 (hex 0x18) is defined in _AEI as ExclusiveAndWake targeting the PCIe bridge device \_SB.PCI0.GPP3: GpioInt (Edge, ActiveLow, ExclusiveAndWake, PullNone, 0x0000, "\\_SB.GPIO", ...) { 0x0018 } When the SoC powers down PCIe root ports into low-power states during s2idle, an electrical edge transition triggers a spurious wake interrupt on GPIO Pin 24. This signals the AMD GPIO controller (AMDI0030:00 / IRQ 7 via pinctrl_amd), aborting sleep across the system. Booting with kernel parameter "gpiolib_acpi.ignore_wake=AMDI0030:00@24" completely resolves the issue: amd_gpio reports ignoring the wakeup on pin 24, and interrupt counts in /proc/interrupts remain at 0 during sleep cycles, allowing uninterrupted s2idle until intentionally woken by the user. Add a DMI quirk entry to ignore wake events on AMDI0030:00@24 for this system. Signed-off-by: Kivan Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260821070651.39763-1-vaknins33@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-acpi-quirks.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/gpio/gpiolib-acpi-quirks.c b/drivers/gpio/gpiolib-acpi-quirks.c index a0116f004975..dd4db58d36ad 100644 --- a/drivers/gpio/gpiolib-acpi-quirks.c +++ b/drivers/gpio/gpiolib-acpi-quirks.c @@ -392,6 +392,19 @@ static const struct dmi_system_id gpiolib_acpi_quirks[] __initconst = { .ignore_wake = "VEN_0488:00@355", }, }, + { + /* + * Spurious wakeups from GPP3 PCIe bridge interrupt + * Found in BIOS UX425UA.301 + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "ZenBook UX425UA_UM425UA"), + }, + .driver_data = &(struct acpi_gpiolib_dmi_quirk) { + .ignore_wake = "AMDI0030:00@24", + }, + }, {} /* Terminating entry */ }; From 884214495d377b97a27bcfc371fa1f319a63155a Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Fri, 28 Aug 2026 15:39:51 +0100 Subject: [PATCH 23/63] dt-bindings: gpio: Add compatible string for Tegra268 Add compatible strings for the GPIO controllers on the Tegra268 device. The Tegra268 device has the same GPIO controllers as Tegra264, but only has a subset of the pins and hence, GPIOs. Signed-off-by: Jon Hunter Acked-by: Conor Dooley Link: https://patch.msgid.link/20260828-tegra-tegra268-gpio-v1-1-317bd91295e2@nvidia.com Signed-off-by: Bartosz Golaszewski --- .../devicetree/bindings/gpio/nvidia,tegra186-gpio.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Documentation/devicetree/bindings/gpio/nvidia,tegra186-gpio.yaml b/Documentation/devicetree/bindings/gpio/nvidia,tegra186-gpio.yaml index adeb3b3a2902..fac53e65909b 100644 --- a/Documentation/devicetree/bindings/gpio/nvidia,tegra186-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/nvidia,tegra186-gpio.yaml @@ -91,6 +91,9 @@ properties: - nvidia,tegra264-gpio - nvidia,tegra264-gpio-uphy - nvidia,tegra264-gpio-aon + - nvidia,tegra268-gpio + - nvidia,tegra268-gpio-uphy + - nvidia,tegra268-gpio-aon reg-names: items: @@ -169,6 +172,8 @@ allOf: - nvidia,tegra256-gpio - nvidia,tegra264-gpio - nvidia,tegra264-gpio-uphy + - nvidia,tegra268-gpio + - nvidia,tegra268-gpio-uphy then: properties: interrupts: @@ -185,6 +190,7 @@ allOf: - nvidia,tegra234-gpio-aon - nvidia,tegra238-gpio-aon - nvidia,tegra264-gpio-aon + - nvidia,tegra268-gpio-aon then: properties: interrupts: @@ -201,6 +207,9 @@ allOf: - nvidia,tegra264-gpio - nvidia,tegra264-gpio-uphy - nvidia,tegra264-gpio-aon + - nvidia,tegra268-gpio + - nvidia,tegra268-gpio-uphy + - nvidia,tegra268-gpio-aon then: required: - wakeup-parent From d5bc04687649ccf7b15b421052943870d76bbc2e Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Fri, 28 Aug 2026 15:39:52 +0100 Subject: [PATCH 24/63] gpio: tegra186: Add support for Tegra268 Add support for the 3 GPIO controllers, main, AON and UPHY, that are present on the Tegra268 device. Tegra268 has the same 3 GPIO controllers as Tegra264, but a subset of the GPIOs. Therefore, we can re-use the Tegra264 device-tree port definitions for Tegra268 and avoid duplicating these. Signed-off-by: Jon Hunter Link: https://patch.msgid.link/20260828-tegra-tegra268-gpio-v1-2-317bd91295e2@nvidia.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-tegra186.c | 74 ++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/drivers/gpio/gpio-tegra186.c b/drivers/gpio/gpio-tegra186.c index d9a2dedf50ea..34e084c95d7a 100644 --- a/drivers/gpio/gpio-tegra186.c +++ b/drivers/gpio/gpio-tegra186.c @@ -1419,6 +1419,71 @@ static const struct tegra_gpio_soc tegra264_uphy_soc = { .has_vm_support = true, }; +static const struct tegra_gpio_port tegra268_main_ports[] = { + TEGRA264_MAIN_GPIO_PORT(F, 3, 0, 8), + TEGRA264_MAIN_GPIO_PORT(G, 3, 1, 5), + TEGRA264_MAIN_GPIO_PORT(H, 1, 0, 8), + TEGRA264_MAIN_GPIO_PORT(J, 1, 1, 8), + TEGRA264_MAIN_GPIO_PORT(K, 1, 2, 8), + TEGRA264_MAIN_GPIO_PORT(L, 1, 3, 8), + TEGRA264_MAIN_GPIO_PORT(M, 1, 4, 2), + TEGRA264_MAIN_GPIO_PORT(P, 2, 0, 8), + TEGRA264_MAIN_GPIO_PORT(Q, 2, 1, 8), + TEGRA264_MAIN_GPIO_PORT(R, 2, 2, 8), + TEGRA264_MAIN_GPIO_PORT(S, 2, 3, 2), + TEGRA264_MAIN_GPIO_PORT(T, 0, 0, 7), + TEGRA264_MAIN_GPIO_PORT(U, 0, 1, 8), + TEGRA264_MAIN_GPIO_PORT(V, 0, 2, 8), + TEGRA264_MAIN_GPIO_PORT(W, 0, 3, 8), + TEGRA264_MAIN_GPIO_PORT(Y, 0, 5, 8), + TEGRA264_MAIN_GPIO_PORT(Z, 0, 6, 8), + TEGRA264_MAIN_GPIO_PORT(AL, 0, 4, 3), +}; + +static const struct tegra_gpio_soc tegra268_main_soc = { + .num_ports = ARRAY_SIZE(tegra268_main_ports), + .ports = tegra268_main_ports, + .name = "tegra268-gpio", + .instance = 0, + .num_irqs_per_bank = 8, + .has_vm_support = true, +}; + +static const struct tegra_gpio_port tegra268_aon_ports[] = { + TEGRA264_AON_GPIO_PORT(AA, 0, 0, 8), + TEGRA264_AON_GPIO_PORT(BB, 0, 1, 2), + TEGRA264_AON_GPIO_PORT(CC, 0, 2, 8), + TEGRA264_AON_GPIO_PORT(DD, 0, 3, 8), + TEGRA264_AON_GPIO_PORT(EE, 0, 4, 2) +}; + +static const struct tegra_gpio_soc tegra268_aon_soc = { + .num_ports = ARRAY_SIZE(tegra268_aon_ports), + .ports = tegra268_aon_ports, + .name = "tegra268-gpio-aon", + .instance = 1, + .num_irqs_per_bank = 8, + .has_gte = true, + .has_vm_support = true, +}; + +static const struct tegra_gpio_port tegra268_uphy_ports[] = { + TEGRA264_UPHY_GPIO_PORT(A, 0, 0, 6), + TEGRA264_UPHY_GPIO_PORT(B, 0, 1, 8), + TEGRA264_UPHY_GPIO_PORT(C, 0, 2, 3), + TEGRA264_UPHY_GPIO_PORT(D, 1, 0, 6), + TEGRA264_UPHY_GPIO_PORT(E, 1, 1, 4), +}; + +static const struct tegra_gpio_soc tegra268_uphy_soc = { + .num_ports = ARRAY_SIZE(tegra268_uphy_ports), + .ports = tegra268_uphy_ports, + .name = "tegra268-gpio-uphy", + .instance = 2, + .num_irqs_per_bank = 8, + .has_vm_support = true, +}; + #define TEGRA256_MAIN_GPIO_PORT(_name, _bank, _port, _pins) \ TEGRA_GPIO_PORT(TEGRA256_MAIN, _name, _bank, _port, _pins) @@ -1528,6 +1593,15 @@ static const struct of_device_id tegra186_gpio_of_match[] = { }, { .compatible = "nvidia,tegra264-gpio-uphy", .data = &tegra264_uphy_soc + }, { + .compatible = "nvidia,tegra268-gpio", + .data = &tegra268_main_soc + }, { + .compatible = "nvidia,tegra268-gpio-aon", + .data = &tegra268_aon_soc + }, { + .compatible = "nvidia,tegra268-gpio-uphy", + .data = &tegra268_uphy_soc }, { /* sentinel */ } From cf176c115497bc6462391c3118bedfd212092c4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ezequiel=20Rodriguez?= Date: Mon, 31 Aug 2026 12:23:06 -0300 Subject: [PATCH 25/63] gpio: cdev: return EOPNOTSUPP when HTE is unavailable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Map hte_ts_get() -ENODEV to -EOPNOTSUPP so userspace sees the same errno as when CONFIG_HTE is disabled or EVENT_CLOCK_HTE is unsupported. Signed-off-by: Iván Ezequiel Rodriguez Link: https://patch.msgid.link/20260831152306.166630-2-ivanrwcm25@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-cdev.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index 9f3b628d5793..6dc01f7ea838 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -715,6 +715,8 @@ static int hte_edge_setup(struct line *line, u64 eflags) line->desc); ret = hte_ts_get(NULL, hdesc, 0); + if (ret == -ENODEV) + return -EOPNOTSUPP; if (ret) return ret; From a2cfc48fee416f0ea78e7dc38acbf2ce072574cc Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 3 Sep 2026 10:07:00 +0700 Subject: [PATCH 26/63] gpio: altera: Fix build failure caused by undeclared 'irq' Use 'mapped_irq' instead of the undefined 'irq' variable in altera_gpio_probe(). The incorrect variable name causes the driver to fail to build with an 'irq' undeclared error. Fixes: 6e6e585b603e4 ("gpio: altera: Handle errors from optional IRQ lookup") Reported-by: Mark Brown Closes: https://lore.kernel.org/all/apgNYDiSiPmNZspF@sirena.org.uk/ Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260903030700.132068-1-phucduc.bui@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-altera.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-altera.c b/drivers/gpio/gpio-altera.c index d22f5be00745..a6ac9356229c 100644 --- a/drivers/gpio/gpio-altera.c +++ b/drivers/gpio/gpio-altera.c @@ -221,8 +221,8 @@ static int altera_gpio_probe(struct platform_device *pdev) mapped_irq = platform_get_irq_optional(pdev, 0); if (mapped_irq < 0) { - if (irq != -ENXIO) - return irq; + if (mapped_irq != -ENXIO) + return mapped_irq; goto skip_irq; } From 32c7f7f80e5f7f2764682fd456054478fc11c8d9 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sat, 5 Sep 2026 02:12:39 -0700 Subject: [PATCH 27/63] gpio: crystalcove: add missing MODULE_ALIAS() The crystalcove GPIO driver lacks a MODULE_ALIAS() to allow it to be auto-loaded. Fix this by adding the missing platform alias. Fixes: 104fb1d5153c ("gpio: Add support for Intel Crystal Cove PMIC") Assisted-by: LLM Signed-off-by: Dmitry Torokhov Reviewed-by: Linus Walleij Signed-off-by: Andy Shevchenko --- drivers/gpio/gpio-crystalcove.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpio/gpio-crystalcove.c b/drivers/gpio/gpio-crystalcove.c index 0fb5c06d0886..ab7fab5b9754 100644 --- a/drivers/gpio/gpio-crystalcove.c +++ b/drivers/gpio/gpio-crystalcove.c @@ -396,3 +396,4 @@ module_platform_driver(crystalcove_gpio_driver); MODULE_AUTHOR("Yang, Bin "); MODULE_DESCRIPTION("Intel Crystal Cove GPIO Driver"); MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:crystal_cove_gpio"); From 31e7eb7e38978f6edb6523e43f3d8738e1837d3f Mon Sep 17 00:00:00 2001 From: Manuel Ebner Date: Thu, 3 Sep 2026 17:53:44 +0200 Subject: [PATCH 28/63] Documentation: gpio: Fix bracket Balance parentheses. Signed-off-by: Manuel Ebner Acked-by: Randy Dunlap Link: https://patch.msgid.link/20260903155345.733244-2-manuelebnerli@mailbox.org Signed-off-by: Bartosz Golaszewski --- Documentation/driver-api/gpio/board.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/driver-api/gpio/board.rst b/Documentation/driver-api/gpio/board.rst index 4ac1e12cf872..83b74843ecf2 100644 --- a/Documentation/driver-api/gpio/board.rst +++ b/Documentation/driver-api/gpio/board.rst @@ -55,7 +55,7 @@ looked up by the gpiod functions internally) used in the device tree. With above Internally, the GPIO subsystem prefixes the GPIO suffix ("gpios" or "gpio") with the string passed in con_id to get the resulting string -(``snprintf(... "%s-%s", con_id, gpio_suffixes[]``). +(``snprintf(... "%s-%s", con_id, gpio_suffixes[])``). ACPI ---- From 6f2e63ef82062a5254f43e0d499053f1e1b19f8b Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 16:29:14 +0530 Subject: [PATCH 29/63] tools: gpio: fix typo "amount of times" in comment Correct "amount of times" to "number of times", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Link: https://patch.msgid.link/20260904105915.4327-1-hemanth.selam@gmail.com Signed-off-by: Bartosz Golaszewski --- tools/gpio/gpio-hammer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/gpio/gpio-hammer.c b/tools/gpio/gpio-hammer.c index ba0866eb3581..1a21e5460242 100644 --- a/tools/gpio/gpio-hammer.c +++ b/tools/gpio/gpio-hammer.c @@ -145,7 +145,7 @@ int main(int argc, char **argv) case 'o': /* * Avoid overflow. Do not immediately error, we want to - * be able to accurately report on the amount of times + * be able to accurately report on the number of times * '-o' was given to give an accurate error message */ if (i < GPIOHANDLES_MAX) From 4d305c2273123119c27d6e4c822215e650c451e3 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Fri, 4 Sep 2026 17:43:34 -0700 Subject: [PATCH 30/63] gpio: mvebu: name regmaps to fix debugfs collisions Each marvell,armada-370-gpio node provides both 'gpio' and 'pwm' register windows, and the driver creates one regmap per window on the same platform device. The shared regmap_config was unnamed, so both maps defaulted their debugfs directory to dev_name(), e.g. debugfs: 'f1018100.gpio' already exists in 'regmap' because the PWM regmap (and on Armada XP the per-CPU regmap) claimed the same directory as the GPIO regmap. Give each regmap a distinct .name so the debugfs directories become '.gpio', '.percpu' and '.pwm'. This only affects debugfs naming; register access is unchanged. Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260905004334.177074-1-rosenp@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mvebu.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-mvebu.c b/drivers/gpio/gpio-mvebu.c index 93b8a08b04b9..c57758019e92 100644 --- a/drivers/gpio/gpio-mvebu.c +++ b/drivers/gpio/gpio-mvebu.c @@ -599,6 +599,21 @@ static void mvebu_gpio_irq_handler(struct irq_desc *desc) } static const struct regmap_config mvebu_gpio_regmap_config = { + .name = "gpio", + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, +}; + +static const struct regmap_config mvebu_gpio_percpu_regmap_config = { + .name = "percpu", + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, +}; + +static const struct regmap_config mvebu_pwm_regmap_config = { + .name = "pwm", .reg_bits = 32, .reg_stride = 4, .val_bits = 32, @@ -868,7 +883,7 @@ static int mvebu_pwm_probe(struct platform_device *pdev, return PTR_ERR(base); mvpwm->regs = devm_regmap_init_mmio(&pdev->dev, base, - &mvebu_gpio_regmap_config); + &mvebu_pwm_regmap_config); if (IS_ERR(mvpwm->regs)) return PTR_ERR(mvpwm->regs); @@ -1114,7 +1129,7 @@ static int mvebu_gpio_probe_raw(struct platform_device *pdev, mvchip->percpu_regs = devm_regmap_init_mmio(&pdev->dev, base, - &mvebu_gpio_regmap_config); + &mvebu_gpio_percpu_regmap_config); if (IS_ERR(mvchip->percpu_regs)) return PTR_ERR(mvchip->percpu_regs); } From 3f860d25f7be981d78c1b85a8d4fafcd914d4df9 Mon Sep 17 00:00:00 2001 From: Michael Zavertkin Date: Thu, 27 Aug 2026 21:29:30 +0700 Subject: [PATCH 31/63] dt-bindings: gpio: otto-gpio: Add gpio-ranges Add 'gpio-ranges' optional property to reflect driver changes. Signed-off-by: Michael Zavertkin Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/bd1fd3ab47001c303e62d331486779c2be6d6eb3.1787830440.git.misha.zavertkin@mail.ru Signed-off-by: Bartosz Golaszewski --- Documentation/devicetree/bindings/gpio/realtek,otto-gpio.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/gpio/realtek,otto-gpio.yaml b/Documentation/devicetree/bindings/gpio/realtek,otto-gpio.yaml index b18f8f0ca0ae..33c25df7b815 100644 --- a/Documentation/devicetree/bindings/gpio/realtek,otto-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/realtek,otto-gpio.yaml @@ -40,6 +40,8 @@ properties: gpio-controller: true + gpio-ranges: true + ngpios: minimum: 1 maximum: 32 From 7257c35db0fdb4fb02d857a8197a16afa84d92c0 Mon Sep 17 00:00:00 2001 From: Michael Zavertkin Date: Thu, 27 Aug 2026 21:29:31 +0700 Subject: [PATCH 32/63] gpio: realtek-otto: add pinctrl support RTL9607C relies on pinctrl to mux SoC pins. Use pinctrl to request pins for GPIO. pinctrl driver for other SoCs might be implemented later. Signed-off-by: Michael Zavertkin Link: https://patch.msgid.link/adce57b3797cae6f6a3486eb1d21c33be61f14dc.1787830440.git.misha.zavertkin@mail.ru Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-realtek-otto.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-realtek-otto.c b/drivers/gpio/gpio-realtek-otto.c index be743a2eaa42..5d98ad597d9e 100644 --- a/drivers/gpio/gpio-realtek-otto.c +++ b/drivers/gpio/gpio-realtek-otto.c @@ -393,13 +393,14 @@ static int realtek_gpio_probe(struct platform_device *pdev) raw_spin_lock_init(&ctrl->lock); + gen_gc_flags = GPIO_GENERIC_PINCTRL_BACKEND; + if (dev_flags & GPIO_PORTS_REVERSED) { - gen_gc_flags = 0; ctrl->bank_read = realtek_gpio_bank_read; ctrl->bank_write = realtek_gpio_bank_write; ctrl->line_imr_pos = realtek_gpio_line_imr_pos; } else { - gen_gc_flags = GPIO_GENERIC_BIG_ENDIAN_BYTE_ORDER; + gen_gc_flags |= GPIO_GENERIC_BIG_ENDIAN_BYTE_ORDER; ctrl->bank_read = realtek_gpio_bank_read_swapped; ctrl->bank_write = realtek_gpio_bank_write_swapped; ctrl->line_imr_pos = realtek_gpio_line_imr_pos_swapped; From 4bfe5b08a5912d1720689b1fc7f46c9b8641997d Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Mon, 14 Sep 2026 10:51:54 +0200 Subject: [PATCH 33/63] dt-bindings: gpio: zynq: Add parsing of hogs Allow parsing GPIO controller children nodes with GPIO hogs the same way it is done by commit 545908a9fb9c ("dt-bindings: gpio-mmio: Support hogs") and commit e8f2ca6be61f ("dt-bindings: gpio: ast2400-gpio: Add hogs parsing"). Signed-off-by: Michal Simek Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/0b17cd7e24ea9a92a3e5268a1f9e5a840960ac6d.1789375913.git.michal.simek@amd.com Signed-off-by: Bartosz Golaszewski --- Documentation/devicetree/bindings/gpio/gpio-zynq.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/gpio/gpio-zynq.yaml b/Documentation/devicetree/bindings/gpio/gpio-zynq.yaml index de24bb361e9f..fffeb5ec0645 100644 --- a/Documentation/devicetree/bindings/gpio/gpio-zynq.yaml +++ b/Documentation/devicetree/bindings/gpio/gpio-zynq.yaml @@ -45,6 +45,12 @@ properties: power-domains: maxItems: 1 +patternProperties: + "^.+-hog(-[0-9]+)?$": + type: object + required: + - gpio-hog + allOf: - if: properties: From a4319947b59145b95353aabed4db879e9448ea50 Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Thu, 10 Sep 2026 19:29:10 +0200 Subject: [PATCH 34/63] gpio: regmap: Use regmap_test_bits() for single bit reads Use regmap_test_bits() when reading a single GPIO value from a normal register and when reading the direction bit. Reviewed-by: Linus Walleij Reviewed-by: Michael Walle Reviewed-by: Andy Shevchenko Signed-off-by: Janani Sunil Link: https://patch.msgid.link/20260910-ad7768-gpio-v8-1-6aaaa15adeb3@analog.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-regmap.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c index fb21396e1d02..ae6fa42b00a4 100644 --- a/drivers/gpio/gpio-regmap.c +++ b/drivers/gpio/gpio-regmap.c @@ -95,11 +95,11 @@ static int gpio_regmap_get(struct gpio_chip *chip, unsigned int offset) if (ret) return ret; + if (gpio->reg_dat_base != gpio->reg_set_base) + return regmap_test_bits(gpio->regmap, reg, mask); + /* ensure we don't spoil any register cache with pin input values */ - if (gpio->reg_dat_base == gpio->reg_set_base) - ret = regmap_read_bypassed(gpio->regmap, reg, &val); - else - ret = regmap_read(gpio->regmap, reg, &val); + ret = regmap_read_bypassed(gpio->regmap, reg, &val); if (ret) return ret; @@ -183,7 +183,7 @@ static int gpio_regmap_get_direction(struct gpio_chip *chip, unsigned int offset) { struct gpio_regmap *gpio = gpiochip_get_data(chip); - unsigned int base, val, reg, mask; + unsigned int base, reg, mask; int invert, ret; if (gpio_regmap_fixed_direction(gpio, offset)) { @@ -212,14 +212,14 @@ static int gpio_regmap_get_direction(struct gpio_chip *chip, if (ret) return ret; - ret = regmap_read(gpio->regmap, reg, &val); - if (ret) + ret = regmap_test_bits(gpio->regmap, reg, mask); + if (ret < 0) return ret; - if (!!(val & mask) ^ invert) + if (ret ^ invert) return GPIO_LINE_DIRECTION_OUT; - else - return GPIO_LINE_DIRECTION_IN; + + return GPIO_LINE_DIRECTION_IN; } static int gpio_regmap_try_direction_fixed(struct gpio_regmap *gpio, From 73748b4ec54da0323bb2ec62b483ea3f60ff657f Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Thu, 10 Sep 2026 19:29:11 +0200 Subject: [PATCH 35/63] gpio: regmap: Add optional runtime PM support Some gpio-regmap consumers share their regmap with a parent device that may be runtime suspended. GPIO register accesses must resume that device first. Add an optional pm_dev field and acquire it before register translation or access. Release it using runtime autosuspend after each operation. Keep the device active across the complete direction-output sequence and propagate failure when setting the initial output value. Reviewed-by: Andy Shevchenko Reviewed-by: Michael Walle Signed-off-by: Janani Sunil Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260910-ad7768-gpio-v8-2-6aaaa15adeb3@analog.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-regmap.c | 80 ++++++++++++++++++++++++++++++++++--- include/linux/gpio/regmap.h | 2 + 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c index ae6fa42b00a4..2f5f45b84cc9 100644 --- a/drivers/gpio/gpio-regmap.c +++ b/drivers/gpio/gpio-regmap.c @@ -6,11 +6,13 @@ */ #include +#include #include #include #include #include #include +#include #include #include #include @@ -23,6 +25,8 @@ struct gpio_regmap { struct device *parent; struct regmap *regmap; + struct device *pm_dev; + struct gpio_chip gpio_chip; int reg_stride; @@ -79,6 +83,33 @@ static int gpio_regmap_simple_xlate(struct gpio_regmap *gpio, return 0; } +static int gpio_regmap_runtime_get(struct gpio_regmap *gpio) +{ + if (!gpio->pm_dev) + return 0; + + return pm_runtime_get_active(gpio->pm_dev, RPM_TRANSPARENT); +} + +static void gpio_regmap_runtime_put(struct gpio_regmap *gpio) +{ + if (!gpio->pm_dev) + return; + + pm_runtime_put_autosuspend(gpio->pm_dev); +} + +DEFINE_GUARD(gpio_regmap_runtime, struct gpio_regmap *, + gpio_regmap_runtime_get(_T), gpio_regmap_runtime_put(_T)) +DEFINE_GUARD_COND(gpio_regmap_runtime, _try, + gpio_regmap_runtime_get(_T), _RET == 0) + +#define GPIO_REGMAP_RUNTIME_ACQUIRE(_gpio, _var) \ + ACQUIRE(gpio_regmap_runtime_try, _var)(_gpio) + +#define GPIO_REGMAP_RUNTIME_ACQUIRE_ERR(_var_ptr) \ + ACQUIRE_ERR(gpio_regmap_runtime, _var_ptr) + static int gpio_regmap_get(struct gpio_chip *chip, unsigned int offset) { struct gpio_regmap *gpio = gpiochip_get_data(chip); @@ -91,7 +122,13 @@ static int gpio_regmap_get(struct gpio_chip *chip, unsigned int offset) else base = gpio_regmap_addr(gpio->reg_set_base); - ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_GET_OP, base, offset, ®, &mask); + GPIO_REGMAP_RUNTIME_ACQUIRE(gpio, pm); + ret = GPIO_REGMAP_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_GET_OP, base, offset, + ®, &mask); if (ret) return ret; @@ -114,7 +151,13 @@ static int gpio_regmap_set(struct gpio_chip *chip, unsigned int offset, unsigned int reg, mask, mask_val; int ret; - ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_SET_OP, base, offset, ®, &mask); + GPIO_REGMAP_RUNTIME_ACQUIRE(gpio, pm); + ret = GPIO_REGMAP_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_SET_OP, base, offset, + ®, &mask); if (ret) return ret; @@ -146,6 +189,11 @@ static int gpio_regmap_set_with_clear(struct gpio_chip *chip, unsigned int base, reg, mask, value = 0; int ret; + GPIO_REGMAP_RUNTIME_ACQUIRE(gpio, pm); + ret = GPIO_REGMAP_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + if (val) base = gpio_regmap_addr(gpio->reg_set_base); else @@ -208,7 +256,13 @@ static int gpio_regmap_get_direction(struct gpio_chip *chip, return -ENOTSUPP; } - ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_GET_DIR_OP, base, offset, ®, &mask); + GPIO_REGMAP_RUNTIME_ACQUIRE(gpio, pm); + ret = GPIO_REGMAP_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_GET_DIR_OP, base, offset, + ®, &mask); if (ret) return ret; @@ -262,7 +316,13 @@ static int gpio_regmap_set_direction(struct gpio_chip *chip, return -ENOTSUPP; } - ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_SET_DIR_OP, base, offset, ®, &mask); + GPIO_REGMAP_RUNTIME_ACQUIRE(gpio, pm); + ret = GPIO_REGMAP_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_SET_DIR_OP, base, offset, + ®, &mask); if (ret) return ret; @@ -293,6 +353,11 @@ static int gpio_regmap_direction_output(struct gpio_chip *chip, struct gpio_regmap *gpio = gpiochip_get_data(chip); int ret; + GPIO_REGMAP_RUNTIME_ACQUIRE(gpio, pm); + ret = GPIO_REGMAP_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + /* * First check if this is gonna work on a fixed direction line, * if it doesn't (i.e. this is a fixed input line), then do not @@ -304,7 +369,9 @@ static int gpio_regmap_direction_output(struct gpio_chip *chip, return ret; } - gpio_regmap_set(chip, offset, value); + ret = gpio_regmap_set(chip, offset, value); + if (ret) + return ret; return gpio_regmap_set_direction(chip, offset, true); } @@ -399,6 +466,7 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config gpio->reg_clr_base = config->reg_clr_base; gpio->reg_dir_in_base = config->reg_dir_in_base; gpio->reg_dir_out_base = config->reg_dir_out_base; + gpio->pm_dev = config->pm_dev; chip = &gpio->gpio_chip; chip->parent = config->parent; @@ -406,7 +474,7 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config chip->base = -1; chip->names = config->names; chip->label = config->label ?: dev_name(config->parent); - chip->can_sleep = regmap_might_sleep(config->regmap); + chip->can_sleep = config->pm_dev || regmap_might_sleep(config->regmap); chip->init_valid_mask = config->init_valid_mask; chip->request = gpiochip_generic_request; diff --git a/include/linux/gpio/regmap.h b/include/linux/gpio/regmap.h index 6f52c140e50d..6fe0877717cb 100644 --- a/include/linux/gpio/regmap.h +++ b/include/linux/gpio/regmap.h @@ -46,6 +46,7 @@ enum gpio_regmap_operation { * @parent: The parent device * @regmap: The regmap used to access the registers * given, the name of the device is used + * @pm_dev: (Optional) Device to use for runtime power management. * @fwnode: (Optional) The firmware node. * If not given, the fwnode of the parent is used. * @label: (Optional) Descriptive name for GPIO controller. @@ -117,6 +118,7 @@ enum gpio_regmap_operation { struct gpio_regmap_config { struct device *parent; struct regmap *regmap; + struct device *pm_dev; struct fwnode_handle *fwnode; const char *label; From ea4f5a734cd7625ab161ead75403aded18c0c5ab Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Thu, 10 Sep 2026 19:29:12 +0200 Subject: [PATCH 36/63] gpio: ad7768: Add AD7768 GPIO auxiliary driver The AD7768 provides five GPIOs controlled through registers shared with the parent IIO device. Register an auxiliary gpio-regmap driver and use the parent device for runtime PM. The device has separate input-state and output-latch registers. Add a reg_mask_xlate() callback that checks the line direction and reads the programmed output latch for output lines while retaining input-state reads for input lines. Reviewed-by: Andy Shevchenko Reviewed-by: Linus Walleij Signed-off-by: Janani Sunil Link: https://patch.msgid.link/20260910-ad7768-gpio-v8-3-6aaaa15adeb3@analog.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 12 ++++ drivers/gpio/Makefile | 1 + drivers/gpio/gpio-ad7768.c | 121 +++++++++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 drivers/gpio/gpio-ad7768.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index a48586bb8edb..07b7f8ab0ccb 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1818,6 +1818,18 @@ endmenu menu "Auxiliary Bus GPIO drivers" depends on AUXILIARY_BUS +config GPIO_AD7768 + tristate "Analog Devices AD7768 GPIO support" + depends on AD7768 + depends on GPIOLIB + select GPIO_REGMAP + help + Say yes here to expose the AD7768 utility pins as GPIOs when the + device tree node is marked as a GPIO controller. + + To compile this driver as a module, choose M here: the module will be + called gpio-ad7768. + config GPIO_LTC4283 tristate "Analog Devices LTC4283 GPIO support" depends on SENSORS_LTC4283 diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index dc9e6d643b5b..ce56e9b3f55d 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_GPIO_104_IDI_48) += gpio-104-idi-48.o obj-$(CONFIG_GPIO_104_IDIO_16) += gpio-104-idio-16.o obj-$(CONFIG_GPIO_74X164) += gpio-74x164.o obj-$(CONFIG_GPIO_74XX_MMIO) += gpio-74xx-mmio.o +obj-$(CONFIG_GPIO_AD7768) += gpio-ad7768.o obj-$(CONFIG_GPIO_ADNP) += gpio-adnp.o obj-$(CONFIG_GPIO_ADP5520) += gpio-adp5520.o obj-$(CONFIG_GPIO_ADP5585) += gpio-adp5585.o diff --git a/drivers/gpio/gpio-ad7768.c b/drivers/gpio/gpio-ad7768.c new file mode 100644 index 000000000000..c37e1ac30612 --- /dev/null +++ b/drivers/gpio/gpio-ad7768.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices AD7768 GPIO auxiliary driver + * + * Copyright 2026 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AD7768_REG_GPIO_CONTROL 0x0E +#define AD7768_GPIO_UGPIO_ENABLE BIT(7) + +#define AD7768_REG_GPIO_WRITE 0x0F +#define AD7768_REG_GPIO_READ 0x10 + +#define AD7768_NUM_GPIOS 5 +#define AD7768_FILTER_GPIO 4 + +static int ad7768_gpio_init_valid_mask(struct gpio_chip *gc, + unsigned long *valid_mask, + unsigned int ngpios) +{ + if (ngpios > AD7768_FILTER_GPIO && + fwnode_property_match_string(gc->fwnode, "clock-names", "mclk") < 0) + __clear_bit(AD7768_FILTER_GPIO, valid_mask); + + return 0; +} + +static int ad7768_gpio_reg_mask_xlate(struct gpio_regmap *gpio, + enum gpio_regmap_operation op, + unsigned int base, unsigned int offset, + unsigned int *reg, unsigned int *mask) +{ + struct regmap *regmap = gpio_regmap_get_drvdata(gpio); + int ret; + + *reg = base; + *mask = BIT(offset); + + if (op != GPIO_REGMAP_GET_OP) + return 0; + + /* + * AD7768 has separate input-state and output-latch registers. For an + * output line, report the programmed value from the output latch; + * input lines continue to use the input-state register. + */ + ret = regmap_test_bits(regmap, AD7768_REG_GPIO_CONTROL, *mask); + if (ret < 0) + return ret; + if (ret) + *reg = AD7768_REG_GPIO_WRITE; + + return 0; +} + +static int ad7768_gpio_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) +{ + struct gpio_regmap_config config; + struct device *dev = &adev->dev; + struct device *parent = dev->parent; + struct regmap *map; + int ret; + + map = dev_get_regmap(parent, NULL); + if (!map) + return -ENODEV; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(parent, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + ret = regmap_set_bits(map, AD7768_REG_GPIO_CONTROL, AD7768_GPIO_UGPIO_ENABLE); + if (ret) + return ret; + + config = (struct gpio_regmap_config) { + .parent = dev, + .regmap = map, + .label = dev_name(parent), + .ngpio = AD7768_NUM_GPIOS, + .reg_dat_base = AD7768_REG_GPIO_READ, + .reg_set_base = AD7768_REG_GPIO_WRITE, + .reg_dir_out_base = AD7768_REG_GPIO_CONTROL, + .pm_dev = parent, + .fwnode = dev_fwnode(parent), + .reg_mask_xlate = ad7768_gpio_reg_mask_xlate, + .init_valid_mask = ad7768_gpio_init_valid_mask, + .drvdata = map, + }; + + return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &config)); +} + +static const struct auxiliary_device_id ad7768_gpio_ids[] = { + { .name = "ad7768.gpio" }, + { } +}; +MODULE_DEVICE_TABLE(auxiliary, ad7768_gpio_ids); + +static struct auxiliary_driver ad7768_gpio_driver = { + .probe = ad7768_gpio_probe, + .id_table = ad7768_gpio_ids, +}; +module_auxiliary_driver(ad7768_gpio_driver); + +MODULE_AUTHOR("Janani Sunil "); +MODULE_DESCRIPTION("Analog Devices AD7768 GPIO auxiliary driver"); +MODULE_LICENSE("GPL"); From 373c23eab4581c653e875257b72dd239fb64b738 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 17 Sep 2026 05:54:16 +0200 Subject: [PATCH 37/63] dt-bindings: gpio: renesas,rcar-gpio: Document R-Car X5H (R8A78000) support Document support for the GPIO controller blocks in the Renesas R-Car X5H (R8A78000) SoC. Acked-by: Conor Dooley Reviewed-by: Geert Uytterhoeven Signed-off-by: Marek Vasut Link: https://patch.msgid.link/20260917035438.523010-1-marek.vasut+renesas@mailbox.org Signed-off-by: Bartosz Golaszewski --- .../devicetree/bindings/gpio/renesas,rcar-gpio.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/gpio/renesas,rcar-gpio.yaml b/Documentation/devicetree/bindings/gpio/renesas,rcar-gpio.yaml index d32e103a64aa..95f5175c7a9d 100644 --- a/Documentation/devicetree/bindings/gpio/renesas,rcar-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/renesas,rcar-gpio.yaml @@ -56,6 +56,11 @@ properties: - renesas,gpio-r8a779h0 # R-Car V4M - const: renesas,rcar-gen4-gpio # R-Car Gen4 + - items: + - enum: + - renesas,gpio-r8a78000 # R-Car X5H + - const: renesas,rcar-gen5-gpio # R-Car Gen5 + reg: maxItems: 1 From 79ec1b1b33242fd58865a56c1565be5c1de0e837 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 17 Sep 2026 05:54:17 +0200 Subject: [PATCH 38/63] gpio: rcar: Add R-Car X5H (R8A78000) support R-Car X5H (R8A78000) is the first member of the R-Car Gen5 family. Add support for R-Car X5H, which has slightly different GPIO block register layout compared to previous generations. Introduce offset remap table which performs 1:1 remap for R-Car Gen1..4 and a bit more complex remap for R-Car Gen5. The GPIO block register offsets on R-Car Gen5 changed and the change can be divided into five groups, registers which remained at the same offset, INDT register shifted by +0x10, OUTDTSEL register shifted by -0x34, INEN register shifted by -0x38 and the rest of the registers used by the driver shifted by +0x70 . Signed-off-by: Marek Vasut Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260917035438.523010-2-marek.vasut+renesas@mailbox.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-rcar.c | 86 +++++++++++++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 18 deletions(-) diff --git a/drivers/gpio/gpio-rcar.c b/drivers/gpio/gpio-rcar.c index 09bebde5c426..54dc47d45de7 100644 --- a/drivers/gpio/gpio-rcar.c +++ b/drivers/gpio/gpio-rcar.c @@ -36,6 +36,7 @@ struct gpio_rcar_info { bool has_both_edge_trigger; bool has_always_in; bool has_inen; + const u8 *offsetmap; }; struct gpio_rcar_priv { @@ -49,34 +50,68 @@ struct gpio_rcar_priv { struct gpio_rcar_bank_info bank_info; }; -#define IOINTSEL 0x00 /* General IO/Interrupt Switching Register */ -#define INOUTSEL 0x04 /* General Input/Output Switching Register */ -#define OUTDT 0x08 /* General Output Register */ -#define INDT 0x0c /* General Input Register */ -#define INTDT 0x10 /* Interrupt Display Register */ -#define INTCLR 0x14 /* Interrupt Clear Register */ -#define INTMSK 0x18 /* Interrupt Mask Register */ -#define MSKCLR 0x1c /* Interrupt Mask Clear Register */ -#define POSNEG 0x20 /* Positive/Negative Logic Select Register */ -#define EDGLEVEL 0x24 /* Edge/level Select Register */ -#define OUTDTSEL 0x40 /* Output Data Select Register */ -#define BOTHEDGE 0x4c /* One Edge/Both Edge Select Register */ -#define INEN 0x50 /* General Input Enable Register */ +enum gpio_rcar_reg { + IOINTSEL, /* General IO/Interrupt Switching Register */ + INOUTSEL, /* General Input/Output Switching Register */ + OUTDT, /* General Output Register */ + INDT, /* General Input Register */ + INTDT, /* Interrupt Display Register */ + INTCLR, /* Interrupt Clear Register */ + INTMSK, /* Interrupt Mask Register */ + MSKCLR, /* Interrupt Mask Clear Register */ + POSNEG, /* Positive/Negative Logic Select Register */ + EDGLEVEL, /* Edge/level Select Register */ + OUTDTSEL, /* Output Data Select Register */ + BOTHEDGE, /* One Edge/Both Edge Select Register */ + INEN, /* General Input Enable Register */ +}; + +static const u8 gpio_rcar_offsetmap_gen1[] = { + [IOINTSEL] = 0x00, + [INOUTSEL] = 0x04, + [OUTDT] = 0x08, + [INDT] = 0x0c, + [INTDT] = 0x10, + [INTCLR] = 0x14, + [INTMSK] = 0x18, + [MSKCLR] = 0x1c, + [POSNEG] = 0x20, + [EDGLEVEL] = 0x24, + [OUTDTSEL] = 0x40, + [BOTHEDGE] = 0x4c, + [INEN] = 0x50, +}; + +static const u8 gpio_rcar_offsetmap_gen5[] = { + [IOINTSEL] = 0x00, + [INOUTSEL] = 0x04, + [OUTDT] = 0x08, + [INDT] = 0x1c, + [INTDT] = 0x80, + [INTCLR] = 0x84, + [INTMSK] = 0x88, + [MSKCLR] = 0x8c, + [POSNEG] = 0x90, + [EDGLEVEL] = 0x94, + [OUTDTSEL] = 0x0c, + [BOTHEDGE] = 0xbc, + [INEN] = 0x18, +}; #define RCAR_MAX_GPIO_PER_BANK 32 -static inline u32 gpio_rcar_read(struct gpio_rcar_priv *p, int offs) +static inline u32 gpio_rcar_read(struct gpio_rcar_priv *p, enum gpio_rcar_reg offs) { - return ioread32(p->base + offs); + return ioread32(p->base + p->info.offsetmap[offs]); } -static inline void gpio_rcar_write(struct gpio_rcar_priv *p, int offs, +static inline void gpio_rcar_write(struct gpio_rcar_priv *p, enum gpio_rcar_reg offs, u32 value) { - iowrite32(value, p->base + offs); + iowrite32(value, p->base + p->info.offsetmap[offs]); } -static void gpio_rcar_modify_bit(struct gpio_rcar_priv *p, int offs, +static void gpio_rcar_modify_bit(struct gpio_rcar_priv *p, enum gpio_rcar_reg offs, int bit, bool value) { u32 tmp = gpio_rcar_read(p, offs); @@ -399,6 +434,7 @@ static const struct gpio_rcar_info gpio_rcar_info_gen1 = { .has_both_edge_trigger = false, .has_always_in = false, .has_inen = false, + .offsetmap = gpio_rcar_offsetmap_gen1, }; static const struct gpio_rcar_info gpio_rcar_info_gen2 = { @@ -406,6 +442,7 @@ static const struct gpio_rcar_info gpio_rcar_info_gen2 = { .has_both_edge_trigger = true, .has_always_in = false, .has_inen = false, + .offsetmap = gpio_rcar_offsetmap_gen1, }; static const struct gpio_rcar_info gpio_rcar_info_gen3 = { @@ -413,6 +450,7 @@ static const struct gpio_rcar_info gpio_rcar_info_gen3 = { .has_both_edge_trigger = true, .has_always_in = true, .has_inen = false, + .offsetmap = gpio_rcar_offsetmap_gen1, }; static const struct gpio_rcar_info gpio_rcar_info_gen4 = { @@ -420,6 +458,15 @@ static const struct gpio_rcar_info gpio_rcar_info_gen4 = { .has_both_edge_trigger = true, .has_always_in = true, .has_inen = true, + .offsetmap = gpio_rcar_offsetmap_gen1, +}; + +static const struct gpio_rcar_info gpio_rcar_info_gen5 = { + .has_outdtsel = true, + .has_both_edge_trigger = true, + .has_always_in = true, + .has_inen = true, + .offsetmap = gpio_rcar_offsetmap_gen5, }; static const struct of_device_id gpio_rcar_of_table[] = { @@ -438,6 +485,9 @@ static const struct of_device_id gpio_rcar_of_table[] = { }, { .compatible = "renesas,rcar-gen4-gpio", .data = &gpio_rcar_info_gen4, + }, { + .compatible = "renesas,rcar-gen5-gpio", + .data = &gpio_rcar_info_gen5, }, { .compatible = "renesas,gpio-rcar", .data = &gpio_rcar_info_gen1, From b44aec87658e5d1882503ad1208ebf0b052f0ac0 Mon Sep 17 00:00:00 2001 From: Mehmet Fide Date: Tue, 15 Sep 2026 10:04:58 +0200 Subject: [PATCH 39/63] pinctrl: make the CONFIG_PINCTRL=n gpio config stubs return -ENOTSUPP With CONFIG_PINCTRL=n, pinctrl_gpio_get_config() and pinctrl_gpio_set_config() are stubs that return 0. The getter reports success without touching the config it was asked to fill, so a caller reads the packed parameter it passed in back as if it were the pin's state; the setter reports success for a configuration nobody applied. With CONFIG_PINCTRL=y the same callers already get -ENOTSUPP from gpiochip_generic_config() and gpiochip_generic_get_config() for a chip without pin ranges, and gpiolib treats that value as "the backend cannot do this" and carries on. Return it from the stubs too, so a kernel without pinctrl behaves like a chip without pin ranges instead of claiming success. Suggested-by: Haibo Chen Signed-off-by: Mehmet Fide Reviewed-by: Bartosz Golaszewski Signed-off-by: Linus Walleij --- include/linux/pinctrl/consumer.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/pinctrl/consumer.h b/include/linux/pinctrl/consumer.h index 11b8f0b8da0c..7be49d447426 100644 --- a/include/linux/pinctrl/consumer.h +++ b/include/linux/pinctrl/consumer.h @@ -107,14 +107,14 @@ static inline int pinctrl_gpio_get_config(struct gpio_chip *gc, unsigned int offset, unsigned long *config) { - return 0; + return -ENOTSUPP; } static inline int pinctrl_gpio_set_config(struct gpio_chip *gc, unsigned int offset, unsigned long config) { - return 0; + return -ENOTSUPP; } static inline struct pinctrl * __must_check pinctrl_get(struct device *dev) From 7077059b3fce189ede20bec28366558b1069c321 Mon Sep 17 00:00:00 2001 From: Mehmet Fide Date: Tue, 15 Sep 2026 10:04:59 +0200 Subject: [PATCH 40/63] pinctrl: imx: answer OUTPUT_ENABLE/INPUT_ENABLE queries from the pad register The mmio pinconf get callback ignores which parameter was requested and returns the raw conf register, so a generic query through pinctrl_gpio_get_config() gets register bits back instead of the packed parameter it asked for. Decode the requested parameter and answer PIN_CONFIG_OUTPUT_ENABLE and PIN_CONFIG_INPUT_ENABLE on SoCs that declare where those bits live in the pad register; Vybrid has OBE at bit 1 and IBE at bit 0. The answer is 0 with the bit value as the argument, which is what the pinctrl_gpio_get_config() users (gpio-by-pinctrl, and gpio-mmio in the next patch) expect. Other parameters and the SCU based SoCs, whose firmware call returns the raw pad value as well, get -ENOTSUPP; a pin the device tree never configured gets -EINVAL, as the raw helper already does, so a caller can tell "no answer for this pin" from "this controller never answers". The only in-tree user of the raw register was the debugfs group dump, which called the callback with an uninitialized config; it now reads the register through its own helper, like the single pin dump already did. The set callback is not touched: the fsl,pins binding hands it the raw pad register value and that stays the only thing it accepts. Nothing in-tree sends generic parameters to it on these SoCs; making it understand them is a separate change. This gives gpio-mmio a way to read back the line direction on chips whose direction lives in the pin controller. Suggested-by: Bartosz Golaszewski Signed-off-by: Mehmet Fide Reviewed-by: Haibo Chen Reviewed-by: Linus Walleij Reviewed-by: Bartosz Golaszewski Signed-off-by: Linus Walleij --- drivers/pinctrl/freescale/pinctrl-imx.c | 54 +++++++++++++++++++++-- drivers/pinctrl/freescale/pinctrl-imx.h | 4 ++ drivers/pinctrl/freescale/pinctrl-vf610.c | 2 + 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/drivers/pinctrl/freescale/pinctrl-imx.c b/drivers/pinctrl/freescale/pinctrl-imx.c index 9a45b376d36f..f08ef52bf4f8 100644 --- a/drivers/pinctrl/freescale/pinctrl-imx.c +++ b/drivers/pinctrl/freescale/pinctrl-imx.c @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -291,8 +292,8 @@ struct pinmux_ops imx_pmx_ops = { .set_mux = imx_pmx_set, }; -static int imx_pinconf_get_mmio(struct pinctrl_dev *pctldev, unsigned pin_id, - unsigned long *config) +static int imx_pinconf_get_raw_mmio(struct pinctrl_dev *pctldev, + unsigned int pin_id, unsigned long *config) { struct imx_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev); const struct imx_pinctrl_soc_info *info = ipctl->info; @@ -312,16 +313,61 @@ static int imx_pinconf_get_mmio(struct pinctrl_dev *pctldev, unsigned pin_id, return 0; } +static int imx_pinconf_get_mmio(struct pinctrl_dev *pctldev, + unsigned int pin_id, unsigned long *config) +{ + struct imx_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev); + const struct imx_pinctrl_soc_info *info = ipctl->info; + const struct imx_pin_reg *pin_reg = &ipctl->pin_regs[pin_id]; + enum pin_config_param param = pinconf_to_config_param(*config); + unsigned int mask; + u32 raw; + + switch (param) { + case PIN_CONFIG_OUTPUT_ENABLE: + mask = info->obe_mask; + break; + case PIN_CONFIG_INPUT_ENABLE: + mask = info->ibe_mask; + break; + default: + mask = 0; + break; + } + + if (!mask) + return -ENOTSUPP; + if (pin_reg->conf_reg == -1) + return -EINVAL; + + raw = readl(ipctl->base + pin_reg->conf_reg); + *config = pinconf_to_config_packed(param, !!(raw & mask)); + + return 0; +} + static int imx_pinconf_get(struct pinctrl_dev *pctldev, unsigned pin_id, unsigned long *config) { struct imx_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev); const struct imx_pinctrl_soc_info *info = ipctl->info; + if (info->flags & IMX_USE_SCU) + return -ENOTSUPP; + + return imx_pinconf_get_mmio(pctldev, pin_id, config); +} + +static int imx_pinconf_get_raw(struct pinctrl_dev *pctldev, + unsigned int pin_id, unsigned long *config) +{ + struct imx_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev); + const struct imx_pinctrl_soc_info *info = ipctl->info; + if (info->flags & IMX_USE_SCU) return info->imx_pinconf_get(pctldev, pin_id, config); else - return imx_pinconf_get_mmio(pctldev, pin_id, config); + return imx_pinconf_get_raw_mmio(pctldev, pin_id, config); } static int imx_pinconf_set_mmio(struct pinctrl_dev *pctldev, @@ -426,7 +472,7 @@ static void imx_pinconf_group_dbg_show(struct pinctrl_dev *pctldev, struct imx_pin *pin = &((struct imx_pin *)(grp->data))[i]; name = pin_get_name(pctldev, pin->pin); - ret = imx_pinconf_get(pctldev, pin->pin, &config); + ret = imx_pinconf_get_raw(pctldev, pin->pin, &config); if (ret) return; seq_printf(s, " %s: 0x%lx\n", name, config); diff --git a/drivers/pinctrl/freescale/pinctrl-imx.h b/drivers/pinctrl/freescale/pinctrl-imx.h index f65ff45b4003..8fa7e1e2521d 100644 --- a/drivers/pinctrl/freescale/pinctrl-imx.h +++ b/drivers/pinctrl/freescale/pinctrl-imx.h @@ -91,6 +91,10 @@ struct imx_pinctrl_soc_info { unsigned int mux_mask; u8 mux_shift; + /* OBE/IBE bits in the conf register, 0 if the pad does not have them */ + unsigned int obe_mask; + unsigned int ibe_mask; + int (*gpio_set_direction)(struct pinctrl_dev *pctldev, struct pinctrl_gpio_range *range, unsigned offset, diff --git a/drivers/pinctrl/freescale/pinctrl-vf610.c b/drivers/pinctrl/freescale/pinctrl-vf610.c index 76a4bc0181a0..77d077618782 100644 --- a/drivers/pinctrl/freescale/pinctrl-vf610.c +++ b/drivers/pinctrl/freescale/pinctrl-vf610.c @@ -319,6 +319,8 @@ static const struct imx_pinctrl_soc_info vf610_pinctrl_info = { .gpio_set_direction = vf610_pmx_gpio_set_direction, .mux_mask = 0x700000, .mux_shift = 20, + .obe_mask = 0x2, + .ibe_mask = 0x1, }; static const struct of_device_id vf610_pinctrl_of_match[] = { From 24715edd98ba62840214a35137ed23e611348797 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 17 Sep 2026 14:11:26 -0700 Subject: [PATCH 41/63] gpio: it87: Remove const from labels_table allocation type In preparation for converting the kmalloc family of allocators to the type-aware kmalloc_obj family, we need to make sure that the returned type from the allocation matches the type of the variable being assigned. (The kmalloc family returns "void *", which can be implicitly cast to any pointer type.) The assigned type is "char **", but the converted allocation type would be "const char **", which is const qualified. As there is no general way to remove const qualifiers, take the size from the assignment target instead. No change in allocation size results. Build tested ARCH=x86_64 allmodconfig with GCC 16.2.0: drivers/gpio/gpio-it87.o Assisted-by: LLM coccinelle Signed-off-by: Kees Cook Link: https://patch.msgid.link/20260917211125.i.966-kees@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-it87.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-it87.c b/drivers/gpio/gpio-it87.c index 2ad3c239367b..699325129a7d 100644 --- a/drivers/gpio/gpio-it87.c +++ b/drivers/gpio/gpio-it87.c @@ -362,7 +362,7 @@ static int __init it87_gpio_init(void) */ labels = kcalloc(it87_gpio->chip.ngpio, sizeof("it87_gpXY"), GFP_KERNEL); - labels_table = kcalloc(it87_gpio->chip.ngpio, sizeof(const char *), + labels_table = kcalloc(it87_gpio->chip.ngpio, sizeof(*labels_table), GFP_KERNEL); if (!labels || !labels_table) { From fc291104d01a4e42b44132925c46655d4be5ed13 Mon Sep 17 00:00:00 2001 From: Mehmet Fide Date: Tue, 15 Sep 2026 10:05:00 +0200 Subject: [PATCH 42/63] gpiolib: add get_config() and gpiochip_generic_get_config() A chip with a pin control backend sets a line's configuration through set_config() and gpiochip_generic_config(), but has no way to read one back. gpio-mmio needs that to learn the direction of a line whose direction lives in the pin controller. Add the optional get_config() callback, taking the packed parameter to query and returning its bare argument the way pinctrl_gpio_get_config() does, and gpiochip_generic_get_config() as the pin control backed implementation, the mirror of gpiochip_generic_config(): a chip without pin ranges, or a kernel without pinctrl, gets -ENOTSUPP. Nothing in gpiolib calls get_config() and there is no consumer API; it is for the chip's own use. Suggested-by: Linus Walleij Signed-off-by: Mehmet Fide Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260915080501.329424-4-mehmet.fide@gmail.com Signed-off-by: Bartosz Golaszewski --- Documentation/driver-api/gpio/driver.rst | 7 +++++++ drivers/gpio/gpiolib.c | 21 +++++++++++++++++++++ include/linux/gpio/driver.h | 9 +++++++++ 3 files changed, 37 insertions(+) diff --git a/Documentation/driver-api/gpio/driver.rst b/Documentation/driver-api/gpio/driver.rst index a4f160b95089..3e53374c7e9f 100644 --- a/Documentation/driver-api/gpio/driver.rst +++ b/Documentation/driver-api/gpio/driver.rst @@ -134,6 +134,13 @@ ending up in the pin control back-end "behind" the GPIO controller, usually closer to the actual pins. This way the pin controller can manage the below listed GPIO configurations. +The optional .get_config() callback reads a configuration back: the packed +parameter to query goes in, its bare argument comes out, the way +pinctrl_gpio_get_config() answers. gpiochip_generic_get_config() is its pin +control backed counterpart. Nothing in gpiolib calls it; it is for the GPIO +driver's own use, for example to learn the direction of a line when the pin +controller owns it. + If a pin controller back-end is used, the GPIO controller or hardware description needs to provide "GPIO ranges" mapping the GPIO line offsets to pin numbers on the pin controller so they can properly cross-reference each other. diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index ef8ccaf17c9c..0586fc1f9564 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -2440,6 +2440,27 @@ int gpiochip_generic_config(struct gpio_chip *gc, unsigned int offset, } EXPORT_SYMBOL_GPL(gpiochip_generic_config); +/** + * gpiochip_generic_get_config() - read back the configuration of a pin + * @gc: the gpiochip owning the GPIO + * @offset: the offset of the GPIO to query + * @config: the packed parameter to query, replaced by its bare argument + * + * Returns: + * 0 on success, or negative errno on failure. + */ +int gpiochip_generic_get_config(struct gpio_chip *gc, unsigned int offset, + unsigned long *config) +{ +#ifdef CONFIG_PINCTRL + if (list_empty(&gc->gpiodev->pin_ranges)) + return -ENOTSUPP; +#endif + + return pinctrl_gpio_get_config(gc, offset, config); +} +EXPORT_SYMBOL_GPL(gpiochip_generic_get_config); + #ifdef CONFIG_PINCTRL /** diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h index 17511434ed07..4077dc678cae 100644 --- a/include/linux/gpio/driver.h +++ b/include/linux/gpio/driver.h @@ -359,6 +359,10 @@ struct gpio_irq_chip { * @set_config: optional hook for all kinds of settings. Uses the same * packed config format as generic pinconf. Must return 0 on success and * a negative error number on failure. + * @get_config: optional hook to read back a setting. Takes the packed + * generic pinconf parameter to query and returns its bare argument in + * the same variable, like pinctrl_gpio_get_config(). Must return 0 on + * success and a negative error number on failure. * @to_irq: optional hook supporting non-static gpiod_to_irq() mappings; * implementation may not sleep * @dbg_show: optional routine to show contents in debugfs; default code @@ -434,6 +438,9 @@ struct gpio_chip { int (*set_config)(struct gpio_chip *gc, unsigned int offset, unsigned long config); + int (*get_config)(struct gpio_chip *gc, + unsigned int offset, + unsigned long *config); int (*to_irq)(struct gpio_chip *gc, unsigned int offset); @@ -708,6 +715,8 @@ int gpiochip_generic_request(struct gpio_chip *gc, unsigned int offset); void gpiochip_generic_free(struct gpio_chip *gc, unsigned int offset); int gpiochip_generic_config(struct gpio_chip *gc, unsigned int offset, unsigned long config); +int gpiochip_generic_get_config(struct gpio_chip *gc, unsigned int offset, + unsigned long *config); /** * struct gpio_pin_range - pin range controlled by a gpio chip From b5c26686305e070ecb8a4af3acbc4aa92266029e Mon Sep 17 00:00:00 2001 From: Mehmet Fide Date: Tue, 15 Sep 2026 10:05:01 +0200 Subject: [PATCH 43/63] gpio: mmio: track the direction of chips without direction registers A generic chip with GPIO_GENERIC_PINCTRL_BACKEND and no direction registers sets the direction through pinctrl but has no get_direction callback, so every gpiod_get_direction() call trips the WARN in gpiolib and the direction gpiolib reports is whatever it assumed. On a Vybrid Colibri module the i2c core, asking for the SDA line's direction before bus recovery, gets that backtrace on every boot, and so does any other caller outside gpiolib. Keep the direction of such a chip in the existing shadow: the direction setters update sdir under the chip lock, and get_direction() is the shadow-reading path already used for unreadable direction registers. That keeps the callback usable in atomic context, which it has to be: gpiochip_lock_as_irq() calls it for !can_sleep chips from gpiochip_irq_domain_activate(), under the irq descriptor lock. The pad's actual state is read once, in process context, when a line is requested: gpiolib calls request() right before get_direction() for a new line, so the shadow is seeded there from PIN_CONFIG_OUTPUT_ENABLE through the chip's get_config(), which is gpiochip_generic_get_config() for the pinctrl backend, and the line reports what the pin controller says. Lines pinctrl cannot answer for keep the input default, which is what gpiolib assumed before. dir_unreadable, so far set only for GPIO_GENERIC_UNREADABLE_REG_DIR, is now also set for a pinctrl-backed chip without direction registers: both kinds of chip have no register to read the direction from and serve it from the shadow. The answers depend on the pinctrl driver decoding the parameter. On Vybrid that is the preceding pinctrl-imx patch: without it pin_config_get() returns the raw pad register, which would seed every requested line as an output, so this patch must not land ahead of it. Suggested-by: Bartosz Golaszewski Signed-off-by: Mehmet Fide Reviewed-by: Haibo Chen Link: https://patch.msgid.link/20260915080501.329424-5-mehmet.fide@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mmio.c | 60 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpio-mmio.c b/drivers/gpio/gpio-mmio.c index 7e4b3e8d609f..0709ffaab4de 100644 --- a/drivers/gpio/gpio-mmio.c +++ b/drivers/gpio/gpio-mmio.c @@ -49,6 +49,7 @@ o ` ~~~~\___/~~~~ ` controller in FPGA is ,.` #include #include #include +#include #include #include #include @@ -372,7 +373,17 @@ static int gpio_mmio_dir_in_err(struct gpio_chip *gc, unsigned int gpio) static int gpio_mmio_simple_dir_in(struct gpio_chip *gc, unsigned int gpio) { - return gpio_mmio_dir_return(gc, gpio, false); + struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); + int ret; + + ret = gpio_mmio_dir_return(gc, gpio, false); + if (ret) + return ret; + + guard(raw_spinlock_irqsave)(&chip->lock); + chip->sdir &= ~gpio_mmio_line2mask(gc, gpio); + + return 0; } static int gpio_mmio_dir_out_err(struct gpio_chip *gc, unsigned int gpio, @@ -384,9 +395,19 @@ static int gpio_mmio_dir_out_err(struct gpio_chip *gc, unsigned int gpio, static int gpio_mmio_simple_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) { + struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); + int ret; + gc->set(gc, gpio, val); - return gpio_mmio_dir_return(gc, gpio, true); + ret = gpio_mmio_dir_return(gc, gpio, true); + if (ret) + return ret; + + guard(raw_spinlock_irqsave)(&chip->lock); + chip->sdir |= gpio_mmio_line2mask(gc, gpio); + + return 0; } static int gpio_mmio_dir_in(struct gpio_chip *gc, unsigned int gpio) @@ -601,20 +622,51 @@ static int gpio_mmio_setup_direction(struct gpio_generic_chip *chip, gc->direction_input = gpio_mmio_dir_in_err; else gc->direction_input = gpio_mmio_simple_dir_in; + + if (cfg->flags & GPIO_GENERIC_PINCTRL_BACKEND) { + chip->dir_unreadable = true; + gc->get_direction = gpio_mmio_get_dir; + gc->get_config = gpiochip_generic_get_config; + } } return 0; } +static void gpio_mmio_seed_dir_from_pinctrl(struct gpio_chip *gc, + unsigned int gpio) +{ + struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); + unsigned long config; + + if (!gc->get_config || chip->reg_dir_out || chip->reg_dir_in) + return; + + config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT_ENABLE, 0); + if (gc->get_config(gc, gpio, &config)) + return; + + guard(raw_spinlock_irqsave)(&chip->lock); + if (config) + chip->sdir |= gpio_mmio_line2mask(gc, gpio); + else + chip->sdir &= ~gpio_mmio_line2mask(gc, gpio); +} + static int gpio_mmio_request(struct gpio_chip *gc, unsigned int gpio_pin) { struct gpio_generic_chip *chip = to_gpio_generic_chip(gc); + int ret; if (gpio_pin >= gc->ngpio) return -EINVAL; - if (chip->pinctrl) - return gpiochip_generic_request(gc, gpio_pin); + if (chip->pinctrl) { + ret = gpiochip_generic_request(gc, gpio_pin); + if (ret) + return ret; + gpio_mmio_seed_dir_from_pinctrl(gc, gpio_pin); + } return 0; } From b399579a8e7b2a442f456df86cafc33628da420d Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 18 Sep 2026 22:17:13 +0800 Subject: [PATCH 44/63] gpio: dln2: use assign_bit() where applicable Convert open-coded if/else with set_bit/clear_bit to the assign_bit API. Signed-off-by: Peng Fan Link: https://patch.msgid.link/20260918141713.3022960-1-peng.fan@oss.nxp.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-dln2.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpio-dln2.c b/drivers/gpio/gpio-dln2.c index 4670ffd7ea7f..aef4345d265e 100644 --- a/drivers/gpio/gpio-dln2.c +++ b/drivers/gpio/gpio-dln2.c @@ -243,10 +243,8 @@ static int dln2_gpio_set_direction(struct gpio_chip *chip, unsigned offset, if (ret < 0) return ret; - if (dir == DLN2_GPIO_DIRECTION_OUT) - set_bit(offset, dln2->output_enabled); - else - clear_bit(offset, dln2->output_enabled); + assign_bit(offset, dln2->output_enabled, + dir == DLN2_GPIO_DIRECTION_OUT); return ret; } From 209fcf625880cd1128bc98ca835d4cb5c3b8e136 Mon Sep 17 00:00:00 2001 From: Ridham Khurana Date: Tue, 22 Sep 2026 21:16:58 +0000 Subject: [PATCH 45/63] gpio: siox: normalize return value of gpio_get() gpio_siox_get() returns the masked bit from the cached input or output byte, so for most lines a high level reads back as a value between 2 and 128 instead of 1. The GPIO get callback is expected to return 0 or 1 (or a negative error code). Since commit 86ef402d805d ("gpiolib: sanitize the return value of gpio_chip::get()"), gpiolib rejected such values with -EBADE and reads of a high line failed. Commit ec2cceadfae7 ("gpiolib: normalize the return value of gc->get() on behalf of buggy drivers") made gpiolib clamp the value instead, but it now warns on every such read. Normalize the value returned by gpio_siox_get() to the [0, 1] range. Signed-off-by: Ridham Khurana Link: https://patch.msgid.link/20260922211702.1480614-1-khurana.ridham222@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-siox.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-siox.c b/drivers/gpio/gpio-siox.c index 958034b9f3f3..d60680ace144 100644 --- a/drivers/gpio/gpio-siox.c +++ b/drivers/gpio/gpio-siox.c @@ -148,11 +148,11 @@ static int gpio_siox_get(struct gpio_chip *chip, unsigned int offset) if (offset >= 12) { unsigned int bitpos = 19 - offset; - ret = ddata->setdata[0] & (1 << bitpos); + ret = !!(ddata->setdata[0] & (1 << bitpos)); } else { unsigned int bitpos = 11 - offset; - ret = ddata->getdata[bitpos / 8] & (1 << (bitpos % 8)); + ret = !!(ddata->getdata[bitpos / 8] & (1 << (bitpos % 8))); } mutex_unlock(&ddata->lock); From 8204b924c6cac2d77af0d000985593d58b8c2ade Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Wed, 16 Sep 2026 09:06:05 +0300 Subject: [PATCH 46/63] dt-bindings: mfd: Common ROHM PMIC properties ROHM PMICs keep having similar functionalities from one generation to the next. Keeping the device-trees describing these functionalities similar helps users because: 1. They can re-use existing device-tree properties when switching from one project, using a ROHM PMIC, to next project using newer variant. 2. The users can find and re-use examples from old projects - even if they never worked with ROHM PMICs before. Similarly, introducing new and different properties for same functionality previous variant had, will be adding confusion. Sometimes the existing properties aren't 100% what reviewers would prefer. When issues are minor or just cosmetic, changing the existing properties is not feasible. Reviewers can't be expected to know which properties are new, and which are existing - and this can lead to unnecessary review discussion wasting time and energy from everyone. Adding a common file for re-used ROHM PMIC properties should make it clearly visible that a property is re-used, when a new PMIC binding refers to this file instead of (re-)describing all the existing bindings. This will also help keeping the common properties identical across the variants. Add new file listing commonly used ROHM PMIC properties. Signed-off-by: Matti Vaittinen Reviewed-by: Rob Herring (Arm) Reviewed-by: Linus Walleij Link: https://patch.msgid.link/114ad461ace2f76e6a04a482d3c31ee0b372e672.1789538455.git.mazziesaccount@gmail.com Signed-off-by: Lee Jones --- .../bindings/mfd/rohm,pmic-pins.yaml | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/rohm,pmic-pins.yaml diff --git a/Documentation/devicetree/bindings/mfd/rohm,pmic-pins.yaml b/Documentation/devicetree/bindings/mfd/rohm,pmic-pins.yaml new file mode 100644 index 000000000000..0d1082804f5a --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/rohm,pmic-pins.yaml @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mfd/rohm,pmic-pins.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ROHM PMICs' OTP configured pin descriptions + +maintainers: + - Matti Vaittinen + +description: + Collection of properties which are commonly used to describe various + ROHM PMIC's pins. + +properties: + rohm,clkout-open-drain: + description: clk32kout mode. Set to 1 for "open-drain" or 0 for "cmos" + a.k.a "push-pull". + $ref: /schemas/types.yaml#/definitions/uint32 + maximum: 1 + + rohm,pin-clkout: + $ref: /schemas/types.yaml#/definitions/string + description: + The IC has different OTP options to use clkout-pin for different + purposes. Set this property to indicate the OTP variant. + + Many of the ROHM ICs have pins, which function can be defined in + OTP. Some of them provide no means to detect the pin configuration + at run-time, leaving software unaware of which features the IC + really has. These properties can be used to inform which pins + provide which functionality. IOW, these properties aren't used to + configure pin, they are used to tell what functionality pins on + this specific IC have. + + # Supported functionalities depend on the exact IC model. Following + # values are already defined: gpi, gpo. + # Please, re-use them when adding new ICs. + + rohm,pin-fault_b: + $ref: /schemas/types.yaml#/definitions/string + description: + The IC has different OTP options to use fault_b-pin for different + purposes. Set this property to indicate the OTP variant. + + # Supported functionalities depend on the exact IC model. Following + # values are already defined: gpi, gpo, faultb, readyind, pwrseq. + # Please, re-use them when adding new ICs. + +patternProperties: + "^rohm,pin-dvs[0-1]$": + $ref: /schemas/types.yaml#/definitions/string + description: + The IC has different OTP options to use dvs-pins for different + purposes. Set this property to indicate the OTP variant. + # Supported functionalities depend on the exact IC model. Following + # values are already defined: dvs-input, gpi, gpo, pwrseq. + # Please, re-use them when adding new ICs. + + "^rohm,pin-exten([0-1])?$": + $ref: /schemas/types.yaml#/definitions/string + description: + The IC has an OTP option to use exten -pin(s) for different purposes. + Set this property to indicate the OTP variant. + # Supported functionalities depend on the exact IC model. Following + # values are already defined: gpi, gpo, pwrseq. + # Please, re-use them when adding new ICs. + +# The schema is expected to be reference by IC schemas, which may introduce +# other properties that must be allowed. +additionalProperties: true From f6a6d948c2e0bd35aa31f635ec5ef42ac7f804c5 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Wed, 16 Sep 2026 09:07:36 +0300 Subject: [PATCH 47/63] dt-bindings: rohm,bd*: Ref common ROHM bindings The commonly used ROHM PMIC bindings were added into rohm,pmic-pins.yaml. Keeping commonly used properties in one file can ease both the maintaining the bindings and the reviewing process. Reference the common bindings from rohm,pmic-pins.yaml, instead of declaring them from the scratch for every PMIC using them. Keep the PMIC variant specific information (like supported pin functions) in PMIC the specific files, so unsupported values won't be allowed. Signed-off-by: Matti Vaittinen Reviewed-by: Rob Herring (Arm) Reviewed-by: Linus Walleij Link: https://patch.msgid.link/3cf6bb01a3a129f2b0b1d6418049dbdb1711873a.1789538455.git.mazziesaccount@gmail.com Signed-off-by: Lee Jones --- .../bindings/mfd/rohm,bd71815-pmic.yaml | 9 +++--- .../bindings/mfd/rohm,bd71828-pmic.yaml | 9 +++--- .../bindings/mfd/rohm,bd72720-pmic.yaml | 29 +++---------------- 3 files changed, 12 insertions(+), 35 deletions(-) diff --git a/Documentation/devicetree/bindings/mfd/rohm,bd71815-pmic.yaml b/Documentation/devicetree/bindings/mfd/rohm,bd71815-pmic.yaml index 4fc78efaa550..0a67de054674 100644 --- a/Documentation/devicetree/bindings/mfd/rohm,bd71815-pmic.yaml +++ b/Documentation/devicetree/bindings/mfd/rohm,bd71815-pmic.yaml @@ -15,6 +15,9 @@ description: | for LED and a 500 mA single-cell linear charger. Also included is a Coulomb counter, a real-time clock (RTC), and a 32.768 kHz clock gate and two GPOs. +allOf: + - $ref: rohm,pmic-pins.yaml# + properties: compatible: const: rohm,bd71815 @@ -44,11 +47,7 @@ properties: clock-output-names: const: bd71815-32k-out - rohm,clkout-open-drain: - description: clk32kout mode. Set to 1 for "open-drain" or 0 for "cmos". - $ref: /schemas/types.yaml#/definitions/uint32 - minimum: 0 - maximum: 1 + rohm,clkout-open-drain: true rohm,charger-sense-resistor-micro-ohms: minimum: 10000 diff --git a/Documentation/devicetree/bindings/mfd/rohm,bd71828-pmic.yaml b/Documentation/devicetree/bindings/mfd/rohm,bd71828-pmic.yaml index 09e7d68e92bf..e5043dec990d 100644 --- a/Documentation/devicetree/bindings/mfd/rohm,bd71828-pmic.yaml +++ b/Documentation/devicetree/bindings/mfd/rohm,bd71828-pmic.yaml @@ -15,6 +15,9 @@ description: | single-cell linear charger. Also included is a Coulomb counter, a real-time clock (RTC), and a 32.768 kHz clock gate. +allOf: + - $ref: rohm,pmic-pins.yaml# + properties: compatible: oneOf: @@ -49,11 +52,7 @@ properties: clock-output-names: const: bd71828-32k-out - rohm,clkout-open-drain: - description: clk32kout mode. Set to 1 for "open-drain" or 0 for "cmos". - $ref: /schemas/types.yaml#/definitions/uint32 - minimum: 0 - maximum: 1 + rohm,clkout-open-drain: true rohm,charger-sense-resistor-micro-ohms: minimum: 10000 diff --git a/Documentation/devicetree/bindings/mfd/rohm,bd72720-pmic.yaml b/Documentation/devicetree/bindings/mfd/rohm,bd72720-pmic.yaml index b094542339e8..52d0f6e4ef0f 100644 --- a/Documentation/devicetree/bindings/mfd/rohm,bd72720-pmic.yaml +++ b/Documentation/devicetree/bindings/mfd/rohm,bd72720-pmic.yaml @@ -15,6 +15,9 @@ description: and a 3000 mA switching charger. ICs also include a Coulomb counter, a real-time clock (RTC), GPIOs and a 32.768 kHz clock gate. +allOf: + - $ref: rohm,pmic-pins.yaml# + # In addition to the properties found from the charger node, PMICs # use properties from a static battery node. Please see the: # Documentation/devicetree/bindings/power/supply/battery.yaml @@ -80,10 +83,7 @@ properties: clock-output-names: const: bd71828-32k-out - rohm,clkout-open-drain: - description: clk32kout mode. Set to 1 for "open-drain" or 0 for "cmos". - $ref: /schemas/types.yaml#/definitions/uint32 - maximum: 1 + rohm,clkout-open-drain: true rohm,charger-sense-resistor-micro-ohms: minimum: 10000 @@ -103,14 +103,6 @@ properties: $ref: /schemas/leds/rohm,bd71828-leds.yaml rohm,pin-fault_b: - $ref: /schemas/types.yaml#/definitions/string - description: - PMIC has an OTP option to use fault_b-pin for different - purposes. Set this property accordingly. OTP options are - OTP0 - bi-directional FAULT_B or READY indicator depending on a - 'sub option' - OTP1 - GPO - OTP2 - Power sequencer output. enum: - faultb - readyind @@ -119,14 +111,6 @@ properties: patternProperties: "^rohm,pin-dvs[0-1]$": - $ref: /schemas/types.yaml#/definitions/string - description: - PMIC has 4 different OTP options to determine the use of dvs-pins. - OTP0 - regulator RUN state control. - OTP1 - GPI. - OTP2 - GPO. - OTP3 - Power sequencer output. - This property specifies the use of the pin. enum: - dvs-input - gpi @@ -134,11 +118,6 @@ patternProperties: - pwrseq "^rohm,pin-exten[0-1]$": - $ref: /schemas/types.yaml#/definitions/string - description: PMIC has an OTP option to use exten0-pin for different - purposes. Set this property accordingly. - OTP0 - GPO - OTP1 - Power sequencer output. enum: - gpo - pwrseq From f08afff0ddd74e2edb11d4be9cdc7b4919d68ee8 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Wed, 16 Sep 2026 09:08:13 +0300 Subject: [PATCH 48/63] dt-bindings: regulator: ROHM BD73800 regulators Add bindings for the BUCKs and LDOs on ROHM BD73800. The PMIC state specific voltages can be set in same fashion as with a few other ROHM PMICs (for example with BD718[15,28,37,47,50,79]). Same properties are recycled :) The LDOs 1 and 4 can use different voltage ranges depending on the OTP configuration. Signed-off-by: Matti Vaittinen Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/0f001ca0d7463cd66777d8ad723212fadb199ce1.1789538455.git.mazziesaccount@gmail.com Signed-off-by: Lee Jones --- .../regulator/rohm,bd73800-regulator.yaml | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 Documentation/devicetree/bindings/regulator/rohm,bd73800-regulator.yaml diff --git a/Documentation/devicetree/bindings/regulator/rohm,bd73800-regulator.yaml b/Documentation/devicetree/bindings/regulator/rohm,bd73800-regulator.yaml new file mode 100644 index 000000000000..2db4585272c5 --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/rohm,bd73800-regulator.yaml @@ -0,0 +1,99 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/regulator/rohm,bd73800-regulator.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ROHM BD73800 Power Management Integrated Circuit regulators + +maintainers: + - Matti Vaittinen + +description: | + This module is part of the ROHM BD73800 MFD device. For more details + see Documentation/devicetree/bindings/mfd/rohm,bd73800-pmic.yaml. + + The regulator controller is represented as a sub-node of the PMIC node + on the device tree. + + Regulator nodes should be named to buck and ldo. + The valid names for BD73800 regulator nodes are + buck1, buck2, buck3, buck4, buck5, buck6, buck7, buck8 + ldo1, ldo2, ldo3, ldo4 + +patternProperties: + "^buck[1-8]$": + type: object + description: + Properties for a single BUCK regulator. + allOf: + - $ref: regulator.yaml# + - $ref: rohm,pmic-states.yaml# + + properties: + regulator-name: + pattern: "^buck[1-8]$" + description: + should be "buck1", ..., "buck8" + + rohm,dvs-run-voltage: + minimum: 0 + maximum: 3500000 + + rohm,dvs-idle-voltage: + minimum: 0 + maximum: 3500000 + + rohm,dvs-suspend-voltage: + minimum: 0 + maximum: 3500000 + + # BUCKs 1,2,3,4 and 8 support voltages 0.5 - 1.3V + # BUCK 5 supports voltages 0.3 - 1.3V + # BUCKs 6 and 7 support voltages 1.5 - 3.5V + + required: + - regulator-name + + unevaluatedProperties: false + + "^ldo[1-4]$": + type: object + description: + Properties for single LDO regulator. + allOf: + - $ref: regulator.yaml# + - $ref: rohm,pmic-states.yaml# + + properties: + regulator-name: + pattern: "^ldo[1-4]$" + description: + should be "ldo1", ..., "ldo4" + + rohm,dvs-run-voltage: + minimum: 0 + maximum: 1 + + rohm,dvs-idle-voltage: + minimum: 0 + maximum: 1 + + rohm,dvs-suspend-voltage: + minimum: 0 + maximum: 1 + + rohm,ldo-range-high: + type: boolean + description: + LDO1 and LDO3 voltage ranges can be "high" or "low" depending on + OTP. Indicate that the "high" range is used. See comment below + for voltages. + + # LDOs 2 and 4 support voltages 0.75 - 3.3V + # LDOs 1 and 3 may support different ranges depending on OTP. + # either 0.6 - 1.8V or 0.75 - 3.3V. + + unevaluatedProperties: false + +additionalProperties: false From 579eae348f7393f14cc864af8c99f7c675e5b1f2 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Wed, 16 Sep 2026 09:08:47 +0300 Subject: [PATCH 49/63] dt-bindings: mfd: ROHM BD73800 PMIC The ROHM BD73800 is a power management IC which integrates 8 BUCKs and 4 LDOs. There is also an ADC and operation amplifier intended for current / temperature measurement and accumulation. RTC and 32.768 kHz clock gate are also included. The PMIC can be customized via OTP and it has options for operating as a main PMIC in multi-PMIC installation. Some of the pins can also be used for GPO or GPI (including interrupt support). There are also ROHM BD71851 and BD71885 PMICs out there. These are, from the SW-perspective, similar to the BD73800. There is only some different default values and OTP settings. The driver should be able to handle them just fine. Signed-off-by: Matti Vaittinen Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/2bf2278e66e106aa9550ffc43197441182388572.1789538455.git.mazziesaccount@gmail.com Signed-off-by: Lee Jones --- .../bindings/mfd/rohm,bd73800-pmic.yaml | 218 ++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/rohm,bd73800-pmic.yaml diff --git a/Documentation/devicetree/bindings/mfd/rohm,bd73800-pmic.yaml b/Documentation/devicetree/bindings/mfd/rohm,bd73800-pmic.yaml new file mode 100644 index 000000000000..8371e7f2ab84 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/rohm,bd73800-pmic.yaml @@ -0,0 +1,218 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mfd/rohm,bd73800-pmic.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ROHM BD73800 Power Management Integrated Circuit + +maintainers: + - Matti Vaittinen + +description: + BD73800GW is a single-chip power management IC for battery-powered + portable devices. It integrates 8 buck converters, 4 LDOs and a current + sense amplifier with ADC. Also included is a Real Time Clock (RTC) and a + 32.768 kHz clock gate. Depending on the OTP configuration the BD73800 + may also have interrupt controller and GPIOs. + + There are also different variants called BD71851 and BD71885 which are + almost identical from the software point of view. + +allOf: + - $ref: rohm,pmic-pins.yaml# + +properties: + compatible: + oneOf: + - const: rohm,bd73800 + + - items: + - enum: + - rohm,bd71851 + - rohm,bd71885 + - const: rohm,bd73800 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + # The GPIO1, CLKOUT (GPIO2), FAULT_B and EXTEN_OUT pins can be + # configured to interrupt pins by OTP. + interrupt-controller: true + + "#interrupt-cells": + const: 1 + description: + The IRQ number. 0 is GPIO1, 1 CLKOUT (GPIO2), 2 FAULT_B and 3 EXTEN_OUT. + NOTE, A pin can operate as IRQ source only when the OTP + configuration for it has been set to GPI. + + gpio-controller: true + + "#gpio-cells": + const: 2 + + rohm,pin-gpio1: + $ref: /schemas/types.yaml#/definitions/string + description: + Indicate if the GPIO1 pin has been set to GPI or GPO at manufacturing. + The GPIO1, CLKOUT, FAULT_B and EXTEN_OUT pins may be configured for a + specific purpose (like ADC input, 32.768 clk output, fault indicator or + delivering power sequence to a companion PMIC when multiple PMICs are + used) - but also to be either a GPO or GPI. (When used as a GPI the pin + can also be used as an IRQ source). The pin purpose is determined by + OTP (One Time Programmable memory), typically during device + manufacturing. The OTP can't be read at runtime so device-tree should + describe the GPIO pins. + enum: [gpi, gpo] + + rohm,pin-clkout: + enum: [gpi, gpo] + + rohm,pin-fault_b: + enum: [gpi, gpo] + + rohm,pin-exten: + enum: [gpi, gpo] + + clocks: + description: + The CLKOUT pin may have its purpose overridden by OTP configuration. It + is possible the BD73800 does not output a clock signal. Hence the clk + properties are optional. + maxItems: 1 + + "#clock-cells": + const: 0 + + clock-output-names: + maxItems: 1 + + rohm,clkout-open-drain: true + + regulators: + $ref: /schemas/regulator/rohm,bd73800-regulator.yaml + description: + List of child nodes that specify the regulators. + +required: + - compatible + - reg + - interrupts + - regulators + +additionalProperties: false + +examples: + - | + #include + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + pmic: pmic@4b { + compatible = "rohm,bd73800"; + reg = <0x4b>; + + interrupt-parent = <&gpio1>; + interrupts = <29 IRQ_TYPE_LEVEL_LOW>; + + clocks = <&osc 0>; + #clock-cells = <0>; + clock-output-names = "bd73800-32k-out"; + + gpio-controller; + #gpio-cells = <2>; + + rohm,pin-gpio1 = "gpo"; + rohm,pin-exten = "gpi"; + + regulators { + buck1: buck1 { + regulator-name = "buck1"; + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <2000000>; + regulator-always-on; + regulator-ramp-delay = <1250>; + rohm,dvs-run-voltage = <1150000>; + rohm,dvs-suspend-voltage = <950000>; + }; + buck2: buck2 { + regulator-name = "buck2"; + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <2000000>; + regulator-always-on; + regulator-ramp-delay = <1250>; + rohm,dvs-run-voltage = <1150000>; + rohm,dvs-suspend-voltage = <950000>; + }; + buck3: buck3 { + regulator-name = "buck3"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <2700000>; + rohm,dvs-run-voltage = <1150000>; + rohm,dvs-idle-voltage = <1100000>; + rohm,dvs-suspend-voltage = <950000>; + regulator-always-on; + }; + buck4: buck4 { + regulator-name = "buck4"; + regulator-min-microvolt = <1100000>; + regulator-max-microvolt = <1850000>; + regulator-always-on; + }; + buck5: buck5 { + regulator-name = "buck5"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + buck6: buck6 { + regulator-name = "buck6"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + buck7: buck7 { + regulator-name = "buck7"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + buck8: buck8 { + regulator-name = "buck8"; + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <1300000>; + regulator-always-on; + }; + ldo1: ldo1 { + regulator-name = "ldo1"; + rohm,ldo-range-high; + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + ldo2: ldo2 { + regulator-name = "ldo2"; + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + ldo3: ldo3 { + regulator-name = "ldo3"; + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + ldo4: ldo4 { + regulator-name = "ldo4"; + regulator-min-microvolt = <650000>; + regulator-max-microvolt = <1750000>; + regulator-always-on; + }; + }; + }; + }; From 6f6ebfdd38126edc6cd3f1427ee261d57a83fff9 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Wed, 16 Sep 2026 09:09:09 +0300 Subject: [PATCH 50/63] mfd: Support for ROHM BD73800 PMIC core The BD73800 integrates regulators, ADC (intended for accumulating current / voltage / power values), a real-time clock (RTC), clock gate and GPIOs. Add core support for ROHM BD73800 Power Management IC. Signed-off-by: Matti Vaittinen Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/3e3a25abe337e3f68cde585674019dca439ac897.1789538455.git.mazziesaccount@gmail.com Signed-off-by: Lee Jones --- drivers/mfd/Kconfig | 15 +- drivers/mfd/rohm-bd71828.c | 147 ++++++++++++++- include/linux/mfd/rohm-bd73800.h | 306 +++++++++++++++++++++++++++++++ include/linux/mfd/rohm-generic.h | 1 + 4 files changed, 461 insertions(+), 8 deletions(-) create mode 100644 include/linux/mfd/rohm-bd73800.h diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index e4fd4572472f..439dbf5ee110 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -2226,7 +2226,7 @@ config MFD_ROHM_BD718XX and emergency shut down as well as 32,768KHz clock output. config MFD_ROHM_BD71828 - tristate "ROHM BD718[15/28/79], BD72720 and BD73900 PMICs" + tristate "ROHM BD718[15/28/79], BD72720 and BD73[8/9]00 PMICs" depends on I2C=y depends on OF select REGMAP_I2C @@ -2235,14 +2235,17 @@ config MFD_ROHM_BD71828 select MFD_ROHM_PWRBUTTON help Select this option to get support for the ROHM BD71815, BD71828, - BD71879, BD72720 and BD73900 Power Management ICs (PMICs). These are - single-chip Power Management ICs (PMIC), mainly for battery-powered - portable devices. + BD71879, BD72720, BD73800 and BD73900 Power Management ICs (PMICs). + These are single-chip Power Management ICs (PMIC), mainly for + battery-powered portable devices. The BD71815 has 5 bucks, 7 LDOs, and a boost for driving LEDs. The BD718[28/79] have 7 buck converters and 7 LDOs. The BD72720 and the BD73900 have 10 bucks and 11 LDOs. - All ICs provide a single-cell linear charger, a Coulomb counter, - a Real-Time Clock (RTC), GPIOs and a 32.768 kHz clock gate. + All the above ICs provide a single-cell linear charger, and a Coulomb + counter. + The BD73800 has 8 bucks, 4 LDOs and ADC for power/current + accumulation but no charging logic. All these PMICs integrate also a + Real-Time Clock (RTC), GPIOs and a 32.768 kHz clock gate. config MFD_ROHM_BD957XMUF tristate "ROHM BD9576MUF and BD9573MUF Power Management ICs" diff --git a/drivers/mfd/rohm-bd71828.c b/drivers/mfd/rohm-bd71828.c index bf4df8e1b34c..46faef7058f8 100644 --- a/drivers/mfd/rohm-bd71828.c +++ b/drivers/mfd/rohm-bd71828.c @@ -1,8 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (C) 2019 ROHM Semiconductors + * Copyright (C) 2019, 2026 ROHM Semiconductor * - * ROHM BD718[15/28/79] and BD72720 PMIC driver + * ROHM BD718[15/28/79], BD72720 and BD73[8/9]00 PMIC driver */ #include @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -56,6 +57,12 @@ static const struct resource bd72720_rtc_irqs[] = { DEFINE_RES_IRQ_NAMED(BD72720_INT_RTC2, "bd70528-rtc-alm-2"), }; +static const struct resource bd73800_rtc_irqs[] = { + DEFINE_RES_IRQ_NAMED(BD73800_INT_RTC0, "bd70528-rtc-alm-0"), + DEFINE_RES_IRQ_NAMED(BD73800_INT_RTC1, "bd70528-rtc-alm-1"), + DEFINE_RES_IRQ_NAMED(BD73800_INT_RTC2, "bd70528-rtc-alm-2"), +}; + static const struct resource bd71815_power_irqs[] = { DEFINE_RES_IRQ_NAMED(BD71815_INT_DCIN_RMV, "bd71815-dcin-rmv"), DEFINE_RES_IRQ_NAMED(BD71815_INT_CLPS_OUT, "bd71815-dcin-clps-out"), @@ -230,6 +237,17 @@ static const struct mfd_cell bd72720_mfd_cells[] = { /* Power button is registered separately */ }; +static const struct mfd_cell bd73800_mfd_cells[] = { + { .name = "bd73800-pmic", }, + { .name = "bd73800-clk", }, + { .name = "bd73800-gpio", }, + { + .name = "bd73800-rtc", + .num_resources = ARRAY_SIZE(bd73800_rtc_irqs), + .resources = bd73800_rtc_irqs, + }, +}; + static const struct regmap_range bd71815_volatile_ranges[] = { regmap_reg_range(BD71815_REG_SEC, BD71815_REG_YEAR), regmap_reg_range(BD71815_REG_CONF, BD71815_REG_BAT_TEMP), @@ -339,6 +357,17 @@ static const struct regmap_range bd72720_volatile_ranges_4c[] = { BD72720_UNWRAP_REG_RANGE(BD72720_REG_IMPCHK_CTRL, BD72720_REG_IMPCHK_CTRL), }; +static const struct regmap_range bd73800_volatile_ranges[] = { + regmap_reg_range(BD73800_REG_POR_REASON, BD73800_REG_POW_STATE), + regmap_reg_range(BD73800_REG_PS_CTRL_1, BD73800_REG_PS_CTRL_2), + regmap_reg_range(BD73800_REG_RCVNUM, BD73800_REG_RCVNUM), + regmap_reg_range(BD73800_REG_RTC_SEC, BD73800_REG_RTC_YEAR), + regmap_reg_range(BD73800_REG_RTC_CONF, BD73800_REG_RTC_CONF), + regmap_reg_range(BD73800_REG_ADC_ACCUM_KICK, BD73800_REG_ADC_TEMP_VAL0), + regmap_reg_range(BD73800_REG_INT_MAIN_STAT, BD73800_REG_INT_5_STAT), + regmap_reg_range(BD73800_REG_INT_MAIN_SRC, BD73800_REG_INT_5_SRC), +}; + static const struct regmap_access_table bd71815_volatile_regs = { .yes_ranges = &bd71815_volatile_ranges[0], .n_yes_ranges = ARRAY_SIZE(bd71815_volatile_ranges), @@ -364,6 +393,24 @@ static const struct regmap_access_table bd72720_volatile_regs_4c = { .n_yes_ranges = ARRAY_SIZE(bd72720_volatile_ranges_4c), }; +static const struct regmap_access_table bd73800_volatile_regs = { + .yes_ranges = bd73800_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(bd73800_volatile_ranges), +}; + +static const struct regmap_range bd73800_read_only_ranges[] = { + regmap_reg_range(BD73800_REG_PRODUCT_ID, BD73800_REG_NVMVERSION), + regmap_reg_range(BD73800_REG_POW_STATE, BD73800_REG_POW_STATE), + regmap_reg_range(BD73800_REG_ADC_ACCUM_CNT2, BD73800_REG_ADC_TEMP_VAL0), + regmap_reg_range(BD73800_REG_INT_MAIN_STAT, BD73800_REG_INT_MAIN_STAT), + regmap_reg_range(BD73800_REG_INT_MAIN_SRC, BD73800_REG_INT_5_SRC), +}; + +static const struct regmap_access_table bd73800_ro_regs = { + .no_ranges = bd73800_read_only_ranges, + .n_no_ranges = ARRAY_SIZE(bd73800_read_only_ranges), +}; + static const struct regmap_config bd71815_regmap = { .reg_bits = 8, .val_bits = 8, @@ -448,6 +495,15 @@ static const struct regmap_config bd72720_regmap_4c = { .cache_type = REGCACHE_MAPLE, }; +static const struct regmap_config bd73800_regmap = { + .reg_bits = 8, + .val_bits = 8, + .wr_table = &bd73800_ro_regs, + .volatile_table = &bd73800_volatile_regs, + .max_register = BD73800_MAX_REGISTER - 1, + .cache_type = REGCACHE_MAPLE, +}; + /* * Mapping of main IRQ register bits to sub-IRQ register offsets so that we can * access corect sub-IRQ registers based on bits that are set in main IRQ @@ -779,6 +835,60 @@ static int bd72720_set_type_config(unsigned int **buf, unsigned int type, return regmap_irq_set_type_config_simple(buf, type, irq_data, idx, irq_drv_data); } +static const struct regmap_irq bd73800_irqs[] = { + /* INT_STAT_1 register IRQs, ADC and RTC */ + REGMAP_IRQ_REG(BD73800_INT_ADC_ACCUM_DONE, 0, BD73800_INT_ADC_ACCUM_DONE_MASK), + REGMAP_IRQ_REG(BD73800_INT_ADC_ACCUM_OVF, 0, BD73800_INT_ADC_ACCUM_OVF_MASK), + REGMAP_IRQ_REG(BD73800_INT_ADC_ACCUM_VAL, 0, BD73800_INT_ADC_ACCUM_VAL_MASK), + REGMAP_IRQ_REG(BD73800_INT_ADC_ACCUM_TW, 0, BD73800_INT_ADC_ACCUM_TW_MASK), + REGMAP_IRQ_REG(BD73800_INT_ADC_POW_VAL, 0, BD73800_INT_ADC_POW_VAL_MASK), + REGMAP_IRQ_REG(BD73800_INT_RTC0, 0, BD73800_INT_RTC0_MASK), + REGMAP_IRQ_REG(BD73800_INT_RTC1, 0, BD73800_INT_RTC1_MASK), + REGMAP_IRQ_REG(BD73800_INT_RTC2, 0, BD73800_INT_RTC2_MASK), + + /* BUCK reg interrupts */ + /* INT_STAT_2 IRQs */ + REGMAP_IRQ_REG(BD73800_INT_BUCK1_DVS_DONE, 1, BD73800_INT_BUCK1_DVS_DONE_MASK), + REGMAP_IRQ_REG(BD73800_INT_BUCK2_DVS_DONE, 1, BD73800_INT_BUCK2_DVS_DONE_MASK), + REGMAP_IRQ_REG(BD73800_INT_BUCK3_DVS_DONE, 1, BD73800_INT_BUCK3_DVS_DONE_MASK), + REGMAP_IRQ_REG(BD73800_INT_BUCK4_DVS_DONE, 1, BD73800_INT_BUCK4_DVS_DONE_MASK), + REGMAP_IRQ_REG(BD73800_INT_BUCK5_DVS_DONE, 1, BD73800_INT_BUCK5_DVS_DONE_MASK), + REGMAP_IRQ_REG(BD73800_INT_BUCK6_DVS_DONE, 1, BD73800_INT_BUCK6_DVS_DONE_MASK), + REGMAP_IRQ_REG(BD73800_INT_BUCK7_DVS_DONE, 1, BD73800_INT_BUCK7_DVS_DONE_MASK), + REGMAP_IRQ_REG(BD73800_INT_BUCK8_DVS_DONE, 1, BD73800_INT_BUCK8_DVS_DONE_MASK), + /* INT_STAT_3 IRQs */ + REGMAP_IRQ_REG(BD73800_INT_BUCK1_OCP, 2, BD73800_INT_BUCK1_OCP_MASK), + REGMAP_IRQ_REG(BD73800_INT_BUCK2_OCP, 2, BD73800_INT_BUCK2_OCP_MASK), + REGMAP_IRQ_REG(BD73800_INT_BUCK3_OCP, 2, BD73800_INT_BUCK3_OCP_MASK), + REGMAP_IRQ_REG(BD73800_INT_BUCK4_OCP, 2, BD73800_INT_BUCK4_OCP_MASK), + REGMAP_IRQ_REG(BD73800_INT_BUCK5_OCP, 2, BD73800_INT_BUCK5_OCP_MASK), + REGMAP_IRQ_REG(BD73800_INT_BUCK6_OCP, 2, BD73800_INT_BUCK6_OCP_MASK), + REGMAP_IRQ_REG(BD73800_INT_BUCK7_OCP, 2, BD73800_INT_BUCK7_OCP_MASK), + REGMAP_IRQ_REG(BD73800_INT_BUCK8_OCP, 2, BD73800_INT_BUCK8_OCP_MASK), + + /* INT_STAT_4 IRQs, power-button, WDG and reset */ + REGMAP_IRQ_REG(BD73800_INT_PBTN_LONG_PRESS, 3, BD73800_INT_PBTN_LONG_PRESS_MASK), + REGMAP_IRQ_REG(BD73800_INT_PBTN_MID_PRESS, 3, BD73800_INT_PBTN_MID_PRESS_MASK), + /* + * The SHORT_PUSH is generated when button is first pressed (longer + * than configured time limit), and then released before the MID_PRESS + * time limit. The SHORT_PRESS is generated immediately when button is + * pressed for longer than configured limit, whether it is released or + * not. + */ + REGMAP_IRQ_REG(BD73800_INT_PBTN_SHORT_PUSH, 3, BD73800_INT_PBTN_SHORT_PUSH_MASK), + REGMAP_IRQ_REG(BD73800_INT_PBTN_SHORT_PRESS, 3, BD73800_INT_PBTN_SHORT_PRESS_MASK), + REGMAP_IRQ_REG(BD73800_INT_WDG, 3, BD73800_INT_WDG_MASK), + REGMAP_IRQ_REG(BD73800_INT_SWRESET, 3, BD73800_INT_SWRESET_MASK), + REGMAP_IRQ_REG(BD73800_INT_SEQ_DONE, 3, BD73800_INT_SEQ_DONE_MASK), + + /* INT_STAT_5 IRQs, GPIO */ + REGMAP_IRQ_REG(BD73800_INT_GPIO1, 4, BD73800_INT_GPIO1_MASK), + REGMAP_IRQ_REG(BD73800_INT_GPIO2, 4, BD73800_INT_GPIO2_MASK), + REGMAP_IRQ_REG(BD73800_INT_GPIO3, 4, BD73800_INT_GPIO3_MASK), + REGMAP_IRQ_REG(BD73800_INT_GPIO4, 4, BD73800_INT_GPIO4_MASK), +}; + static const struct regmap_irq_chip bd71828_irq_chip = { .name = "bd71828_irq", .main_status = BD71828_REG_INT_MAIN, @@ -833,6 +943,25 @@ static const struct regmap_irq_chip bd72720_irq_chip = { .irq_reg_stride = 1, }; +static const struct regmap_irq_chip bd73800_irq_chip = { + .name = "bd73800_irq", + .main_status = BD73800_REG_INT_MAIN_STAT, + .irqs = bd73800_irqs, + .num_irqs = ARRAY_SIZE(bd73800_irqs), + .status_base = BD73800_REG_INT_1_STAT, + .unmask_base = BD73800_REG_INT_1_EN, + .ack_base = BD73800_REG_INT_1_STAT, + .init_ack_masked = true, + .num_regs = 5, + .num_main_regs = 1, + /* + * Ignore mirrored bits [7:5]. They are handled as part of normal INT_4 + * handling. + */ + .num_main_status_bits = 5, + .irq_reg_stride = 1, +}; + static int set_clk_mode(struct device *dev, struct regmap *regmap, int clkmode_reg) { @@ -971,6 +1100,17 @@ static int bd71828_i2c_probe(struct i2c_client *i2c) main_lvl_val = BD72720_MASK_LVL1_EN_ALL; break; } + case ROHM_CHIP_TYPE_BD73800: + mfd = bd73800_mfd_cells; + cells = ARRAY_SIZE(bd73800_mfd_cells); + regmap_config = &bd73800_regmap; + irqchip = &bd73800_irq_chip; + clkmode_reg = BD73800_REG_OUT32K; + button_irq = BD73800_INT_PBTN_SHORT_PUSH; + main_lvl_mask_reg = BD73800_REG_INT_MAIN_EN; + main_lvl_val = BD73800_INT_MAIN_EN_ALL; + break; + default: dev_err(&i2c->dev, "Unknown device type"); return -EINVAL; @@ -1051,6 +1191,9 @@ static const struct of_device_id bd71828_of_match[] = { }, { .compatible = "rohm,bd72720", .data = (void *)ROHM_CHIP_TYPE_BD72720, + }, { + .compatible = "rohm,bd73800", + .data = (void *)ROHM_CHIP_TYPE_BD73800, }, { }, }; diff --git a/include/linux/mfd/rohm-bd73800.h b/include/linux/mfd/rohm-bd73800.h new file mode 100644 index 000000000000..c6c0d453a40d --- /dev/null +++ b/include/linux/mfd/rohm-bd73800.h @@ -0,0 +1,306 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright 2026 ROHM Semiconductors. + * + * Author: Matti Vaittinen + */ + +#ifndef _MFD_BD73800_H +#define _MFD_BD73800_H + +#include + +enum { + BD73800_BUCK1 = 0, + BD73800_BUCK2, + BD73800_BUCK3, + BD73800_BUCK4, + BD73800_BUCK5, + BD73800_BUCK6, + BD73800_BUCK7, + BD73800_BUCK8, + BD73800_LDO1, + BD73800_LDO2, + BD73800_LDO3, + BD73800_LDO4, +}; + +/* + * All regulators except BUCK 5 have full 8-bit register of valid voltage + * values, including 0. + */ +#define BD73800_NUM_VOLTS (0xff + 1) +/* + * BUCK 5 has two sets of voltage ranges, both having valid voltage selectors + * from 0x0 to 0x7f + */ +#define BD73800_BUCK5_VOLTS (0x80 + 0x80) + +/* BD73800 interrupts */ +enum { + /* INT_STAT_1 register IRQs, ADC and RTC */ + BD73800_INT_ADC_ACCUM_DONE, + BD73800_INT_ADC_ACCUM_OVF, + BD73800_INT_ADC_ACCUM_VAL, + BD73800_INT_ADC_ACCUM_TW, + BD73800_INT_ADC_POW_VAL, + BD73800_INT_RTC0, + BD73800_INT_RTC1, + BD73800_INT_RTC2, + + /* BUCK reg interrupts */ + /* INT_STAT_2 IRQs */ + BD73800_INT_BUCK1_DVS_DONE, + BD73800_INT_BUCK2_DVS_DONE, + BD73800_INT_BUCK3_DVS_DONE, + BD73800_INT_BUCK4_DVS_DONE, + BD73800_INT_BUCK5_DVS_DONE, + BD73800_INT_BUCK6_DVS_DONE, + BD73800_INT_BUCK7_DVS_DONE, + BD73800_INT_BUCK8_DVS_DONE, + /* INT_STAT_3 IRQs */ + BD73800_INT_BUCK1_OCP, + BD73800_INT_BUCK2_OCP, + BD73800_INT_BUCK3_OCP, + BD73800_INT_BUCK4_OCP, + BD73800_INT_BUCK5_OCP, + BD73800_INT_BUCK6_OCP, + BD73800_INT_BUCK7_OCP, + BD73800_INT_BUCK8_OCP, + + /* INT_STAT_4 IRQs, power-button, WDG and reset */ + BD73800_INT_PBTN_LONG_PRESS, + BD73800_INT_PBTN_MID_PRESS, + /* + * The SHORT_PUSH is generated when button is first pressed (longer + * than configured time limit), and then released before the MID_PRESS + * time limit. The SHORT_PRESS is generated immediately when button is + * pressed for longer than configured limit, whether it is released or + * not. + */ + BD73800_INT_PBTN_SHORT_PUSH, + BD73800_INT_PBTN_SHORT_PRESS, + BD73800_INT_WDG, + BD73800_INT_SWRESET, + BD73800_INT_SEQ_DONE, + + /* INT_STAT_5 IRQs, GPIO */ + BD73800_INT_GPIO1, + BD73800_INT_GPIO2, + BD73800_INT_GPIO3, + BD73800_INT_GPIO4, +}; + +#define BD73800_MASK_RUN_EN BIT(2) +#define BD73800_MASK_SUSP_EN BIT(1) +#define BD73800_MASK_IDLE_EN BIT(0) +#define BD73800_MASK_VOLT GENMASK(7, 0) +#define BD73800_MASK_BUCK5_VOLT GENMASK(6, 0) +#define BD73800_MASK_RAMP_DELAY GENMASK(2, 1) +#define BD73800_BUCK5_RANGE_MASK BIT(7) + +/* BD73800 registers */ +enum { + BD73800_REG_PRODUCT_ID = 0x0, + BD73800_REG_MANUFACTURER_ID, + BD73800_REG_REVISION, + BD73800_REG_NVMVERSION, + BD73800_REG_POR_REASON, + BD73800_REG_RESET_REASON1, + BD73800_REG_RESET_REASON2, + BD73800_REG_RESET_REASON3, + BD73800_REG_POW_STATE, + BD73800_REG_WRST_SEL, + BD73800_REG_PS_CTRL_1, + BD73800_REG_PS_CTRL_2, + BD73800_REG_RCVCFG, + BD73800_REG_RCVNUM, + BD73800_REG_CRDCFG, /* 0x0f, followed by undocumented reg */ + + BD73800_REG_BUCK1_ON = 0x11, + BD73800_REG_BUCK1_MODE, + BD73800_REG_BUCK1_VOLT_RUN, + BD73800_REG_BUCK1_VOLT_IDLE, + BD73800_REG_BUCK1_VOLT_SUSP, /* 0x15, followed by undocumented reg */ + + BD73800_REG_BUCK2_ON = 0x17, + BD73800_REG_BUCK2_MODE, + BD73800_REG_BUCK2_VOLT_RUN, + BD73800_REG_BUCK2_VOLT_IDLE, + BD73800_REG_BUCK2_VOLT_SUSP, /* 0x1b */ + + BD73800_REG_BUCK3_ON = 0x1d, + BD73800_REG_BUCK3_MODE, + BD73800_REG_BUCK3_VOLT_RUN, + BD73800_REG_BUCK3_VOLT_IDLE, + BD73800_REG_BUCK3_VOLT_SUSP, /* 0x21 */ + + BD73800_REG_BUCK4_ON = 0x23, + BD73800_REG_BUCK4_MODE, + BD73800_REG_BUCK4_VOLT_RUN, + BD73800_REG_BUCK4_VOLT_IDLE, + BD73800_REG_BUCK4_VOLT_SUSP, /* 0x27 */ + + BD73800_REG_BUCK5_ON = 0x29, + BD73800_REG_BUCK5_MODE, + BD73800_REG_BUCK5_VOLT_RUN, + BD73800_REG_BUCK5_VOLT_IDLE, + BD73800_REG_BUCK5_VOLT_SUSP, /* 0x2d */ + + BD73800_REG_BUCK6_ON = 0x2f, + BD73800_REG_BUCK6_MODE, + BD73800_REG_BUCK6_VOLT_RUN, + BD73800_REG_BUCK6_VOLT_IDLE, + BD73800_REG_BUCK6_VOLT_SUSP, /* 0x33 */ + + BD73800_REG_BUCK7_ON = 0x35, + BD73800_REG_BUCK7_MODE, + BD73800_REG_BUCK7_VOLT_RUN, + BD73800_REG_BUCK7_VOLT_IDLE, + BD73800_REG_BUCK7_VOLT_SUSP, /* 0x39 */ + + BD73800_REG_BUCK8_ON = 0x3b, + BD73800_REG_BUCK8_MODE, + BD73800_REG_BUCK8_VOLT_RUN, + BD73800_REG_BUCK8_VOLT_IDLE, + BD73800_REG_BUCK8_VOLT_SUSP, /* 0x3f */ + + BD73800_REG_LDO1_ON = 0x41, + BD73800_REG_LDO1_VOLT, + BD73800_REG_LDO1_MODE, + BD73800_REG_LDO2_ON, + BD73800_REG_LDO2_VOLT, + BD73800_REG_LDO2_MODE, + BD73800_REG_LDO3_ON, + BD73800_REG_LDO3_VOLT, + BD73800_REG_LDO3_MODE, + BD73800_REG_LDO4_ON, + BD73800_REG_LDO4_VOLT, + BD73800_REG_LDO4_MODE, /* 0x4c */ + + BD73800_REG_GPO_OUT = 0x4e, + BD73800_REG_OUT32K = 0x50, + BD73800_REG_RTC_SEC, + BD73800_REG_RTC_MIN, + BD73800_REG_RTC_HOUR, + BD73800_REG_RTC_WEEK, + BD73800_REG_RTC_DAY, + BD73800_REG_RTC_MONTH, + BD73800_REG_RTC_YEAR, + BD73800_REG_RTC_ALM0_SEC, + BD73800_REG_RTC_ALM0_MIN, + BD73800_REG_RTC_ALM0_HOUR, + BD73800_REG_RTC_ALM0_WEEK, + BD73800_REG_RTC_ALM0_DAY, + BD73800_REG_RTC_ALM0_MONTH, + BD73800_REG_RTC_ALM0_YEAR, + BD73800_REG_RTC_ALM1_SEC, + BD73800_REG_RTC_ALM1_MIN, + BD73800_REG_RTC_ALM1_HOUR, + BD73800_REG_RTC_ALM1_WEEK, + BD73800_REG_RTC_ALM1_DAY, + BD73800_REG_RTC_ALM1_MONTH, + BD73800_REG_RTC_ALM1_YEAR, + BD73800_REG_RTC_ALM2, + BD73800_REG_RTC_CONF, /* 0x69 */ + + BD73800_REG_ADC_CTRL_1 = 0x6b, + BD73800_REG_ADC_CTRL_2, + BD73800_REG_ADC_ACCUM_NUM2, + BD73800_REG_ADC_ACCUM_NUM1, + BD73800_REG_ADC_ACCUM_NUM0, + BD73800_REG_ADC_ACCUM_KICK, + BD73800_REG_ADC_ACCUM_CNT2, + BD73800_REG_ADC_ACCUM_CNT1, + BD73800_REG_ADC_ACCUM_CNT0, + BD73800_REG_ADC_ACCUM_VAL2, + BD73800_REG_ADC_ACCUM_VAL1, + BD73800_REG_ADC_ACCUM_VAL0, + BD73800_REG_ADC_VOL_VAL1, + BD73800_REG_ADC_VOL_VAL0, + BD73800_REG_ADC_CUR_VAL1, + BD73800_REG_ADC_CUR_VAL0, + BD73800_REG_ADC_POW_VAL1, + BD73800_REG_ADC_POW_VAL0, + BD73800_REG_ADC_TEMP_VAL1, + BD73800_REG_ADC_TEMP_VAL0, + BD73800_REG_ADC_ACCUM_VAL_INT_TH4, + BD73800_REG_ADC_ACCUM_VAL_INT_TH3, + BD73800_REG_ADC_ACCUM_VAL_INT_TH2, + BD73800_REG_ADC_ACCUM_VAL_INT_TH1, + BD73800_REG_ADC_ACCUM_VAL_INT_TH0, + BD73800_REG_ADC_WARN_TEMP_INT_TH1, + BD73800_REG_ADC_WARN_TEMP_INT_TH0, + BD73800_REG_ADC_POW_VAL_INT_TH1, + BD73800_REG_ADC_POW_VAL_INT_TH0, /* 0x89 */ + + BD73800_REG_PBTN_CONF = 0x8b, + + BD73800_REG_INT_MAIN_EN = 0x8f, + BD73800_REG_INT_1_EN, + BD73800_REG_INT_2_EN, + BD73800_REG_INT_3_EN, + BD73800_REG_INT_4_EN, + BD73800_REG_INT_5_EN, /* 0x94 */ + + BD73800_REG_INT_MAIN_STAT = 0x96, + BD73800_REG_INT_1_STAT, + BD73800_REG_INT_2_STAT, + BD73800_REG_INT_3_STAT, + BD73800_REG_INT_4_STAT, + BD73800_REG_INT_5_STAT, /* 0x9b */ + + BD73800_REG_INT_MAIN_SRC = 0x9d, + BD73800_REG_INT_1_SRC, + BD73800_REG_INT_2_SRC, + BD73800_REG_INT_3_SRC, + BD73800_REG_INT_4_SRC, + BD73800_REG_INT_5_SRC, /* 0xa2 */ + + BD73800_REG_RST_MASK = 0xaf, + BD73800_MAX_REGISTER, +}; + +#define BD73800_REG_RTC_START BD73800_REG_RTC_SEC +#define BD73800_REG_RTC_ALM_START BD73800_REG_RTC_ALM0_SEC + +/* BD73800 IRQ register masks */ + +#define BD73800_INT_MAIN_EN_ALL GENMASK(4, 0) +#define BD73800_INT_ADC_ACCUM_DONE_MASK BIT(0) +#define BD73800_INT_ADC_ACCUM_OVF_MASK BIT(1) +#define BD73800_INT_ADC_ACCUM_VAL_MASK BIT(2) +#define BD73800_INT_ADC_ACCUM_TW_MASK BIT(3) +#define BD73800_INT_ADC_POW_VAL_MASK BIT(4) +#define BD73800_INT_RTC0_MASK BIT(5) +#define BD73800_INT_RTC1_MASK BIT(6) +#define BD73800_INT_RTC2_MASK BIT(7) +#define BD73800_INT_BUCK1_DVS_DONE_MASK BIT(0) +#define BD73800_INT_BUCK2_DVS_DONE_MASK BIT(1) +#define BD73800_INT_BUCK3_DVS_DONE_MASK BIT(2) +#define BD73800_INT_BUCK4_DVS_DONE_MASK BIT(3) +#define BD73800_INT_BUCK5_DVS_DONE_MASK BIT(4) +#define BD73800_INT_BUCK6_DVS_DONE_MASK BIT(5) +#define BD73800_INT_BUCK7_DVS_DONE_MASK BIT(6) +#define BD73800_INT_BUCK8_DVS_DONE_MASK BIT(7) +#define BD73800_INT_BUCK1_OCP_MASK BIT(0) +#define BD73800_INT_BUCK2_OCP_MASK BIT(1) +#define BD73800_INT_BUCK3_OCP_MASK BIT(2) +#define BD73800_INT_BUCK4_OCP_MASK BIT(3) +#define BD73800_INT_BUCK5_OCP_MASK BIT(4) +#define BD73800_INT_BUCK6_OCP_MASK BIT(5) +#define BD73800_INT_BUCK7_OCP_MASK BIT(6) +#define BD73800_INT_BUCK8_OCP_MASK BIT(7) +#define BD73800_INT_PBTN_LONG_PRESS_MASK BIT(0) +#define BD73800_INT_PBTN_MID_PRESS_MASK BIT(1) +#define BD73800_INT_PBTN_SHORT_PUSH_MASK BIT(2) +#define BD73800_INT_PBTN_SHORT_PRESS_MASK BIT(3) +#define BD73800_INT_WDG_MASK BIT(4) +#define BD73800_INT_SWRESET_MASK BIT(5) +#define BD73800_INT_SEQ_DONE_MASK BIT(6) +#define BD73800_INT_GPIO1_MASK BIT(0) +#define BD73800_INT_GPIO2_MASK BIT(1) +#define BD73800_INT_GPIO3_MASK BIT(2) +#define BD73800_INT_GPIO4_MASK BIT(3) + +#endif /* _MFD_BD73800_H */ diff --git a/include/linux/mfd/rohm-generic.h b/include/linux/mfd/rohm-generic.h index 0a284919a6c3..3ec87428ee97 100644 --- a/include/linux/mfd/rohm-generic.h +++ b/include/linux/mfd/rohm-generic.h @@ -17,6 +17,7 @@ enum rohm_chip_type { ROHM_CHIP_TYPE_BD71837, ROHM_CHIP_TYPE_BD71847, ROHM_CHIP_TYPE_BD72720, + ROHM_CHIP_TYPE_BD73800, ROHM_CHIP_TYPE_BD96801, ROHM_CHIP_TYPE_BD96802, ROHM_CHIP_TYPE_BD96805, From fcf132b701bc8efdef211588f64434b5fea5750d Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Wed, 16 Sep 2026 09:09:32 +0300 Subject: [PATCH 51/63] rtc: bd70528: Support RTC on ROHM BD73800 BD73800 contains similar RTC block as BD71828 and BD71815. Only the address offsets seem different. Support also BD73800 RTC using the rtc-bd70528. Signed-off-by: Matti Vaittinen Acked-by: Alexandre Belloni Link: https://patch.msgid.link/1ff511f419fcbd72dd241f598127608a6a0c9598.1789538455.git.mazziesaccount@gmail.com Signed-off-by: Lee Jones --- drivers/rtc/rtc-bd70528.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/rtc/rtc-bd70528.c b/drivers/rtc/rtc-bd70528.c index 482810b61495..fd415e327ea6 100644 --- a/drivers/rtc/rtc-bd70528.c +++ b/drivers/rtc/rtc-bd70528.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -284,6 +285,12 @@ static int bd70528_probe(struct platform_device *pdev) bd_rtc->bd718xx_alm_block_start = BD72720_REG_RTC_ALM_START; hour_reg = BD72720_REG_RTC_HOUR; break; + case ROHM_CHIP_TYPE_BD73800: + bd_rtc->reg_time_start = BD73800_REG_RTC_START; + bd_rtc->bd718xx_alm_block_start = BD73800_REG_RTC_ALM_START; + hour_reg = BD73800_REG_RTC_HOUR; + break; + default: dev_err(&pdev->dev, "Unknown chip\n"); return -ENOENT; @@ -344,6 +351,7 @@ static const struct platform_device_id bd718x7_rtc_id[] = { { .name = "bd71828-rtc", .driver_data = ROHM_CHIP_TYPE_BD71828 }, { .name = "bd71815-rtc", .driver_data = ROHM_CHIP_TYPE_BD71815 }, { .name = "bd72720-rtc", .driver_data = ROHM_CHIP_TYPE_BD72720 }, + { .name = "bd73800-rtc", .driver_data = ROHM_CHIP_TYPE_BD73800 }, { } }; MODULE_DEVICE_TABLE(platform, bd718x7_rtc_id); From e2be91507689f46f973d3ab83901d5e31dbd650c Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Wed, 16 Sep 2026 09:10:00 +0300 Subject: [PATCH 52/63] regulator: bd71828: Support ROHM BD73800 The ROHM BD73800 is a power management IC which integrates 8 BUCKs and 4 LDOs. The PMIC has internal state-machine and it can support transitions to RUN, SUSPEND and IDLE states. The LDOs 1 and 3 have two different voltage range configurations that can be set at the manufacturing phase by OTP. By default driver assumes low voltage ranges to be used because the data-sheet indicates the higher voltage ranges to be an 'OTP option'. The high voltage range can be indicated via device-tree property. Signed-off-by: Matti Vaittinen Acked-by: Mark Brown Link: https://patch.msgid.link/94a1cef828fb5f6ea1703167c819f321ee3bde77.1789538455.git.mazziesaccount@gmail.com Signed-off-by: Lee Jones --- drivers/regulator/Kconfig | 4 +- drivers/regulator/bd71828-regulator.c | 558 +++++++++++++++++++++++++- 2 files changed, 559 insertions(+), 3 deletions(-) diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 89789ac7a786..13928af8cad4 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -241,12 +241,12 @@ config REGULATOR_BD71815 will be called bd71815-regulator. config REGULATOR_BD71828 - tristate "ROHM BD71828, BD72720 and BD73900 Power Regulators" + tristate "ROHM BD71828, BD72720 and BD73[8/9]00 Power Regulators" depends on MFD_ROHM_BD71828 select REGULATOR_ROHM help This driver supports voltage regulators on ROHM BD71828, - BD71879, BD72720 and BD73900 PMICs. This will enable + BD71879, BD72720 and BD73[8/9]00 PMICs. This will enable support for the software controllable buck and LDO regulators. This driver can also be built as a module. If so, the module diff --git a/drivers/regulator/bd71828-regulator.c b/drivers/regulator/bd71828-regulator.c index 2ced81df0c02..dea551cf658a 100644 --- a/drivers/regulator/bd71828-regulator.c +++ b/drivers/regulator/bd71828-regulator.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only // Copyright (C) 2019 ROHM Semiconductors // bd71828-regulator.c ROHM BD71828GW-DS1 regulator driver -// #include #include @@ -10,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +31,13 @@ struct bd71828_regulator_data { int reg_init_amnt; }; +/* + * LDO1 and LDO3 node names are used also in the driver to find OTP setting for + * the used voltage range. + */ +#define LDO1_NODE_NAME "ldo1" +#define LDO3_NODE_NAME "ldo3" + static const struct reg_init bd71828_buck1_inits[] = { /* * DVS Buck voltages can be changed by register values or via GPIO. @@ -172,6 +179,55 @@ static const struct linear_range bd72720_ldo6_volts[] = { REGULATOR_LINEAR_RANGE(1800000, 0x79, 0x7f, 0), }; +static const struct linear_range bd73800_buck12348_volts[] = { + REGULATOR_LINEAR_RANGE(500000, 0x00, 0x50, 10000), + REGULATOR_LINEAR_RANGE(1300000, 0x51, 0xff, 0), +}; + +static const unsigned int bd73800_buck5_volt_range_sel[] = { + 0x0, 0x0, 0x1, 0x1, +}; + +static const struct linear_range bd73800_buck5_volts[] = { + /* range selector 0 */ + REGULATOR_LINEAR_RANGE(500000, 0x00, 0x50, 10000), + REGULATOR_LINEAR_RANGE(1300000, 0x51, 0x7f, 0), + /* range selector 1 */ + REGULATOR_LINEAR_RANGE(300000, 0x00, 0x46, 10000), + REGULATOR_LINEAR_RANGE(1000000, 0x47, 0x7f, 0), +}; + +/* + * BUCK5 has two different pickable ranges, each having voltages matching + * selectors 0x0 to 0x7f. This gives 0x7f + 1 different voltages for each of + * the ranges. + */ +#define BD73800_NUM_BUCK5_VOLTS ((0x7f + 1) * 2) + +static const struct linear_range bd73800_buck67_volts[] = { + REGULATOR_LINEAR_RANGE(1500000, 0x00, 0xc8, 10000), + REGULATOR_LINEAR_RANGE(3500000, 0xc9, 0xff, 0), +}; + +/* + * On the BD73800 the LDO1 and LDO3 support two different voltage areas. + * Whether to use 'high' or 'low' ranges is configured by OTP. There seems + * to be no register to read the configured area so it must be given via + * device-tree properties. + */ +static const struct linear_range bd73800_ldo13_low_volts[] = { + REGULATOR_LINEAR_RANGE(600000, 0x00, 0x78, 10000), + REGULATOR_LINEAR_RANGE(1800000, 0x79, 0xff, 0), +}; + +static const struct linear_range bd73800_ldo13_high_volts[] = { + REGULATOR_LINEAR_RANGE(750000, 0x00, 0xff, 10000), +}; + +static const struct linear_range bd73800_ldo24_volts[] = { + REGULATOR_LINEAR_RANGE(750000, 0x00, 0xff, 10000), +}; + static const unsigned int bd71828_ramp_delay[] = { 2500, 5000, 10000, 20000 }; /* @@ -225,6 +281,36 @@ static int bd71828_ldo6_parse_dt(struct device_node *np, return 0; } +/* Operations for all other BUCKs but BUCK 5 */ +static const struct regulator_ops bd73800_buck_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_ramp_delay = regulator_set_ramp_delay_regmap, +}; + +static const struct regulator_ops bd73800_buck5_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_pickable_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_pickable_regmap, + .get_voltage_sel = regulator_get_voltage_sel_pickable_regmap, + .set_ramp_delay = regulator_set_ramp_delay_regmap, +}; + +static const struct regulator_ops bd73800_ldo_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, +}; + static const struct regulator_ops bd71828_buck_ops = { .enable = regulator_enable_regmap, .disable = regulator_disable_regmap, @@ -1565,6 +1651,412 @@ static const struct bd71828_regulator_data bd72720_rdata[] = { }, }; +static const struct bd71828_regulator_data bd73800_rdata[] = { + { + .desc = { + .name = "buck1", + .of_match = of_match_ptr("buck1"), + .regulators_node = of_match_ptr("regulators"), + .id = BD73800_BUCK1, + .ops = &bd73800_buck_ops, + .type = REGULATOR_VOLTAGE, + .linear_ranges = bd73800_buck12348_volts, + .n_linear_ranges = ARRAY_SIZE(bd73800_buck12348_volts), + .n_voltages = BD73800_NUM_VOLTS, + .enable_reg = BD73800_REG_BUCK1_ON, + .enable_mask = BD73800_MASK_RUN_EN, + .vsel_reg = BD73800_REG_BUCK1_VOLT_RUN, + .vsel_mask = BD73800_MASK_VOLT, + .ramp_delay_table = bd71828_ramp_delay, + .n_ramp_values = ARRAY_SIZE(bd71828_ramp_delay), + .ramp_reg = BD73800_REG_BUCK1_MODE, + .ramp_mask = BD73800_MASK_RAMP_DELAY, + .owner = THIS_MODULE, + .of_parse_cb = buck_set_hw_dvs_levels, + }, + .dvs = { + .level_map = ROHM_DVS_LEVEL_RUN | ROHM_DVS_LEVEL_IDLE | + ROHM_DVS_LEVEL_SUSPEND, + .run_reg = BD73800_REG_BUCK1_VOLT_RUN, + .run_mask = BD73800_MASK_VOLT, + .idle_reg = BD73800_REG_BUCK1_VOLT_IDLE, + .idle_mask = BD73800_MASK_VOLT, + .idle_on_mask = BD73800_MASK_IDLE_EN, + .suspend_reg = BD73800_REG_BUCK1_VOLT_SUSP, + .suspend_mask = BD73800_MASK_VOLT, + .suspend_on_mask = BD73800_MASK_SUSP_EN, + }, + }, { + .desc = { + .name = "buck2", + .of_match = of_match_ptr("buck2"), + .regulators_node = of_match_ptr("regulators"), + .id = BD73800_BUCK2, + .ops = &bd73800_buck_ops, + .type = REGULATOR_VOLTAGE, + .linear_ranges = bd73800_buck12348_volts, + .n_linear_ranges = ARRAY_SIZE(bd73800_buck12348_volts), + .n_voltages = BD73800_NUM_VOLTS, + .enable_reg = BD73800_REG_BUCK2_ON, + .enable_mask = BD73800_MASK_RUN_EN, + .vsel_reg = BD73800_REG_BUCK2_VOLT_RUN, + .vsel_mask = BD73800_MASK_VOLT, + .ramp_delay_table = bd71828_ramp_delay, + .n_ramp_values = ARRAY_SIZE(bd71828_ramp_delay), + .ramp_reg = BD73800_REG_BUCK2_MODE, + .ramp_mask = BD73800_MASK_RAMP_DELAY, + .owner = THIS_MODULE, + .of_parse_cb = buck_set_hw_dvs_levels, + }, + .dvs = { + .level_map = ROHM_DVS_LEVEL_RUN | ROHM_DVS_LEVEL_IDLE | + ROHM_DVS_LEVEL_SUSPEND, + .run_reg = BD73800_REG_BUCK2_VOLT_RUN, + .run_mask = BD73800_MASK_VOLT, + .idle_reg = BD73800_REG_BUCK2_VOLT_IDLE, + .idle_mask = BD73800_MASK_VOLT, + .idle_on_mask = BD73800_MASK_IDLE_EN, + .suspend_reg = BD73800_REG_BUCK2_VOLT_SUSP, + .suspend_mask = BD73800_MASK_VOLT, + .suspend_on_mask = BD73800_MASK_SUSP_EN, + }, + }, { + .desc = { + .name = "buck3", + .of_match = of_match_ptr("buck3"), + .regulators_node = of_match_ptr("regulators"), + .id = BD73800_BUCK3, + .ops = &bd73800_buck_ops, + .type = REGULATOR_VOLTAGE, + .linear_ranges = bd73800_buck12348_volts, + .n_linear_ranges = ARRAY_SIZE(bd73800_buck12348_volts), + .n_voltages = BD73800_NUM_VOLTS, + .enable_reg = BD73800_REG_BUCK3_ON, + .enable_mask = BD73800_MASK_RUN_EN, + .vsel_reg = BD73800_REG_BUCK3_VOLT_RUN, + .vsel_mask = BD73800_MASK_VOLT, + .ramp_delay_table = bd71828_ramp_delay, + .n_ramp_values = ARRAY_SIZE(bd71828_ramp_delay), + .ramp_reg = BD73800_REG_BUCK3_MODE, + .ramp_mask = BD73800_MASK_RAMP_DELAY, + .owner = THIS_MODULE, + .of_parse_cb = buck_set_hw_dvs_levels, + }, + .dvs = { + .level_map = ROHM_DVS_LEVEL_RUN | ROHM_DVS_LEVEL_IDLE | + ROHM_DVS_LEVEL_SUSPEND, + .run_reg = BD73800_REG_BUCK3_VOLT_RUN, + .run_mask = BD73800_MASK_VOLT, + .idle_reg = BD73800_REG_BUCK3_VOLT_IDLE, + .idle_mask = BD73800_MASK_VOLT, + .idle_on_mask = BD73800_MASK_IDLE_EN, + .suspend_reg = BD73800_REG_BUCK3_VOLT_SUSP, + .suspend_mask = BD73800_MASK_VOLT, + .suspend_on_mask = BD73800_MASK_SUSP_EN, + }, + + }, { + .desc = { + .name = "buck4", + .of_match = of_match_ptr("buck4"), + .regulators_node = of_match_ptr("regulators"), + .id = BD73800_BUCK4, + .ops = &bd73800_buck_ops, + .type = REGULATOR_VOLTAGE, + .linear_ranges = bd73800_buck12348_volts, + .n_linear_ranges = ARRAY_SIZE(bd73800_buck12348_volts), + .n_voltages = BD73800_NUM_VOLTS, + .enable_reg = BD73800_REG_BUCK4_ON, + .enable_mask = BD73800_MASK_RUN_EN, + .vsel_reg = BD73800_REG_BUCK4_VOLT_RUN, + .vsel_mask = BD73800_MASK_VOLT, + .ramp_delay_table = bd71828_ramp_delay, + .n_ramp_values = ARRAY_SIZE(bd71828_ramp_delay), + .ramp_reg = BD73800_REG_BUCK4_MODE, + .ramp_mask = BD73800_MASK_RAMP_DELAY, + .owner = THIS_MODULE, + .of_parse_cb = buck_set_hw_dvs_levels, + }, + .dvs = { + .level_map = ROHM_DVS_LEVEL_RUN | ROHM_DVS_LEVEL_IDLE | + ROHM_DVS_LEVEL_SUSPEND, + .run_reg = BD73800_REG_BUCK4_VOLT_RUN, + .run_mask = BD73800_MASK_VOLT, + .idle_reg = BD73800_REG_BUCK4_VOLT_IDLE, + .idle_mask = BD73800_MASK_VOLT, + .idle_on_mask = BD73800_MASK_IDLE_EN, + .suspend_reg = BD73800_REG_BUCK4_VOLT_SUSP, + .suspend_mask = BD73800_MASK_VOLT, + .suspend_on_mask = BD73800_MASK_SUSP_EN, + }, + }, { + .desc = { + .name = "buck5", + .of_match = of_match_ptr("buck5"), + .regulators_node = of_match_ptr("regulators"), + .id = BD73800_BUCK5, + .ops = &bd73800_buck5_ops, + .type = REGULATOR_VOLTAGE, + .linear_ranges = bd73800_buck5_volts, + .n_linear_ranges = ARRAY_SIZE(bd73800_buck5_volts), + .linear_range_selectors_bitfield = + bd73800_buck5_volt_range_sel, + .n_voltages = BD73800_NUM_BUCK5_VOLTS, + .enable_reg = BD73800_REG_BUCK5_ON, + .enable_mask = BD73800_MASK_RUN_EN, + .vsel_reg = BD73800_REG_BUCK5_VOLT_RUN, + .vsel_mask = BD73800_MASK_BUCK5_VOLT, + .vsel_range_reg = BD73800_REG_BUCK5_MODE, + .vsel_range_mask = BD73800_BUCK5_RANGE_MASK, + .range_applied_by_vsel = true, + .ramp_delay_table = bd71828_ramp_delay, + .n_ramp_values = ARRAY_SIZE(bd71828_ramp_delay), + .ramp_reg = BD73800_REG_BUCK5_MODE, + .ramp_mask = BD73800_MASK_RAMP_DELAY, + .owner = THIS_MODULE, + }, + .dvs = { + /* + * We don't support setting the IDLE / SUSPEND voltages + * for the BUCK 5 for now. Reason is that the pickable + * range selector bit is common for all states (RUN, + * SUSPEND and IDLE), and toggling it for one impacts + * also others. + * Furthermore, writing the BD73800 range selector does + * not impact the RUN voltage until a new voltage value + * is also written to the vsel register. Hence the + * voltage change can be done without any 'intermediate + * voltages', even when the range is changed when the + * BUCK is enabled. + * It, however, is not documented how the IDLE / SUSPEND + * states work in this regard. I expect the full + * combination of the range selector and vsel is taken + * into account at the state change, no matter when they + * have been written to. + */ + .level_map = ROHM_DVS_LEVEL_RUN | ROHM_DVS_LEVEL_IDLE | + ROHM_DVS_LEVEL_SUSPEND, + .run_reg = BD73800_REG_BUCK5_VOLT_RUN, + .run_mask = BD73800_MASK_VOLT, + .idle_on_mask = BD73800_MASK_IDLE_EN, + .suspend_on_mask = BD73800_MASK_SUSP_EN, + }, + + }, { + .desc = { + .name = "buck6", + .of_match = of_match_ptr("buck6"), + .regulators_node = of_match_ptr("regulators"), + .id = BD73800_BUCK6, + .ops = &bd73800_buck_ops, + .type = REGULATOR_VOLTAGE, + .linear_ranges = bd73800_buck67_volts, + .n_linear_ranges = ARRAY_SIZE(bd73800_buck67_volts), + .n_voltages = BD73800_NUM_VOLTS, + .enable_reg = BD73800_REG_BUCK6_ON, + .enable_mask = BD73800_MASK_RUN_EN, + .vsel_reg = BD73800_REG_BUCK6_VOLT_RUN, + .vsel_mask = BD73800_MASK_VOLT, + .ramp_delay_table = bd71828_ramp_delay, + .n_ramp_values = ARRAY_SIZE(bd71828_ramp_delay), + .ramp_reg = BD73800_REG_BUCK6_MODE, + .ramp_mask = BD73800_MASK_RAMP_DELAY, + .owner = THIS_MODULE, + .of_parse_cb = buck_set_hw_dvs_levels, + }, + .dvs = { + .level_map = ROHM_DVS_LEVEL_RUN | ROHM_DVS_LEVEL_IDLE | + ROHM_DVS_LEVEL_SUSPEND, + .run_reg = BD73800_REG_BUCK6_VOLT_RUN, + .run_mask = BD73800_MASK_VOLT, + .idle_reg = BD73800_REG_BUCK6_VOLT_IDLE, + .idle_mask = BD73800_MASK_VOLT, + .idle_on_mask = BD73800_MASK_IDLE_EN, + .suspend_reg = BD73800_REG_BUCK6_VOLT_SUSP, + .suspend_mask = BD73800_MASK_VOLT, + .suspend_on_mask = BD73800_MASK_SUSP_EN, + }, + }, { + .desc = { + .name = "buck7", + .of_match = of_match_ptr("buck7"), + .regulators_node = of_match_ptr("regulators"), + .id = BD73800_BUCK7, + .ops = &bd73800_buck_ops, + .type = REGULATOR_VOLTAGE, + .linear_ranges = bd73800_buck67_volts, + .n_linear_ranges = ARRAY_SIZE(bd73800_buck67_volts), + .n_voltages = BD73800_NUM_VOLTS, + .enable_reg = BD73800_REG_BUCK7_ON, + .enable_mask = BD73800_MASK_RUN_EN, + .vsel_reg = BD73800_REG_BUCK7_VOLT_RUN, + .vsel_mask = BD73800_MASK_VOLT, + .ramp_delay_table = bd71828_ramp_delay, + .n_ramp_values = ARRAY_SIZE(bd71828_ramp_delay), + .ramp_reg = BD73800_REG_BUCK7_MODE, + .ramp_mask = BD73800_MASK_RAMP_DELAY, + .owner = THIS_MODULE, + .of_parse_cb = buck_set_hw_dvs_levels, + }, + .dvs = { + .level_map = ROHM_DVS_LEVEL_RUN | ROHM_DVS_LEVEL_IDLE | + ROHM_DVS_LEVEL_SUSPEND, + .run_reg = BD73800_REG_BUCK7_VOLT_RUN, + .run_mask = BD73800_MASK_VOLT, + .idle_reg = BD73800_REG_BUCK7_VOLT_IDLE, + .idle_mask = BD73800_MASK_VOLT, + .idle_on_mask = BD73800_MASK_IDLE_EN, + .suspend_reg = BD73800_REG_BUCK7_VOLT_SUSP, + .suspend_mask = BD73800_MASK_VOLT, + .suspend_on_mask = BD73800_MASK_SUSP_EN, + }, + }, { + .desc = { + .name = "buck8", + .of_match = of_match_ptr("buck8"), + .regulators_node = of_match_ptr("regulators"), + .id = BD73800_BUCK8, + .ops = &bd73800_buck_ops, + .type = REGULATOR_VOLTAGE, + .linear_ranges = bd73800_buck12348_volts, + .n_linear_ranges = ARRAY_SIZE(bd73800_buck12348_volts), + .n_voltages = BD73800_NUM_VOLTS, + .enable_reg = BD73800_REG_BUCK8_ON, + .enable_mask = BD73800_MASK_RUN_EN, + .vsel_reg = BD73800_REG_BUCK8_VOLT_RUN, + .vsel_mask = BD73800_MASK_VOLT, + .ramp_delay_table = bd71828_ramp_delay, + .n_ramp_values = ARRAY_SIZE(bd71828_ramp_delay), + .ramp_reg = BD73800_REG_BUCK8_MODE, + .ramp_mask = BD73800_MASK_RAMP_DELAY, + .owner = THIS_MODULE, + .of_parse_cb = buck_set_hw_dvs_levels, + }, + .dvs = { + .level_map = ROHM_DVS_LEVEL_RUN | ROHM_DVS_LEVEL_IDLE | + ROHM_DVS_LEVEL_SUSPEND, + .run_reg = BD73800_REG_BUCK8_VOLT_RUN, + .run_mask = BD73800_MASK_VOLT, + .idle_reg = BD73800_REG_BUCK8_VOLT_IDLE, + .idle_mask = BD73800_MASK_VOLT, + .idle_on_mask = BD73800_MASK_IDLE_EN, + .suspend_reg = BD73800_REG_BUCK8_VOLT_SUSP, + .suspend_mask = BD73800_MASK_VOLT, + .suspend_on_mask = BD73800_MASK_SUSP_EN, + }, + }, { + .desc = { + .name = "ldo1", + .of_match = of_match_ptr(LDO1_NODE_NAME), + .regulators_node = of_match_ptr("regulators"), + .id = BD73800_LDO1, + .ops = &bd73800_ldo_ops, + .type = REGULATOR_VOLTAGE, + .linear_ranges = bd73800_ldo13_low_volts, + .n_linear_ranges = ARRAY_SIZE(bd73800_ldo13_low_volts), + .n_voltages = BD73800_NUM_VOLTS, + .enable_reg = BD73800_REG_LDO1_ON, + .enable_mask = BD73800_MASK_RUN_EN, + .vsel_reg = BD73800_REG_LDO1_VOLT, + .vsel_mask = BD73800_MASK_VOLT, + .owner = THIS_MODULE, + .of_parse_cb = buck_set_hw_dvs_levels, + }, + .dvs = { + /* + * LDOs support only single voltage for all states. + * Voltage can be individually enabled for each state + * though. Allow setting enable/disable config by + * setting the _on_mask for all supported states. + */ + .level_map = ROHM_DVS_LEVEL_RUN | ROHM_DVS_LEVEL_IDLE | + ROHM_DVS_LEVEL_SUSPEND, + .run_reg = BD73800_REG_LDO1_VOLT, + .run_mask = BD73800_MASK_VOLT, + .idle_on_mask = BD73800_MASK_IDLE_EN, + .suspend_on_mask = BD73800_MASK_SUSP_EN, + }, + }, { + .desc = { + .name = "ldo2", + .of_match = of_match_ptr("ldo2"), + .regulators_node = of_match_ptr("regulators"), + .id = BD73800_LDO2, + .ops = &bd73800_ldo_ops, + .type = REGULATOR_VOLTAGE, + .linear_ranges = bd73800_ldo24_volts, + .n_linear_ranges = ARRAY_SIZE(bd73800_ldo24_volts), + .n_voltages = BD73800_NUM_VOLTS, + .enable_reg = BD73800_REG_LDO2_ON, + .enable_mask = BD73800_MASK_RUN_EN, + .vsel_reg = BD73800_REG_LDO2_VOLT, + .vsel_mask = BD73800_MASK_VOLT, + .owner = THIS_MODULE, + .of_parse_cb = buck_set_hw_dvs_levels, + }, + .dvs = { + .level_map = ROHM_DVS_LEVEL_RUN | ROHM_DVS_LEVEL_IDLE | + ROHM_DVS_LEVEL_SUSPEND, + .run_reg = BD73800_REG_LDO2_VOLT, + .run_mask = BD73800_MASK_VOLT, + .idle_on_mask = BD73800_MASK_IDLE_EN, + .suspend_on_mask = BD73800_MASK_SUSP_EN, + }, + }, { + .desc = { + .name = "ldo3", + .of_match = of_match_ptr(LDO3_NODE_NAME), + .regulators_node = of_match_ptr("regulators"), + .id = BD73800_LDO3, + .ops = &bd73800_ldo_ops, + .type = REGULATOR_VOLTAGE, + .linear_ranges = bd73800_ldo13_low_volts, + .n_linear_ranges = ARRAY_SIZE(bd73800_ldo13_low_volts), + .n_voltages = BD73800_NUM_VOLTS, + .enable_reg = BD73800_REG_LDO3_ON, + .enable_mask = BD73800_MASK_RUN_EN, + .vsel_reg = BD73800_REG_LDO3_VOLT, + .vsel_mask = BD73800_MASK_VOLT, + .owner = THIS_MODULE, + .of_parse_cb = buck_set_hw_dvs_levels, + }, + .dvs = { + .level_map = ROHM_DVS_LEVEL_RUN | ROHM_DVS_LEVEL_IDLE | + ROHM_DVS_LEVEL_SUSPEND, + .run_reg = BD73800_REG_LDO3_VOLT, + .run_mask = BD73800_MASK_VOLT, + .idle_on_mask = BD73800_MASK_IDLE_EN, + .suspend_on_mask = BD73800_MASK_SUSP_EN, + }, + }, { + .desc = { + .name = "ldo4", + .of_match = of_match_ptr("ldo4"), + .regulators_node = of_match_ptr("regulators"), + .id = BD73800_LDO4, + .ops = &bd73800_ldo_ops, + .type = REGULATOR_VOLTAGE, + .linear_ranges = bd73800_ldo24_volts, + .n_linear_ranges = ARRAY_SIZE(bd73800_ldo24_volts), + .n_voltages = BD73800_NUM_VOLTS, + .enable_reg = BD73800_REG_LDO4_ON, + .enable_mask = BD73800_MASK_RUN_EN, + .vsel_reg = BD73800_REG_LDO4_VOLT, + .vsel_mask = BD73800_MASK_VOLT, + .owner = THIS_MODULE, + .of_parse_cb = buck_set_hw_dvs_levels, + }, + .dvs = { + .level_map = ROHM_DVS_LEVEL_RUN | ROHM_DVS_LEVEL_IDLE | + ROHM_DVS_LEVEL_SUSPEND, + .run_reg = BD73800_REG_LDO4_VOLT, + .run_mask = BD73800_MASK_VOLT, + .idle_on_mask = BD73800_MASK_IDLE_EN, + .suspend_on_mask = BD73800_MASK_SUSP_EN, + }, + }, +}; + static int bd72720_buck10_ldon_head_mode(struct device *dev, struct device_node *npreg, struct regmap *regmap, @@ -1619,6 +2111,52 @@ static int bd72720_dt_parse(struct device *dev, return bd72720_buck10_ldon_head_mode(dev, nproot, regmap, buck10_desc); } +static bool bd73800_use_ldo_high_range(struct device_node *nproot, + const char *ldo_node) +{ + struct device_node *np __free(device_node) = + of_get_child_by_name(nproot, ldo_node); + + if (!np) + return false; + + return of_property_read_bool(np, "rohm,ldo-range-high"); +} + +static int bd73800_check_ldo_otp_options(struct device *dev, + struct bd71828_regulator_data *d, + unsigned int num_reg_data) +{ + struct device_node *nproot __free(device_node) = + of_get_child_by_name(dev->parent->of_node, "regulators"); + + /* + * The code assumes regulator IDs to start from 0 and to match the + * indexing of regulator data arrays. WARN if someone changes this. + */ + WARN_ON(d[BD73800_LDO1].desc.id != BD73800_LDO1); + WARN_ON(d[BD73800_LDO3].desc.id != BD73800_LDO3); + + if (!nproot) { + dev_err(dev, "failed to find regulators node\n"); + return -ENODEV; + } + + if (bd73800_use_ldo_high_range(nproot, LDO1_NODE_NAME)) { + d[BD73800_LDO1].desc.linear_ranges = bd73800_ldo13_high_volts; + d[BD73800_LDO1].desc.n_linear_ranges = + ARRAY_SIZE(bd73800_ldo13_high_volts); + } + + if (bd73800_use_ldo_high_range(nproot, LDO3_NODE_NAME)) { + d[BD73800_LDO3].desc.linear_ranges = bd73800_ldo13_high_volts; + d[BD73800_LDO3].desc.n_linear_ranges = + ARRAY_SIZE(bd73800_ldo13_high_volts); + } + + return 0; +} + static int bd71828_probe(struct platform_device *pdev) { int i, j, ret, num_regulators; @@ -1656,6 +2194,23 @@ static int bd71828_probe(struct platform_device *pdev) num_regulators = ARRAY_SIZE(bd71828_rdata); break; + + case ROHM_CHIP_TYPE_BD73800: + { + rdata = devm_kmemdup(&pdev->dev, bd73800_rdata, + sizeof(bd73800_rdata), GFP_KERNEL); + if (!rdata) + return -ENOMEM; + + num_regulators = ARRAY_SIZE(bd73800_rdata); + + ret = bd73800_check_ldo_otp_options(&pdev->dev, rdata, + num_regulators); + if (ret) + return ret; + + break; + } default: return dev_err_probe(&pdev->dev, -EINVAL, "Unsupported device\n"); @@ -1692,6 +2247,7 @@ static int bd71828_probe(struct platform_device *pdev) static const struct platform_device_id bd71828_pmic_id[] = { { .name = "bd71828-pmic", .driver_data = ROHM_CHIP_TYPE_BD71828 }, { .name = "bd72720-pmic", .driver_data = ROHM_CHIP_TYPE_BD72720 }, + { .name = "bd73800-pmic", .driver_data = ROHM_CHIP_TYPE_BD73800 }, { } }; MODULE_DEVICE_TABLE(platform, bd71828_pmic_id); From 9dd502dde33c60274e1b4962f05194bc16006d7d Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Wed, 16 Sep 2026 09:11:42 +0300 Subject: [PATCH 53/63] clk: bd718x7: Support ROHM BD73800 The ROHM BD73800 PMIC has a 32.768 kHz clock gate. Add support for controlling this clock. NOTE: The CLKOUT pin can be muxed by an OTP option. On some OTP configurations the CLKOUT is not outputting the clk signal. Signed-off-by: Matti Vaittinen Reviewed-by: Jerome Brunet Reviewed-by: Alexander Sverdlin Link: https://patch.msgid.link/dfc72dbe7227d830040ae32c7f55cc18cf42f75d.1789538455.git.mazziesaccount@gmail.com Signed-off-by: Lee Jones --- drivers/clk/clk-bd718x7.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/clk/clk-bd718x7.c b/drivers/clk/clk-bd718x7.c index 8ddb50c67757..1698b763bcb7 100644 --- a/drivers/clk/clk-bd718x7.c +++ b/drivers/clk/clk-bd718x7.c @@ -21,6 +21,9 @@ #define BD718XX_REG_OUT32K 0x2E /* BD72720 */ #define BD72720_REG_OUT32K 0x9a +/* BD73800 */ +#define BD73800_REG_OUT32K 0x50 + /* * BD71837, BD71847, and BD71828 all use bit [0] to clk output control */ @@ -123,6 +126,10 @@ static int bd71837_clk_probe(struct platform_device *pdev) c->reg = BD72720_REG_OUT32K; c->mask = CLK_OUT_EN_MASK; break; + case ROHM_CHIP_TYPE_BD73800: + c->reg = BD73800_REG_OUT32K; + c->mask = CLK_OUT_EN_MASK; + break; default: dev_err(&pdev->dev, "Unknown clk chip\n"); return -EINVAL; @@ -152,6 +159,7 @@ static const struct platform_device_id bd718x7_clk_id[] = { { .name = "bd71828-clk", .driver_data = ROHM_CHIP_TYPE_BD71828 }, { .name = "bd71815-clk", .driver_data = ROHM_CHIP_TYPE_BD71815 }, { .name = "bd72720-clk", .driver_data = ROHM_CHIP_TYPE_BD72720 }, + { .name = "bd73800-clk", .driver_data = ROHM_CHIP_TYPE_BD73800 }, { } }; MODULE_DEVICE_TABLE(platform, bd718x7_clk_id); From 03888668ce533f948a1de975523e2663d723bbe8 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Wed, 16 Sep 2026 09:12:18 +0300 Subject: [PATCH 54/63] gpio: bd73800: Support ROHM BD73800 PMIC GPIOs The ROHM BD73800 PMIC has 4 pins (named GPIO1, CLKOUT, FAULT_B and EXTEN_OUT) which might have been set to operate as a GPI or GPO when OTP (One Time Programmable memory) is written at device manufacturing. Support the GPI/GPO use-case via GPIO framework. The default OTP for these pins is to not use any of them as GPI or GPO. (The GPIO1 defaults as an ADC input regardless the naming). Hence the driver assumes none of these pins is a GPI/GPO unless explicitly pointed as GPI or GPO via device tree. Furthermore, pin's direction can't be changed after OTP configuration is done. Also the default drive type for a GPO (CMOS / Open Drain) is set by the OTP configuration. The BD73800 has a set of undocumented test registers which should allow changing the drive type. Access to the test register area or the test registers aren't documented and so this driver does not support configuring the drive type even though it might be doable. Signed-off-by: Matti Vaittinen Acked-by: Bartosz Golaszewski Reviewed-by: Linus Walleij Link: https://patch.msgid.link/e3e6ac712519326cc25fcdd0a0c8e3cc42ef55ab.1789538455.git.mazziesaccount@gmail.com Signed-off-by: Lee Jones --- drivers/gpio/Kconfig | 12 +++ drivers/gpio/Makefile | 1 + drivers/gpio/gpio-bd73800.c | 209 ++++++++++++++++++++++++++++++++++++ 3 files changed, 222 insertions(+) create mode 100644 drivers/gpio/gpio-bd73800.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index a48586bb8edb..64127740a248 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1385,6 +1385,18 @@ config GPIO_BD72720 be configured to GPO on the ROHM PMIC. The pin configuration is done on OTP at manufacturing. +config GPIO_BD73800 + tristate "ROHM BD73800 GPIO support" + depends on MFD_ROHM_BD71828 + select GPIO_REGMAP + help + Support for GPIOs on ROHM BD73800 PMIC. There can be up to 4 + GPI or GPO pins available on the PMIC in total. The purpose of + the pins is decided at the device manufacturing by OTP + configuration and can't be reconfigured later. Enable this + if your PMIC has pins set as GPIs or GPOs and if you wish to + control the pins via the GPIO framework. + config GPIO_BD9571MWV tristate "ROHM BD9571 GPIO support" depends on MFD_BD9571MWV diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index dc9e6d643b5b..dc5e0583e328 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -45,6 +45,7 @@ obj-$(CONFIG_GPIO_BCM_XGS_IPROC) += gpio-xgs-iproc.o obj-$(CONFIG_GPIO_BD71815) += gpio-bd71815.o obj-$(CONFIG_GPIO_BD71828) += gpio-bd71828.o obj-$(CONFIG_GPIO_BD72720) += gpio-bd72720.o +obj-$(CONFIG_GPIO_BD73800) += gpio-bd73800.o obj-$(CONFIG_GPIO_BD9571MWV) += gpio-bd9571mwv.o obj-$(CONFIG_GPIO_BLZP1600) += gpio-blzp1600.o obj-$(CONFIG_GPIO_BRCMSTB) += gpio-brcmstb.o diff --git a/drivers/gpio/gpio-bd73800.c b/drivers/gpio/gpio-bd73800.c new file mode 100644 index 000000000000..2be2a885820e --- /dev/null +++ b/drivers/gpio/gpio-bd73800.c @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Support to GPIOs on ROHM BD73800 + * Copyright 2024 ROHM Semiconductors. + * Author: Matti Vaittinen + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define BD73800_GPIO_MAX_PINS 4 +/* + * The BD73800 has several "one time programmable" (OTP) configurations which + * can be set at manufacturing phase. Some of these options allow using + * individual pins as GPI or GPO (not both at the same time). The OTP + * configuration can't be read at run-time, so drivers rely on device-tree to + * advertise the OTP programmed in manufacturing. + * + * The pins which can be used as GPIO are: + * GPIO1, CLKOUT (GPIO2), FAULT_B, EXTEN_OUT. + * + * The OTP options 2 and 3 state for all the pins: + * - OTP2: GPI (also IRQ source) + * - OTP3: GPO (NOTE: This is actually 2 different OTP options. Either a + * register controllable output or a power-sequence controlled output. + * The "gpo" referred here means only the register controllable output.) + * The datasheet refers to this as: " output is controlled by + * GPIO_OUT or power on/off sequencer to control external VRs. ON/OFF + * sequence timing is configurable." + * + * The data-sheet further says that the GPI/GPO is not a default OTP + * configuration for any of the pins. Hence the GPIO driver defaults to a pin + * not being a GPI or GPO, but requires the pin to be explicitly marked as a + * GPI or GPO in the device-tree. + * + * DT properties: + * "rohm,pin-gpio1", "rohm,pin-clkout", "rohm,pin-fault_b", "rohm,pin-exten" + * can be set to one of the values "gpi" or "gpo" to enable them to be used as + * GPIO. + * + * The amount of GPIO lines the chip exposes to the user-space (chip.ngpio) is + * always the same, regardless of the OTP variant in use. The lines which are + * not usable as GPIO on a given OTP variant are marked invalid via the GPIO + * valid_mask. This way the user-space always sees a constant amount of GPIO + * lines, where numbering (relative to the chip's base) stays also the same. + * Users can then use the valid_mask to find out which of the lines are usable. + */ + +struct bd73800_gpio { + /* dev points to the platform device for devm and prints */ + struct device *dev; + struct regmap *regmap; + /* Pins which have been OTP configured as GPI or GPO */ + DECLARE_BITMAP(valid_mask, BD73800_GPIO_MAX_PINS); + /* Subset of valid_mask - pins which have been OTP configured as GPO */ + DECLARE_BITMAP(output_mask, BD73800_GPIO_MAX_PINS); +}; + +static const char * const bd73800_gpio_properties[BD73800_GPIO_MAX_PINS] = { + "rohm,pin-gpio1", + "rohm,pin-clkout", + "rohm,pin-fault_b", + "rohm,pin-exten" +}; + +static int bd73800_gpio_get_pins(struct bd73800_gpio *data) +{ + struct device *parent = data->dev->parent; + const char *val; + int i, ret; + + for (i = 0; i < BD73800_GPIO_MAX_PINS; i++) { + ret = device_property_read_string(parent, + bd73800_gpio_properties[i], + &val); + if (ret) { + if (ret == -EINVAL) + continue; + + return dev_err_probe(data->dev, ret, + "pin %d (%s), bad configuration\n", i, + bd73800_gpio_properties[i]); + } + + if (!strcmp(val, "gpi")) { + __set_bit(i, data->valid_mask); + } else if (!strcmp(val, "gpo")) { + __set_bit(i, data->valid_mask); + __set_bit(i, data->output_mask); + } else { + dev_warn(data->dev, + "pin %d (%s), unknown value '%s' ignored\n", i, + bd73800_gpio_properties[i], val); + } + } + + return 0; +} + +static int bd73800_gpio_init_valid_mask(struct gpio_chip *gc, + unsigned long *valid_mask, + unsigned int ngpios) +{ + struct gpio_regmap *gpio = gpiochip_get_data(gc); + struct bd73800_gpio *data = gpio_regmap_get_drvdata(gpio); + + bitmap_copy(valid_mask, data->valid_mask, ngpios); + + return 0; +} + +/* + * The used register depends on OTP: + * - If OTP has set pin as GPO, only the GPO_OUT register is valid. + * - If OTP has set pin as GPI, only the INT_5_SRC register is valid. + */ +static int bd73800_gpio_reg_mask_xlate(struct gpio_regmap *gpio, + enum gpio_regmap_operation op, + unsigned int base, unsigned int offset, + unsigned int *reg, unsigned int *mask) +{ + struct bd73800_gpio *data = gpio_regmap_get_drvdata(gpio); + bool is_output = test_bit(offset, data->output_mask); + + if (is_output) + *reg = BD73800_REG_GPO_OUT; + else + *reg = base; + + *mask = BIT(offset); + + return 0; +} + +static int gpo_bd73800_probe(struct platform_device *pdev) +{ + struct gpio_regmap_config config = { }; + struct bd73800_gpio *data; + struct device *parent, *dev; + int ret; + + dev = &pdev->dev; + /* The device-tree and regmap come from MFD => use parent for that */ + parent = dev->parent; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = dev; + data->regmap = dev_get_regmap(parent, NULL); + if (!data->regmap) + return dev_err_probe(dev, -ENODEV, "no parent regmap\n"); + + ret = bd73800_gpio_get_pins(data); + if (ret) + return ret; + + if (bitmap_empty(data->valid_mask, BD73800_GPIO_MAX_PINS)) { + /* + * The BD73800 may or may not have pins allocated for GPIO + * depending on the OTP used at manufacturing. + * If there are no pins, then we have nothing to do. + */ + dev_dbg(dev, "no GPIO pins\n"); + return -ENODEV; + } + + config.parent = parent; + config.regmap = data->regmap; + config.label = "bd73800"; + config.ngpio = BD73800_GPIO_MAX_PINS; + config.reg_dat_base = BD73800_REG_INT_5_SRC; + config.reg_set_base = BD73800_REG_GPO_OUT; + config.reg_mask_xlate = bd73800_gpio_reg_mask_xlate; + config.init_valid_mask = bd73800_gpio_init_valid_mask; + /* All pins that are valid GPIO lines also have a fixed direction */ + config.fixed_direction_mask = data->valid_mask; + config.fixed_direction_output = data->output_mask; + config.drvdata = data; + + return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &config)); +} + +static const struct platform_device_id bd73800_gpio_id[] = { + { "bd73800-gpio" }, + { }, +}; +MODULE_DEVICE_TABLE(platform, bd73800_gpio_id); + +static struct platform_driver gpo_bd73800_driver = { + .driver = { + .name = "bd73800-gpio", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .probe = gpo_bd73800_probe, + .id_table = bd73800_gpio_id, +}; +module_platform_driver(gpo_bd73800_driver); + +MODULE_AUTHOR("Matti Vaittinen "); +MODULE_DESCRIPTION("GPIO interface for BD73800"); +MODULE_LICENSE("GPL"); From e8e8d60fe8cc35340f6110ff70ef7959de1c3c0b Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Wed, 16 Sep 2026 09:12:50 +0300 Subject: [PATCH 55/63] MAINTAINERS: Add ROHM BD73800 PMIC files Add the undersigned as a maintainer for the ROHM BD73800 PMIC related files. Signed-off-by: Matti Vaittinen Reviewed-by: Alexander Sverdlin Link: https://patch.msgid.link/078e51b1a71ca5d823c81cde8d3bde358742b2c9.1789538455.git.mazziesaccount@gmail.com Signed-off-by: Lee Jones --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..b0925fa212cc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23821,6 +23821,7 @@ F: drivers/clk/clk-bd718x7.c F: drivers/gpio/gpio-bd71815.c F: drivers/gpio/gpio-bd71828.c F: drivers/gpio/gpio-bd72720.c +F: drivers/gpio/gpio-bd73800.c F: drivers/mfd/rohm-bd71828.c F: drivers/mfd/rohm-bd718x7.c F: drivers/mfd/rohm-bd9576.c @@ -23840,6 +23841,7 @@ F: include/linux/mfd/rohm-bd71815.h F: include/linux/mfd/rohm-bd71828.h F: include/linux/mfd/rohm-bd718x7.h F: include/linux/mfd/rohm-bd72720.h +F: include/linux/mfd/rohm-bd73800.h F: include/linux/mfd/rohm-bd957x.h F: include/linux/mfd/rohm-bd96801.h F: include/linux/mfd/rohm-bd96802.h From 8d37f20173a2760718bb02b58bafc5a7cac93d67 Mon Sep 17 00:00:00 2001 From: Mateo Borda Date: Sun, 20 Sep 2026 14:39:04 -0300 Subject: [PATCH 56/63] gpiolib: acpi: Add quirk for Lenovo IdeaPad Slim 3 15ABR8 On the Lenovo IdeaPad Slim 3 15ABR8 (82XM), the touchscreen (GTCH7503:00) GPIO interrupt on pin 10 is armed as a wake source. As soon as the system enters s2idle suspend the interrupt fires, causing an immediate spurious resume, so suspend is unusable. Disabling wakeup for the device via sysfs or booting with gpiolib_acpi.ignore_wake=GTCH7503:00@10 avoids the problem, so add a DMI quirk that ignores wakeup on that pin. Signed-off-by: Mateo Borda Acked-by: Mika Westerberg Signed-off-by: Andy Shevchenko --- drivers/gpio/gpiolib-acpi-quirks.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/gpio/gpiolib-acpi-quirks.c b/drivers/gpio/gpiolib-acpi-quirks.c index a0116f004975..22c5fb149377 100644 --- a/drivers/gpio/gpiolib-acpi-quirks.c +++ b/drivers/gpio/gpiolib-acpi-quirks.c @@ -392,6 +392,19 @@ static const struct dmi_system_id gpiolib_acpi_quirks[] __initconst = { .ignore_wake = "VEN_0488:00@355", }, }, + { + /* + * Lenovo IdeaPad Slim 3 15ABR8: the touchscreen wake IRQ fires as soon + * as the system enters suspend, causing an immediate spurious resume. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "82XM"), + }, + .driver_data = &(struct acpi_gpiolib_dmi_quirk) { + .ignore_wake = "GTCH7503:00@10", + }, + }, {} /* Terminating entry */ }; From a7787b818204d6de78e2e83ded0155d8195119e6 Mon Sep 17 00:00:00 2001 From: Petar Stepanovic Date: Mon, 21 Sep 2026 00:20:22 -0700 Subject: [PATCH 57/63] dt-bindings: gpio: add Axiado AX3005 SGPIO controller Add a Devicetree binding for the SGPIO controller found on the Axiado AX3005 SoC. Each SGPIO position provides one fixed-direction input GPIO and one fixed-direction output GPIO. Input GPIOs support edge-triggered interrupts. The controller serializes output values and samples input values over dedicated data, clock, and latch signals. Acked-by: Conor Dooley Signed-off-by: Petar Stepanovic Link: https://patch.msgid.link/20260921-axiado-ax3000-sgpio-controller-v4-1-79c444be7faf@axiado.com Signed-off-by: Bartosz Golaszewski --- .../bindings/gpio/axiado,ax3005-sgpio.yaml | 112 ++++++++++++++++++ MAINTAINERS | 8 ++ 2 files changed, 120 insertions(+) create mode 100644 Documentation/devicetree/bindings/gpio/axiado,ax3005-sgpio.yaml diff --git a/Documentation/devicetree/bindings/gpio/axiado,ax3005-sgpio.yaml b/Documentation/devicetree/bindings/gpio/axiado,ax3005-sgpio.yaml new file mode 100644 index 000000000000..47ae1285d482 --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/axiado,ax3005-sgpio.yaml @@ -0,0 +1,112 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/gpio/axiado,ax3005-sgpio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Axiado AX3005 SGPIO Controller + +maintainers: + - Petar Stepanovic + - SriNavmani A + - Prasad Bolisetty + +description: | + The Axiado SGPIO controller provides a serial GPIO expansion interface + that multiplexes multiple GPIO signals over a small set of physical lines. + + The controller serializes output values on a data output line (DOUT) and + samples input values from a data input line (DIN) in synchronization with + a shift clock (SCLK). A latch signal (LATCH) updates the output states + after each transfer cycle. + + SGPIO positions are organized in banks of 32, with registers controlling + output values, input sampling, and interrupt status. The number of SGPIO + positions supported depends on the controller configuration. + + The controller detects changes on input GPIOs. Interrupt events are + aggregated and signaled through a single interrupt line to the parent + interrupt controller. + + Register access is performed over the APB bus. The SGPIO shift clock is + derived from the APB clock using a programmable divider. + + Each SGPIO position provides one input and one output, and both are + exposed as GPIO lines. Position N is described by two consecutive lines: + line 2 * N is the input and line 2 * N + 1 is the output. Directions are + fixed by the hardware, so even lines are always inputs and odd lines are + always outputs. + + Only input lines can generate interrupts. Rising, falling, and both-edge + interrupts are supported. + +properties: + compatible: + const: axiado,ax3005-sgpio + + reg: + maxItems: 1 + + clocks: + description: APB interface clock + maxItems: 1 + + bus-frequency: + description: Desired SGPIO shift clock frequency in Hz + minimum: 1 + + gpio-controller: true + + '#gpio-cells': + const: 2 + + interrupt-controller: true + + '#interrupt-cells': + const: 2 + + interrupts: + maxItems: 1 + + ngpios: + description: | + Total number of GPIO lines provided by the controller. Each SGPIO + position wired up on the board contributes one input and one output + line, so this is twice the number of positions and must be even. It + may be lower than the maximum supported by the hardware. + minimum: 2 + maximum: 1024 + multipleOf: 2 + +required: + - compatible + - reg + - clocks + - bus-frequency + - gpio-controller + - '#gpio-cells' + - interrupts + - interrupt-controller + - '#interrupt-cells' + - ngpios + +additionalProperties: false + +examples: + - | + #include + #include + + gpio@33201c00 { + compatible = "axiado,ax3005-sgpio"; + reg = <0x33201c00 0x400>; + clocks = <&apb_clk>; + bus-frequency = <1000000>; + gpio-controller; + #gpio-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + interrupt-parent = <&gic500>; + interrupts = ; + ngpios = <256>; + }; diff --git a/MAINTAINERS b/MAINTAINERS index b0925fa212cc..e31c0ab6f1f8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4439,6 +4439,14 @@ S: Orphan F: Documentation/devicetree/bindings/sound/axentia,* F: sound/soc/atmel/tse850-pcm5142.c +AXIADO SGPIO DRIVER +M: Petar Stepanovic +M: SriNavmani A +M: Prasad Bolisetty +L: linux-gpio@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/gpio/axiado,ax3005-sgpio.yaml + AXIS ARTPEC ARM64 SoC SUPPORT M: Jesper Nilsson M: Lars Persson From dd36a4b7f391eec4daa336c837a197dab0cf6401 Mon Sep 17 00:00:00 2001 From: Petar Stepanovic Date: Mon, 21 Sep 2026 00:20:23 -0700 Subject: [PATCH 58/63] gpio: axiado: add AX3005 SGPIO controller support Add support for the Axiado AX3005 SGPIO controller. Each SGPIO position provides one input GPIO and one output GPIO with fixed directions. The driver registers the controller as a gpio_chip, supports interrupts on input GPIOs, and uses regmap for register access. Signed-off-by: Petar Stepanovic Link: https://patch.msgid.link/20260921-axiado-ax3000-sgpio-controller-v4-2-79c444be7faf@axiado.com Signed-off-by: Bartosz Golaszewski --- MAINTAINERS | 1 + drivers/gpio/Kconfig | 21 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-axiado-sgpio.c | 736 +++++++++++++++++++++++++++++++ 4 files changed, 759 insertions(+) create mode 100644 drivers/gpio/gpio-axiado-sgpio.c diff --git a/MAINTAINERS b/MAINTAINERS index e31c0ab6f1f8..9aea4c3fd3ae 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4446,6 +4446,7 @@ M: Prasad Bolisetty L: linux-gpio@vger.kernel.org S: Supported F: Documentation/devicetree/bindings/gpio/axiado,ax3005-sgpio.yaml +F: drivers/gpio/gpio-axiado-sgpio.c AXIS ARTPEC ARM64 SoC SUPPORT M: Jesper Nilsson diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 4bf91d309079..0e3865725d74 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -204,6 +204,27 @@ config GPIO_ATH79 Select this option to enable GPIO driver for Atheros AR71XX/AR724X/AR913X SoC devices. +config GPIO_AXIADO_SGPIO + tristate "Axiado AX3005 SGPIO support" + depends on ARCH_AXIADO || COMPILE_TEST + select GPIOLIB_IRQCHIP + select GPIO_REGMAP + select REGMAP_MMIO + help + Enable support for the Axiado AX3005 Serial GPIO (SGPIO) + controller. + + The SGPIO controller provides a serialized interface for + controlling multiple GPIO signals over a limited number of + physical lines. Each signal is exposed as one input and one + output line, and input lines can raise interrupts. + + This driver integrates with the Linux GPIO subsystem and + exposes the controller as a standard GPIO provider. + + To compile this driver as a module, choose M here: the module + will be called gpio-axiado-sgpio. + config GPIO_RASPBERRYPI_EXP tristate "Raspberry Pi 3 GPIO Expander" default RASPBERRYPI_FIRMWARE diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 8b7ac831fa58..f0ab389763d4 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -41,6 +41,7 @@ obj-$(CONFIG_GPIO_ARIZONA) += gpio-arizona.o obj-$(CONFIG_GPIO_ASPEED) += gpio-aspeed.o obj-$(CONFIG_GPIO_ASPEED_SGPIO) += gpio-aspeed-sgpio.o obj-$(CONFIG_GPIO_ATH79) += gpio-ath79.o +obj-$(CONFIG_GPIO_AXIADO_SGPIO) += gpio-axiado-sgpio.o obj-$(CONFIG_GPIO_BCM_KONA) += gpio-bcm-kona.o obj-$(CONFIG_GPIO_BCM_XGS_IPROC) += gpio-xgs-iproc.o obj-$(CONFIG_GPIO_BD71815) += gpio-bd71815.o diff --git a/drivers/gpio/gpio-axiado-sgpio.c b/drivers/gpio/gpio-axiado-sgpio.c new file mode 100644 index 000000000000..b57a5074ccef --- /dev/null +++ b/drivers/gpio/gpio-axiado-sgpio.c @@ -0,0 +1,736 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022-2026 Axiado Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct axiado_sgpio_reg_offsets { + u32 mux_0; + u32 preset_0; + u32 count_0; + u32 pos_0; + + u32 mux_1; + u32 ld; + u32 ld_ss; + + u32 preset_1; + u32 count_1; + u32 pos_1; + + u32 mux_2; + u32 dout; + u32 dout_ss; + + u32 preset_2; + u32 count_2; + u32 pos_2; + + u32 mux_3; + u32 preset_3; + u32 count_3; + u32 pos_3; + + u32 mux_4; + u32 oe; + u32 oe_ss; + + u32 preset_4; + u32 count_4; + u32 pos_4; + + u32 mask; + u32 ctrl_en; + u32 ctrl_en_pos; + + u32 din_ss; + u32 status; +}; + +static const struct axiado_sgpio_reg_offsets axiado_sgpio_offsets = { + .mux_0 = 0x000, + .preset_0 = 0x1dc, + .count_0 = 0x1f0, + .pos_0 = 0x204, + + .mux_1 = 0x004, + .ld = 0x014, + .ld_ss = 0x0d8, + + .preset_1 = 0x1e0, + .count_1 = 0x1f4, + .pos_1 = 0x208, + + .mux_2 = 0x008, + .dout = 0x054, + .dout_ss = 0x158, + + .preset_2 = 0x1e4, + .count_2 = 0x1f8, + .pos_2 = 0x20c, + + .mux_3 = 0x00c, + .preset_3 = 0x1e8, + .count_3 = 0x1fc, + .pos_3 = 0x210, + + .mux_4 = 0x010, + .oe = 0x0d4, + .oe_ss = 0x1d8, + + .preset_4 = 0x1ec, + .count_4 = 0x200, + .pos_4 = 0x214, + + .mask = 0x224, + .ctrl_en = 0x218, + .ctrl_en_pos = 0x21c, + + .din_ss = 0x198, + .status = 0x228, +}; + +/* Number of SGPIO positions held by one bank register. */ +#define SGPIO_BANK_SIZE 32 +#define SGPIO_MAX_SIGNALS 512 +#define SGPIO_NUM_BANKS 16 + +/* + * Slice mux encodings taken from the AX3005 SGPIO programming sequence. + */ +#define SGPIO_MUX_CLK 0x306 +#define SGPIO_MUX_DATA 0x30c +#define SGPIO_MUX_DIN 0x14c +#define SGPIO_MUX_OE 0x10c + +/* A POS register holds the first and the last position driven by the slice. */ +#define SGPIO_POS_FIRST GENMASK(31, 16) +#define SGPIO_POS_LAST GENMASK(15, 0) +#define SGPIO_POS(pos) (FIELD_PREP(SGPIO_POS_FIRST, (pos)) | \ + FIELD_PREP(SGPIO_POS_LAST, (pos))) + +/* The clock and output enable slices always cover a full bank. */ +#define SGPIO_POS_FULL_BANK SGPIO_POS(SGPIO_BANK_SIZE - 1) + +/* The enable and interrupt registers hold one bit per slice. */ +#define SGPIO_SLICE_MASK GENMASK(15, 0) + +/* + * Interrupt sources enabled by the programming sequence. Bit 13 is left + * masked, the remaining slice events are all reported. + */ +#define SGPIO_IRQ_ENABLE 0xdfff + +/* The upper half of the status register reports one bit per data bank. */ +#define SGPIO_STATUS_EXCHANGE GENMASK(31, 16) + +struct axiado_sgpio { + u32 preset_value; + u32 count_value; + u32 pos_reg; + /* Last sampled input word of each bank, for edge detection. */ + u32 *din_shadow; + unsigned long *irq_unmasked; + unsigned long *irq_rising; + unsigned long *irq_falling; + u32 nsignals; + u32 din_bank_shift; + unsigned long *dir_out; + struct gpio_regmap *gpio; + struct irq_domain *domain; + struct regmap *regmap; + const struct axiado_sgpio_reg_offsets *regs; +}; + +/* + * Tell lockdep that these interrupts are in a different category than the + * parent they are handled from, so that the nested handling is not reported + * as recursion. + */ +static struct lock_class_key axiado_sgpio_irq_lock_class; +static struct lock_class_key axiado_sgpio_irq_request_class; + +/* + * Each SGPIO signal is exposed as two GPIO lines: an even line for the serial + * input and the following odd line for the serial output. Signal n therefore + * lives in bit (n % 32) of bank (n / 32) of either the input or the output + * register. + */ +static int axiado_sgpio_reg_mask_xlate(struct gpio_regmap *gpio, + enum gpio_regmap_operation op, + unsigned int base, unsigned int offset, + unsigned int *reg, unsigned int *mask) +{ + struct axiado_sgpio *sgpio = gpio_regmap_get_drvdata(gpio); + unsigned int signal = offset / 2; + unsigned int bank = signal / SGPIO_BANK_SIZE; + + /* + * An output line lives in the output register for both reads and + * writes, so the register follows from the line rather than from the + * operation. The AX3005 input banks are right aligned, hence the + * shift. + */ + if (offset % 2) + base = sgpio->regs->dout_ss; + else + bank += sgpio->din_bank_shift; + + *reg = base + bank * sizeof(u32); + *mask = BIT(signal % SGPIO_BANK_SIZE); + + return 0; +} + +static irqreturn_t axiado_sgpio_irq_handler(int irq, void *arg) +{ + struct axiado_sgpio *sgpio = arg; + unsigned int nbanks = DIV_ROUND_UP(sgpio->nsignals, SGPIO_BANK_SIZE); + u32 status, new_value, changed_value; + unsigned int bit, reg_ptr, i; + int ret; + + /* Read-on-clear (ACK) parent cause */ + ret = regmap_read(sgpio->regmap, sgpio->regs->status, &status); + if (ret) + return IRQ_NONE; + + /* Nothing pending: the interrupt came from somebody else. */ + if (!status) + return IRQ_NONE; + + status = FIELD_GET(SGPIO_STATUS_EXCHANGE, status); + + reg_ptr = sgpio->din_bank_shift; + + for (i = 0; i < nbanks; i++, reg_ptr++) { + if (status & BIT(reg_ptr)) { + ret = regmap_read(sgpio->regmap, + sgpio->regs->din_ss + reg_ptr * sizeof(u32), + &new_value); + if (ret) + continue; + + /* + * Track every signal so that the shadow always holds + * the last sampled level, then report only the ones + * whose interrupt is unmasked. Transitions of a masked + * signal are dropped rather than delivered later, the + * hardware has no per signal latch to replay them + * from. Only this handler touches the shadow and it is + * not reentrant. + */ + changed_value = sgpio->din_shadow[i] ^ new_value; + sgpio->din_shadow[i] = new_value; + changed_value &= bitmap_read(sgpio->irq_unmasked, + i * SGPIO_BANK_SIZE, + SGPIO_BANK_SIZE); + + while (changed_value) { + unsigned int child_irq; + irq_hw_number_t hwirq; + unsigned int position; + bool rising; + + bit = __ffs(changed_value); + changed_value &= ~BIT(bit); + + position = i * SGPIO_BANK_SIZE + bit; + hwirq = position * 2; + + rising = !!(new_value & BIT(bit)); + + if (!test_bit(position, rising ? sgpio->irq_rising + : sgpio->irq_falling)) + continue; + + child_irq = irq_find_mapping(sgpio->domain, hwirq); + if (child_irq) + handle_nested_irq(child_irq); + } + } + } + + return IRQ_HANDLED; +} + +static int axiado_sgpio_hw_init(struct axiado_sgpio *sgpio) +{ + const struct axiado_sgpio_reg_offsets *regs = sgpio->regs; + unsigned int nbanks = DIV_ROUND_UP(sgpio->nsignals, SGPIO_BANK_SIZE); + unsigned int last_bank = (sgpio->nsignals - 1) / SGPIO_BANK_SIZE; + unsigned int last_bit = (sgpio->nsignals - 1) % SGPIO_BANK_SIZE; + unsigned int i; + int ret; + + /* Slice A0 is the shift clock, slice B1 the data load. */ + const struct reg_sequence pre_load[] = { + { regs->mask, 0 }, + { regs->mux_0, SGPIO_MUX_CLK }, + { regs->preset_0, sgpio->preset_value }, + { regs->count_0, sgpio->count_value }, + { regs->pos_0, SGPIO_POS_FULL_BANK }, + { regs->mux_1, SGPIO_MUX_DATA }, + }; + /* Slice C2 is the data output. */ + const struct reg_sequence pre_dout[] = { + { regs->preset_1, sgpio->preset_value }, + { regs->count_1, sgpio->count_value }, + { regs->pos_1, sgpio->pos_reg }, + { regs->mux_2, SGPIO_MUX_DATA }, + }; + /* Slice D3 is the data input, slice E4 the output enable. */ + const struct reg_sequence post_dout[] = { + { regs->preset_2, sgpio->preset_value }, + { regs->count_2, sgpio->count_value }, + { regs->pos_2, sgpio->pos_reg }, + { regs->mux_3, SGPIO_MUX_DIN }, + { regs->preset_3, sgpio->preset_value }, + { regs->count_3, sgpio->count_value }, + { regs->pos_3, sgpio->pos_reg }, + { regs->mux_4, SGPIO_MUX_OE }, + { regs->oe, GENMASK(31, 0) }, + { regs->oe_ss, GENMASK(31, 0) }, + { regs->preset_4, sgpio->preset_value }, + { regs->count_4, sgpio->count_value }, + { regs->pos_4, SGPIO_POS_FULL_BANK }, + { regs->ctrl_en, SGPIO_SLICE_MASK }, + { regs->ctrl_en_pos, SGPIO_SLICE_MASK }, + }; + + ret = regmap_multi_reg_write(sgpio->regmap, pre_load, + ARRAY_SIZE(pre_load)); + if (ret) + return ret; + + /* + * Latch the signals that are present. Only the last bank may be + * partially populated; a last bit of zero still describes one signal. + */ + for (i = 0; i < nbanks; i++) { + u32 val = i < last_bank ? GENMASK(SGPIO_BANK_SIZE - 1, 0) : + GENMASK(last_bit, 0); + + ret = regmap_write(sgpio->regmap, regs->ld + i * 4, val); + if (ret) + return ret; + + ret = regmap_write(sgpio->regmap, regs->ld_ss + i * 4, val); + if (ret) + return ret; + } + + ret = regmap_multi_reg_write(sgpio->regmap, pre_dout, + ARRAY_SIZE(pre_dout)); + if (ret) + return ret; + + /* All outputs start out driving zero. */ + for (i = 0; i < nbanks; i++) { + ret = regmap_write(sgpio->regmap, regs->dout + i * 4, 0); + if (ret) + return ret; + + ret = regmap_write(sgpio->regmap, regs->dout_ss + i * 4, 0); + if (ret) + return ret; + } + + return regmap_multi_reg_write(sgpio->regmap, post_dout, + ARRAY_SIZE(post_dout)); +} + +static int axiado_sgpio_irq_enable(struct axiado_sgpio *sgpio) +{ + return regmap_write(sgpio->regmap, sgpio->regs->mask, + SGPIO_IRQ_ENABLE); +} + +static void axiado_sgpio_disable(void *data) +{ + struct axiado_sgpio *sgpio = data; + + /* Mask the interrupts and stop the shift engine. */ + regmap_write(sgpio->regmap, sgpio->regs->mask, 0); + regmap_write(sgpio->regmap, sgpio->regs->ctrl_en, 0); +} + +static int axiado_sgpio_set_irq_type(struct irq_data *d, unsigned int type) +{ + struct axiado_sgpio *sgpio = irq_data_get_irq_chip_data(d); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + unsigned int position; + + /* Only even GPIO offsets represent SGPIO inputs. */ + if (hwirq & 1) + return -EINVAL; + + position = hwirq / 2; + if (position >= sgpio->nsignals) + return -EINVAL; + + type &= IRQ_TYPE_SENSE_MASK; + + switch (type) { + case IRQ_TYPE_EDGE_BOTH: + case IRQ_TYPE_EDGE_RISING: + case IRQ_TYPE_EDGE_FALLING: + break; + default: + return -EINVAL; + } + + assign_bit(position, sgpio->irq_rising, type & IRQ_TYPE_EDGE_RISING); + assign_bit(position, sgpio->irq_falling, type & IRQ_TYPE_EDGE_FALLING); + + return 0; +} + +static void axiado_sgpio_mask_irq(struct irq_data *d) +{ + struct axiado_sgpio *sgpio = irq_data_get_irq_chip_data(d); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + + clear_bit(hwirq / 2, sgpio->irq_unmasked); + gpio_regmap_disable_irq(sgpio->gpio, hwirq); +} + +static void axiado_sgpio_unmask_irq(struct irq_data *d) +{ + struct axiado_sgpio *sgpio = irq_data_get_irq_chip_data(d); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + + gpio_regmap_enable_irq(sgpio->gpio, hwirq); + set_bit(hwirq / 2, sgpio->irq_unmasked); +} + +static int axiado_sgpio_irq_request_resources(struct irq_data *d) +{ + struct axiado_sgpio *sgpio = irq_data_get_irq_chip_data(d); + + return gpio_regmap_reqres_irq(sgpio->gpio, d->hwirq); +} + +static void axiado_sgpio_irq_release_resources(struct irq_data *d) +{ + struct axiado_sgpio *sgpio = irq_data_get_irq_chip_data(d); + + gpio_regmap_relres_irq(sgpio->gpio, d->hwirq); +} + +static const struct irq_chip axiado_sgpio_irqchip = { + .name = "axiado-sgpio", + .irq_mask = axiado_sgpio_mask_irq, + .irq_unmask = axiado_sgpio_unmask_irq, + .irq_set_type = axiado_sgpio_set_irq_type, + .flags = IRQCHIP_IMMUTABLE | IRQCHIP_MASK_ON_SUSPEND, + .irq_request_resources = axiado_sgpio_irq_request_resources, + .irq_release_resources = axiado_sgpio_irq_release_resources, +}; + +static int axiado_sgpio_irq_map(struct irq_domain *domain, unsigned int virq, + irq_hw_number_t hwirq) +{ + struct axiado_sgpio *sgpio = domain->host_data; + + /* Only even offsets represent input GPIOs. */ + if (hwirq % 2) + return -EINVAL; + + irq_set_chip_data(virq, sgpio); + irq_set_lockdep_class(virq, &axiado_sgpio_irq_lock_class, + &axiado_sgpio_irq_request_class); + irq_set_chip_and_handler(virq, &axiado_sgpio_irqchip, handle_simple_irq); + irq_set_nested_thread(virq, true); + irq_set_noprobe(virq); + + return 0; +} + +static const struct irq_domain_ops axiado_sgpio_domain_ops = { + .map = axiado_sgpio_irq_map, + .xlate = irq_domain_xlate_twocell, +}; + +static int axiado_sgpio_init_input_cache(struct axiado_sgpio *sgpio) +{ + unsigned int num_banks = DIV_ROUND_UP(sgpio->nsignals, + SGPIO_BANK_SIZE); + unsigned int reg_ptr = sgpio->din_bank_shift; + unsigned int i; + int ret; + + for (i = 0; i < num_banks; i++, reg_ptr++) { + ret = regmap_read(sgpio->regmap, + sgpio->regs->din_ss + reg_ptr * sizeof(u32), + &sgpio->din_shadow[i]); + if (ret) + return ret; + } + + return 0; +} + +static bool axiado_sgpio_dout_reg(const struct axiado_sgpio *sgpio, + unsigned int reg) +{ + unsigned int span = SGPIO_NUM_BANKS * sizeof(u32); + + return reg >= sgpio->regs->dout_ss && + reg < sgpio->regs->dout_ss + span; +} + +static bool axiado_sgpio_readable_reg(struct device *dev, unsigned int reg) +{ + const struct axiado_sgpio *sgpio = dev_get_drvdata(dev); + + /* The status register is the last one of the block. */ + if (reg > sgpio->regs->status) + return false; + + /* + * The output shadow feeds the shift register and cannot be read back, + * the driven value is kept in the register cache instead. + */ + return !axiado_sgpio_dout_reg(sgpio, reg); +} + +static bool axiado_sgpio_volatile_reg(struct device *dev, unsigned int reg) +{ + const struct axiado_sgpio *sgpio = dev_get_drvdata(dev); + + /* + * Everything but the output shadow is live hardware state: shifted in + * data, running counters and the interrupt status. Caching any of it + * would hand out stale values, so the cache holds the output banks + * alone. + */ + return !axiado_sgpio_dout_reg(sgpio, reg); +} + +static bool axiado_sgpio_precious_reg(struct device *dev, unsigned int reg) +{ + const struct axiado_sgpio *sgpio = dev_get_drvdata(dev); + + /* Reading the status register acknowledges the interrupts. */ + return reg == sgpio->regs->status; +} + +static const struct regmap_config axiado_sgpio_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .readable_reg = axiado_sgpio_readable_reg, + .volatile_reg = axiado_sgpio_volatile_reg, + .precious_reg = axiado_sgpio_precious_reg, + .cache_type = REGCACHE_MAPLE, +}; + +static int axiado_sgpio_probe(struct platform_device *pdev) +{ + struct gpio_regmap_config config = { }; + struct irq_domain_info d_info = { }; + struct axiado_sgpio *sgpio; + unsigned int ngpio, i; + unsigned long apb_freq; + struct clk *apb_clk; + void __iomem *base; + u32 sgpio_freq; + int irq, rc; + + sgpio = devm_kzalloc(&pdev->dev, sizeof(*sgpio), GFP_KERNEL); + if (!sgpio) + return -ENOMEM; + + /* The regmap callbacks below need both of these. */ + platform_set_drvdata(pdev, sgpio); + + sgpio->regs = &axiado_sgpio_offsets; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + sgpio->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &axiado_sgpio_regmap_config); + if (IS_ERR(sgpio->regmap)) + return dev_err_probe(&pdev->dev, PTR_ERR(sgpio->regmap), + "Failed to init regmap\n"); + + rc = device_property_read_u32(&pdev->dev, "ngpios", &ngpio); + if (rc) + return dev_err_probe(&pdev->dev, rc, + "Failed to read ngpios property\n"); + + /* + * Each SGPIO signal is exposed as one input and one output line, so + * the number of lines is twice the number of signals. + */ + if (!ngpio || ngpio % 2 || ngpio > SGPIO_MAX_SIGNALS * 2) + return dev_err_probe(&pdev->dev, -EINVAL, + "Invalid ngpios value: %u (even, max %u)\n", + ngpio, SGPIO_MAX_SIGNALS * 2); + + sgpio->nsignals = ngpio / 2; + + sgpio->din_shadow = devm_kcalloc(&pdev->dev, + DIV_ROUND_UP(sgpio->nsignals, + SGPIO_BANK_SIZE), + sizeof(*sgpio->din_shadow), GFP_KERNEL); + sgpio->irq_unmasked = devm_bitmap_zalloc(&pdev->dev, sgpio->nsignals, + GFP_KERNEL); + sgpio->irq_rising = devm_bitmap_zalloc(&pdev->dev, sgpio->nsignals, + GFP_KERNEL); + sgpio->irq_falling = devm_bitmap_zalloc(&pdev->dev, sgpio->nsignals, + GFP_KERNEL); + if (!sgpio->din_shadow || !sgpio->irq_unmasked || + !sgpio->irq_rising || !sgpio->irq_falling) + return -ENOMEM; + + apb_clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(apb_clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(apb_clk), + "Failed to get and enable APB clock\n"); + + rc = device_property_read_u32(&pdev->dev, "bus-frequency", + &sgpio_freq); + if (rc) + return dev_err_probe(&pdev->dev, rc, + "Failed to read bus-frequency\n"); + + apb_freq = clk_get_rate(apb_clk); + + if (!apb_freq || !sgpio_freq || sgpio_freq > apb_freq) + return dev_err_probe(&pdev->dev, -EINVAL, + "Invalid SGPIO bus frequency\n"); + + sgpio->preset_value = (apb_freq / sgpio_freq) - 1; + sgpio->count_value = sgpio->preset_value; + + sgpio->pos_reg = SGPIO_POS(sgpio->nsignals - 1); + + sgpio->din_bank_shift = SGPIO_NUM_BANKS - + DIV_ROUND_UP(sgpio->nsignals, SGPIO_BANK_SIZE); + + /* + * From here on the hardware may be running, so the shutdown action has + * to be in place before the first register is programmed. It only ever + * writes zeroes, which is harmless if initialisation never got that + * far, and it runs before the APB clock is disabled because that was + * requested earlier. + */ + rc = devm_add_action_or_reset(&pdev->dev, axiado_sgpio_disable, sgpio); + if (rc) + return rc; + + rc = axiado_sgpio_hw_init(sgpio); + if (rc) + return dev_err_probe(&pdev->dev, rc, + "Failed to initialize hardware\n"); + + rc = axiado_sgpio_init_input_cache(sgpio); + if (rc) + return dev_err_probe(&pdev->dev, rc, + "Failed to initialize input cache\n"); + + sgpio->dir_out = devm_bitmap_zalloc(&pdev->dev, ngpio, GFP_KERNEL); + if (!sgpio->dir_out) + return -ENOMEM; + + /* Even lines are the serial inputs, odd lines the serial outputs. */ + for (i = 1; i < ngpio; i += 2) + __set_bit(i, sgpio->dir_out); + + d_info.fwnode = dev_fwnode(&pdev->dev); + d_info.size = ngpio; + d_info.hwirq_max = ngpio; + d_info.ops = &axiado_sgpio_domain_ops; + d_info.host_data = sgpio; + + sgpio->domain = devm_irq_domain_instantiate(&pdev->dev, &d_info); + if (IS_ERR(sgpio->domain)) + return PTR_ERR(sgpio->domain); + + config.parent = &pdev->dev; + config.regmap = sgpio->regmap; + config.ngpio = ngpio; + config.reg_dat_base = GPIO_REGMAP_ADDR(sgpio->regs->din_ss); + config.reg_set_base = GPIO_REGMAP_ADDR(sgpio->regs->dout_ss); + config.reg_mask_xlate = axiado_sgpio_reg_mask_xlate; + config.fixed_direction_output = sgpio->dir_out; + config.irq_domain = sgpio->domain; + config.drvdata = sgpio; + + sgpio->gpio = devm_gpio_regmap_register(&pdev->dev, &config); + if (IS_ERR(sgpio->gpio)) + return dev_err_probe(&pdev->dev, PTR_ERR(sgpio->gpio), + "Could not register gpiochip\n"); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + rc = devm_request_threaded_irq(&pdev->dev, irq, NULL, axiado_sgpio_irq_handler, + IRQF_ONESHOT, dev_name(&pdev->dev), sgpio); + if (rc) + return dev_err_probe(&pdev->dev, rc, "Failed to request IRQ\n"); + + rc = axiado_sgpio_irq_enable(sgpio); + if (rc) + return dev_err_probe(&pdev->dev, rc, + "Failed to enable interrupts\n"); + + return 0; +} + +static void axiado_sgpio_remove(struct platform_device *pdev) +{ + struct axiado_sgpio *sgpio = platform_get_drvdata(pdev); + + /* + * Runs before the devres unwind, so the interrupts are masked while the + * parent interrupt is still requested. The shift engine is stopped by + * axiado_sgpio_disable(), registered before the hardware was programmed + * so that it also covers failures during probe. + */ + regmap_write(sgpio->regmap, sgpio->regs->mask, 0); +} + +static const struct of_device_id axiado_sgpio_of_match[] = { + { .compatible = "axiado,ax3005-sgpio" }, + { } +}; +MODULE_DEVICE_TABLE(of, axiado_sgpio_of_match); + +static struct platform_driver axiado_sgpio_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = axiado_sgpio_of_match, + }, + .probe = axiado_sgpio_probe, + .remove = axiado_sgpio_remove, +}; +module_platform_driver(axiado_sgpio_driver); + +MODULE_DESCRIPTION("Axiado AX3005 Serial GPIO Driver"); +MODULE_AUTHOR("Axiado Corporation"); +MODULE_LICENSE("GPL"); From c4e74a7058b573617f142e07b4b5bc9eee04f4c8 Mon Sep 17 00:00:00 2001 From: Areeb Ul Haq Mirza Date: Thu, 24 Sep 2026 15:02:42 +0100 Subject: [PATCH 59/63] gpio: pxa: stop reading gpio_chip::base in direction callbacks __gpio_is_inverted() identifies GPIO86-89 on PXA26x by comparing its argument against hardware line numbers, and its other callers pass the hardware index from irqd_to_hwirq(). The direction_input() and direction_output() callbacks instead pass chip->base + offset. Pass the offset directly, which is the same conversion that commit 566e684e70cb ("gpio: pxa: use new pinctrl GPIO helpers") made for the pinctrl calls in these functions. No functional change: the driver never sets chip->base, which stays zero because struct pxa_gpio_chip is allocated with devm_kzalloc(). This removes a dependency on gpio_chip::base, as tracked in drivers/gpio/TODO. Signed-off-by: Areeb Ul Haq Mirza Link: https://patch.msgid.link/20260924140242.126159-1-auhmirza02@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-pxa.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-pxa.c b/drivers/gpio/gpio-pxa.c index 2b5da973f51f..3ff21e899278 100644 --- a/drivers/gpio/gpio-pxa.c +++ b/drivers/gpio/gpio-pxa.c @@ -268,7 +268,7 @@ static int pxa_gpio_direction_input(struct gpio_chip *chip, unsigned offset) spin_lock_irqsave(&gpio_lock, flags); value = readl_relaxed(base + GPDR_OFFSET); - if (__gpio_is_inverted(chip->base + offset)) + if (__gpio_is_inverted(offset)) value |= mask; else value &= ~mask; @@ -297,7 +297,7 @@ static int pxa_gpio_direction_output(struct gpio_chip *chip, spin_lock_irqsave(&gpio_lock, flags); tmp = readl_relaxed(base + GPDR_OFFSET); - if (__gpio_is_inverted(chip->base + offset)) + if (__gpio_is_inverted(offset)) tmp &= ~mask; else tmp |= mask; From 6da1f3437435c82eaa15b121022474bb358ffe20 Mon Sep 17 00:00:00 2001 From: Abduvaliy Abdulazizov Date: Fri, 2 Oct 2026 15:18:00 +0500 Subject: [PATCH 60/63] gpiolib: acpi: Ignore GPIO 11 interrupt on Acer Nitro V 16S AI On the Acer Nitro V 16S AI (ANV16S-41) the internal USB camera (0408:4035) works on Windows but never shows up on Linux. The firmware drives GPIO 11 of AMDI0030 high to power the camera. The same pin is listed in \_SB.GPIO._AEI as an edge-triggered, active-low wake interrupt whose _EVT case only writes a debug code. gpiolib-acpi requests _AEI pins as inputs, which clears the output enable bit, so the camera loses power during boot. After boot the pin register reads 0x00447a00 (output value 1, output enable 0, line low). With the xHCI controller kept away from Linux (pci-stub), the camera port shows CSC=1, CCS=0: the camera was connected during boot and then dropped off. Booting with gpiolib_acpi.ignore_interrupt=AMDI0030:00@11 keeps the camera powered; it enumerates and works with uvcvideo. Add a DMI quirk so this works out of the box, like the existing one for the Acer Nitro V 14 on the same pin. Closes: https://lore.kernel.org/linux-usb/CAPGafdkBb49YFV+d0rYjbi4QjifCUr1Wos6C5TZtDQzN6-pn+g@mail.gmail.com/ Assisted-by: LLM Signed-off-by: Abduvaliy Abdulazizov Signed-off-by: Andy Shevchenko --- drivers/gpio/gpiolib-acpi-quirks.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/gpio/gpiolib-acpi-quirks.c b/drivers/gpio/gpiolib-acpi-quirks.c index 22c5fb149377..dbfd82056c3e 100644 --- a/drivers/gpio/gpiolib-acpi-quirks.c +++ b/drivers/gpio/gpiolib-acpi-quirks.c @@ -343,6 +343,22 @@ static const struct dmi_system_id gpiolib_acpi_quirks[] __initconst = { .ignore_interrupt = "AMDI0030:00@11", }, }, + { + /* + * GPIO 11 is the power enable of the internal USB camera. + * The firmware drives it high but also lists it in _AEI with + * an empty handler. Requesting it as an input turns the camera + * off, so it never enumerates. + * Found in BIOS V1.14 + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "Acer Nitro V 16S AI"), + }, + .driver_data = &(struct acpi_gpiolib_dmi_quirk) { + .ignore_interrupt = "AMDI0030:00@11", + }, + }, { /* * Wakeup only works when keyboard backlight is turned off From 356cab616b6f07a06c2d279fda8a9ec928fd5fff Mon Sep 17 00:00:00 2001 From: Igor Velkov Date: Fri, 9 Oct 2026 20:45:36 +0000 Subject: [PATCH 61/63] dt-bindings: gpio: pca95xx: add onnn,pca9655 with nxp,pca9555 fallback The ON Semiconductor PCA9655E is a 16-bit I2C GPIO expander that the pca953x driver handles as a PCA9555: both entries of its OF match table carry OF_953X(16, PCA_INT), and onnn,pca9655 has been there since commit 6d49b3a0f351 ("gpio: pca953x: Add support for the On Semi pca9655"). Document it with nxp,pca9555 as the fallback, the form the comments in armada-388-clearfog.dtsi and armada-388-helios4.dts ask for. Signed-off-by: Igor Velkov Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20261009-helios64-pca9655-v1-1-673ee4432650@iav.lv Signed-off-by: Bartosz Golaszewski --- Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml b/Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml index 4631388a7d91..d671aaf83bb7 100644 --- a/Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml +++ b/Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml @@ -25,6 +25,9 @@ properties: - items: - const: kinetic,kts1622 - const: ti,tcal6416 + - items: + - const: onnn,pca9655 + - const: nxp,pca9555 - items: - enum: - exar,xra1202 From 39225ea01d82c85045ab0272edaf550abce7330a Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 15 Jul 2026 21:23:30 +0200 Subject: [PATCH 62/63] gpio: legacy: Kill gpio_set_value_cansleep() No more users. Signed-off-by: Andy Shevchenko Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260715192415.1326037-1-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- include/linux/gpio/legacy.h | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/include/linux/gpio/legacy.h b/include/linux/gpio/legacy.h index 557ef635935e..4001208d5d63 100644 --- a/include/linux/gpio/legacy.h +++ b/include/linux/gpio/legacy.h @@ -63,10 +63,6 @@ static inline int gpio_get_value_cansleep(unsigned gpio) { return gpiod_get_raw_value_cansleep(gpio_to_desc(gpio)); } -static inline void gpio_set_value_cansleep(unsigned gpio, int value) -{ - gpiod_set_raw_value_cansleep(gpio_to_desc(gpio), value); -} static inline int gpio_get_value(unsigned gpio) { @@ -148,12 +144,6 @@ static inline int gpio_get_value_cansleep(unsigned gpio) return 0; } -static inline void gpio_set_value_cansleep(unsigned gpio, int value) -{ - /* GPIO can never have been requested or set as output */ - WARN_ON(1); -} - static inline int gpio_to_irq(unsigned gpio) { /* GPIO can never have been requested or set as input */ From 3fd4bf93d586cb07304cdcb5e8b3c51ac1d8284f Mon Sep 17 00:00:00 2001 From: Alban Bedel Date: Thu, 1 Oct 2026 17:23:43 +0200 Subject: [PATCH 63/63] gpiolib: acpi: Fix GPIO lookup using relative path ACPI allow to use relative path in resources, pass the device handle to acpi_get_gpiod() to allow resolving such path. Signed-off-by: Alban Bedel Acked-by: Mika Westerberg Signed-off-by: Andy Shevchenko --- drivers/gpio/gpiolib-acpi-core.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpiolib-acpi-core.c b/drivers/gpio/gpiolib-acpi-core.c index 220f0ac4204e..f94f456dbe5f 100644 --- a/drivers/gpio/gpiolib-acpi-core.c +++ b/drivers/gpio/gpiolib-acpi-core.c @@ -116,7 +116,8 @@ static int acpi_gpiochip_find(struct gpio_chip *gc, const void *data) /** * acpi_get_gpiod() - Translate ACPI GPIO pin to GPIO descriptor usable with GPIO API - * @path: ACPI GPIO controller full path name, (e.g. "\\_SB.GPO1") + * @parent: parent ACPI handle to resolve relative path + * @path: ACPI GPIO controller path name, (e.g. "\\_SB.GPO1" or "^GP01") * @pin: ACPI GPIO pin number (0-based, controller-relative) * * Returns: @@ -128,12 +129,12 @@ static int acpi_gpiochip_find(struct gpio_chip *gc, const void *data) * controller does not have GPIO chip registered at the moment. This is to * support probe deferral. */ -static struct gpio_desc *acpi_get_gpiod(char *path, unsigned int pin) +static struct gpio_desc *acpi_get_gpiod(acpi_handle parent, char *path, unsigned int pin) { acpi_handle handle; acpi_status status; - status = acpi_get_handle(NULL, path, &handle); + status = acpi_get_handle(parent, path, &handle); if (ACPI_FAILURE(status)) return ERR_PTR(-ENODEV); @@ -733,7 +734,8 @@ static int acpi_populate_gpio_lookup(struct acpi_resource *ares, void *data) if (info->quirks & ACPI_GPIO_QUIRK_ABSOLUTE_NUMBER) desc = gpio_to_desc(agpio->pin_table[pin_index]); else - desc = acpi_get_gpiod(agpio->resource_source.string_ptr, + desc = acpi_get_gpiod(acpi_device_handle(info->adev), + agpio->resource_source.string_ptr, agpio->pin_table[pin_index]); lookup->desc = desc; info->pin_config = agpio->pin_config;