diff mbox

soc: qcom: Add support for SAW2 regulators

Message ID 1450455298-1987-1-git-send-email-georgi.djakov@linaro.org
State New
Headers show

Commit Message

Georgi Djakov Dec. 18, 2015, 4:14 p.m. UTC
The SAW2 (Subsystem Power Manager and Adaptive Voltage Scaling Wrapper)
is part of the SPM subsystem. It is a hardware block found on some of the
Qualcomm chipsets, which regulates the power to the CPU cores. Add some
basic support for it, so that we can do dynamic voltage scaling.

Signed-off-by: Georgi Djakov <georgi.djakov@linaro.org>

---
 drivers/soc/qcom/spm.c |  149 +++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 148 insertions(+), 1 deletion(-)

--
To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Comments

Mark Brown Dec. 18, 2015, 5:22 p.m. UTC | #1
On Fri, Dec 18, 2015 at 06:14:58PM +0200, Georgi Djakov wrote:
> The SAW2 (Subsystem Power Manager and Adaptive Voltage Scaling Wrapper)

> is part of the SPM subsystem. It is a hardware block found on some of the

> Qualcomm chipsets, which regulates the power to the CPU cores. Add some

> basic support for it, so that we can do dynamic voltage scaling.

> 

> Signed-off-by: Georgi Djakov <georgi.djakov@linaro.org>

> ---

>  drivers/soc/qcom/spm.c |  149 +++++++++++++++++++++++++++++++++++++++++++++++-


Please don't implement regualtors outside of the regulator driver
directory unless there is a really strong reason to do so, it makes it
much easier to maintain the subsystem and keep track of what's going on.
Georgi Djakov Jan. 7, 2016, 1:47 p.m. UTC | #2
On 12/18/2015 07:22 PM, Mark Brown wrote:
> On Fri, Dec 18, 2015 at 06:14:58PM +0200, Georgi Djakov wrote:

>> The SAW2 (Subsystem Power Manager and Adaptive Voltage Scaling Wrapper)

>> is part of the SPM subsystem. It is a hardware block found on some of the

>> Qualcomm chipsets, which regulates the power to the CPU cores. Add some

>> basic support for it, so that we can do dynamic voltage scaling.

>>

>> Signed-off-by: Georgi Djakov <georgi.djakov@linaro.org>

>> ---

>>  drivers/soc/qcom/spm.c |  149 +++++++++++++++++++++++++++++++++++++++++++++++-

> 

> Please don't implement regualtors outside of the regulator driver

> directory unless there is a really strong reason to do so, it makes it

> much easier to maintain the subsystem and keep track of what's going on.

> 


The reason of implementing the regulator functionality in drivers/soc is
that it is part of the same hardware. The saw2 hardware manages the power
controls - switching to low-power sleep modes, adaptive voltage scaling,
voltage control and messaging to the PMIC. Keeping all the functionality
of this hardware into a single driver seemed the suitable approach to me.
But if you think this is not a strong reason, then the approach probably
would be to export parts of the existing driver and use syscon from a
separate driver in drivers/regulator?

Thanks,
Georgi
--
To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Georgi Djakov Jan. 7, 2016, 1:50 p.m. UTC | #3
On 12/18/2015 07:24 PM, Lina Iyer wrote:
> On Fri, Dec 18 2015 at 09:15 -0700, Georgi Djakov wrote:

>> The SAW2 (Subsystem Power Manager and Adaptive Voltage Scaling Wrapper)

>> is part of the SPM subsystem. It is a hardware block found on some of the

>> Qualcomm chipsets, which regulates the power to the CPU cores. Add some

>> basic support for it, so that we can do dynamic voltage scaling.

>>

>> Signed-off-by: Georgi Djakov <georgi.djakov@linaro.org>

>> ---

[..]
>> @@ -368,6 +510,11 @@ static int spm_dev_probe(struct platform_device *pdev)

>>

>>     per_cpu(cpu_spm_drv, cpu) = drv;

>>

>> +    ret = register_saw2_regulator(drv, pdev, cpu);

>>

> Not all SAWs are regulators. On 8084, only the L2 SAW is a regulator.

> You may want to parse the device node to see if it has a 'regulator'

> property and then initialize the regulator aspect of the SAW. L2 SAW

> nodes are not present in the upstream kernel. You can find a patch that

> adds cache SPM here in my WIP series at [1].


Yes, nice catch. Thank you!

BR,
Georgi

--
To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Stephen Boyd Jan. 12, 2016, 2:34 a.m. UTC | #4
On 01/07, Georgi Djakov wrote:
> On 12/18/2015 07:22 PM, Mark Brown wrote:

> > On Fri, Dec 18, 2015 at 06:14:58PM +0200, Georgi Djakov wrote:

> >> The SAW2 (Subsystem Power Manager and Adaptive Voltage Scaling Wrapper)

