On 25.06.2023 22:25, Dmitry Baryshkov wrote: > The SPM / SAW2 device also provides a voltage regulator functionality > with optional AVS (Adaptive Voltage Scaling) support. The exact register > sequence and voltage ranges differs from device to device. > > Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@xxxxxxxxxx> > --- > drivers/soc/qcom/spm.c | 205 ++++++++++++++++++++++++++++++++++++++++- > include/soc/qcom/spm.h | 9 ++ > 2 files changed, 212 insertions(+), 2 deletions(-) > > diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c > index a6cbeb40831b..3c16a7e1710c 100644 > --- a/drivers/soc/qcom/spm.c > +++ b/drivers/soc/qcom/spm.c > @@ -9,19 +9,31 @@ > #include <linux/kernel.h> > #include <linux/init.h> > #include <linux/io.h> > +#include <linux/iopoll.h> > +#include <linux/linear_range.h> > #include <linux/module.h> > #include <linux/slab.h> > #include <linux/of.h> > #include <linux/of_address.h> > #include <linux/of_device.h> > +#include <linux/bitfield.h> This addition is very out-of-order > #include <linux/err.h> > #include <linux/platform_device.h> > +#include <linux/regulator/driver.h> > +#include <linux/smp.h> > #include <soc/qcom/spm.h> > > +#define FIELD_SET(current, mask, val) \ > + (((current) & ~(mask)) | FIELD_PREP((mask), (val))) > + > #define SPM_CTL_INDEX 0x7f > #define SPM_CTL_INDEX_SHIFT 4 > #define SPM_CTL_EN BIT(0) > > +#define SPM_1_1_AVS_CTL_AVS_ENABLED BIT(27) > +#define SPM_AVS_CTL_MIN_VLVL (0x3f << 10) > +#define SPM_AVS_CTL_MAX_VLVL (0x3f << 17) GENMASK > + > enum spm_reg { > SPM_REG_CFG, > SPM_REG_SPM_CTL, > @@ -31,10 +43,12 @@ enum spm_reg { > SPM_REG_PMIC_DATA_1, > SPM_REG_VCTL, > SPM_REG_SEQ_ENTRY, > - SPM_REG_SPM_STS, > + SPM_REG_STS0, > + SPM_REG_STS1, > SPM_REG_PMIC_STS, > SPM_REG_AVS_CTL, > SPM_REG_AVS_LIMIT, > + SPM_REG_RST, > SPM_REG_NR, > }; > > @@ -171,6 +185,10 @@ static const struct spm_reg_data spm_reg_8226_cpu = { > > static const u16 spm_reg_offset_v1_1[SPM_REG_NR] = { > [SPM_REG_CFG] = 0x08, > + [SPM_REG_STS0] = 0x0c, > + [SPM_REG_STS1] = 0x10, > + [SPM_REG_VCTL] = 0x14, > + [SPM_REG_AVS_CTL] = 0x18, > [SPM_REG_SPM_CTL] = 0x20, > [SPM_REG_PMIC_DLY] = 0x24, > [SPM_REG_PMIC_DATA_0] = 0x28, > @@ -178,7 +196,12 @@ static const u16 spm_reg_offset_v1_1[SPM_REG_NR] = { > [SPM_REG_SEQ_ENTRY] = 0x80, > }; > > +static void smp_set_vdd_v1_1(void *data); > + > /* SPM register data for 8064 */ > +static struct linear_range spm_v1_1_regulator_range = > + REGULATOR_LINEAR_RANGE(700000, 0, 56, 12500); > + > static const struct spm_reg_data spm_reg_8064_cpu = { > .reg_offset = spm_reg_offset_v1_1, > .spm_cfg = 0x1F, > @@ -189,6 +212,10 @@ static const struct spm_reg_data spm_reg_8064_cpu = { > 0x10, 0x54, 0x30, 0x0C, 0x24, 0x30, 0x0F }, > .start_index[PM_SLEEP_MODE_STBY] = 0, > .start_index[PM_SLEEP_MODE_SPC] = 2, > + .set_vdd = smp_set_vdd_v1_1, > + .range = &spm_v1_1_regulator_range, > + .init_uV = 1300000, > + .ramp_delay = 1250, > }; > > static inline void spm_register_write(struct spm_driver_data *drv, > @@ -240,6 +267,179 @@ void spm_set_low_power_mode(struct spm_driver_data *drv, > spm_register_write_sync(drv, SPM_REG_SPM_CTL, ctl_val); > } > > +static int spm_set_voltage_sel(struct regulator_dev *rdev, unsigned int selector) > +{ > + struct spm_driver_data *drv = rdev_get_drvdata(rdev); > + > + drv->volt_sel = selector; > + > + /* Always do the SAW register writes on the corresponding CPU */ > + return smp_call_function_single(drv->reg_cpu, drv->reg_data->set_vdd, drv, true); > +} > + > +static int spm_get_voltage_sel(struct regulator_dev *rdev) > +{ > + struct spm_driver_data *drv = rdev_get_drvdata(rdev); > + > + return drv->volt_sel; > +} > + > +static const struct regulator_ops spm_reg_ops = { > + .set_voltage_sel = spm_set_voltage_sel, > + .get_voltage_sel = spm_get_voltage_sel, > + .list_voltage = regulator_list_voltage_linear_range, > + .set_voltage_time_sel = regulator_set_voltage_time_sel, > +}; > + > +static void smp_set_vdd_v1_1(void *data) > +{ > + struct spm_driver_data *drv = data; > + unsigned int vlevel = drv->volt_sel; > + unsigned int vctl, data0, data1, avs_ctl, sts; > + bool avs_enabled; Reverse-Christmas-tree? > + > + vlevel |= 0x80; /* band */ That's conveniently 1<<7.. do we know if it's a significant number or just a bit that does something within that field? > + > + avs_ctl = spm_register_read(drv, SPM_REG_AVS_CTL); > + 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); > + > + avs_enabled = avs_ctl & SPM_1_1_AVS_CTL_AVS_ENABLED; > + > + /* If AVS is enabled, switch it off during the voltage change */ > + if (avs_enabled) { > + avs_ctl &= ~SPM_1_1_AVS_CTL_AVS_ENABLED; > + spm_register_write(drv, SPM_REG_AVS_CTL, avs_ctl); > + } > + > + /* Kick the state machine back to idle */ > + spm_register_write(drv, SPM_REG_RST, 1); > + > + vctl = FIELD_SET(vctl, 0xff, vlevel); > + data0 = FIELD_SET(data0, 0xff, vlevel); > + data1 = FIELD_SET(data1, 0x3f, vlevel); > + data1 = FIELD_SET(data1, 0x3f << 16, vlevel); GENMASK > + > + 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); > + > + if (read_poll_timeout_atomic(spm_register_read, > + sts, sts == vlevel, > + 1, 200, false, > + drv, SPM_REG_STS1)) { Not sure if misaligned or thunderfox is acting up again > + dev_err_ratelimited(drv->dev, "timeout setting the voltage (%x %x)!\n", sts, vlevel); > + goto enable_avs; > + } > + > + if (avs_enabled) { > + unsigned int max_avs = vlevel & 0x3f; GENMASK > + unsigned int min_avs = max(max_avs, 4U) - 4; So it's 0 or >= (1<<31 - 4)? > + avs_ctl = FIELD_SET(avs_ctl, SPM_AVS_CTL_MIN_VLVL, min_avs); > + avs_ctl = FIELD_SET(avs_ctl, SPM_AVS_CTL_MAX_VLVL, max_avs); > + spm_register_write(drv, SPM_REG_AVS_CTL, avs_ctl); > + } > + > +enable_avs: > + if (avs_enabled) { > + avs_ctl |= SPM_1_1_AVS_CTL_AVS_ENABLED; > + spm_register_write(drv, SPM_REG_AVS_CTL, avs_ctl); > + } > +} > + > +static int spm_get_cpu(struct device *dev) > +{ > + int cpu; > + bool found; Reverse-Christmas-tree? > + > + for_each_possible_cpu(cpu) { > + struct device_node *cpu_node, *saw_node; As long as Linux is running, there should be at least one CPU up, so this always gets entered, perhaps the definitions could be moved to the main function body > + > + cpu_node = of_cpu_device_node_get(cpu); > + if (!cpu_node) > + continue; > + > + saw_node = of_parse_phandle(cpu_node, "qcom,saw", 0); > + found = (saw_node == dev->of_node); The error checking here works out, but it's a bit cryptic.. Though I'm not opposed to saving 3 cycles on slow and old CPUs :D > + of_node_put(saw_node); > + of_node_put(cpu_node); > + > + if (found) > + return cpu; > + } > + > + /* L2 SPM is not bound to any CPU, tie it to CPU0 */ > + > + return 0; > +} > + > +#ifdef CONFIG_REGULATOR > +static int spm_register_regulator(struct device *dev, struct spm_driver_data *drv) > +{ > + struct regulator_config config = { > + .dev = dev, > + .driver_data = drv, > + }; > + struct regulator_desc *rdesc; > + struct regulator_dev *rdev; > + int ret; > + bool found; Reverse-Christmas-tree? Konrad > + > + if (!drv->reg_data->set_vdd) > + return 0; > + > + rdesc = devm_kzalloc(dev, sizeof(*rdesc), GFP_KERNEL); > + if (!rdesc) > + return -ENOMEM; > + > + rdesc->name = "spm"; > + rdesc->of_match = of_match_ptr("regulator"); > + rdesc->type = REGULATOR_VOLTAGE; > + rdesc->owner = THIS_MODULE; > + rdesc->ops = &spm_reg_ops; > + > + rdesc->linear_ranges = drv->reg_data->range; > + rdesc->n_linear_ranges = 1; > + rdesc->n_voltages = rdesc->linear_ranges[rdesc->n_linear_ranges - 1].max_sel + 1; > + rdesc->ramp_delay = drv->reg_data->ramp_delay; > + > + drv->reg_cpu = spm_get_cpu(dev); > + dev_dbg(dev, "SAW2 bound to CPU %d\n", drv->reg_cpu); > + > + /* > + * Program initial voltage, otherwise registration will also try > + * setting the voltage, which might result in undervolting the CPU. > + */ > + drv->volt_sel = DIV_ROUND_UP(drv->reg_data->init_uV - rdesc->min_uV, > + rdesc->uV_step); > + ret = linear_range_get_selector_high(drv->reg_data->range, > + drv->reg_data->init_uV, > + &drv->volt_sel, > + &found); > + if (ret) { > + dev_err(dev, "Initial uV value out of bounds\n"); > + return ret; > + } > + > + /* Always do the SAW register writes on the corresponding CPU */ > + smp_call_function_single(drv->reg_cpu, drv->reg_data->set_vdd, drv, true); > + > + rdev = devm_regulator_register(dev, rdesc, &config); > + if (IS_ERR(rdev)) { > + dev_err(dev, "failed to register regulator\n"); > + return PTR_ERR(rdev); > + } > + > + return 0; > +} > +#else > +static int spm_register_regulator(struct device *dev, struct spm_driver_data *drv) > +{ > + return 0; > +} > +#endif > + > static const struct of_device_id spm_match_table[] = { > { .compatible = "qcom,sdm660-gold-saw2-v4.1-l2", > .data = &spm_reg_660_gold_l2 }, > @@ -292,6 +492,7 @@ static int spm_dev_probe(struct platform_device *pdev) > return -ENODEV; > > drv->reg_data = match_id->data; > + drv->dev = &pdev->dev; > platform_set_drvdata(pdev, drv); > > /* Write the SPM sequences first.. */ > @@ -319,7 +520,7 @@ static int spm_dev_probe(struct platform_device *pdev) > if (drv->reg_data->reg_offset[SPM_REG_SPM_CTL]) > spm_set_low_power_mode(drv, PM_SLEEP_MODE_STBY); > > - return 0; > + return spm_register_regulator(&pdev->dev, drv); > } > > static struct platform_driver spm_driver = { > diff --git a/include/soc/qcom/spm.h b/include/soc/qcom/spm.h > index 4951f9d8b0bd..9859ebe42003 100644 > --- a/include/soc/qcom/spm.h > +++ b/include/soc/qcom/spm.h > @@ -30,11 +30,20 @@ struct spm_reg_data { > u32 avs_limit; > u8 seq[MAX_SEQ_DATA]; > u8 start_index[PM_SLEEP_MODE_NR]; > + > + smp_call_func_t set_vdd; > + /* for now we support only a single range */ > + struct linear_range *range; > + unsigned int ramp_delay; > + unsigned int init_uV; > }; > > struct spm_driver_data { > void __iomem *reg_base; > const struct spm_reg_data *reg_data; > + struct device *dev; > + unsigned int volt_sel; > + int reg_cpu; > }; > > void spm_set_low_power_mode(struct spm_driver_data *drv,