diff options
| author | Janani Sunil <janani.sunil@analog.com> | 2026-09-10 19:29:11 +0200 |
|---|---|---|
| committer | Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com> | 2026-09-16 11:08:26 +0200 |
| commit | 73748b4ec54da0323bb2ec62b483ea3f60ff657f (patch) | |
| tree | 83c405190f61468358c547fbf689bee6e8a7d22b | |
| parent | a4319947b59145b95353aabed4db879e9448ea50 (diff) | |
| download | linux-next-73748b4ec54da0323bb2ec62b483ea3f60ff657f.tar.gz linux-next-73748b4ec54da0323bb2ec62b483ea3f60ff657f.zip | |
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 <andriy.shevchenko@intel.com>
Reviewed-by: Michael Walle <mwalle@kernel.org>
Signed-off-by: Janani Sunil <janani.sunil@analog.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Link: https://patch.msgid.link/20260910-ad7768-gpio-v8-2-6aaaa15adeb3@analog.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
| -rw-r--r-- | drivers/gpio/gpio-regmap.c | 80 | ||||
| -rw-r--r-- | 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 <linux/bits.h> +#include <linux/cleanup.h> #include <linux/compiler_attributes.h> #include <linux/device.h> #include <linux/err.h> #include <linux/io.h> #include <linux/module.h> +#include <linux/pm_runtime.h> #include <linux/regmap.h> #include <linux/slab.h> #include <linux/types.h> @@ -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; |
