aboutsummaryrefslogtreecommitdiff
path: root/drivers/gpio/gpiolib.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpio/gpiolib.c')
-rw-r--r--drivers/gpio/gpiolib.c202
1 files changed, 155 insertions, 47 deletions
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 2b02655abb56..679ed764cb14 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -14,6 +14,7 @@
#include <linux/idr.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
+#include <linux/irqdesc.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/lockdep.h>
@@ -23,7 +24,6 @@
#include <linux/pinctrl/consumer.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
-#include <linux/spinlock.h>
#include <linux/srcu.h>
#include <linux/string.h>
@@ -713,6 +713,45 @@ bool gpiochip_line_is_valid(const struct gpio_chip *gc,
}
EXPORT_SYMBOL_GPL(gpiochip_line_is_valid);
+static void gpiod_free_irqs(struct gpio_desc *desc)
+{
+ int irq = gpiod_to_irq(desc);
+ struct irq_desc *irqd = irq_to_desc(irq);
+ void *cookie;
+
+ for (;;) {
+ /*
+ * Make sure the action doesn't go away while we're
+ * dereferencing it. Retrieve and store the cookie value.
+ * If the irq is freed after we release the lock, that's
+ * alright - the underlying maple tree lookup will return NULL
+ * and nothing will happen in free_irq().
+ */
+ scoped_guard(mutex, &irqd->request_mutex) {
+ if (!irq_desc_has_action(irqd))
+ return;
+
+ cookie = irqd->action->dev_id;
+ }
+
+ free_irq(irq, cookie);
+ }
+}
+
+/*
+ * The chip is going away but there may be users who had requested interrupts
+ * on its GPIO lines who have no idea about its removal and have no way of
+ * being notified about it. We need to free any interrupts still in use here or
+ * we'll leak memory and resources (like procfs files).
+ */
+static void gpiochip_free_remaining_irqs(struct gpio_chip *gc)
+{
+ struct gpio_desc *desc;
+
+ for_each_gpio_desc_with_flag(gc, desc, FLAG_USED_AS_IRQ)
+ gpiod_free_irqs(desc);
+}
+
static void gpiodev_release(struct device *dev)
{
struct gpio_device *gdev = to_gpio_device(dev);
@@ -986,10 +1025,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
}
}
- for (desc_index = 0; desc_index < gc->ngpio; desc_index++)
- gdev->descs[desc_index].gdev = gdev;
-
- BLOCKING_INIT_NOTIFIER_HEAD(&gdev->line_state_notifier);
+ ATOMIC_INIT_NOTIFIER_HEAD(&gdev->line_state_notifier);
BLOCKING_INIT_NOTIFIER_HEAD(&gdev->device_notifier);
ret = init_srcu_struct(&gdev->srcu);
@@ -1018,6 +1054,8 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
for (desc_index = 0; desc_index < gc->ngpio; desc_index++) {
struct gpio_desc *desc = &gdev->descs[desc_index];
+ desc->gdev = gdev;
+
if (gc->get_direction && gpiochip_line_is_valid(gc, desc_index)) {
assign_bit(FLAG_IS_OUT,
&desc->flags, !gc->get_direction(gc, desc_index));
@@ -1125,6 +1163,7 @@ void gpiochip_remove(struct gpio_chip *gc)
/* FIXME: should the legacy sysfs handling be moved to gpio_device? */
gpiochip_sysfs_unregister(gdev);
gpiochip_free_hogs(gc);
+ gpiochip_free_remaining_irqs(gc);
scoped_guard(mutex, &gpio_devices_lock)
list_del_rcu(&gdev->list);
@@ -1183,11 +1222,6 @@ struct gpio_device *gpio_device_find(const void *data,
struct gpio_device *gdev;
struct gpio_chip *gc;
- /*
- * Not yet but in the future the spinlock below will become a mutex.
- * Annotate this function before anyone tries to use it in interrupt
- * context like it happened with gpiochip_find().
- */
might_sleep();
guard(srcu)(&gpio_devices_srcu);
@@ -2392,8 +2426,10 @@ static void gpiod_free_commit(struct gpio_desc *desc)
#endif
desc_set_label(desc, NULL);
WRITE_ONCE(desc->flags, flags);
-
- gpiod_line_state_notify(desc, GPIOLINE_CHANGED_RELEASED);
+#ifdef CONFIG_GPIO_CDEV
+ WRITE_ONCE(desc->debounce_period_us, 0);
+#endif
+ gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_RELEASED);
}
}
@@ -2492,6 +2528,8 @@ struct gpio_desc *gpiochip_request_own_desc(struct gpio_chip *gc,
return ERR_PTR(ret);
}
+ gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_REQUESTED);
+
return desc;
}
EXPORT_SYMBOL_GPL(gpiochip_request_own_desc);
@@ -2520,13 +2558,28 @@ EXPORT_SYMBOL_GPL(gpiochip_free_own_desc);
* rely on gpio_request() having been called beforehand.
*/
-static int gpio_do_set_config(struct gpio_chip *gc, unsigned int offset,
- unsigned long config)
+int gpio_do_set_config(struct gpio_desc *desc, unsigned long config)
{
- if (!gc->set_config)
+ int ret;
+
+ CLASS(gpio_chip_guard, guard)(desc);
+ if (!guard.gc)
+ return -ENODEV;
+
+ if (!guard.gc->set_config)
return -ENOTSUPP;
- return gc->set_config(gc, offset, config);
+ ret = guard.gc->set_config(guard.gc, gpio_chip_hwgpio(desc), config);
+#ifdef CONFIG_GPIO_CDEV
+ /*
+ * Special case - if we're setting debounce period, we need to store
+ * it in the descriptor in case user-space wants to know it.
+ */
+ if (!ret && pinconf_to_config_param(config) == PIN_CONFIG_INPUT_DEBOUNCE)
+ WRITE_ONCE(desc->debounce_period_us,
+ pinconf_to_config_argument(config));
+#endif
+ return ret;
}
static int gpio_set_config_with_argument(struct gpio_desc *desc,
@@ -2535,12 +2588,8 @@ static int gpio_set_config_with_argument(struct gpio_desc *desc,
{
unsigned long config;
- CLASS(gpio_chip_guard, guard)(desc);
- if (!guard.gc)
- return -ENODEV;
-
config = pinconf_to_config_packed(mode, argument);
- return gpio_do_set_config(guard.gc, gpio_chip_hwgpio(desc), config);
+ return gpio_do_set_config(desc, config);
}
static int gpio_set_config_with_argument_optional(struct gpio_desc *desc,
@@ -2615,9 +2664,15 @@ static int gpio_set_bias(struct gpio_desc *desc)
*/
int gpio_set_debounce_timeout(struct gpio_desc *desc, unsigned int debounce)
{
- return gpio_set_config_with_argument_optional(desc,
- PIN_CONFIG_INPUT_DEBOUNCE,
- debounce);
+ int ret;
+
+ ret = gpio_set_config_with_argument_optional(desc,
+ PIN_CONFIG_INPUT_DEBOUNCE,
+ debounce);
+ if (!ret)
+ gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_CONFIG);
+
+ return ret;
}
/**
@@ -2632,10 +2687,22 @@ int gpio_set_debounce_timeout(struct gpio_desc *desc, unsigned int debounce)
*/
int gpiod_direction_input(struct gpio_desc *desc)
{
- int ret = 0;
+ int ret;
VALIDATE_DESC(desc);
+ ret = gpiod_direction_input_nonotify(desc);
+ if (ret == 0)
+ gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_CONFIG);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gpiod_direction_input);
+
+int gpiod_direction_input_nonotify(struct gpio_desc *desc)
+{
+ int ret = 0;
+
CLASS(gpio_chip_guard, guard)(desc);
if (!guard.gc)
return -ENODEV;
@@ -2678,7 +2745,6 @@ int gpiod_direction_input(struct gpio_desc *desc)
return ret;
}
-EXPORT_SYMBOL_GPL(gpiod_direction_input);
static int gpiod_direction_output_raw_commit(struct gpio_desc *desc, int value)
{
@@ -2740,8 +2806,15 @@ static int gpiod_direction_output_raw_commit(struct gpio_desc *desc, int value)
*/
int gpiod_direction_output_raw(struct gpio_desc *desc, int value)
{
+ int ret;
+
VALIDATE_DESC(desc);
- return gpiod_direction_output_raw_commit(desc, value);
+
+ ret = gpiod_direction_output_raw_commit(desc, value);
+ if (ret == 0)
+ gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_CONFIG);
+
+ return ret;
}
EXPORT_SYMBOL_GPL(gpiod_direction_output_raw);
@@ -2760,11 +2833,23 @@ EXPORT_SYMBOL_GPL(gpiod_direction_output_raw);
*/
int gpiod_direction_output(struct gpio_desc *desc, int value)
{
- unsigned long flags;
int ret;
VALIDATE_DESC(desc);
+ ret = gpiod_direction_output_nonotify(desc, value);
+ if (ret == 0)
+ gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_CONFIG);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gpiod_direction_output);
+
+int gpiod_direction_output_nonotify(struct gpio_desc *desc, int value)
+{
+ unsigned long flags;
+ int ret;
+
flags = READ_ONCE(desc->flags);
if (test_bit(FLAG_ACTIVE_LOW, &flags))
@@ -2788,7 +2873,7 @@ int gpiod_direction_output(struct gpio_desc *desc, int value)
goto set_output_value;
/* Emulate open drain by not actively driving the line high */
if (value) {
- ret = gpiod_direction_input(desc);
+ ret = gpiod_direction_input_nonotify(desc);
goto set_output_flag;
}
} else if (test_bit(FLAG_OPEN_SOURCE, &flags)) {
@@ -2797,7 +2882,7 @@ int gpiod_direction_output(struct gpio_desc *desc, int value)
goto set_output_value;
/* Emulate open source by not actively driving the line low */
if (!value) {
- ret = gpiod_direction_input(desc);
+ ret = gpiod_direction_input_nonotify(desc);
goto set_output_flag;
}
} else {
@@ -2821,7 +2906,6 @@ set_output_flag:
set_bit(FLAG_IS_OUT, &desc->flags);
return ret;
}
-EXPORT_SYMBOL_GPL(gpiod_direction_output);
/**
* gpiod_enable_hw_timestamp_ns - Enable hardware timestamp in nanoseconds.
@@ -2900,13 +2984,30 @@ EXPORT_SYMBOL_GPL(gpiod_disable_hw_timestamp_ns);
*/
int gpiod_set_config(struct gpio_desc *desc, unsigned long config)
{
+ int ret;
+
VALIDATE_DESC(desc);
- CLASS(gpio_chip_guard, guard)(desc);
- if (!guard.gc)
- return -ENODEV;
+ ret = gpio_do_set_config(desc, config);
+ if (!ret) {
+ /* These are the only options we notify the userspace about. */
+ switch (pinconf_to_config_param(config)) {
+ case PIN_CONFIG_BIAS_DISABLE:
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ case PIN_CONFIG_BIAS_PULL_UP:
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ case PIN_CONFIG_DRIVE_OPEN_SOURCE:
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ case PIN_CONFIG_INPUT_DEBOUNCE:
+ gpiod_line_state_notify(desc,
+ GPIO_V2_LINE_CHANGED_CONFIG);
+ break;
+ default:
+ break;
+ }
+ }
- return gpio_do_set_config(guard.gc, gpio_chip_hwgpio(desc), config);
+ return ret;
}
EXPORT_SYMBOL_GPL(gpiod_set_config);
@@ -2973,6 +3074,7 @@ void gpiod_toggle_active_low(struct gpio_desc *desc)
{
VALIDATE_DESC_VOID(desc);
change_bit(FLAG_ACTIVE_LOW, &desc->flags);
+ gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_CONFIG);
}
EXPORT_SYMBOL_GPL(gpiod_toggle_active_low);
@@ -3617,9 +3719,15 @@ EXPORT_SYMBOL_GPL(gpiod_cansleep);
*/
int gpiod_set_consumer_name(struct gpio_desc *desc, const char *name)
{
+ int ret;
+
VALIDATE_DESC(desc);
- return desc_set_label(desc, name);
+ ret = desc_set_label(desc, name);
+ if (ret == 0)
+ gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_CONFIG);
+
+ return ret;
}
EXPORT_SYMBOL_GPL(gpiod_set_consumer_name);
@@ -4047,8 +4155,8 @@ EXPORT_SYMBOL_GPL(gpiod_set_array_value_cansleep);
void gpiod_line_state_notify(struct gpio_desc *desc, unsigned long action)
{
- blocking_notifier_call_chain(&desc->gdev->line_state_notifier,
- action, desc);
+ atomic_notifier_call_chain(&desc->gdev->line_state_notifier,
+ action, desc);
}
/**
@@ -4325,7 +4433,7 @@ struct gpio_desc *gpiod_find_and_request(struct device *consumer,
return ERR_PTR(ret);
}
- gpiod_line_state_notify(desc, GPIOLINE_CHANGED_REQUESTED);
+ gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_REQUESTED);
return desc;
}
@@ -4497,10 +4605,10 @@ int gpiod_configure_flags(struct gpio_desc *desc, const char *con_id,
/* Process flags */
if (dflags & GPIOD_FLAGS_BIT_DIR_OUT)
- ret = gpiod_direction_output(desc,
+ ret = gpiod_direction_output_nonotify(desc,
!!(dflags & GPIOD_FLAGS_BIT_DIR_VAL));
else
- ret = gpiod_direction_input(desc);
+ ret = gpiod_direction_input_nonotify(desc);
return ret;
}
@@ -4967,19 +5075,19 @@ static int gpiolib_seq_show(struct seq_file *s, void *v)
struct gpio_chip *gc;
struct device *parent;
+ if (priv->newline)
+ seq_putc(s, '\n');
+
guard(srcu)(&gdev->srcu);
gc = srcu_dereference(gdev->chip, &gdev->srcu);
if (!gc) {
- seq_printf(s, "%s%s: (dangling chip)\n",
- priv->newline ? "\n" : "",
- dev_name(&gdev->dev));
+ seq_printf(s, "%s: (dangling chip)\n", dev_name(&gdev->dev));
return 0;
}
- seq_printf(s, "%s%s: GPIOs %u-%u", priv->newline ? "\n" : "",
- dev_name(&gdev->dev),
- gdev->base, gdev->base + gdev->ngpio - 1);
+ seq_printf(s, "%s: GPIOs %u-%u", dev_name(&gdev->dev), gdev->base,
+ gdev->base + gdev->ngpio - 1);
parent = gc->parent;
if (parent)
seq_printf(s, ", parent: %s/%s",