summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-07-10 09:36:25 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-07-10 09:36:25 -0700
commitf827c27e573b475863d87bd2bab0b06f75aef5cd (patch)
treeea08b002b29739c642f071fda9df5374dd3e1329
parent8f964d992ee0ea1c0aca689b8b7d5607300f5e2a (diff)
parentb11c513ad943f35cf5e8007d3a56279c79b7ed4b (diff)
Merge tag 'gpio-fixes-for-v7.2-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux
Pull gpio fixes from Bartosz Golaszewski: - provide the missing .get_direction() callback in gpio-palmas - fix interrupt handling in gpio-dwapb - add a GPIO self-test program binary to .gitignore - fix a resource leak in gpio-mvebu - make the GPIO sharing heuristic more adaptable * tag 'gpio-fixes-for-v7.2-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux: gpio: mvebu: free generic chips on unbind selftests: gpio: add gpio-cdev-uaf to .gitignore gpio: dwapb: Mask interrupts at hardware initialization gpio: dwapb: Defer clock gating until noirq gpio: shared: make the voting mechanism adaptable gpios: palmas: add .get_direction() op
-rw-r--r--drivers/gpio/gpio-dwapb.c96
-rw-r--r--drivers/gpio/gpio-mvebu.c1
-rw-r--r--drivers/gpio/gpio-palmas.c19
-rw-r--r--drivers/gpio/gpio-shared-proxy.c66
-rw-r--r--drivers/gpio/gpiolib-shared.h3
-rw-r--r--tools/testing/selftests/gpio/.gitignore1
6 files changed, 139 insertions, 47 deletions
diff --git a/drivers/gpio/gpio-dwapb.c b/drivers/gpio/gpio-dwapb.c
index 7b92b233fafe..aa7c08e60707 100644
--- a/drivers/gpio/gpio-dwapb.c
+++ b/drivers/gpio/gpio-dwapb.c
@@ -117,6 +117,7 @@ struct dwapb_gpio {
unsigned int flags;
struct reset_control *rst;
struct clk_bulk_data clks[DWAPB_NR_CLOCKS];
+ bool clocks_on_for_wake;
struct dwapb_gpio_port ports[] __counted_by(nr_ports);
};
@@ -199,6 +200,22 @@ static void dwapb_toggle_trigger(struct dwapb_gpio *gpio, unsigned int offs)
dwapb_write(gpio, GPIO_INT_POLARITY, pol);
}
+static int dwapb_irq_init_hw(struct gpio_chip *gc)
+{
+ struct dwapb_gpio *gpio = to_dwapb_gpio(gc);
+
+ /*
+ * GPIO interrupts may retain stale state across warm reboots when
+ * peripherals stay powered. Force a known-safe state before the GPIO
+ * irqchip and irq domain are set up.
+ */
+ dwapb_write(gpio, GPIO_INTEN, 0);
+ dwapb_write(gpio, GPIO_INTMASK, 0xffffffff);
+ dwapb_write(gpio, GPIO_PORTA_EOI, 0xffffffff);
+
+ return 0;
+}
+
static u32 dwapb_do_irq(struct dwapb_gpio *gpio)
{
struct gpio_generic_chip *gen_gc = &gpio->ports[0].chip;
@@ -364,11 +381,24 @@ static int dwapb_irq_set_wake(struct irq_data *d, unsigned int enable)
struct dwapb_gpio *gpio = to_dwapb_gpio(gc);
struct dwapb_context *ctx = gpio->ports[0].ctx;
irq_hw_number_t bit = irqd_to_hwirq(d);
+ u32 wake_en = ctx->wake_en;
if (enable)
- ctx->wake_en |= BIT(bit);
+ wake_en |= BIT(bit);
else
- ctx->wake_en &= ~BIT(bit);
+ wake_en &= ~BIT(bit);
+
+#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
+ if (d->parent_data && !!ctx->wake_en != !!wake_en) {
+ int err;
+
+ err = irq_chip_set_wake_parent(d, enable);
+ if (err)
+ return err;
+ }
+#endif
+
+ ctx->wake_en = wake_en;
return 0;
}
@@ -457,6 +487,7 @@ static void dwapb_configure_irqs(struct dwapb_gpio *gpio,
girq = &gc->irq;
girq->handler = handle_bad_irq;
girq->default_type = IRQ_TYPE_NONE;
+ girq->init_hw = dwapb_irq_init_hw;
port->pirq = pirq;
@@ -749,6 +780,8 @@ static int dwapb_gpio_suspend(struct device *dev)
int i;
scoped_guard(gpio_generic_lock_irqsave, gen_gc) {
+ gpio->clocks_on_for_wake = false;
+
for (i = 0; i < gpio->nr_ports; i++) {
unsigned int offset;
unsigned int idx = gpio->ports[i].idx;
@@ -770,11 +803,38 @@ static int dwapb_gpio_suspend(struct device *dev)
ctx->int_pol = dwapb_read(gpio, GPIO_INT_POLARITY);
ctx->int_type = dwapb_read(gpio, GPIO_INTTYPE_LEVEL);
ctx->int_deb = dwapb_read(gpio, GPIO_PORTA_DEBOUNCE);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int dwapb_gpio_suspend_noirq(struct device *dev)
+{
+ struct dwapb_gpio *gpio = dev_get_drvdata(dev);
+ struct gpio_generic_chip *gen_gc = &gpio->ports[0].chip;
+ bool wake_enabled = false;
+ int i;
- /* Mask out interrupts */
+ scoped_guard(gpio_generic_lock_irqsave, gen_gc) {
+ for (i = 0; i < gpio->nr_ports; i++) {
+ unsigned int idx = gpio->ports[i].idx;
+ struct dwapb_context *ctx = gpio->ports[i].ctx;
+
+ if (idx == 0) {
+ wake_enabled = ctx->wake_en;
dwapb_write(gpio, GPIO_INTMASK, ~ctx->wake_en);
+ break;
}
}
+
+ gpio->clocks_on_for_wake = wake_enabled;
+ }
+
+ if (wake_enabled) {
+ device_set_wakeup_path(dev);
+ return 0;
}
clk_bulk_disable_unprepare(DWAPB_NR_CLOCKS, gpio->clks);
@@ -782,18 +842,27 @@ static int dwapb_gpio_suspend(struct device *dev)
return 0;
}
-static int dwapb_gpio_resume(struct device *dev)
+static int dwapb_gpio_resume_noirq(struct device *dev)
{
struct dwapb_gpio *gpio = dev_get_drvdata(dev);
- struct gpio_chip *gc = &gpio->ports[0].chip.gc;
- struct gpio_generic_chip *gen_gc = to_gpio_generic_chip(gc);
- int i, err;
+ int err;
+
+ if (gpio->clocks_on_for_wake)
+ return 0;
err = clk_bulk_prepare_enable(DWAPB_NR_CLOCKS, gpio->clks);
- if (err) {
+ if (err)
dev_err(gpio->dev, "Cannot reenable APB/Debounce clocks\n");
- return err;
- }
+
+ return err;
+}
+
+static int dwapb_gpio_resume(struct device *dev)
+{
+ struct dwapb_gpio *gpio = dev_get_drvdata(dev);
+ struct gpio_chip *gc = &gpio->ports[0].chip.gc;
+ struct gpio_generic_chip *gen_gc = to_gpio_generic_chip(gc);
+ int i;
guard(gpio_generic_lock_irqsave)(gen_gc);
@@ -827,8 +896,11 @@ static int dwapb_gpio_resume(struct device *dev)
return 0;
}
-static DEFINE_SIMPLE_DEV_PM_OPS(dwapb_gpio_pm_ops,
- dwapb_gpio_suspend, dwapb_gpio_resume);
+static const struct dev_pm_ops dwapb_gpio_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(dwapb_gpio_suspend, dwapb_gpio_resume)
+ NOIRQ_SYSTEM_SLEEP_PM_OPS(dwapb_gpio_suspend_noirq,
+ dwapb_gpio_resume_noirq)
+};
static struct platform_driver dwapb_gpio_driver = {
.driver = {
diff --git a/drivers/gpio/gpio-mvebu.c b/drivers/gpio/gpio-mvebu.c
index 689dc6354c2d..a010604e5ff7 100644
--- a/drivers/gpio/gpio-mvebu.c
+++ b/drivers/gpio/gpio-mvebu.c
@@ -1110,6 +1110,7 @@ static void mvebu_gpio_remove_irq_domain(void *data)
{
struct irq_domain *domain = data;
+ irq_domain_remove_generic_chips(domain);
irq_domain_remove(domain);
}
diff --git a/drivers/gpio/gpio-palmas.c b/drivers/gpio/gpio-palmas.c
index e377f6dd4ccf..e64ee0487718 100644
--- a/drivers/gpio/gpio-palmas.c
+++ b/drivers/gpio/gpio-palmas.c
@@ -116,6 +116,24 @@ static int palmas_gpio_input(struct gpio_chip *gc, unsigned offset)
return ret;
}
+static int palmas_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+ struct palmas_gpio *pg = gpiochip_get_data(gc);
+ struct palmas *palmas = pg->palmas;
+ unsigned int val;
+ unsigned int reg;
+ int ret;
+ int gpio16 = (offset/8);
+
+ offset %= 8;
+ reg = (gpio16) ? PALMAS_GPIO_DATA_DIR2 : PALMAS_GPIO_DATA_DIR;
+ ret = palmas_read(palmas, PALMAS_GPIO_BASE, reg, &val);
+ if (ret)
+ return ret;
+
+ return (val & BIT(offset)) ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN;
+}
+
static int palmas_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
{
struct palmas_gpio *pg = gpiochip_get_data(gc);
@@ -165,6 +183,7 @@ static int palmas_gpio_probe(struct platform_device *pdev)
palmas_gpio->gpio_chip.can_sleep = true;
palmas_gpio->gpio_chip.direction_input = palmas_gpio_input;
palmas_gpio->gpio_chip.direction_output = palmas_gpio_output;
+ palmas_gpio->gpio_chip.get_direction = palmas_gpio_get_direction;
palmas_gpio->gpio_chip.to_irq = palmas_gpio_to_irq;
palmas_gpio->gpio_chip.set = palmas_gpio_set;
palmas_gpio->gpio_chip.get = palmas_gpio_get;
diff --git a/drivers/gpio/gpio-shared-proxy.c b/drivers/gpio/gpio-shared-proxy.c
index 0f39d23ea9cb..bc69b8729d19 100644
--- a/drivers/gpio/gpio-shared-proxy.c
+++ b/drivers/gpio/gpio-shared-proxy.c
@@ -21,7 +21,7 @@ struct gpio_shared_proxy_data {
struct gpio_chip gc;
struct gpio_shared_desc *shared_desc;
struct device *dev;
- bool voted_high;
+ bool voted_change;
};
static int
@@ -33,52 +33,54 @@ gpio_shared_proxy_set_unlocked(struct gpio_shared_proxy_data *proxy, int value)
lockdep_assert_held(&shared_desc->mutex);
- if (value) {
- /* User wants to set value to high. */
- if (proxy->voted_high)
- /* Already voted for high, nothing to do. */
+ if (value != shared_desc->def_val) {
+ /* User wants to vote for a value change. */
+ if (proxy->voted_change)
+ /* Already voted for a change, nothing to do. */
goto out;
- /* Haven't voted for high yet. */
- if (!shared_desc->highcnt) {
+ /* Haven't voted for a value change yet. */
+ if (!shared_desc->votecnt) {
/*
- * Current value is low, need to actually set value
- * to high.
+ * Current value is default, need to actually set value
+ * to the opposite.
*/
- ret = gpiod_set_value_cansleep(desc, 1);
+ ret = gpiod_set_value_cansleep(desc, value);
if (ret)
goto out;
}
- shared_desc->highcnt++;
- proxy->voted_high = true;
+ shared_desc->votecnt++;
+ proxy->voted_change = true;
goto out;
}
- /* Desired value is low. */
- if (!proxy->voted_high)
- /* We didn't vote for high, nothing to do. */
+ /* Desired value is the default. */
+ if (!proxy->voted_change)
+ /* We didn't vote for change previously, nothing to do. */
goto out;
- /* We previously voted for high. */
- if (shared_desc->highcnt == 1) {
- /* This is the last remaining vote for high, set value to low. */
- ret = gpiod_set_value_cansleep(desc, 0);
+ /* We previously voted for change. */
+ if (shared_desc->votecnt == 1) {
+ /* This is the last remaining vote for change, set value to default. */
+ ret = gpiod_set_value_cansleep(desc, shared_desc->def_val);
if (ret)
goto out;
}
- shared_desc->highcnt--;
- proxy->voted_high = false;
+ shared_desc->votecnt--;
+ proxy->voted_change = false;
out:
- if (shared_desc->highcnt)
+ if (shared_desc->votecnt)
dev_dbg(proxy->dev,
- "Voted for value '%s', effective value is 'high', number of votes for 'high': %u\n",
- str_high_low(value), shared_desc->highcnt);
+ "Voted for value '%s', effective value is '%s', number of votes: %u\n",
+ str_high_low(value), str_high_low(!shared_desc->def_val),
+ shared_desc->votecnt);
else
- dev_dbg(proxy->dev, "Voted for value 'low', effective value is 'low'\n");
+ dev_dbg(proxy->dev, "Voted for value '%s', effective value is '%s'\n",
+ str_high_low(value), str_high_low(shared_desc->def_val));
return ret;
}
@@ -106,8 +108,8 @@ static void gpio_shared_proxy_free(struct gpio_chip *gc, unsigned int offset)
guard(mutex)(&shared_desc->mutex);
- if (proxy->voted_high) {
- ret = gpio_shared_proxy_set_unlocked(proxy, 0);
+ if (proxy->voted_change) {
+ ret = gpio_shared_proxy_set_unlocked(proxy, shared_desc->def_val);
if (ret)
dev_err(proxy->dev,
"Failed to unset the shared GPIO value on release: %d\n", ret);
@@ -196,13 +198,9 @@ static int gpio_shared_proxy_direction_output(struct gpio_chip *gc,
if (ret)
return ret;
- if (value) {
- proxy->voted_high = true;
- shared_desc->highcnt = 1;
- } else {
- proxy->voted_high = false;
- shared_desc->highcnt = 0;
- }
+ shared_desc->def_val = value;
+ shared_desc->votecnt = 0;
+ proxy->voted_change = false;
return 0;
}
diff --git a/drivers/gpio/gpiolib-shared.h b/drivers/gpio/gpiolib-shared.h
index bbdc0ab7b647..618756f6c6aa 100644
--- a/drivers/gpio/gpiolib-shared.h
+++ b/drivers/gpio/gpiolib-shared.h
@@ -41,7 +41,8 @@ struct gpio_shared_desc {
struct gpio_desc *desc;
unsigned long cfg;
unsigned int usecnt;
- unsigned int highcnt;
+ unsigned int votecnt;
+ int def_val;
struct mutex mutex; /* serializes all proxy operations on this descriptor */
};
diff --git a/tools/testing/selftests/gpio/.gitignore b/tools/testing/selftests/gpio/.gitignore
index ededb077a3a6..16f74de479f1 100644
--- a/tools/testing/selftests/gpio/.gitignore
+++ b/tools/testing/selftests/gpio/.gitignore
@@ -2,3 +2,4 @@
gpio-mockup-cdev
gpio-chip-info
gpio-line-name
+gpio-cdev-uaf