> >> is part of the SPM subsystem. It is a hardware block found on some of the

> >> Qualcomm chipsets, which regulates the power to the CPU cores. Add some

> >> basic support for it, so that we can do dynamic voltage scaling.

> >>

> >> Signed-off-by: Georgi Djakov <georgi.djakov@linaro.org>

> >> ---

> >>  drivers/soc/qcom/spm.c |  149 +++++++++++++++++++++++++++++++++++++++++++++++-

> > 

> > Please don't implement regualtors outside of the regulator driver

> > directory unless there is a really strong reason to do so, it makes it

> > much easier to maintain the subsystem and keep track of what's going on.

> > 

> 

> The reason of implementing the regulator functionality in drivers/soc is

> that it is part of the same hardware. The saw2 hardware manages the power

> controls - switching to low-power sleep modes, adaptive voltage scaling,

> voltage control and messaging to the PMIC. Keeping all the functionality

> of this hardware into a single driver seemed the suitable approach to me.


It's also weird because sometimes we need to configure something
for the regulator that lives on the PMIC with SSBI or SPMI
transactions. So we get this split bus driver where we want to do
some stuff over the pmic bus (SSBI/SPMI) and then change voltages
with MMIO writes in the SAW IP block. Plus we can't figure out
the initial voltage for the regulator without reading the voltage
out over the pmic bus. A plain MMIO regulator through the SAW
hardware is pretty much limited to setting voltages and on/off.

-- 
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project
--
To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c
index b04b05a0904e..03fcee4b85d9 100644
--- a/drivers/soc/qcom/spm.c
+++ b/drivers/soc/qcom/spm.c
@@ -20,11 +20,14 @@ 
 #include <linux/of.h>
 #include <linux/of_address.h>
 #include <linux/of_device.h>
+#include <linux/delay.h>
 #include <linux/err.h>
 #include <linux/platform_device.h>
 #include <linux/cpuidle.h>
 #include <linux/cpu_pm.h>
 #include <linux/qcom_scm.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
 
 #include <asm/cpuidle.h>
 #include <asm/proc-fns.h>
