[PATCH v3 12/14] gpio: regmap: Add optional runtime PM support
Janani Sunil <[email protected]>
| Newsgroups | org.kernel.vger.linux-iio,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-doc,org.kernel.vger.linux-gpio,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
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. Signed-off-by: Janani Sunil <[email protected]> --- drivers/gpio/gpio-regmap.c | 102 ++++++++++++++++++++++++++++++++++++-------- include/linux/gpio/regmap.h | 2 + 2 files changed, 87 insertions(+), 17 deletions(-) diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c index a5188f8569e3..4d5d92c15679 100644 --- a/drivers/gpio/gpio-regmap.c +++ b/drivers/gpio/gpio-regmap.c @@ -10,6 +10,7 @@ #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> @@ -31,6 +32,7 @@ struct gpio_regmap { unsigned int reg_clr_base; unsigned int reg_dir_in_base; unsigned int reg_dir_out_base; + struct device *pm_dev; unsigned long *fixed_direction_mask; unsigned long *fixed_direction_output; @@ -67,6 +69,22 @@ 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_resume_and_get(gpio->pm_dev); +} + +static void gpio_regmap_runtime_put(struct gpio_regmap *gpio) +{ + if (!gpio->pm_dev) + return; + + pm_runtime_put_autosuspend(gpio->pm_dev); +} + static int gpio_regmap_get(struct gpio_chip *chip, unsigned int offset) { struct gpio_regmap *gpio = gpiochip_get_data(chip); @@ -79,20 +97,28 @@ 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, base, offset, ®, &mask); + ret = gpio_regmap_runtime_get(gpio); if (ret) return ret; + ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); + if (ret) + goto out_pm_put; + /* ensure we don't spoil any register cache with pin input values */ if (gpio->reg_dat_base == gpio->reg_set_base) { ret = regmap_read_bypassed(gpio->regmap, reg, &val); if (ret) - return ret; + goto out_pm_put; - return !!(val & mask); + ret = !!(val & mask); + } else { + ret = regmap_test_bits(gpio->regmap, reg, mask); } - return regmap_test_bits(gpio->regmap, reg, mask); +out_pm_put: + gpio_regmap_runtime_put(gpio); + return ret; } static int gpio_regmap_set(struct gpio_chip *chip, unsigned int offset, @@ -103,10 +129,14 @@ 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, base, offset, ®, &mask); + ret = gpio_regmap_runtime_get(gpio); if (ret) return ret; + ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); + if (ret) + goto out_pm_put; + if (val) mask_val = mask; else @@ -118,6 +148,8 @@ static int gpio_regmap_set(struct gpio_chip *chip, unsigned int offset, else ret = regmap_update_bits(gpio->regmap, reg, mask, mask_val); +out_pm_put: + gpio_regmap_runtime_put(gpio); return ret; } @@ -128,6 +160,10 @@ static int gpio_regmap_set_with_clear(struct gpio_chip *chip, unsigned int base, reg, mask; int ret; + ret = gpio_regmap_runtime_get(gpio); + if (ret) + return ret; + if (val) base = gpio_regmap_addr(gpio->reg_set_base); else @@ -135,9 +171,13 @@ static int gpio_regmap_set_with_clear(struct gpio_chip *chip, ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); if (ret) - return ret; + goto out_pm_put; - return regmap_write(gpio->regmap, reg, mask); + ret = regmap_write(gpio->regmap, reg, mask); + +out_pm_put: + gpio_regmap_runtime_put(gpio); + return ret; } static bool gpio_regmap_fixed_direction(struct gpio_regmap *gpio, @@ -183,18 +223,26 @@ static int gpio_regmap_get_direction(struct gpio_chip *chip, return -ENOTSUPP; } - ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); + ret = gpio_regmap_runtime_get(gpio); if (ret) return ret; + ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); + if (ret) + goto out_pm_put; + ret = regmap_test_bits(gpio->regmap, reg, mask); if (ret < 0) - return ret; + goto out_pm_put; if (ret ^ invert) - return GPIO_LINE_DIRECTION_OUT; + ret = GPIO_LINE_DIRECTION_OUT; else - return GPIO_LINE_DIRECTION_IN; + ret = GPIO_LINE_DIRECTION_IN; + +out_pm_put: + gpio_regmap_runtime_put(gpio); + return ret; } static int gpio_regmap_try_direction_fixed(struct gpio_regmap *gpio, @@ -237,16 +285,24 @@ static int gpio_regmap_set_direction(struct gpio_chip *chip, return -ENOTSUPP; } - ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); + ret = gpio_regmap_runtime_get(gpio); if (ret) return ret; + ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); + if (ret) + goto out_pm_put; + if (invert) val = output ? 0 : mask; else val = output ? mask : 0; - return regmap_update_bits(gpio->regmap, reg, mask, val); + ret = regmap_update_bits(gpio->regmap, reg, mask, val); + +out_pm_put: + gpio_regmap_runtime_put(gpio); + return ret; } static int gpio_regmap_direction_input(struct gpio_chip *chip, @@ -261,6 +317,10 @@ static int gpio_regmap_direction_output(struct gpio_chip *chip, struct gpio_regmap *gpio = gpiochip_get_data(chip); int ret; + ret = gpio_regmap_runtime_get(gpio); + 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 @@ -269,12 +329,18 @@ static int gpio_regmap_direction_output(struct gpio_chip *chip, if (gpio_regmap_fixed_direction(gpio, offset)) { ret = gpio_regmap_try_direction_fixed(gpio, offset, true); if (ret) - return ret; + goto out_pm_put; } - gpio_regmap_set(chip, offset, value); + ret = gpio_regmap_set(chip, offset, value); + if (ret) + goto out_pm_put; - return gpio_regmap_set_direction(chip, offset, true); + ret = gpio_regmap_set_direction(chip, offset, true); + +out_pm_put: + gpio_regmap_runtime_put(gpio); + return ret; } void *gpio_regmap_get_drvdata(struct gpio_regmap *gpio) @@ -324,6 +390,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; @@ -331,7 +398,8 @@ 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 06255756710d..aae32a569914 100644 --- a/include/linux/gpio/regmap.h +++ b/include/linux/gpio/regmap.h @@ -29,6 +29,7 @@ struct regmap; * @reg_clr_base: (Optional) clear register base address * @reg_dir_in_base: (Optional) in setting register base address * @reg_dir_out_base: (Optional) out setting register base address + * @pm_dev: (Optional) Device to use for runtime power management. * @reg_stride: (Optional) May be set if the registers (of the * same type, dat, set, etc) are not consecutive. * @ngpio_per_reg: (Optional) Number of GPIOs per register @@ -92,6 +93,7 @@ struct gpio_regmap_config { unsigned int reg_clr_base; unsigned int reg_dir_in_base; unsigned int reg_dir_out_base; + struct device *pm_dev; int reg_stride; int ngpio_per_reg; struct irq_domain *irq_domain; -- 2.43.0