[PATCH v5 2/2] pinctrl: qcom: lpass-lpi: Switch to PM clock framework for runtime PM
From: Ajay Kumar Nandam
Date: Wed May 13 2026 - 10:19:12 EST
Convert the LPASS LPI pinctrl driver to use the PM clock framework for
runtime power management.
This allows the LPASS LPI pinctrl driver to drop clock votes when idle,
improves power efficiency on platforms using LPASS LPI island mode, and
aligns the driver with common runtime PM patterns used across Qualcomm
LPASS subsystems.
Guard GPIO register read/write helpers and slew-rate register programming
with synchronous runtime PM calls so the device is active during MMIO
operations whenever autosuspend is enabled.
Signed-off-by: Ajay Kumar Nandam <ajay.nandam@xxxxxxxxxxxxxxxx>
---
drivers/pinctrl/qcom/pinctrl-lpass-lpi.c | 114 +++++++++++-------
.../pinctrl/qcom/pinctrl-sc7280-lpass-lpi.c | 19 ++-
2 files changed, 86 insertions(+), 47 deletions(-)
diff --git a/drivers/pinctrl/qcom/pinctrl-lpass-lpi.c b/drivers/pinctrl/qcom/pinctrl-lpass-lpi.c
index 15ced5027579..c8c08f81191e 100644
--- a/drivers/pinctrl/qcom/pinctrl-lpass-lpi.c
+++ b/drivers/pinctrl/qcom/pinctrl-lpass-lpi.c
@@ -15,6 +15,9 @@
#include <linux/pinctrl/pinconf-generic.h>
#include <linux/pinctrl/pinconf.h>
#include <linux/pinctrl/pinmux.h>
+#include <linux/cleanup.h>
+#include <linux/pm_clock.h>
+#include <linux/pm_runtime.h>
#include "../pinctrl-utils.h"
@@ -22,7 +25,6 @@
#define MAX_NR_GPIO 32
#define GPIO_FUNC 0
-#define MAX_LPI_NUM_CLKS 2
struct lpi_pinctrl {
struct device *dev;
@@ -31,7 +33,6 @@ struct lpi_pinctrl {
struct pinctrl_desc desc;
char __iomem *tlmm_base;
char __iomem *slew_base;
- struct clk_bulk_data clks[MAX_LPI_NUM_CLKS];
/* Protects from concurrent register updates */
struct mutex lock;
DECLARE_BITMAP(ever_gpio, MAX_NR_GPIO);
@@ -39,31 +40,41 @@ struct lpi_pinctrl {
};
static int lpi_gpio_read(struct lpi_pinctrl *state, unsigned int pin,
- unsigned int addr)
+ unsigned int addr, u32 *val)
{
u32 pin_offset;
+ int ret;
if (state->data->flags & LPI_FLAG_USE_PREDEFINED_PIN_OFFSET)
pin_offset = state->data->groups[pin].pin_offset;
else
pin_offset = LPI_TLMM_REG_OFFSET * pin;
- return ioread32(state->tlmm_base + pin_offset + addr);
+ ret = pm_runtime_resume_and_get(state->dev);
+ if (ret < 0)
+ return ret;
+
+ *val = ioread32(state->tlmm_base + pin_offset + addr);
+ return pm_runtime_put_autosuspend(state->dev);
}
static int lpi_gpio_write(struct lpi_pinctrl *state, unsigned int pin,
unsigned int addr, unsigned int val)
{
u32 pin_offset;
+ int ret;
if (state->data->flags & LPI_FLAG_USE_PREDEFINED_PIN_OFFSET)
pin_offset = state->data->groups[pin].pin_offset;
else
pin_offset = LPI_TLMM_REG_OFFSET * pin;
- iowrite32(val, state->tlmm_base + pin_offset + addr);
+ ret = pm_runtime_resume_and_get(state->dev);
+ if (ret < 0)
+ return ret;
- return 0;
+ iowrite32(val, state->tlmm_base + pin_offset + addr);
+ return pm_runtime_put_autosuspend(state->dev);
}
static const struct pinctrl_ops lpi_gpio_pinctrl_ops = {
@@ -107,8 +118,8 @@ static int lpi_gpio_set_mux(struct pinctrl_dev *pctldev, unsigned int function,
{
struct lpi_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev);
const struct lpi_pingroup *g = &pctrl->data->groups[group];
- u32 val;
- int i, pin = g->pin;
+ u32 val, io_val;
+ int i, pin = g->pin, ret;
for (i = 0; i < g->nfuncs; i++) {
if (g->funcs[i] == function)
@@ -118,8 +129,10 @@ static int lpi_gpio_set_mux(struct pinctrl_dev *pctldev, unsigned int function,
if (WARN_ON(i == g->nfuncs))
return -EINVAL;
- mutex_lock(&pctrl->lock);
- val = lpi_gpio_read(pctrl, pin, LPI_GPIO_CFG_REG);
+ guard(mutex)(&pctrl->lock);
+ ret = lpi_gpio_read(pctrl, pin, LPI_GPIO_CFG_REG, &val);
+ if (ret)
+ return ret;
/*
* If this is the first time muxing to GPIO and the direction is
@@ -129,24 +142,27 @@ static int lpi_gpio_set_mux(struct pinctrl_dev *pctldev, unsigned int function,
*/
if (i == GPIO_FUNC && (val & LPI_GPIO_OE_MASK) &&
!test_and_set_bit(group, pctrl->ever_gpio)) {
- u32 io_val = lpi_gpio_read(pctrl, group, LPI_GPIO_VALUE_REG);
+ ret = lpi_gpio_read(pctrl, group, LPI_GPIO_VALUE_REG, &io_val);
+ if (ret)
+ return ret;
if (io_val & LPI_GPIO_VALUE_IN_MASK) {
if (!(io_val & LPI_GPIO_VALUE_OUT_MASK))
- lpi_gpio_write(pctrl, group, LPI_GPIO_VALUE_REG,
- io_val | LPI_GPIO_VALUE_OUT_MASK);
+ ret = lpi_gpio_write(pctrl, group,
+ LPI_GPIO_VALUE_REG,
+ io_val | LPI_GPIO_VALUE_OUT_MASK);
} else {
if (io_val & LPI_GPIO_VALUE_OUT_MASK)
- lpi_gpio_write(pctrl, group, LPI_GPIO_VALUE_REG,
- io_val & ~LPI_GPIO_VALUE_OUT_MASK);
+ ret = lpi_gpio_write(pctrl, group,
+ LPI_GPIO_VALUE_REG,
+ io_val & ~LPI_GPIO_VALUE_OUT_MASK);
}
+ if (ret)
+ return ret;
}
u32p_replace_bits(&val, i, LPI_GPIO_FUNCTION_MASK);
- lpi_gpio_write(pctrl, pin, LPI_GPIO_CFG_REG, val);
- mutex_unlock(&pctrl->lock);
-
- return 0;
+ return lpi_gpio_write(pctrl, pin, LPI_GPIO_CFG_REG, val);
}
static const struct pinmux_ops lpi_gpio_pinmux_ops = {
@@ -162,11 +178,15 @@ static int lpi_config_get(struct pinctrl_dev *pctldev,
unsigned int param = pinconf_to_config_param(*config);
struct lpi_pinctrl *state = dev_get_drvdata(pctldev->dev);
unsigned int arg = 0;
+ int ret;
int is_out;
int pull;
u32 ctl_reg;
- ctl_reg = lpi_gpio_read(state, pin, LPI_GPIO_CFG_REG);
+ ret = lpi_gpio_read(state, pin, LPI_GPIO_CFG_REG, &ctl_reg);
+ if (ret)
+ return ret;
+
is_out = ctl_reg & LPI_GPIO_OE_MASK;
pull = FIELD_GET(LPI_GPIO_PULL_MASK, ctl_reg);
@@ -206,7 +226,7 @@ static int lpi_config_set_slew_rate(struct lpi_pinctrl *pctrl,
{
unsigned long sval;
void __iomem *reg;
- int slew_offset;
+ int slew_offset, ret;
if (slew > LPI_SLEW_RATE_MAX) {
dev_err(pctrl->dev, "invalid slew rate %u for pin: %d\n",
@@ -225,6 +245,10 @@ static int lpi_config_set_slew_rate(struct lpi_pinctrl *pctrl,
else
reg = pctrl->slew_base + LPI_SLEW_RATE_CTL_REG;
+ ret = pm_runtime_resume_and_get(pctrl->dev);
+ if (ret < 0)
+ return ret;
+
mutex_lock(&pctrl->lock);
sval = ioread32(reg);
@@ -233,8 +257,7 @@ static int lpi_config_set_slew_rate(struct lpi_pinctrl *pctrl,
iowrite32(sval, reg);
mutex_unlock(&pctrl->lock);
-
- return 0;
+ return pm_runtime_put_autosuspend(pctrl->dev);
}
static int lpi_config_set(struct pinctrl_dev *pctldev, unsigned int group,
@@ -291,21 +314,22 @@ static int lpi_config_set(struct pinctrl_dev *pctldev, unsigned int group,
*/
if (output_enabled) {
val = u32_encode_bits(value ? 1 : 0, LPI_GPIO_VALUE_OUT_MASK);
- lpi_gpio_write(pctrl, group, LPI_GPIO_VALUE_REG, val);
+ ret = lpi_gpio_write(pctrl, group, LPI_GPIO_VALUE_REG, val);
+ if (ret)
+ return ret;
}
- mutex_lock(&pctrl->lock);
- val = lpi_gpio_read(pctrl, group, LPI_GPIO_CFG_REG);
+ guard(mutex)(&pctrl->lock);
+ ret = lpi_gpio_read(pctrl, group, LPI_GPIO_CFG_REG, &val);
+ if (ret)
+ return ret;
u32p_replace_bits(&val, pullup, LPI_GPIO_PULL_MASK);
u32p_replace_bits(&val, LPI_GPIO_DS_TO_VAL(strength),
LPI_GPIO_OUT_STRENGTH_MASK);
u32p_replace_bits(&val, output_enabled, LPI_GPIO_OE_MASK);
- lpi_gpio_write(pctrl, group, LPI_GPIO_CFG_REG, val);
- mutex_unlock(&pctrl->lock);
-
- return 0;
+ return lpi_gpio_write(pctrl, group, LPI_GPIO_CFG_REG, val);
}
static const struct pinconf_ops lpi_gpio_pinconf_ops = {
@@ -354,9 +378,14 @@ static int lpi_gpio_direction_output(struct gpio_chip *chip,
static int lpi_gpio_get(struct gpio_chip *chip, unsigned int pin)
{
struct lpi_pinctrl *state = gpiochip_get_data(chip);
+ u32 val;
+ int ret;
- return lpi_gpio_read(state, pin, LPI_GPIO_VALUE_REG) &
- LPI_GPIO_VALUE_IN_MASK;
+ ret = lpi_gpio_read(state, pin, LPI_GPIO_VALUE_REG, &val);
+ if (ret)
+ return ret;
+
+ return val & LPI_GPIO_VALUE_IN_MASK;
}
static int lpi_gpio_set(struct gpio_chip *chip, unsigned int pin, int value)
@@ -399,7 +428,9 @@ static void lpi_gpio_dbg_show_one(struct seq_file *s,
pctldev = pctldev ? : state->ctrl;
pindesc = pctldev->desc->pins[offset];
- ctl_reg = lpi_gpio_read(state, offset, LPI_GPIO_CFG_REG);
+ if (lpi_gpio_read(state, offset, LPI_GPIO_CFG_REG, &ctl_reg))
+ return;
+
is_out = ctl_reg & LPI_GPIO_OE_MASK;
func = FIELD_GET(LPI_GPIO_FUNCTION_MASK, ctl_reg);
@@ -482,9 +513,6 @@ int lpi_pinctrl_probe(struct platform_device *pdev)
pctrl->data = data;
pctrl->dev = &pdev->dev;
- pctrl->clks[0].id = "core";
- pctrl->clks[1].id = "audio";
-
pctrl->tlmm_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(pctrl->tlmm_base))
return dev_err_probe(dev, PTR_ERR(pctrl->tlmm_base),
@@ -497,13 +525,19 @@ int lpi_pinctrl_probe(struct platform_device *pdev)
"Slew resource not provided\n");
}
- ret = devm_clk_bulk_get_optional(dev, MAX_LPI_NUM_CLKS, pctrl->clks);
+ ret = devm_pm_clk_create(dev);
if (ret)
return ret;
- ret = clk_bulk_prepare_enable(MAX_LPI_NUM_CLKS, pctrl->clks);
+ ret = of_pm_clk_add_clks(dev);
+ if (ret < 0 && ret != -ENODEV)
+ return ret;
+
+ pm_runtime_set_autosuspend_delay(dev, 100);
+ pm_runtime_use_autosuspend(dev);
+ ret = devm_pm_runtime_enable(dev);
if (ret)
- return dev_err_probe(dev, ret, "Can't enable clocks\n");
+ return ret;
pctrl->desc.pctlops = &lpi_gpio_pinctrl_ops;
pctrl->desc.pmxops = &lpi_gpio_pinmux_ops;
@@ -542,7 +576,6 @@ int lpi_pinctrl_probe(struct platform_device *pdev)
err_pinctrl:
mutex_destroy(&pctrl->lock);
- clk_bulk_disable_unprepare(MAX_LPI_NUM_CLKS, pctrl->clks);
return ret;
}
@@ -554,7 +587,6 @@ void lpi_pinctrl_remove(struct platform_device *pdev)
int i;
mutex_destroy(&pctrl->lock);
- clk_bulk_disable_unprepare(MAX_LPI_NUM_CLKS, pctrl->clks);
for (i = 0; i < pctrl->data->npins; i++)
pinctrl_generic_remove_group(pctrl->ctrl, i);
diff --git a/drivers/pinctrl/qcom/pinctrl-sc7280-lpass-lpi.c b/drivers/pinctrl/qcom/pinctrl-sc7280-lpass-lpi.c
index 750f410311a8..a61df10d46cb 100644
--- a/drivers/pinctrl/qcom/pinctrl-sc7280-lpass-lpi.c
+++ b/drivers/pinctrl/qcom/pinctrl-sc7280-lpass-lpi.c
@@ -7,6 +7,8 @@
#include <linux/gpio/driver.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/pm_clock.h>
+#include <linux/pm_runtime.h>
#include "pinctrl-lpass-lpi.h"
@@ -129,20 +131,25 @@ static const struct lpi_pinctrl_variant_data sc7280_lpi_data = {
static const struct of_device_id lpi_pinctrl_of_match[] = {
{
- .compatible = "qcom,sc7280-lpass-lpi-pinctrl",
- .data = &sc7280_lpi_data,
+ .compatible = "qcom,sc7280-lpass-lpi-pinctrl",
+ .data = &sc7280_lpi_data,
}, {
- .compatible = "qcom,sm8350-lpass-lpi-pinctrl",
- .data = &sc7280_lpi_data,
+ .compatible = "qcom,sm8350-lpass-lpi-pinctrl",
+ .data = &sc7280_lpi_data,
},
{ }
};
MODULE_DEVICE_TABLE(of, lpi_pinctrl_of_match);
+static const struct dev_pm_ops lpi_pinctrl_pm_ops = {
+ RUNTIME_PM_OPS(pm_clk_suspend, pm_clk_resume, NULL)
+};
+
static struct platform_driver lpi_pinctrl_driver = {
.driver = {
- .name = "qcom-sc7280-lpass-lpi-pinctrl",
- .of_match_table = lpi_pinctrl_of_match,
+ .name = "qcom-sc7280-lpass-lpi-pinctrl",
+ .of_match_table = lpi_pinctrl_of_match,
+ .pm = pm_ptr(&lpi_pinctrl_pm_ops),
},
.probe = lpi_pinctrl_probe,
.remove = lpi_pinctrl_remove,
--
2.34.1