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
This commit is contained in:
Linus Torvalds
2026-07-10 09:36:25 -07:00
6 changed files with 141 additions and 49 deletions

View File

@@ -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,30 +803,66 @@ 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);
/* Mask out interrupts */
dwapb_write(gpio, GPIO_INTMASK, ~ctx->wake_en);
}
}
}
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;
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);
return 0;
}
static int dwapb_gpio_resume_noirq(struct device *dev)
{
struct dwapb_gpio *gpio = dev_get_drvdata(dev);
int err;
if (gpio->clocks_on_for_wake)
return 0;
err = clk_bulk_prepare_enable(DWAPB_NR_CLOCKS, gpio->clks);
if (err)
dev_err(gpio->dev, "Cannot reenable APB/Debounce clocks\n");
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, err;
err = clk_bulk_prepare_enable(DWAPB_NR_CLOCKS, gpio->clks);
if (err) {
dev_err(gpio->dev, "Cannot reenable APB/Debounce clocks\n");
return err;
}
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 = {

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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 */
};

View File

@@ -2,3 +2,4 @@
gpio-mockup-cdev
gpio-chip-info
gpio-line-name
gpio-cdev-uaf