@@ -51,6 +54,8 @@  enum spm_reg {
 	SPM_REG_PMIC_DLY,
 	SPM_REG_PMIC_DATA_0,
 	SPM_REG_PMIC_DATA_1,
+	SPM_REG_RST,
+	SPM_REG_STS_1,
 	SPM_REG_VCTL,
 	SPM_REG_SEQ_ENTRY,
 	SPM_REG_SPM_STS,
@@ -68,9 +73,22 @@  struct spm_reg_data {
 	u8 start_index[PM_SLEEP_MODE_NR];
 };
 
+struct spm_vlevel_data {
+	struct spm_driver_data *drv;
+	unsigned selector;
+};
+
+struct saw2_vreg {
+	struct regulator_desc	rdesc;
+	struct regulator_dev	*rdev;
+	struct spm_driver_data	*drv;
+	u32			selector;
+};
+
 struct spm_driver_data {
 	void __iomem *reg_base;
 	const struct spm_reg_data *reg_data;
+	struct saw2_vreg *vreg;
 };
 
 static const u8 spm_reg_offset_v2_1[SPM_REG_NR] = {
@@ -94,10 +112,13 @@  static const struct spm_reg_data spm_reg_8974_8084_cpu  = {
 
 static const u8 spm_reg_offset_v1_1[SPM_REG_NR] = {
 	[SPM_REG_CFG]		= 0x08,
+	[SPM_REG_STS_1]		= 0x10,
+	[SPM_REG_VCTL]		= 0x14,
 	[SPM_REG_SPM_CTL]	= 0x20,
 	[SPM_REG_PMIC_DLY]	= 0x24,
 	[SPM_REG_PMIC_DATA_0]	= 0x28,
 	[SPM_REG_PMIC_DATA_1]	= 0x2C,
+	[SPM_REG_RST]		= 0x30,
 	[SPM_REG_SEQ_ENTRY]	= 0x80,
 };
 
@@ -282,6 +303,127 @@  static struct cpuidle_ops qcom_cpuidle_ops __initdata = {
 CPUIDLE_METHOD_OF_DECLARE(qcom_idle_v1, "qcom,kpss-acc-v1", &qcom_cpuidle_ops);
 CPUIDLE_METHOD_OF_DECLARE(qcom_idle_v2, "qcom,kpss-acc-v2", &qcom_cpuidle_ops);
 
+static int saw2_regulator_get_voltage(struct regulator_dev *rdev)
+{
+	struct spm_driver_data *drv = rdev_get_drvdata(rdev);
+
+	return regulator_list_voltage_linear_range(rdev, drv->vreg->selector);
+}
+
+static void spm_smp_set_vdd(void *data)
+{
+	struct spm_vlevel_data *vdata = (struct spm_vlevel_data *)data;
+	struct spm_driver_data *drv = vdata->drv;
+	struct saw2_vreg *vreg = drv->vreg;
+	u32 sel = vdata->selector;
+	u32 val, new_val;
+	u32 vctl, data0, data1;
+	int timeout_us = 50;
+
+	if (vreg->selector == sel)
+		return;
+
+	vctl = spm_register_read(drv, SPM_REG_VCTL);
+	data0 = spm_register_read(drv, SPM_REG_PMIC_DATA_0);
+	data1 = spm_register_read(drv, SPM_REG_PMIC_DATA_1);
+
+	/* select the band */
+	val = 0x80 | sel;
+
+	vctl &= ~0xff;
+	vctl |= val;
+
+	data0 &= ~0xff;
+	data0 |= val;
+
+	data1 &= ~0x3f;
+	data1 |= val & 0x3f;
+	data1 &= ~0x3f0000;
+	data1 |= (val & 0x3f) << 16;
+
+	spm_register_write(drv, SPM_REG_RST, 1);
+	spm_register_write(drv, SPM_REG_VCTL, vctl);
+	spm_register_write(drv, SPM_REG_PMIC_DATA_0, data0);
+	spm_register_write(drv, SPM_REG_PMIC_DATA_1, data1);
+
+	do {
+		new_val = spm_register_read(drv, SPM_REG_STS_1) & 0xff;
+		if (new_val == val)
+			break;
+		udelay(1);
+	} while (--timeout_us);
+
+	if (!timeout_us) {
+		pr_err("%s: Voltage not changed: %#x\n", __func__, new_val);
+		return;
+	}
+
+	if (sel > vreg->selector) {
+		/* PMIC internal slew rate is 1250 uV per us */
+		udelay((sel - vreg->selector) * 10);
+	}
+
+	vreg->selector = sel;
+}
+
+static int saw2_regulator_set_voltage_sel(struct regulator_dev *rdev,
+					  unsigned selector)
+{
+	struct spm_driver_data *drv = rdev_get_drvdata(rdev);
+	struct spm_vlevel_data data;
+	int cpu = rdev_get_id(rdev);
+
+	data.drv = drv;
+	data.selector = selector;
+
+	return smp_call_function_single(cpu, spm_smp_set_vdd, &data, true);
+}
+
+static struct regulator_ops saw2_regulator_ops = {
+	.list_voltage = regulator_list_voltage_linear_range,
+	.set_voltage_sel = saw2_regulator_set_voltage_sel,
+	.get_voltage = saw2_regulator_get_voltage,
+};
+
+static struct regulator_desc saw2_regulator = {
+	.owner = THIS_MODULE,
+	.type = REGULATOR_VOLTAGE,
+	.ops  = &saw2_regulator_ops,
+	.linear_ranges = (struct regulator_linear_range[]) {
+		REGULATOR_LINEAR_RANGE(700000, 0, 56, 12500),
+	},
+	.n_linear_ranges = 1,
+	.n_voltages = 57,
+};
+
+static int register_saw2_regulator(struct spm_driver_data *drv,
+				   struct platform_device *pdev, int cpu)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct saw2_vreg *vreg;
+	struct regulator_config config = { };
+
+	vreg = devm_kzalloc(&pdev->dev, sizeof(*vreg), GFP_KERNEL);
+	if (!vreg)
+		return -ENOMEM;
+
+	drv->vreg = vreg;
+	config.driver_data = drv;
+	config.dev = &pdev->dev;
+	config.of_node = np;
+
+	vreg->rdesc = saw2_regulator;
+	vreg->rdesc.id = cpu;
+	vreg->rdesc.name = of_get_property(np, "regulator-name", NULL);
+	config.init_data = of_get_regulator_init_data(&pdev->dev,
+						      pdev->dev.of_node,
+						      &vreg->rdesc);
+
+	vreg->rdev = devm_regulator_register(&pdev->dev, &vreg->rdesc, &config);
+
+	return PTR_ERR_OR_ZERO(vreg->rdev);
+}
+
 static struct spm_driver_data *spm_get_drv(struct platform_device *pdev,
 		int *spm_cpu)
 {
@@ -327,7 +469,7 @@  static int spm_dev_probe(struct platform_device *pdev)
 	struct resource *res;
 	const struct of_device_id *match_id;
 	void __iomem *addr;
-	int cpu;
+	int cpu, ret;
 
 	drv = spm_get_drv(pdev, &cpu);
 	if (!drv)
@@ -368,6 +510,11 @@  static int spm_dev_probe(struct platform_device *pdev)
 
 	per_cpu(cpu_spm_drv, cpu) = drv;
 
+	ret = register_saw2_regulator(drv, pdev, cpu);
+	if (ret)
+		dev_err(&pdev->dev, "error registering SAW2 regulator: %d\n",
+			ret);
+
 	return 0;
 }