mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-16 17:57:38 -04:00
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:
@@ -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 = {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
};
|
||||
|
||||
|
||||
1
tools/testing/selftests/gpio/.gitignore
vendored
1
tools/testing/selftests/gpio/.gitignore
vendored
@@ -2,3 +2,4 @@
|
||||
gpio-mockup-cdev
|
||||
gpio-chip-info
|
||||
gpio-line-name
|
||||
gpio-cdev-uaf
|
||||
|
||||
Reference in New Issue
Block a user