summaryrefslogtreecommitdiff
path: root/drivers/regulator
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-08-07 15:19:44 +0100
committerMark Brown <broonie@kernel.org>2026-08-07 15:19:44 +0100
commit6ab838a01b12f9d101c26ff8cebbf4029a8c9e09 (patch)
tree6d525cc2d4557fb63a34f6f6f050c81c7ce766e0 /drivers/regulator
parent36c3ea84289913d16dd2ed783534193c957055b9 (diff)
parent216e8873bc80e9e9ab5bfe325a4f6d17bb3b8229 (diff)
regulator: qcom-rpmh: Support RPMH address reads and use it for rpmh-regulators
Kamal Wadhwa <kamal.wadhwa@oss.qualcomm.com> says: This patch series adds a new `rpmh_read()` API to allow reading RPMH addresses. Using this API enhances the RPMH regulator driver by adding readback of the voltage/bypass/mode settings as they have been applied by APPS during the bootloader stage, so regulator framework can get them via `get_mode`, `get_bypass` & `get_voltage_selector` callbacks during regulator registration. This is needed because currently regulator framework does a unnecessary write with `min-microvolt` DT setting for all the RPMH regulators during regulator registration, because the first time after boot the value is seen as -ENOTRECOVERABLE, as there is no option to read these regulator settings. With this change this unnecessary write can be avoided and regulator framework gets a sense of the initial state set during the bootloader stage for all regulator settings. NOTE - During discussion on the v2 series - PATCH 3/4, reviewer had inquired about possible need for the use of the sync_state() to handle the "multiple" client case - for maintaining the regulator settings till all the clients are probed. This case was not covered in my previous series and had originally planned to do that series separately. But after the discussion decided to merge the 2 series as it seemed this would be a better approach. But after working on sync_state change. I realized a basic issue with using sync_state() for regulators - that its per-driver and not per-regulator resource. But we needed a sync_state callback for each regulator separately. I had been experimenting with few ideas but seems its going to need more time for me to close on the equivalent solution that has per-regulator sync_state or something to that effect. So I thought to close on this series and attend to that separately. Link: https://patch.msgid.link/20260801-b4-read-rpmh-v5-v6-0-9fcb54928523@oss.qualcomm.com
Diffstat (limited to 'drivers/regulator')
-rw-r--r--drivers/regulator/qcom-rpmh-regulator.c128
1 files changed, 126 insertions, 2 deletions
diff --git a/drivers/regulator/qcom-rpmh-regulator.c b/drivers/regulator/qcom-rpmh-regulator.c
index 756a4201225e..250b2c6bb795 100644
--- a/drivers/regulator/qcom-rpmh-regulator.c
+++ b/drivers/regulator/qcom-rpmh-regulator.c
@@ -4,6 +4,7 @@
#define pr_fmt(fmt) "%s: " fmt, __func__
+#include <linux/bits.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/module.h>
@@ -61,8 +62,13 @@ static const struct resource_name_formats vreg_rsc_name_lookup[NUM_REGULATOR_TYP
};
#define RPMH_REGULATOR_REG_VRM_VOLTAGE 0x0
+#define RPMH_REGULATOR_VOLTAGE_MASK GENMASK(12, 0)
+
#define RPMH_REGULATOR_REG_ENABLE 0x4
+#define RPMH_REGULATOR_ENABLE_MASK BIT(0)
+
#define RPMH_REGULATOR_REG_VRM_MODE 0x8
+#define RPMH_REGULATOR_MODE_MASK GENMASK(2, 0)
#define PMIC4_LDO_MODE_RETENTION 4
#define PMIC4_LDO_MODE_LPM 5
@@ -104,13 +110,14 @@ static const struct resource_name_formats vreg_rsc_name_lookup[NUM_REGULATOR_TYP
* regulator
* @ops: Pointer to regulator ops callback structure
* @voltage_ranges: The possible ranges of voltages supported by this
- * PMIC regulator type
+ * PMIC regulator type
* @n_linear_ranges: Number of entries in voltage_ranges
* @n_voltages: The number of unique voltage set points defined
* by voltage_ranges
* @hpm_min_load_uA: Minimum load current in microamps that requires
* high power mode (HPM) operation. This is used
* for LDO hardware type regulators only.
+ * @pmic_bypass_mode: The PMIC bypass mode value.
* @pmic_mode_map: Array indexed by regulator framework mode
* containing PMIC hardware modes. Must be large
* enough to index all framework modes supported
@@ -125,6 +132,7 @@ struct rpmh_vreg_hw_data {
int n_linear_ranges;
int n_voltages;
int hpm_min_load_uA;
+ int pmic_bypass_mode;
const int *pmic_mode_map;
unsigned int (*of_map_mode)(unsigned int mode);
};
@@ -246,9 +254,34 @@ static int rpmh_regulator_vrm_set_voltage_sel(struct regulator_dev *rdev,
selector > vreg->voltage_selector);
}
+static int _rpmh_regulator_vrm_get_voltage(struct regulator_dev *rdev, int *uV)
+{
+ struct rpmh_vreg *vreg = rdev_get_drvdata(rdev);
+ struct tcs_cmd cmd = {
+ .addr = vreg->addr + RPMH_REGULATOR_REG_VRM_VOLTAGE,
+ };
+ int ret;
+
+ ret = rpmh_read(vreg->dev, &cmd);
+ if (!ret)
+ *uV = (cmd.data & RPMH_REGULATOR_VOLTAGE_MASK) * 1000;
+ else
+ dev_err(vreg->dev, "failed to read VOLTAGE ret = %d\n", ret);
+
+ return ret;
+}
+
static int rpmh_regulator_vrm_get_voltage_sel(struct regulator_dev *rdev)
{
struct rpmh_vreg *vreg = rdev_get_drvdata(rdev);
+ int ret, uV = 0;
+
+ if (vreg->voltage_selector < 0) {
+ ret = _rpmh_regulator_vrm_get_voltage(rdev, &uV);
+ if (!ret && uV != 0)
+ vreg->voltage_selector = regulator_map_voltage_linear_range(rdev,
+ uV, INT_MAX);
+ }
return vreg->voltage_selector;
}
@@ -311,7 +344,7 @@ static int rpmh_regulator_vrm_set_mode_bypass(struct rpmh_vreg *vreg,
return pmic_mode;
if (bypassed)
- cmd.data = PMIC4_BOB_MODE_PASS;
+ cmd.data = vreg->hw_data->pmic_bypass_mode;
else
cmd.data = pmic_mode;
@@ -334,6 +367,22 @@ static int rpmh_regulator_vrm_set_mode(struct regulator_dev *rdev,
return ret;
}
+static int rpmh_regulator_vrm_get_pmic_mode(struct rpmh_vreg *vreg, int *pmic_mode)
+{
+ struct tcs_cmd cmd = {
+ .addr = vreg->addr + RPMH_REGULATOR_REG_VRM_MODE,
+ };
+ int ret;
+
+ ret = rpmh_read(vreg->dev, &cmd);
+ if (!ret)
+ *pmic_mode = cmd.data & RPMH_REGULATOR_MODE_MASK;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
static unsigned int rpmh_regulator_vrm_get_mode(struct regulator_dev *rdev)
{
struct rpmh_vreg *vreg = rdev_get_drvdata(rdev);
@@ -538,6 +587,73 @@ static int rpmh_regulator_init_vreg(struct rpmh_vreg *vreg, struct device *dev,
return 0;
}
+static int rpmh_regulator_determine_initial_mode(struct rpmh_vreg *vreg)
+{
+ struct tcs_cmd cmd = {
+ .addr = vreg->addr + RPMH_REGULATOR_REG_ENABLE,
+ };
+ int ret, pmic_mode, mode;
+ int sts;
+
+ ret = rpmh_read(vreg->dev, &cmd);
+ if (ret) {
+ dev_err(vreg->dev, "failed to read ENABLE status ret = %d\n", ret);
+
+ return ret;
+ }
+
+ sts = cmd.data & RPMH_REGULATOR_ENABLE_MASK;
+ if (!sts)
+ return 0;
+
+ if (vreg->hw_data->regulator_type == XOB)
+ return 0;
+
+ ret = rpmh_regulator_vrm_get_pmic_mode(vreg, &pmic_mode);
+ if (ret < 0) {
+ vreg->mode = REGULATOR_MODE_INVALID;
+ dev_err(vreg->dev, "failed to read pmic_mode ret = %d\n", ret);
+
+ return ret;
+ }
+
+ /*
+ * NOTE: Since BOB4 BYPASS_MODE value = 0 we cannot confirm if that BOB
+ * regulator has been sent into bypass mode by bootloader or if bootloader
+ * just has not requested for any mode voting. Due this limitation, we
+ * must check if the read pmic_mode value is non-zero before comparing it
+ * to bypass mode value. This also is needed to avoid setting BYPASS status
+ * for LDOs which dont support bypass mode, and have the pmic_bypass_mode
+ * uninitialized value as zero in the vreg hw data. For such cases assume
+ * lowest mode, if pmic_mode is zero, to allow for mode voting.
+ */
+ if (!pmic_mode) {
+ for (mode = REGULATOR_MODE_STANDBY; mode > REGULATOR_MODE_INVALID; mode >>= 1) {
+ if (vreg->hw_data->pmic_mode_map[mode] >= 0) {
+ vreg->mode = mode;
+ break;
+ }
+ }
+
+ return 0;
+ }
+
+ if (vreg->hw_data->pmic_bypass_mode == pmic_mode) {
+ vreg->bypassed = true;
+
+ return 0;
+ }
+
+ for (mode = REGULATOR_MODE_STANDBY; mode > REGULATOR_MODE_INVALID; mode >>= 1) {
+ if (pmic_mode == vreg->hw_data->pmic_mode_map[mode]) {
+ vreg->mode = mode;
+ break;
+ }
+ }
+
+ return 0;
+}
+
static const int pmic_mode_map_pmic4_ldo[REGULATOR_MODE_STANDBY + 1] = {
[REGULATOR_MODE_INVALID] = -EINVAL,
[REGULATOR_MODE_STANDBY] = PMIC4_LDO_MODE_RETENTION,
@@ -767,6 +883,7 @@ static const struct rpmh_vreg_hw_data pmic4_bob = {
},
.n_linear_ranges = 1,
.n_voltages = 84,
+ .pmic_bypass_mode = PMIC4_BOB_MODE_PASS,
.pmic_mode_map = pmic_mode_map_pmic4_bob,
.of_map_mode = rpmh_regulator_pmic4_bob_of_map_mode,
};
@@ -975,6 +1092,7 @@ static const struct rpmh_vreg_hw_data pmic5_bob = {
},
.n_linear_ranges = 1,
.n_voltages = 32,
+ .pmic_bypass_mode = PMIC5_BOB_MODE_PASS,
.pmic_mode_map = pmic_mode_map_pmic5_bob,
.of_map_mode = rpmh_regulator_pmic4_bob_of_map_mode,
};
@@ -1834,6 +1952,12 @@ static int rpmh_regulator_probe(struct platform_device *pdev)
vreg_data);
if (ret < 0)
return ret;
+
+ ret = rpmh_regulator_determine_initial_mode(vreg);
+ if (ret < 0)
+ dev_err(dev, "failed to read initial mode for %s\n",
+ vreg->rdesc.name);
+
}
return 0;