From 126187dafd221d672abc2c9839453023b884b5c9 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Thu, 30 Apr 2015 15:38:59 +0530 Subject: regulator: Fix spelling error in bindings Minor spell fix, s/intialised/initialised. Signed-off-by: Viresh Kumar Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/regulator/regulator.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/regulator/regulator.txt b/Documentation/devicetree/bindings/regulator/regulator.txt index abb26b58c83e..606391bea89b 100644 --- a/Documentation/devicetree/bindings/regulator/regulator.txt +++ b/Documentation/devicetree/bindings/regulator/regulator.txt @@ -13,7 +13,7 @@ Optional properties: - -supply: phandle to the parent supply/regulator node - regulator-ramp-delay: ramp delay for regulator(in uV/uS) For hardware which supports disabling ramp rate, it should be explicitly - intialised to zero (regulator-ramp-delay = <0>) for disabling ramp delay. + initialised to zero (regulator-ramp-delay = <0>) for disabling ramp delay. - regulator-enable-ramp-delay: The time taken, in microseconds, for the supply rail to reach the target voltage, plus/minus whatever tolerance the board design requires. This property describes the total system ramp time -- cgit v1.2.3 From c2ffa9737878ad9843d1e0e904dc9a086438aff8 Mon Sep 17 00:00:00 2001 From: Laxman Dewangan Date: Thu, 23 Apr 2015 16:10:23 +0530 Subject: regulator: max8973: add DT parsing of platform specific parameter There are some platform specific parameter required to configure the device like enable external control, DVS gpio etc. Add DT parsing of such properties to make platform specific data. Update DT binding doc accordingly. Signed-off-by: Laxman Dewangan Signed-off-by: Mark Brown --- .../bindings/regulator/max8973-regulator.txt | 14 ++++ drivers/regulator/max8973-regulator.c | 85 +++++++++++++++++----- 2 files changed, 79 insertions(+), 20 deletions(-) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/regulator/max8973-regulator.txt b/Documentation/devicetree/bindings/regulator/max8973-regulator.txt index 4f15d8a1bfd0..f63de7d8dc23 100644 --- a/Documentation/devicetree/bindings/regulator/max8973-regulator.txt +++ b/Documentation/devicetree/bindings/regulator/max8973-regulator.txt @@ -8,6 +8,20 @@ Required properties: Any standard regulator properties can be used to configure the single max8973 DCDC. +Optional properties: + +-maxim,externally-enable: boolean, externally control the regulator output + enable/disable. +-maxim,dvs-gpio: GPIO which is connected to DVS pin of device. +-maxim,dvs-default-state: Default state of GPIO during initialisation. + 1 for HIGH and 0 for LOW. +-maxim,enable-remote-sense: boolean, enable reote sense. +-maxim,enable-falling-slew-rate: boolean, enable falling slew rate. +-maxim,enable-active-discharge: boolean: enable active discharge. +-maxim,enable-frequency-shift: boolean, enable 9% frequency shift. +-maxim,enable-bias-control: boolean, enable bias control. By enabling this + startup delay can be reduce to 20us from 220us. + Example: max8973@1b { diff --git a/drivers/regulator/max8973-regulator.c b/drivers/regulator/max8973-regulator.c index 190db9c3e6b7..1442f920c80e 100644 --- a/drivers/regulator/max8973-regulator.c +++ b/drivers/regulator/max8973-regulator.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -364,6 +365,46 @@ static const struct regmap_config max8973_regmap_config = { .cache_type = REGCACHE_RBTREE, }; +static struct max8973_regulator_platform_data *max8973_parse_dt( + struct device *dev) +{ + struct max8973_regulator_platform_data *pdata; + struct device_node *np = dev->of_node; + int ret; + u32 pval; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + pdata->enable_ext_control = of_property_read_bool(np, + "maxim,externally-enable"); + pdata->dvs_gpio = of_get_named_gpio(np, "maxim,dvs-gpio", 0); + + ret = of_property_read_u32(np, "maxim,dvs-default-state", &pval); + if (!ret) + pdata->dvs_def_state = pval; + + if (of_property_read_bool(np, "maxim,enable-remote-sense")) + pdata->control_flags |= MAX8973_CONTROL_REMOTE_SENSE_ENABLE; + + if (of_property_read_bool(np, "maxim,enable-falling-slew-rate")) + pdata->control_flags |= + MAX8973_CONTROL_FALLING_SLEW_RATE_ENABLE; + + if (of_property_read_bool(np, "maxim,enable-active-discharge")) + pdata->control_flags |= + MAX8973_CONTROL_OUTPUT_ACTIVE_DISCH_ENABLE; + + if (of_property_read_bool(np, "maxim,enable-frequency-shift")) + pdata->control_flags |= MAX8973_CONTROL_FREQ_SHIFT_9PER_ENABLE; + + if (of_property_read_bool(np, "maxim,enable-bias-control")) + pdata->control_flags |= MAX8973_BIAS_ENABLE; + + return pdata; +} + static int max8973_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -371,15 +412,24 @@ static int max8973_probe(struct i2c_client *client, struct regulator_config config = { }; struct regulator_dev *rdev; struct max8973_chip *max; + bool pdata_from_dt = false; int ret; pdata = dev_get_platdata(&client->dev); - if (!pdata && !client->dev.of_node) { + if (!pdata && client->dev.of_node) { + pdata = max8973_parse_dt(&client->dev); + pdata_from_dt = true; + } + + if (!pdata) { dev_err(&client->dev, "No Platform data"); return -EIO; } + if (pdata->dvs_gpio == -EPROBE_DEFER) + return -EPROBE_DEFER; + max = devm_kzalloc(&client->dev, sizeof(*max), GFP_KERNEL); if (!max) return -ENOMEM; @@ -403,7 +453,7 @@ static int max8973_probe(struct i2c_client *client, max->desc.uV_step = MAX8973_VOLATGE_STEP; max->desc.n_voltages = MAX8973_BUCK_N_VOLTAGE; - if (!pdata || !pdata->enable_ext_control) { + if (!pdata->enable_ext_control) { max->desc.enable_reg = MAX8973_VOUT; max->desc.enable_mask = MAX8973_VOUT_ENABLE; max->ops.enable = regulator_enable_regmap; @@ -411,15 +461,10 @@ static int max8973_probe(struct i2c_client *client, max->ops.is_enabled = regulator_is_enabled_regmap; } - if (pdata) { - max->dvs_gpio = (pdata->dvs_gpio) ? pdata->dvs_gpio : -EINVAL; - max->enable_external_control = pdata->enable_ext_control; - max->curr_gpio_val = pdata->dvs_def_state; - max->curr_vout_reg = MAX8973_VOUT + pdata->dvs_def_state; - } else { - max->dvs_gpio = -EINVAL; - max->curr_vout_reg = MAX8973_VOUT; - } + max->dvs_gpio = (pdata->dvs_gpio) ? pdata->dvs_gpio : -EINVAL; + max->enable_external_control = pdata->enable_ext_control; + max->curr_gpio_val = pdata->dvs_def_state; + max->curr_vout_reg = MAX8973_VOUT + pdata->dvs_def_state; max->lru_index[0] = max->curr_vout_reg; @@ -448,18 +493,18 @@ static int max8973_probe(struct i2c_client *client, max->lru_index[max->curr_vout_reg] = 0; } - if (pdata) { - ret = max8973_init_dcdc(max, pdata); - if (ret < 0) { - dev_err(max->dev, "Max8973 Init failed, err = %d\n", ret); - return ret; - } + if (pdata_from_dt) + pdata->reg_init_data = of_get_regulator_init_data(&client->dev, + client->dev.of_node, &max->desc); + + ret = max8973_init_dcdc(max, pdata); + if (ret < 0) { + dev_err(max->dev, "Max8973 Init failed, err = %d\n", ret); + return ret; } config.dev = &client->dev; - config.init_data = pdata ? pdata->reg_init_data : - of_get_regulator_init_data(&client->dev, client->dev.of_node, - &max->desc); + config.init_data = pdata->reg_init_data; config.driver_data = max; config.of_node = client->dev.of_node; config.regmap = max->regmap; -- cgit v1.2.3 From 69eb0980ab4ced06f7c2b4774575337ce32912fb Mon Sep 17 00:00:00 2001 From: Laxman Dewangan Date: Thu, 23 Apr 2015 16:10:24 +0530 Subject: regulator: max8973: add mechanism to enable/disable through GPIO MAX8973 supports the voltage output enable/disable through its EN pin. This EN pin can be connected through GPIO from host processor. Add support to provide GPIO number from platform/DT and if it is valid GPIO then enable external control default. Signed-off-by: Laxman Dewangan Signed-off-by: Mark Brown --- .../bindings/regulator/max8973-regulator.txt | 2 ++ drivers/regulator/max8973-regulator.c | 29 +++++++++++++++++----- include/linux/regulator/max8973-regulator.h | 4 +++ 3 files changed, 29 insertions(+), 6 deletions(-) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/regulator/max8973-regulator.txt b/Documentation/devicetree/bindings/regulator/max8973-regulator.txt index f63de7d8dc23..201a26337387 100644 --- a/Documentation/devicetree/bindings/regulator/max8973-regulator.txt +++ b/Documentation/devicetree/bindings/regulator/max8973-regulator.txt @@ -12,6 +12,8 @@ Optional properties: -maxim,externally-enable: boolean, externally control the regulator output enable/disable. +-maxim,enable-gpio: GPIO for enable control. If the valid GPIO is provided + then externally enable control will be considered. -maxim,dvs-gpio: GPIO which is connected to DVS pin of device. -maxim,dvs-default-state: Default state of GPIO during initialisation. 1 for HIGH and 0 for LOW. diff --git a/drivers/regulator/max8973-regulator.c b/drivers/regulator/max8973-regulator.c index 1442f920c80e..00cf91cc4c85 100644 --- a/drivers/regulator/max8973-regulator.c +++ b/drivers/regulator/max8973-regulator.c @@ -96,6 +96,7 @@ struct max8973_chip { struct regulator_desc desc; struct regmap *regmap; bool enable_external_control; + int enable_gpio; int dvs_gpio; int lru_index[MAX8973_MAX_VOUT_REG]; int curr_vout_val[MAX8973_MAX_VOUT_REG]; @@ -379,6 +380,7 @@ static struct max8973_regulator_platform_data *max8973_parse_dt( pdata->enable_ext_control = of_property_read_bool(np, "maxim,externally-enable"); + pdata->enable_gpio = of_get_named_gpio(np, "maxim,enable-gpio", 0); pdata->dvs_gpio = of_get_named_gpio(np, "maxim,dvs-gpio", 0); ret = of_property_read_u32(np, "maxim,dvs-default-state", &pval); @@ -409,6 +411,7 @@ static int max8973_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct max8973_regulator_platform_data *pdata; + struct regulator_init_data *ridata; struct regulator_config config = { }; struct regulator_dev *rdev; struct max8973_chip *max; @@ -427,7 +430,8 @@ static int max8973_probe(struct i2c_client *client, return -EIO; } - if (pdata->dvs_gpio == -EPROBE_DEFER) + if ((pdata->dvs_gpio == -EPROBE_DEFER) || + (pdata->enable_gpio == -EPROBE_DEFER)) return -EPROBE_DEFER; max = devm_kzalloc(&client->dev, sizeof(*max), GFP_KERNEL); @@ -453,6 +457,15 @@ static int max8973_probe(struct i2c_client *client, max->desc.uV_step = MAX8973_VOLATGE_STEP; max->desc.n_voltages = MAX8973_BUCK_N_VOLTAGE; + max->dvs_gpio = (pdata->dvs_gpio) ? pdata->dvs_gpio : -EINVAL; + max->enable_gpio = (pdata->enable_gpio) ? pdata->enable_gpio : -EINVAL; + max->enable_external_control = pdata->enable_ext_control; + max->curr_gpio_val = pdata->dvs_def_state; + max->curr_vout_reg = MAX8973_VOUT + pdata->dvs_def_state; + + if (gpio_is_valid(max->enable_gpio)) + max->enable_external_control = true; + if (!pdata->enable_ext_control) { max->desc.enable_reg = MAX8973_VOUT; max->desc.enable_mask = MAX8973_VOUT_ENABLE; @@ -461,11 +474,6 @@ static int max8973_probe(struct i2c_client *client, max->ops.is_enabled = regulator_is_enabled_regmap; } - max->dvs_gpio = (pdata->dvs_gpio) ? pdata->dvs_gpio : -EINVAL; - max->enable_external_control = pdata->enable_ext_control; - max->curr_gpio_val = pdata->dvs_def_state; - max->curr_vout_reg = MAX8973_VOUT + pdata->dvs_def_state; - max->lru_index[0] = max->curr_vout_reg; if (gpio_is_valid(max->dvs_gpio)) { @@ -509,6 +517,15 @@ static int max8973_probe(struct i2c_client *client, config.of_node = client->dev.of_node; config.regmap = max->regmap; + if (gpio_is_valid(max->enable_gpio)) { + ridata = pdata->reg_init_data; + config.ena_gpio_flags = GPIOF_OUT_INIT_LOW; + if (ridata && (ridata->constraints.always_on || + ridata->constraints.boot_on)) + config.ena_gpio_flags = GPIOF_OUT_INIT_HIGH; + config.ena_gpio = max->enable_gpio; + } + /* Register the regulators */ rdev = devm_regulator_register(&client->dev, &max->desc, &config); if (IS_ERR(rdev)) { diff --git a/include/linux/regulator/max8973-regulator.h b/include/linux/regulator/max8973-regulator.h index f8acc052e353..f6a8a16a0d4d 100644 --- a/include/linux/regulator/max8973-regulator.h +++ b/include/linux/regulator/max8973-regulator.h @@ -58,6 +58,9 @@ * control signal from EN input pin. If it is false then * voltage output will be enabled/disabled through EN bit of * device register. + * @enable_gpio: Enable GPIO. If EN pin is controlled through GPIO from host + * then GPIO number can be provided. If no GPIO controlled then + * it should be -1. * @dvs_gpio: GPIO for dvs. It should be -1 if this is tied with fixed logic. * @dvs_def_state: Default state of dvs. 1 if it is high else 0. */ @@ -65,6 +68,7 @@ struct max8973_regulator_platform_data { struct regulator_init_data *reg_init_data; unsigned long control_flags; bool enable_ext_control; + int enable_gpio; int dvs_gpio; unsigned dvs_def_state:1; }; -- cgit v1.2.3 From 0f7d6ece6363f315b3b830dc19e6732537719224 Mon Sep 17 00:00:00 2001 From: Laxman Dewangan Date: Tue, 9 Jun 2015 19:17:53 +0530 Subject: regulator: max8973: add support for MAX77621 Maxim MAX77621 device is high-efficiency, three-phase, DC-DC step-down switching regulator delivers peak output currents up to 16A. This device is extension of MAX8973 and compatible with the register definition. The MAX77621 has the SHUTDOWN pin which is EN pin on the MAX8973. On MAX77621, the SHUTDOWN pin (active low) reset device register to its POR/OTP value. The voltage output is enabled when SHUTDONW pin is HIGH and EN bit on VOUT register is HIGH. For MAX8973, VOUT is enabled when EN bit or EN pin is high. Add support of the MAX77621 device on max8973 regulator driver with following changes: - Make sure SHUTDOWN pin is set HIGH through GPIO calls if GPIO from AP connected to SHUTDOWN pin provided. - Enable/disable the rail through register access only. Signed-off-by: Laxman Dewangan Signed-off-by: Mark Brown --- .../bindings/regulator/max8973-regulator.txt | 4 +- drivers/regulator/max8973-regulator.c | 104 +++++++++++++++++---- 2 files changed, 87 insertions(+), 21 deletions(-) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/regulator/max8973-regulator.txt b/Documentation/devicetree/bindings/regulator/max8973-regulator.txt index 201a26337387..55efb24e5683 100644 --- a/Documentation/devicetree/bindings/regulator/max8973-regulator.txt +++ b/Documentation/devicetree/bindings/regulator/max8973-regulator.txt @@ -2,7 +2,9 @@ Required properties: -- compatible: must be "maxim,max8973" +- compatible: must be one of following: + "maxim,max8973" + "maxim,max77621". - reg: the i2c slave address of the regulator. It should be 0x1b. Any standard regulator properties can be used to configure the single max8973 diff --git a/drivers/regulator/max8973-regulator.c b/drivers/regulator/max8973-regulator.c index 663e4df44048..89e53e049399 100644 --- a/drivers/regulator/max8973-regulator.c +++ b/drivers/regulator/max8973-regulator.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -91,6 +92,11 @@ #define MAX8973_VOLATGE_STEP 6250 #define MAX8973_BUCK_N_VOLTAGE 0x80 +enum device_id { + MAX8973, + MAX77621 +}; + /* Maxim 8973 chip information */ struct max8973_chip { struct device *dev; @@ -104,6 +110,7 @@ struct max8973_chip { int curr_vout_reg; int curr_gpio_val; struct regulator_ops ops; + enum device_id id; }; /* @@ -390,7 +397,7 @@ static int max8973_init_dcdc(struct max8973_chip *max, } /* If external control is enabled then disable EN bit */ - if (max->enable_external_control) { + if (max->enable_external_control && (max->id == MAX8973)) { ret = regmap_update_bits(max->regmap, MAX8973_VOUT, MAX8973_VOUT_ENABLE, 0); if (ret < 0) @@ -448,6 +455,13 @@ static struct max8973_regulator_platform_data *max8973_parse_dt( return pdata; } +static const struct of_device_id of_max8973_match_tbl[] = { + { .compatible = "maxim,max8973", .data = (void *)MAX8973, }, + { .compatible = "maxim,max77621", .data = (void *)MAX77621, }, + { }, +}; +MODULE_DEVICE_TABLE(of, of_max8973_match_tbl); + static int max8973_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -457,6 +471,7 @@ static int max8973_probe(struct i2c_client *client, struct regulator_dev *rdev; struct max8973_chip *max; bool pdata_from_dt = false; + unsigned int chip_id; int ret; pdata = dev_get_platdata(&client->dev); @@ -486,6 +501,27 @@ static int max8973_probe(struct i2c_client *client, return ret; } + if (client->dev.of_node) { + const struct of_device_id *match; + + match = of_match_device(of_match_ptr(of_max8973_match_tbl), + &client->dev); + if (!match) + return -ENODATA; + max->id = (u32)((uintptr_t)match->data); + } else { + max->id = id->driver_data; + } + + ret = regmap_read(max->regmap, MAX8973_CHIPID1, &chip_id); + if (ret < 0) { + dev_err(&client->dev, "register CHIPID1 read failed, %d", ret); + return ret; + } + + dev_info(&client->dev, "CHIP-ID OTP: 0x%02x ID_M: 0x%02x\n", + (chip_id >> 4) & 0xF, (chip_id >> 1) & 0x7); + i2c_set_clientdata(client, max); max->ops = max8973_dcdc_ops; max->dev = &client->dev; @@ -507,14 +543,6 @@ static int max8973_probe(struct i2c_client *client, if (gpio_is_valid(max->enable_gpio)) max->enable_external_control = true; - if (!pdata->enable_ext_control) { - max->desc.enable_reg = MAX8973_VOUT; - max->desc.enable_mask = MAX8973_VOUT_ENABLE; - max->ops.enable = regulator_enable_regmap; - max->ops.disable = regulator_disable_regmap; - max->ops.is_enabled = regulator_is_enabled_regmap; - } - max->lru_index[0] = max->curr_vout_reg; if (gpio_is_valid(max->dvs_gpio)) { @@ -546,6 +574,50 @@ static int max8973_probe(struct i2c_client *client, pdata->reg_init_data = of_get_regulator_init_data(&client->dev, client->dev.of_node, &max->desc); + ridata = pdata->reg_init_data; + switch (max->id) { + case MAX8973: + if (!pdata->enable_ext_control) { + max->desc.enable_reg = MAX8973_VOUT; + max->desc.enable_mask = MAX8973_VOUT_ENABLE; + max->ops.enable = regulator_enable_regmap; + max->ops.disable = regulator_disable_regmap; + max->ops.is_enabled = regulator_is_enabled_regmap; + break; + } + + if (gpio_is_valid(max->enable_gpio)) { + config.ena_gpio_flags = GPIOF_OUT_INIT_LOW; + if (ridata && (ridata->constraints.always_on || + ridata->constraints.boot_on)) + config.ena_gpio_flags = GPIOF_OUT_INIT_HIGH; + config.ena_gpio = max->enable_gpio; + } + break; + + case MAX77621: + if (gpio_is_valid(max->enable_gpio)) { + ret = devm_gpio_request_one(&client->dev, + max->enable_gpio, GPIOF_OUT_INIT_HIGH, + "max8973-en-gpio"); + if (ret) { + dev_err(&client->dev, + "gpio_request for gpio %d failed: %d\n", + max->enable_gpio, ret); + return ret; + } + } + + max->desc.enable_reg = MAX8973_VOUT; + max->desc.enable_mask = MAX8973_VOUT_ENABLE; + max->ops.enable = regulator_enable_regmap; + max->ops.disable = regulator_disable_regmap; + max->ops.is_enabled = regulator_is_enabled_regmap; + break; + default: + break; + } + ret = max8973_init_dcdc(max, pdata); if (ret < 0) { dev_err(max->dev, "Max8973 Init failed, err = %d\n", ret); @@ -558,15 +630,6 @@ static int max8973_probe(struct i2c_client *client, config.of_node = client->dev.of_node; config.regmap = max->regmap; - if (gpio_is_valid(max->enable_gpio)) { - ridata = pdata->reg_init_data; - config.ena_gpio_flags = GPIOF_OUT_INIT_LOW; - if (ridata && (ridata->constraints.always_on || - ridata->constraints.boot_on)) - config.ena_gpio_flags = GPIOF_OUT_INIT_HIGH; - config.ena_gpio = max->enable_gpio; - } - /* Register the regulators */ rdev = devm_regulator_register(&client->dev, &max->desc, &config); if (IS_ERR(rdev)) { @@ -579,15 +642,16 @@ static int max8973_probe(struct i2c_client *client, } static const struct i2c_device_id max8973_id[] = { - {.name = "max8973",}, + {.name = "max8973", .driver_data = MAX8973}, + {.name = "max77621", .driver_data = MAX77621}, {}, }; - MODULE_DEVICE_TABLE(i2c, max8973_id); static struct i2c_driver max8973_i2c_driver = { .driver = { .name = "max8973", + .of_match_table = of_max8973_match_tbl, .owner = THIS_MODULE, }, .probe = max8973_probe, -- cgit v1.2.3 From 22a10bca280073f81e9e2d9fed6f90a3bcf00236 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Thu, 11 Jun 2015 17:37:03 -0700 Subject: regulator: Add system_load constraint Some regulators have a fixed load that isn't captured by consumers that the kernel knows about. Add a constraint to support this. Signed-off-by: Stephen Boyd Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/regulator/regulator.txt | 2 ++ drivers/regulator/core.c | 2 ++ drivers/regulator/of_regulator.c | 3 +++ include/linux/regulator/machine.h | 3 +++ 4 files changed, 10 insertions(+) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/regulator/regulator.txt b/Documentation/devicetree/bindings/regulator/regulator.txt index abb26b58c83e..553d2d0fe6d9 100644 --- a/Documentation/devicetree/bindings/regulator/regulator.txt +++ b/Documentation/devicetree/bindings/regulator/regulator.txt @@ -37,6 +37,8 @@ Optional properties: - regulator-initial-mode: initial operating mode. The set of possible operating modes depends on the capabilities of every hardware so each device binding documentation explains which values the regulator supports. +- regulator-system-load: Load in uA present on regulator that is not captured by + any consumer request. Deprecated properties: - regulator-compatible: If a regulator chip contains multiple diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 443eaab933fc..c8d5e2b05fdf 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -678,6 +678,8 @@ static int drms_uA_update(struct regulator_dev *rdev) list_for_each_entry(sibling, &rdev->consumer_list, list) current_uA += sibling->uA_load; + current_uA += rdev->constraints->system_load; + if (rdev->desc->ops->set_load) { /* set the optimum mode for our new total regulator load */ err = rdev->desc->ops->set_load(rdev, current_uA); diff --git a/drivers/regulator/of_regulator.c b/drivers/regulator/of_regulator.c index 24e812c48d93..482a86f90839 100644 --- a/drivers/regulator/of_regulator.c +++ b/drivers/regulator/of_regulator.c @@ -95,6 +95,9 @@ static void of_get_regulation_constraints(struct device_node *np, } } + if (!of_property_read_u32(np, "regulator-system-load", &pval)) + constraints->system_load = pval; + for (i = 0; i < ARRAY_SIZE(regulator_states); i++) { switch (i) { case PM_SUSPEND_MEM: diff --git a/include/linux/regulator/machine.h b/include/linux/regulator/machine.h index b07562e082c4..01526559c8c3 100644 --- a/include/linux/regulator/machine.h +++ b/include/linux/regulator/machine.h @@ -75,6 +75,7 @@ struct regulator_state { * * @min_uA: Smallest current consumers may set. * @max_uA: Largest current consumers may set. + * @system_load: Load that isn't captured by any consumer requests. * * @valid_modes_mask: Mask of modes which may be configured by consumers. * @valid_ops_mask: Operations which may be performed by consumers. @@ -112,6 +113,8 @@ struct regulation_constraints { int min_uA; int max_uA; + int system_load; + /* valid regulator operating modes for this machine */ unsigned int valid_modes_mask; -- cgit v1.2.3 From 23c779b9f9161d6568d3b2fca06e70ad182c480c Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Thu, 11 Jun 2015 17:37:04 -0700 Subject: regulator: Add pull down support Some regulators need to be configured to pull down a resistor when the regulator is disabled. Add an op (set_pull_down) and a DT property + constraint to support this. Signed-off-by: Stephen Boyd Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/regulator/regulator.txt | 1 + drivers/regulator/core.c | 8 ++++++++ drivers/regulator/of_regulator.c | 2 ++ include/linux/regulator/driver.h | 5 +++++ include/linux/regulator/machine.h | 2 ++ 5 files changed, 18 insertions(+) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/regulator/regulator.txt b/Documentation/devicetree/bindings/regulator/regulator.txt index 553d2d0fe6d9..6c79fd70ab5a 100644 --- a/Documentation/devicetree/bindings/regulator/regulator.txt +++ b/Documentation/devicetree/bindings/regulator/regulator.txt @@ -39,6 +39,7 @@ Optional properties: documentation explains which values the regulator supports. - regulator-system-load: Load in uA present on regulator that is not captured by any consumer request. +- regulator-pull-down: Enable pull down resistor when the regulator is disabled. Deprecated properties: - regulator-compatible: If a regulator chip contains multiple diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index c8d5e2b05fdf..60fcfba52592 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -1051,6 +1051,14 @@ static int set_machine_constraints(struct regulator_dev *rdev, } } + if (rdev->constraints->pull_down && ops->set_pull_down) { + ret = ops->set_pull_down(rdev); + if (ret < 0) { + rdev_err(rdev, "failed to set pull down\n"); + goto out; + } + } + print_constraints(rdev); return 0; out: diff --git a/drivers/regulator/of_regulator.c b/drivers/regulator/of_regulator.c index 482a86f90839..c3433db0acda 100644 --- a/drivers/regulator/of_regulator.c +++ b/drivers/regulator/of_regulator.c @@ -67,6 +67,8 @@ static void of_get_regulation_constraints(struct device_node *np, if (!constraints->always_on) /* status change should be possible. */ constraints->valid_ops_mask |= REGULATOR_CHANGE_STATUS; + constraints->pull_down = of_property_read_bool(np, "regulator-pull-down"); + if (of_property_read_bool(np, "regulator-allow-bypass")) constraints->valid_ops_mask |= REGULATOR_CHANGE_BYPASS; diff --git a/include/linux/regulator/driver.h b/include/linux/regulator/driver.h index fffa688ac3a7..76144a337ff7 100644 --- a/include/linux/regulator/driver.h +++ b/include/linux/regulator/driver.h @@ -121,6 +121,9 @@ struct regulator_linear_range { * @set_suspend_mode: Set the operating mode for the regulator when the * system is suspended. * + * @set_pull_down: Configure the regulator to pull down when the regulator + * is disabled. + * * This struct describes regulator operations which can be implemented by * regulator chip drivers. */ @@ -187,6 +190,8 @@ struct regulator_ops { /* set regulator suspend operating mode (defined in consumer.h) */ int (*set_suspend_mode) (struct regulator_dev *, unsigned int mode); + + int (*set_pull_down) (struct regulator_dev *); }; /* diff --git a/include/linux/regulator/machine.h b/include/linux/regulator/machine.h index 01526559c8c3..8ffb0619a03c 100644 --- a/include/linux/regulator/machine.h +++ b/include/linux/regulator/machine.h @@ -87,6 +87,7 @@ struct regulator_state { * applied. * @apply_uV: Apply the voltage constraint when initialising. * @ramp_disable: Disable ramp delay when initialising or when setting voltage. + * @pull_down: Enable pull down when regulator is disabled. * * @input_uV: Input voltage for regulator when supplied by another regulator. * @@ -141,6 +142,7 @@ struct regulation_constraints { unsigned boot_on:1; /* bootloader/firmware enabled regulator */ unsigned apply_uV:1; /* apply uV constraint if min == max */ unsigned ramp_disable:1; /* disable ramp delay */ + unsigned pull_down:1; /* pull down resistor when regulator off */ }; /** -- cgit v1.2.3 From 57f66b78860968fc7eddc9ce25f8e57f7e5000bd Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Thu, 11 Jun 2015 17:37:05 -0700 Subject: regulator: Add soft start support Some regulators support a "soft start" feature where the voltage ramps up slowly when the regulator is enabled. Add an op (set_soft_start) and a DT property + constraint to support this. Signed-off-by: Stephen Boyd Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/regulator/regulator.txt | 1 + drivers/regulator/core.c | 8 ++++++++ drivers/regulator/of_regulator.c | 3 +++ include/linux/regulator/driver.h | 2 ++ include/linux/regulator/machine.h | 1 + 5 files changed, 15 insertions(+) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/regulator/regulator.txt b/Documentation/devicetree/bindings/regulator/regulator.txt index 6c79fd70ab5a..4b1df61ccbd7 100644 --- a/Documentation/devicetree/bindings/regulator/regulator.txt +++ b/Documentation/devicetree/bindings/regulator/regulator.txt @@ -19,6 +19,7 @@ Optional properties: design requires. This property describes the total system ramp time required due to the combination of internal ramping of the regulator itself, and board design issues such as trace capacitance and load on the supply. +- regulator-soft-start: Enable soft start so that voltage ramps slowly - regulator-state-mem sub-root node for Suspend-to-RAM mode : suspend to memory, the device goes to sleep, but all data stored in memory, only some external interrupt can wake the device. diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 60fcfba52592..6dfb2d6c19ae 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -1059,6 +1059,14 @@ static int set_machine_constraints(struct regulator_dev *rdev, } } + if (rdev->constraints->soft_start && ops->set_soft_start) { + ret = ops->set_soft_start(rdev); + if (ret < 0) { + rdev_err(rdev, "failed to set soft start\n"); + goto out; + } + } + print_constraints(rdev); return 0; out: diff --git a/drivers/regulator/of_regulator.c b/drivers/regulator/of_regulator.c index c3433db0acda..207da037cd2d 100644 --- a/drivers/regulator/of_regulator.c +++ b/drivers/regulator/of_regulator.c @@ -84,6 +84,9 @@ static void of_get_regulation_constraints(struct device_node *np, if (!ret) constraints->enable_time = pval; + constraints->soft_start = of_property_read_bool(np, + "regulator-soft-start"); + if (!of_property_read_u32(np, "regulator-initial-mode", &pval)) { if (desc && desc->of_map_mode) { ret = desc->of_map_mode(pval); diff --git a/include/linux/regulator/driver.h b/include/linux/regulator/driver.h index 76144a337ff7..e0635d0894aa 100644 --- a/include/linux/regulator/driver.h +++ b/include/linux/regulator/driver.h @@ -161,6 +161,8 @@ struct regulator_ops { unsigned int old_selector, unsigned int new_selector); + int (*set_soft_start) (struct regulator_dev *); + /* report regulator status ... most other accessors report * control inputs, this reports results of combining inputs * from Linux (and other sources) with the actual load. diff --git a/include/linux/regulator/machine.h b/include/linux/regulator/machine.h index 8ffb0619a03c..7f7d0a3fe1e1 100644 --- a/include/linux/regulator/machine.h +++ b/include/linux/regulator/machine.h @@ -142,6 +142,7 @@ struct regulation_constraints { unsigned boot_on:1; /* bootloader/firmware enabled regulator */ unsigned apply_uV:1; /* apply uV constraint if min == max */ unsigned ramp_disable:1; /* disable ramp delay */ + unsigned soft_start:1; /* ramp voltage slowly */ unsigned pull_down:1; /* pull down resistor when regulator off */ }; -- cgit v1.2.3 From 36e4f839de59b6216a16cdf5c1d3263f4dbd9421 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Thu, 11 Jun 2015 17:37:06 -0700 Subject: regulator: Add input current limit support Some regulators can limit their input current (typically annotated as ilim). Add an op (set_input_current_limit) and a DT property + constraint to support this. Signed-off-by: Stephen Boyd Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/regulator/regulator.txt | 1 + drivers/regulator/core.c | 9 +++++++++ drivers/regulator/of_regulator.c | 4 ++++ include/linux/regulator/driver.h | 3 +++ include/linux/regulator/machine.h | 2 ++ 5 files changed, 19 insertions(+) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/regulator/regulator.txt b/Documentation/devicetree/bindings/regulator/regulator.txt index 4b1df61ccbd7..e29db73e55f4 100644 --- a/Documentation/devicetree/bindings/regulator/regulator.txt +++ b/Documentation/devicetree/bindings/regulator/regulator.txt @@ -7,6 +7,7 @@ Optional properties: - regulator-microvolt-offset: Offset applied to voltages to compensate for voltage drops - regulator-min-microamp: smallest current consumers may set - regulator-max-microamp: largest current consumers may set +- regulator-input-current-limit-microamp: maximum input current regulator allows - regulator-always-on: boolean, regulator should never be disabled - regulator-boot-on: bootloader/firmware enabled regulator - regulator-allow-bypass: allow the regulator to go into bypass mode diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 6dfb2d6c19ae..ba565416d1d0 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -1008,6 +1008,15 @@ static int set_machine_constraints(struct regulator_dev *rdev, if (ret != 0) goto out; + if (rdev->constraints->ilim_uA && ops->set_input_current_limit) { + ret = ops->set_input_current_limit(rdev, + rdev->constraints->ilim_uA); + if (ret < 0) { + rdev_err(rdev, "failed to set input limit\n"); + goto out; + } + } + /* do we need to setup our suspend state */ if (rdev->constraints->initial_state) { ret = suspend_prepare(rdev, rdev->constraints->initial_state); diff --git a/drivers/regulator/of_regulator.c b/drivers/regulator/of_regulator.c index 207da037cd2d..f025c1047d0a 100644 --- a/drivers/regulator/of_regulator.c +++ b/drivers/regulator/of_regulator.c @@ -58,6 +58,10 @@ static void of_get_regulation_constraints(struct device_node *np, if (!of_property_read_u32(np, "regulator-max-microamp", &pval)) constraints->max_uA = pval; + if (!of_property_read_u32(np, "regulator-input-current-limit-microamp", + &pval)) + constraints->ilim_uA = pval; + /* Current change possible? */ if (constraints->min_uA != constraints->max_uA) constraints->valid_ops_mask |= REGULATOR_CHANGE_CURRENT; diff --git a/include/linux/regulator/driver.h b/include/linux/regulator/driver.h index e0635d0894aa..125264f8be93 100644 --- a/include/linux/regulator/driver.h +++ b/include/linux/regulator/driver.h @@ -91,6 +91,7 @@ struct regulator_linear_range { * @set_current_limit: Configure a limit for a current-limited regulator. * The driver should select the current closest to max_uA. * @get_current_limit: Get the configured limit for a current-limited regulator. + * @set_input_current_limit: Configure an input limit. * * @set_mode: Set the configured operating mode for the regulator. * @get_mode: Get the configured operating mode for the regulator. @@ -145,6 +146,8 @@ struct regulator_ops { int min_uA, int max_uA); int (*get_current_limit) (struct regulator_dev *); + int (*set_input_current_limit) (struct regulator_dev *, int lim_uA); + /* enable/disable regulator */ int (*enable) (struct regulator_dev *); int (*disable) (struct regulator_dev *); diff --git a/include/linux/regulator/machine.h b/include/linux/regulator/machine.h index 7f7d0a3fe1e1..85a3b457de51 100644 --- a/include/linux/regulator/machine.h +++ b/include/linux/regulator/machine.h @@ -75,6 +75,7 @@ struct regulator_state { * * @min_uA: Smallest current consumers may set. * @max_uA: Largest current consumers may set. + * @ilim_uA: Maximum input current. * @system_load: Load that isn't captured by any consumer requests. * * @valid_modes_mask: Mask of modes which may be configured by consumers. @@ -113,6 +114,7 @@ struct regulation_constraints { /* current output range (inclusive) - for current control */ int min_uA; int max_uA; + int ilim_uA; int system_load; -- cgit v1.2.3 From e92a4047419c805d08ad136fbc72368249d9f091 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Fri, 12 Jun 2015 15:47:10 -0700 Subject: regulator: Add QCOM SPMI regulator driver Add an SPMI regulator driver for Qualcomm's PM8841, PM8941, and PM8916 PMICs. This driver is based largely on code from codeaurora.org[1]. [1] https://www.codeaurora.org/cgit/quic/la/kernel/msm-3.10/tree/drivers/regulator/qpnp-regulator.c?h=msm-3.10 Cc: David Collins Cc: Signed-off-by: Stephen Boyd Signed-off-by: Mark Brown --- .../bindings/regulator/qcom,spmi-regulator.txt | 121 ++ drivers/regulator/Kconfig | 11 + drivers/regulator/Makefile | 1 + drivers/regulator/qcom_spmi-regulator.c | 1437 ++++++++++++++++++++ 4 files changed, 1570 insertions(+) create mode 100644 Documentation/devicetree/bindings/regulator/qcom,spmi-regulator.txt create mode 100644 drivers/regulator/qcom_spmi-regulator.c (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/regulator/qcom,spmi-regulator.txt b/Documentation/devicetree/bindings/regulator/qcom,spmi-regulator.txt new file mode 100644 index 000000000000..75b4604bad07 --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/qcom,spmi-regulator.txt @@ -0,0 +1,121 @@ +Qualcomm SPMI Regulators + +- compatible: + Usage: required + Value type: + Definition: must be one of: + "qcom,pm8841-regulators" + "qcom,pm8916-regulators" + "qcom,pm8941-regulators" + +- interrupts: + Usage: optional + Value type: + Definition: List of OCP interrupts. + +- interrupt-names: + Usage: required if 'interrupts' property present + Value type: + Definition: List of strings defining the names of the + interrupts in the 'interrupts' property 1-to-1. + Supported values are "ocp-", where + corresponds to a voltage switch + type regulator. + +- vdd_s1-supply: +- vdd_s2-supply: +- vdd_s3-supply: +- vdd_s4-supply: +- vdd_s5-supply: +- vdd_s6-supply: +- vdd_s7-supply: +- vdd_s8-supply: + Usage: optional (pm8841 only) + Value type: + Definition: Reference to regulator supplying the input pin, as + described in the data sheet. + +- vdd_s1-supply: +- vdd_s2-supply: +- vdd_s3-supply: +- vdd_s4-supply: +- vdd_l1_l3-supply: +- vdd_l2-supply: +- vdd_l4_l5_l6-supply: +- vdd_l7-supply: +- vdd_l8_l11_l14_l15_l16-supply: +- vdd_l9_l10_l12_l13_l17_l18-supply: + Usage: optional (pm8916 only) + Value type: + Definition: Reference to regulator supplying the input pin, as + described in the data sheet. + +- vdd_s1-supply: +- vdd_s2-supply: +- vdd_s3-supply: +- vdd_l1_l3-supply: +- vdd_l2_lvs_1_2_3-supply: +- vdd_l4_l11-supply: +- vdd_l5_l7-supply: +- vdd_l6_l12_l14_l15-supply: +- vdd_l8_l16_l18_19-supply: +- vdd_l9_l10_l17_l22-supply: +- vdd_l13_l20_l23_l24-supply: +- vdd_l21-supply: +- vin_5vs-supply: + Usage: optional (pm8941 only) + Value type: + Definition: Reference to regulator supplying the input pin, as + described in the data sheet. + + +The regulator node houses sub-nodes for each regulator within the device. Each +sub-node is identified using the node's name, with valid values listed for each +of the PMICs below. + +pm8841: + s1, s2, s3, s4, s5, s6, s7, s8 + +pm8916: + s1, s2, s3, s4, l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, + l14, l15, l16, l17, l18 + +pm8941: + s1, s2, s3, l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, + l15, l16, l17, l18, l19, l20, l21, l22, l23, l24, lvs1, lvs2, lvs3, + mvs1, mvs2 + +The content of each sub-node is defined by the standard binding for regulators - +see regulator.txt - with additional custom properties described below: + +- regulator-initial-mode: + Usage: optional + Value type: + Descrption: 1 = Set initial mode to high power mode (HPM), also referred + to as NPM. HPM consumes more ground current than LPM, but + it can source significantly higher load current. HPM is not + available on boost type regulators. For voltage switch type + regulators, HPM implies that over current protection and + soft start are active all the time. 0 = Set initial mode to + low power mode (LPM). + +Example: + + regulators { + compatible = "qcom,pm8941-regulators"; + vdd_l1_l3-supply = <&s1>; + + s1: s1 { + regulator-min-microvolt = <1300000>; + regulator-max-microvolt = <1400000>; + }; + + ... + + l1: l1 { + regulator-min-microvolt = <1225000>; + regulator-max-microvolt = <1300000>; + }; + + .... + }; diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index a6f116aa5235..53b3e25a98a1 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -512,6 +512,17 @@ config REGULATOR_QCOM_RPM Qualcomm RPM as a module. The module will be named "qcom_rpm-regulator". +config REGULATOR_QCOM_SPMI + tristate "Qualcomm SPMI regulator driver" + depends on SPMI || COMPILE_TEST + help + If you say yes to this option, support will be included for the + regulators found in Qualcomm SPMI PMICs. + + Say M here if you want to include support for the regulators on the + Qualcomm SPMI PMICs as a module. The module will be named + "qcom_spmi-regulator". + config REGULATOR_RC5T583 tristate "RICOH RC5T583 Power regulators" depends on MFD_RC5T583 diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 2c4da15e1545..7152c979c935 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -61,6 +61,7 @@ obj-$(CONFIG_REGULATOR_MC13892) += mc13892-regulator.o obj-$(CONFIG_REGULATOR_MC13XXX_CORE) += mc13xxx-regulator-core.o obj-$(CONFIG_REGULATOR_MT6397) += mt6397-regulator.o obj-$(CONFIG_REGULATOR_QCOM_RPM) += qcom_rpm-regulator.o +obj-$(CONFIG_REGULATOR_QCOM_SPMI) += qcom_spmi-regulator.o obj-$(CONFIG_REGULATOR_PALMAS) += palmas-regulator.o obj-$(CONFIG_REGULATOR_PFUZE100) += pfuze100-regulator.o obj-$(CONFIG_REGULATOR_PWM) += pwm-regulator.o diff --git a/drivers/regulator/qcom_spmi-regulator.c b/drivers/regulator/qcom_spmi-regulator.c new file mode 100644 index 000000000000..162b86501a91 --- /dev/null +++ b/drivers/regulator/qcom_spmi-regulator.c @@ -0,0 +1,1437 @@ +/* + * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* These types correspond to unique register layouts. */ +enum spmi_regulator_logical_type { + SPMI_REGULATOR_LOGICAL_TYPE_SMPS, + SPMI_REGULATOR_LOGICAL_TYPE_LDO, + SPMI_REGULATOR_LOGICAL_TYPE_VS, + SPMI_REGULATOR_LOGICAL_TYPE_BOOST, + SPMI_REGULATOR_LOGICAL_TYPE_FTSMPS, + SPMI_REGULATOR_LOGICAL_TYPE_BOOST_BYP, + SPMI_REGULATOR_LOGICAL_TYPE_LN_LDO, + SPMI_REGULATOR_LOGICAL_TYPE_ULT_LO_SMPS, + SPMI_REGULATOR_LOGICAL_TYPE_ULT_HO_SMPS, + SPMI_REGULATOR_LOGICAL_TYPE_ULT_LDO, +}; + +enum spmi_regulator_type { + SPMI_REGULATOR_TYPE_BUCK = 0x03, + SPMI_REGULATOR_TYPE_LDO = 0x04, + SPMI_REGULATOR_TYPE_VS = 0x05, + SPMI_REGULATOR_TYPE_BOOST = 0x1b, + SPMI_REGULATOR_TYPE_FTS = 0x1c, + SPMI_REGULATOR_TYPE_BOOST_BYP = 0x1f, + SPMI_REGULATOR_TYPE_ULT_LDO = 0x21, + SPMI_REGULATOR_TYPE_ULT_BUCK = 0x22, +}; + +enum spmi_regulator_subtype { + SPMI_REGULATOR_SUBTYPE_GP_CTL = 0x08, + SPMI_REGULATOR_SUBTYPE_RF_CTL = 0x09, + SPMI_REGULATOR_SUBTYPE_N50 = 0x01, + SPMI_REGULATOR_SUBTYPE_N150 = 0x02, + SPMI_REGULATOR_SUBTYPE_N300 = 0x03, + SPMI_REGULATOR_SUBTYPE_N600 = 0x04, + SPMI_REGULATOR_SUBTYPE_N1200 = 0x05, + SPMI_REGULATOR_SUBTYPE_N600_ST = 0x06, + SPMI_REGULATOR_SUBTYPE_N1200_ST = 0x07, + SPMI_REGULATOR_SUBTYPE_N900_ST = 0x14, + SPMI_REGULATOR_SUBTYPE_N300_ST = 0x15, + SPMI_REGULATOR_SUBTYPE_P50 = 0x08, + SPMI_REGULATOR_SUBTYPE_P150 = 0x09, + SPMI_REGULATOR_SUBTYPE_P300 = 0x0a, + SPMI_REGULATOR_SUBTYPE_P600 = 0x0b, + SPMI_REGULATOR_SUBTYPE_P1200 = 0x0c, + SPMI_REGULATOR_SUBTYPE_LN = 0x10, + SPMI_REGULATOR_SUBTYPE_LV_P50 = 0x28, + SPMI_REGULATOR_SUBTYPE_LV_P150 = 0x29, + SPMI_REGULATOR_SUBTYPE_LV_P300 = 0x2a, + SPMI_REGULATOR_SUBTYPE_LV_P600 = 0x2b, + SPMI_REGULATOR_SUBTYPE_LV_P1200 = 0x2c, + SPMI_REGULATOR_SUBTYPE_LV_P450 = 0x2d, + SPMI_REGULATOR_SUBTYPE_LV100 = 0x01, + SPMI_REGULATOR_SUBTYPE_LV300 = 0x02, + SPMI_REGULATOR_SUBTYPE_MV300 = 0x08, + SPMI_REGULATOR_SUBTYPE_MV500 = 0x09, + SPMI_REGULATOR_SUBTYPE_HDMI = 0x10, + SPMI_REGULATOR_SUBTYPE_OTG = 0x11, + SPMI_REGULATOR_SUBTYPE_5V_BOOST = 0x01, + SPMI_REGULATOR_SUBTYPE_FTS_CTL = 0x08, + SPMI_REGULATOR_SUBTYPE_FTS2p5_CTL = 0x09, + SPMI_REGULATOR_SUBTYPE_BB_2A = 0x01, + SPMI_REGULATOR_SUBTYPE_ULT_HF_CTL1 = 0x0d, + SPMI_REGULATOR_SUBTYPE_ULT_HF_CTL2 = 0x0e, + SPMI_REGULATOR_SUBTYPE_ULT_HF_CTL3 = 0x0f, + SPMI_REGULATOR_SUBTYPE_ULT_HF_CTL4 = 0x10, +}; + +enum spmi_common_regulator_registers { + SPMI_COMMON_REG_DIG_MAJOR_REV = 0x01, + SPMI_COMMON_REG_TYPE = 0x04, + SPMI_COMMON_REG_SUBTYPE = 0x05, + SPMI_COMMON_REG_VOLTAGE_RANGE = 0x40, + SPMI_COMMON_REG_VOLTAGE_SET = 0x41, + SPMI_COMMON_REG_MODE = 0x45, + SPMI_COMMON_REG_ENABLE = 0x46, + SPMI_COMMON_REG_PULL_DOWN = 0x48, + SPMI_COMMON_REG_SOFT_START = 0x4c, + SPMI_COMMON_REG_STEP_CTRL = 0x61, +}; + +enum spmi_vs_registers { + SPMI_VS_REG_OCP = 0x4a, + SPMI_VS_REG_SOFT_START = 0x4c, +}; + +enum spmi_boost_registers { + SPMI_BOOST_REG_CURRENT_LIMIT = 0x4a, +}; + +enum spmi_boost_byp_registers { + SPMI_BOOST_BYP_REG_CURRENT_LIMIT = 0x4b, +}; + +/* Used for indexing into ctrl_reg. These are offets from 0x40 */ +enum spmi_common_control_register_index { + SPMI_COMMON_IDX_VOLTAGE_RANGE = 0, + SPMI_COMMON_IDX_VOLTAGE_SET = 1, + SPMI_COMMON_IDX_MODE = 5, + SPMI_COMMON_IDX_ENABLE = 6, +}; + +/* Common regulator control register layout */ +#define SPMI_COMMON_ENABLE_MASK 0x80 +#define SPMI_COMMON_ENABLE 0x80 +#define SPMI_COMMON_DISABLE 0x00 +#define SPMI_COMMON_ENABLE_FOLLOW_HW_EN3_MASK 0x08 +#define SPMI_COMMON_ENABLE_FOLLOW_HW_EN2_MASK 0x04 +#define SPMI_COMMON_ENABLE_FOLLOW_HW_EN1_MASK 0x02 +#define SPMI_COMMON_ENABLE_FOLLOW_HW_EN0_MASK 0x01 +#define SPMI_COMMON_ENABLE_FOLLOW_ALL_MASK 0x0f + +/* Common regulator mode register layout */ +#define SPMI_COMMON_MODE_HPM_MASK 0x80 +#define SPMI_COMMON_MODE_AUTO_MASK 0x40 +#define SPMI_COMMON_MODE_BYPASS_MASK 0x20 +#define SPMI_COMMON_MODE_FOLLOW_AWAKE_MASK 0x10 +#define SPMI_COMMON_MODE_FOLLOW_HW_EN3_MASK 0x08 +#define SPMI_COMMON_MODE_FOLLOW_HW_EN2_MASK 0x04 +#define SPMI_COMMON_MODE_FOLLOW_HW_EN1_MASK 0x02 +#define SPMI_COMMON_MODE_FOLLOW_HW_EN0_MASK 0x01 +#define SPMI_COMMON_MODE_FOLLOW_ALL_MASK 0x1f + +/* Common regulator pull down control register layout */ +#define SPMI_COMMON_PULL_DOWN_ENABLE_MASK 0x80 + +/* LDO regulator current limit control register layout */ +#define SPMI_LDO_CURRENT_LIMIT_ENABLE_MASK 0x80 + +/* LDO regulator soft start control register layout */ +#define SPMI_LDO_SOFT_START_ENABLE_MASK 0x80 + +/* VS regulator over current protection control register layout */ +#define SPMI_VS_OCP_OVERRIDE 0x01 +#define SPMI_VS_OCP_NO_OVERRIDE 0x00 + +/* VS regulator soft start control register layout */ +#define SPMI_VS_SOFT_START_ENABLE_MASK 0x80 +#define SPMI_VS_SOFT_START_SEL_MASK 0x03 + +/* Boost regulator current limit control register layout */ +#define SPMI_BOOST_CURRENT_LIMIT_ENABLE_MASK 0x80 +#define SPMI_BOOST_CURRENT_LIMIT_MASK 0x07 + +#define SPMI_VS_OCP_DEFAULT_MAX_RETRIES 10 +#define SPMI_VS_OCP_DEFAULT_RETRY_DELAY_MS 30 +#define SPMI_VS_OCP_FALL_DELAY_US 90 +#define SPMI_VS_OCP_FAULT_DELAY_US 20000 + +#define SPMI_FTSMPS_STEP_CTRL_STEP_MASK 0x18 +#define SPMI_FTSMPS_STEP_CTRL_STEP_SHIFT 3 +#define SPMI_FTSMPS_STEP_CTRL_DELAY_MASK 0x07 +#define SPMI_FTSMPS_STEP_CTRL_DELAY_SHIFT 0 + +/* Clock rate in kHz of the FTSMPS regulator reference clock. */ +#define SPMI_FTSMPS_CLOCK_RATE 19200 + +/* Minimum voltage stepper delay for each step. */ +#define SPMI_FTSMPS_STEP_DELAY 8 + +/* + * The ratio SPMI_FTSMPS_STEP_MARGIN_NUM/SPMI_FTSMPS_STEP_MARGIN_DEN is used to + * adjust the step rate in order to account for oscillator variance. + */ +#define SPMI_FTSMPS_STEP_MARGIN_NUM 4 +#define SPMI_FTSMPS_STEP_MARGIN_DEN 5 + +/* + * This voltage in uV is returned by get_voltage functions when there is no way + * to determine the current voltage level. It is needed because the regulator + * framework treats a 0 uV voltage as an error. + */ +#define VOLTAGE_UNKNOWN 1 + +/* VSET value to decide the range of ULT SMPS */ +#define ULT_SMPS_RANGE_SPLIT 0x60 + +/** + * struct spmi_voltage_range - regulator set point voltage mapping description + * @min_uV: Minimum programmable output voltage resulting from + * set point register value 0x00 + * @max_uV: Maximum programmable output voltage + * @step_uV: Output voltage increase resulting from the set point + * register value increasing by 1 + * @set_point_min_uV: Minimum allowed voltage + * @set_point_max_uV: Maximum allowed voltage. This may be tweaked in order + * to pick which range should be used in the case of + * overlapping set points. + * @n_voltages: Number of preferred voltage set points present in this + * range + * @range_sel: Voltage range register value corresponding to this range + * + * The following relationships must be true for the values used in this struct: + * (max_uV - min_uV) % step_uV == 0 + * (set_point_min_uV - min_uV) % step_uV == 0* + * (set_point_max_uV - min_uV) % step_uV == 0* + * n_voltages = (set_point_max_uV - set_point_min_uV) / step_uV + 1 + * + * *Note, set_point_min_uV == set_point_max_uV == 0 is allowed in order to + * specify that the voltage range has meaning, but is not preferred. + */ +struct spmi_voltage_range { + int min_uV; + int max_uV; + int step_uV; + int set_point_min_uV; + int set_point_max_uV; + unsigned n_voltages; + u8 range_sel; +}; + +/* + * The ranges specified in the spmi_voltage_set_points struct must be listed + * so that range[i].set_point_max_uV < range[i+1].set_point_min_uV. + */ +struct spmi_voltage_set_points { + struct spmi_voltage_range *range; + int count; + unsigned n_voltages; +}; + +struct spmi_regulator { + struct regulator_desc desc; + struct device *dev; + struct delayed_work ocp_work; + struct regmap *regmap; + struct spmi_voltage_set_points *set_points; + enum spmi_regulator_logical_type logical_type; + int ocp_irq; + int ocp_count; + int ocp_max_retries; + int ocp_retry_delay_ms; + int hpm_min_load; + int slew_rate; + ktime_t vs_enable_time; + u16 base; + struct list_head node; +}; + +struct spmi_regulator_mapping { + enum spmi_regulator_type type; + enum spmi_regulator_subtype subtype; + enum spmi_regulator_logical_type logical_type; + u32 revision_min; + u32 revision_max; + struct regulator_ops *ops; + struct spmi_voltage_set_points *set_points; + int hpm_min_load; +}; + +struct spmi_regulator_data { + const char *name; + u16 base; + const char *supply; + const char *ocp; + u16 force_type; +}; + +#define SPMI_VREG(_type, _subtype, _dig_major_min, _dig_major_max, \ + _logical_type, _ops_val, _set_points_val, _hpm_min_load) \ + { \ + .type = SPMI_REGULATOR_TYPE_##_type, \ + .subtype = SPMI_REGULATOR_SUBTYPE_##_subtype, \ + .revision_min = _dig_major_min, \ + .revision_max = _dig_major_max, \ + .logical_type = SPMI_REGULATOR_LOGICAL_TYPE_##_logical_type, \ + .ops = &spmi_##_ops_val##_ops, \ + .set_points = &_set_points_val##_set_points, \ + .hpm_min_load = _hpm_min_load, \ + } + +#define SPMI_VREG_VS(_subtype, _dig_major_min, _dig_major_max) \ + { \ + .type = SPMI_REGULATOR_TYPE_VS, \ + .subtype = SPMI_REGULATOR_SUBTYPE_##_subtype, \ + .revision_min = _dig_major_min, \ + .revision_max = _dig_major_max, \ + .logical_type = SPMI_REGULATOR_LOGICAL_TYPE_VS, \ + .ops = &spmi_vs_ops, \ + } + +#define SPMI_VOLTAGE_RANGE(_range_sel, _min_uV, _set_point_min_uV, \ + _set_point_max_uV, _max_uV, _step_uV) \ + { \ + .min_uV = _min_uV, \ + .max_uV = _max_uV, \ + .set_point_min_uV = _set_point_min_uV, \ + .set_point_max_uV = _set_point_max_uV, \ + .step_uV = _step_uV, \ + .range_sel = _range_sel, \ + } + +#define DEFINE_SPMI_SET_POINTS(name) \ +struct spmi_voltage_set_points name##_set_points = { \ + .range = name##_ranges, \ + .count = ARRAY_SIZE(name##_ranges), \ +} + +/* + * These tables contain the physically available PMIC regulator voltage setpoint + * ranges. Where two ranges overlap in hardware, one of the ranges is trimmed + * to ensure that the setpoints available to software are monotonically + * increasing and unique. The set_voltage callback functions expect these + * properties to hold. + */ +static struct spmi_voltage_range pldo_ranges[] = { + SPMI_VOLTAGE_RANGE(2, 750000, 750000, 1537500, 1537500, 12500), + SPMI_VOLTAGE_RANGE(3, 1500000, 1550000, 3075000, 3075000, 25000), + SPMI_VOLTAGE_RANGE(4, 1750000, 3100000, 4900000, 4900000, 50000), +}; + +static struct spmi_voltage_range nldo1_ranges[] = { + SPMI_VOLTAGE_RANGE(2, 750000, 750000, 1537500, 1537500, 12500), +}; + +static struct spmi_voltage_range nldo2_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 375000, 0, 0, 1537500, 12500), + SPMI_VOLTAGE_RANGE(1, 375000, 375000, 768750, 768750, 6250), + SPMI_VOLTAGE_RANGE(2, 750000, 775000, 1537500, 1537500, 12500), +}; + +static struct spmi_voltage_range nldo3_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 375000, 375000, 1537500, 1537500, 12500), + SPMI_VOLTAGE_RANGE(1, 375000, 0, 0, 1537500, 12500), + SPMI_VOLTAGE_RANGE(2, 750000, 0, 0, 1537500, 12500), +}; + +static struct spmi_voltage_range ln_ldo_ranges[] = { + SPMI_VOLTAGE_RANGE(1, 690000, 690000, 1110000, 1110000, 60000), + SPMI_VOLTAGE_RANGE(0, 1380000, 1380000, 2220000, 2220000, 120000), +}; + +static struct spmi_voltage_range smps_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 375000, 375000, 1562500, 1562500, 12500), + SPMI_VOLTAGE_RANGE(1, 1550000, 1575000, 3125000, 3125000, 25000), +}; + +static struct spmi_voltage_range ftsmps_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 0, 350000, 1275000, 1275000, 5000), + SPMI_VOLTAGE_RANGE(1, 0, 1280000, 2040000, 2040000, 10000), +}; + +static struct spmi_voltage_range ftsmps2p5_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 80000, 350000, 1355000, 1355000, 5000), + SPMI_VOLTAGE_RANGE(1, 160000, 1360000, 2200000, 2200000, 10000), +}; + +static struct spmi_voltage_range boost_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 4000000, 4000000, 5550000, 5550000, 50000), +}; + +static struct spmi_voltage_range boost_byp_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 2500000, 2500000, 5200000, 5650000, 50000), +}; + +static struct spmi_voltage_range ult_lo_smps_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 375000, 375000, 1562500, 1562500, 12500), + SPMI_VOLTAGE_RANGE(1, 750000, 0, 0, 1525000, 25000), +}; + +static struct spmi_voltage_range ult_ho_smps_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 1550000, 1550000, 2325000, 2325000, 25000), +}; + +static struct spmi_voltage_range ult_nldo_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 375000, 375000, 1537500, 1537500, 12500), +}; + +static struct spmi_voltage_range ult_pldo_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 1750000, 1750000, 3337500, 3337500, 12500), +}; + +static DEFINE_SPMI_SET_POINTS(pldo); +static DEFINE_SPMI_SET_POINTS(nldo1); +static DEFINE_SPMI_SET_POINTS(nldo2); +static DEFINE_SPMI_SET_POINTS(nldo3); +static DEFINE_SPMI_SET_POINTS(ln_ldo); +static DEFINE_SPMI_SET_POINTS(smps); +static DEFINE_SPMI_SET_POINTS(ftsmps); +static DEFINE_SPMI_SET_POINTS(ftsmps2p5); +static DEFINE_SPMI_SET_POINTS(boost); +static DEFINE_SPMI_SET_POINTS(boost_byp); +static DEFINE_SPMI_SET_POINTS(ult_lo_smps); +static DEFINE_SPMI_SET_POINTS(ult_ho_smps); +static DEFINE_SPMI_SET_POINTS(ult_nldo); +static DEFINE_SPMI_SET_POINTS(ult_pldo); + +static inline int spmi_vreg_read(struct spmi_regulator *vreg, u16 addr, u8 *buf, + int len) +{ + return regmap_bulk_read(vreg->regmap, vreg->base + addr, buf, len); +} + +static inline int spmi_vreg_write(struct spmi_regulator *vreg, u16 addr, + u8 *buf, int len) +{ + return regmap_bulk_write(vreg->regmap, vreg->base + addr, buf, len); +} + +static int spmi_vreg_update_bits(struct spmi_regulator *vreg, u16 addr, u8 val, + u8 mask) +{ + return regmap_update_bits(vreg->regmap, vreg->base + addr, mask, val); +} + +static int spmi_regulator_common_is_enabled(struct regulator_dev *rdev) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + u8 reg; + + spmi_vreg_read(vreg, SPMI_COMMON_REG_ENABLE, ®, 1); + + return (reg & SPMI_COMMON_ENABLE_MASK) == SPMI_COMMON_ENABLE; +} + +static int spmi_regulator_common_enable(struct regulator_dev *rdev) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + + return spmi_vreg_update_bits(vreg, SPMI_COMMON_REG_ENABLE, + SPMI_COMMON_ENABLE, SPMI_COMMON_ENABLE_MASK); +} + +static int spmi_regulator_vs_enable(struct regulator_dev *rdev) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + + if (vreg->ocp_irq) { + vreg->ocp_count = 0; + vreg->vs_enable_time = ktime_get(); + } + + return spmi_regulator_common_enable(rdev); +} + +static int spmi_regulator_common_disable(struct regulator_dev *rdev) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + + return spmi_vreg_update_bits(vreg, SPMI_COMMON_REG_ENABLE, + SPMI_COMMON_DISABLE, SPMI_COMMON_ENABLE_MASK); +} + +static int spmi_regulator_select_voltage(struct spmi_regulator *vreg, + int min_uV, int max_uV, u8 *range_sel, u8 *voltage_sel, + unsigned *selector) +{ + const struct spmi_voltage_range *range; + int uV = min_uV; + int lim_min_uV, lim_max_uV, i, range_id, range_max_uV; + + /* Check if request voltage is outside of physically settable range. */ + lim_min_uV = vreg->set_points->range[0].set_point_min_uV; + lim_max_uV = + vreg->set_points->range[vreg->set_points->count - 1].set_point_max_uV; + + if (uV < lim_min_uV && max_uV >= lim_min_uV) + uV = lim_min_uV; + + if (uV < lim_min_uV || uV > lim_max_uV) { + dev_err(vreg->dev, + "request v=[%d, %d] is outside possible v=[%d, %d]\n", + min_uV, max_uV, lim_min_uV, lim_max_uV); + return -EINVAL; + } + + /* Find the range which uV is inside of. */ + for (i = vreg->set_points->count - 1; i > 0; i--) { + range_max_uV = vreg->set_points->range[i - 1].set_point_max_uV; + if (uV > range_max_uV && range_max_uV > 0) + break; + } + + range_id = i; + range = &vreg->set_points->range[range_id]; + *range_sel = range->range_sel; + + /* + * Force uV to be an allowed set point by applying a ceiling function to + * the uV value. + */ + *voltage_sel = (uV - range->min_uV + range->step_uV - 1) + / range->step_uV; + uV = *voltage_sel * range->step_uV + range->min_uV; + + if (uV > max_uV) { + dev_err(vreg->dev, + "request v=[%d, %d] cannot be met by any set point; " + "next set point: %d\n", + min_uV, max_uV, uV); + return -EINVAL; + } + + *selector = 0; + for (i = 0; i < range_id; i++) + *selector += vreg->set_points->range[i].n_voltages; + *selector += (uV - range->set_point_min_uV) / range->step_uV; + + return 0; +} + +static const struct spmi_voltage_range * +spmi_regulator_find_range(struct spmi_regulator *vreg) +{ + u8 range_sel; + const struct spmi_voltage_range *range, *end; + + range = vreg->set_points->range; + end = range + vreg->set_points->count; + + spmi_vreg_read(vreg, SPMI_COMMON_REG_VOLTAGE_RANGE, &range_sel, 1); + + for (; range < end; range++) + if (range->range_sel == range_sel) + return range; + + return NULL; +} + +static int spmi_regulator_select_voltage_same_range(struct spmi_regulator *vreg, + int min_uV, int max_uV, u8 *range_sel, u8 *voltage_sel, + unsigned *selector) +{ + const struct spmi_voltage_range *range; + int uV = min_uV; + int i; + + range = spmi_regulator_find_range(vreg); + if (!range) + goto different_range; + + if (uV < range->min_uV && max_uV >= range->min_uV) + uV = range->min_uV; + + if (uV < range->min_uV || uV > range->max_uV) { + /* Current range doesn't support the requested voltage. */ + goto different_range; + } + + /* + * Force uV to be an allowed set point by applying a ceiling function to + * the uV value. + */ + *voltage_sel = DIV_ROUND_UP(uV - range->min_uV, range->step_uV); + uV = *voltage_sel * range->step_uV + range->min_uV; + + if (uV > max_uV) { + /* + * No set point in the current voltage range is within the + * requested min_uV to max_uV range. + */ + goto different_range; + } + + *selector = 0; + for (i = 0; i < vreg->set_points->count; i++) { + if (uV >= vreg->set_points->range[i].set_point_min_uV + && uV <= vreg->set_points->range[i].set_point_max_uV) + *selector += + (uV - vreg->set_points->range[i].set_point_min_uV) + / vreg->set_points->range[i].step_uV; + break; + + *selector += vreg->set_points->range[i].n_voltages; + } + + if (*selector >= vreg->set_points->n_voltages) + goto different_range; + + return 0; + +different_range: + return spmi_regulator_select_voltage(vreg, min_uV, max_uV, + range_sel, voltage_sel, selector); +} + +static int spmi_regulator_common_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV, unsigned *selector) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + int ret; + u8 buf[2]; + u8 range_sel, voltage_sel; + + /* + * Favor staying in the current voltage range if possible. This avoids + * voltage spikes that occur when changing the voltage range. + */ + ret = spmi_regulator_select_voltage_same_range(vreg, min_uV, max_uV, + &range_sel, &voltage_sel, selector); + if (ret) + return ret; + + buf[0] = range_sel; + buf[1] = voltage_sel; + return spmi_vreg_write(vreg, SPMI_COMMON_REG_VOLTAGE_RANGE, buf, 2); +} + +static int spmi_regulator_set_voltage_time_sel(struct regulator_dev *rdev, + unsigned int old_selector, unsigned int new_selector) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + const struct spmi_voltage_range *range; + int diff_uV; + + range = spmi_regulator_find_range(vreg); + if (!range) + return -EINVAL; + + diff_uV = abs(new_selector - old_selector) * range->step_uV; + + return DIV_ROUND_UP(diff_uV, vreg->slew_rate); +} + +static int spmi_regulator_common_get_voltage(struct regulator_dev *rdev) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + const struct spmi_voltage_range *range; + u8 voltage_sel; + + spmi_vreg_read(vreg, SPMI_COMMON_REG_VOLTAGE_SET, &voltage_sel, 1); + + range = spmi_regulator_find_range(vreg); + if (!range) + return VOLTAGE_UNKNOWN; + + return range->step_uV * voltage_sel + range->min_uV; +} + +static int spmi_regulator_single_range_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV, unsigned *selector) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + int ret; + u8 range_sel, sel; + + ret = spmi_regulator_select_voltage(vreg, min_uV, max_uV, &range_sel, + &sel, selector); + if (ret) { + dev_err(vreg->dev, "could not set voltage, ret=%d\n", ret); + return ret; + } + + /* + * Certain types of regulators do not have a range select register so + * only voltage set register needs to be written. + */ + return spmi_vreg_write(vreg, SPMI_COMMON_REG_VOLTAGE_SET, &sel, 1); +} + +static int spmi_regulator_single_range_get_voltage(struct regulator_dev *rdev) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + const struct spmi_voltage_range *range = vreg->set_points->range; + u8 voltage_sel; + + spmi_vreg_read(vreg, SPMI_COMMON_REG_VOLTAGE_SET, &voltage_sel, 1); + + return range->step_uV * voltage_sel + range->min_uV; +} + +static int spmi_regulator_ult_lo_smps_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV, unsigned *selector) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + int ret; + u8 range_sel, voltage_sel; + + /* + * Favor staying in the current voltage range if possible. This avoids + * voltage spikes that occur when changing the voltage range. + */ + ret = spmi_regulator_select_voltage_same_range(vreg, min_uV, max_uV, + &range_sel, &voltage_sel, selector); + if (ret) + return ret; + + /* + * Calculate VSET based on range + * In case of range 0: voltage_sel is a 7 bit value, can be written + * witout any modification. + * In case of range 1: voltage_sel is a 5 bit value, bits[7-5] set to + * [011]. + */ + if (range_sel == 1) + voltage_sel |= ULT_SMPS_RANGE_SPLIT; + + ret = spmi_vreg_update_bits(vreg, SPMI_COMMON_REG_VOLTAGE_SET, + voltage_sel, 0xff); + if (ret) + return ret; + + return 0; +} + +static int spmi_regulator_ult_lo_smps_get_voltage(struct regulator_dev *rdev) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + const struct spmi_voltage_range *range; + u8 voltage_sel; + + spmi_vreg_read(vreg, SPMI_COMMON_REG_VOLTAGE_SET, &voltage_sel, 1); + + range = spmi_regulator_find_range(vreg); + if (!range) + return VOLTAGE_UNKNOWN; + + if (range->range_sel == 1) + voltage_sel &= ~ULT_SMPS_RANGE_SPLIT; + + return range->step_uV * voltage_sel + range->min_uV; +} + +static int spmi_regulator_common_list_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + int uV = 0; + int i; + + if (selector >= vreg->set_points->n_voltages) + return 0; + + for (i = 0; i < vreg->set_points->count; i++) { + if (selector < vreg->set_points->range[i].n_voltages) + uV = selector * vreg->set_points->range[i].step_uV + + vreg->set_points->range[i].set_point_min_uV; + break; + + selector -= vreg->set_points->range[i].n_voltages; + } + + return uV; +} + +static int +spmi_regulator_common_set_bypass(struct regulator_dev *rdev, bool enable) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + u8 mask = SPMI_COMMON_MODE_BYPASS_MASK; + u8 val = 0; + + if (enable) + val = mask; + + return spmi_vreg_update_bits(vreg, SPMI_COMMON_REG_MODE, val, mask); +} + +static int +spmi_regulator_common_get_bypass(struct regulator_dev *rdev, bool *enable) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + u8 val; + int ret; + + ret = spmi_vreg_read(vreg, SPMI_COMMON_REG_MODE, &val, 1); + *enable = val & SPMI_COMMON_MODE_BYPASS_MASK; + + return ret; +} + +static unsigned int spmi_regulator_common_get_mode(struct regulator_dev *rdev) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + u8 reg; + + spmi_vreg_read(vreg, SPMI_COMMON_REG_MODE, ®, 1); + + if (reg & SPMI_COMMON_MODE_HPM_MASK) + return REGULATOR_MODE_NORMAL; + + return REGULATOR_MODE_IDLE; +} + +static int +spmi_regulator_common_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + u8 mask = SPMI_COMMON_MODE_HPM_MASK; + u8 val = 0; + + if (mode == REGULATOR_MODE_NORMAL) + val = mask; + + return spmi_vreg_update_bits(vreg, SPMI_COMMON_REG_MODE, val, mask); +} + +static int +spmi_regulator_common_set_load(struct regulator_dev *rdev, int load_uA) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + unsigned int mode; + + if (load_uA >= vreg->hpm_min_load) + mode = REGULATOR_MODE_NORMAL; + else + mode = REGULATOR_MODE_IDLE; + + return spmi_regulator_common_set_mode(rdev, mode); +} + +static int spmi_regulator_common_set_pull_down(struct regulator_dev *rdev) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + unsigned int mask = SPMI_COMMON_PULL_DOWN_ENABLE_MASK; + + return spmi_vreg_update_bits(vreg, SPMI_COMMON_REG_PULL_DOWN, + mask, mask); +} + +static int spmi_regulator_common_set_soft_start(struct regulator_dev *rdev) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + unsigned int mask = SPMI_LDO_SOFT_START_ENABLE_MASK; + + return spmi_vreg_update_bits(vreg, SPMI_COMMON_REG_SOFT_START, + mask, mask); +} + +static int spmi_regulator_set_ilim(struct regulator_dev *rdev, int ilim_uA) +{ + struct spmi_regulator *vreg = rdev_get_drvdata(rdev); + enum spmi_regulator_logical_type type = vreg->logical_type; + unsigned int current_reg; + u8 reg; + u8 mask = SPMI_BOOST_CURRENT_LIMIT_MASK | + SPMI_BOOST_CURRENT_LIMIT_ENABLE_MASK; + int max = (SPMI_BOOST_CURRENT_LIMIT_MASK + 1) * 500; + + if (type == SPMI_REGULATOR_LOGICAL_TYPE_BOOST) + current_reg = SPMI_BOOST_REG_CURRENT_LIMIT; + else + current_reg = SPMI_BOOST_BYP_REG_CURRENT_LIMIT; + + if (ilim_uA > max || ilim_uA <= 0) + return -EINVAL; + + reg = (ilim_uA - 1) / 500; + reg |= SPMI_BOOST_CURRENT_LIMIT_ENABLE_MASK; + + return spmi_vreg_update_bits(vreg, current_reg, reg, mask); +} + +static int spmi_regulator_vs_clear_ocp(struct spmi_regulator *vreg) +{ + int ret; + + ret = spmi_vreg_update_bits(vreg, SPMI_COMMON_REG_ENABLE, + SPMI_COMMON_DISABLE, SPMI_COMMON_ENABLE_MASK); + + vreg->vs_enable_time = ktime_get(); + + ret = spmi_vreg_update_bits(vreg, SPMI_COMMON_REG_ENABLE, + SPMI_COMMON_ENABLE, SPMI_COMMON_ENABLE_MASK); + + return ret; +} + +static void spmi_regulator_vs_ocp_work(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct spmi_regulator *vreg + = container_of(dwork, struct spmi_regulator, ocp_work); + + spmi_regulator_vs_clear_ocp(vreg); +} + +static irqreturn_t spmi_regulator_vs_ocp_isr(int irq, void *data) +{ + struct spmi_regulator *vreg = data; + ktime_t ocp_irq_time; + s64 ocp_trigger_delay_us; + + ocp_irq_time = ktime_get(); + ocp_trigger_delay_us = ktime_us_delta(ocp_irq_time, + vreg->vs_enable_time); + + /* + * Reset the OCP count if there is a large delay between switch enable + * and when OCP triggers. This is indicative of a hotplug event as + * opposed to a fault. + */ + if (ocp_trigger_delay_us > SPMI_VS_OCP_FAULT_DELAY_US) + vreg->ocp_count = 0; + + /* Wait for switch output to settle back to 0 V after OCP triggered. */ + udelay(SPMI_VS_OCP_FALL_DELAY_US); + + vreg->ocp_count++; + + if (vreg->ocp_count == 1) { + /* Immediately clear the over current condition. */ + spmi_regulator_vs_clear_ocp(vreg); + } else if (vreg->ocp_count <= vreg->ocp_max_retries) { + /* Schedule the over current clear task to run later. */ + schedule_delayed_work(&vreg->ocp_work, + msecs_to_jiffies(vreg->ocp_retry_delay_ms) + 1); + } else { + dev_err(vreg->dev, + "OCP triggered %d times; no further retries\n", + vreg->ocp_count); + } + + return IRQ_HANDLED; +} + +static struct regulator_ops spmi_smps_ops = { + .enable = spmi_regulator_common_enable, + .disable = spmi_regulator_common_disable, + .is_enabled = spmi_regulator_common_is_enabled, + .set_voltage = spmi_regulator_common_set_voltage, + .get_voltage = spmi_regulator_common_get_voltage, + .list_voltage = spmi_regulator_common_list_voltage, + .set_mode = spmi_regulator_common_set_mode, + .get_mode = spmi_regulator_common_get_mode, + .set_load = spmi_regulator_common_set_load, + .set_pull_down = spmi_regulator_common_set_pull_down, +}; + +static struct regulator_ops spmi_ldo_ops = { + .enable = spmi_regulator_common_enable, + .disable = spmi_regulator_common_disable, + .is_enabled = spmi_regulator_common_is_enabled, + .set_voltage = spmi_regulator_common_set_voltage, + .get_voltage = spmi_regulator_common_get_voltage, + .list_voltage = spmi_regulator_common_list_voltage, + .set_mode = spmi_regulator_common_set_mode, + .get_mode = spmi_regulator_common_get_mode, + .set_load = spmi_regulator_common_set_load, + .set_bypass = spmi_regulator_common_set_bypass, + .get_bypass = spmi_regulator_common_get_bypass, + .set_pull_down = spmi_regulator_common_set_pull_down, + .set_soft_start = spmi_regulator_common_set_soft_start, +}; + +static struct regulator_ops spmi_ln_ldo_ops = { + .enable = spmi_regulator_common_enable, + .disable = spmi_regulator_common_disable, + .is_enabled = spmi_regulator_common_is_enabled, + .set_voltage = spmi_regulator_common_set_voltage, + .get_voltage = spmi_regulator_common_get_voltage, + .list_voltage = spmi_regulator_common_list_voltage, + .set_bypass = spmi_regulator_common_set_bypass, + .get_bypass = spmi_regulator_common_get_bypass, +}; + +static struct regulator_ops spmi_vs_ops = { + .enable = spmi_regulator_vs_enable, + .disable = spmi_regulator_common_disable, + .is_enabled = spmi_regulator_common_is_enabled, + .set_pull_down = spmi_regulator_common_set_pull_down, + .set_soft_start = spmi_regulator_common_set_soft_start, +}; + +static struct regulator_ops spmi_boost_ops = { + .enable = spmi_regulator_common_enable, + .disable = spmi_regulator_common_disable, + .is_enabled = spmi_regulator_common_is_enabled, + .set_voltage = spmi_regulator_single_range_set_voltage, + .get_voltage = spmi_regulator_single_range_get_voltage, + .list_voltage = spmi_regulator_common_list_voltage, + .set_input_current_limit = spmi_regulator_set_ilim, +}; + +static struct regulator_ops spmi_ftsmps_ops = { + .enable = spmi_regulator_common_enable, + .disable = spmi_regulator_common_disable, + .is_enabled = spmi_regulator_common_is_enabled, + .set_voltage = spmi_regulator_common_set_voltage, + .set_voltage_time_sel = spmi_regulator_set_voltage_time_sel, + .get_voltage = spmi_regulator_common_get_voltage, + .list_voltage = spmi_regulator_common_list_voltage, + .set_mode = spmi_regulator_common_set_mode, + .get_mode = spmi_regulator_common_get_mode, + .set_load = spmi_regulator_common_set_load, + .set_pull_down = spmi_regulator_common_set_pull_down, +}; + +static struct regulator_ops spmi_ult_lo_smps_ops = { + .enable = spmi_regulator_common_enable, + .disable = spmi_regulator_common_disable, + .is_enabled = spmi_regulator_common_is_enabled, + .set_voltage = spmi_regulator_ult_lo_smps_set_voltage, + .get_voltage = spmi_regulator_ult_lo_smps_get_voltage, + .list_voltage = spmi_regulator_common_list_voltage, + .set_mode = spmi_regulator_common_set_mode, + .get_mode = spmi_regulator_common_get_mode, + .set_load = spmi_regulator_common_set_load, + .set_pull_down = spmi_regulator_common_set_pull_down, +}; + +static struct regulator_ops spmi_ult_ho_smps_ops = { + .enable = spmi_regulator_common_enable, + .disable = spmi_regulator_common_disable, + .is_enabled = spmi_regulator_common_is_enabled, + .set_voltage = spmi_regulator_single_range_set_voltage, + .get_voltage = spmi_regulator_single_range_get_voltage, + .list_voltage = spmi_regulator_common_list_voltage, + .set_mode = spmi_regulator_common_set_mode, + .get_mode = spmi_regulator_common_get_mode, + .set_load = spmi_regulator_common_set_load, + .set_pull_down = spmi_regulator_common_set_pull_down, +}; + +static struct regulator_ops spmi_ult_ldo_ops = { + .enable = spmi_regulator_common_enable, + .disable = spmi_regulator_common_disable, + .is_enabled = spmi_regulator_common_is_enabled, + .set_voltage = spmi_regulator_single_range_set_voltage, + .get_voltage = spmi_regulator_single_range_get_voltage, + .list_voltage = spmi_regulator_common_list_voltage, + .set_mode = spmi_regulator_common_set_mode, + .get_mode = spmi_regulator_common_get_mode, + .set_load = spmi_regulator_common_set_load, + .set_bypass = spmi_regulator_common_set_bypass, + .get_bypass = spmi_regulator_common_get_bypass, + .set_pull_down = spmi_regulator_common_set_pull_down, + .set_soft_start = spmi_regulator_common_set_soft_start, +}; + +/* Maximum possible digital major revision value */ +#define INF 0xFF + +static const struct spmi_regulator_mapping supported_regulators[] = { + /* type subtype dig_min dig_max ltype ops setpoints hpm_min */ + SPMI_VREG(BUCK, GP_CTL, 0, INF, SMPS, smps, smps, 100000), + SPMI_VREG(LDO, N300, 0, INF, LDO, ldo, nldo1, 10000), + SPMI_VREG(LDO, N600, 0, 0, LDO, ldo, nldo2, 10000), + SPMI_VREG(LDO, N1200, 0, 0, LDO, ldo, nldo2, 10000), + SPMI_VREG(LDO, N600, 1, INF, LDO, ldo, nldo3, 10000), + SPMI_VREG(LDO, N1200, 1, INF, LDO, ldo, nldo3, 10000), + SPMI_VREG(LDO, N600_ST, 0, 0, LDO, ldo, nldo2, 10000), + SPMI_VREG(LDO, N1200_ST, 0, 0, LDO, ldo, nldo2, 10000), + SPMI_VREG(LDO, N600_ST, 1, INF, LDO, ldo, nldo3, 10000), + SPMI_VREG(LDO, N1200_ST, 1, INF, LDO, ldo, nldo3, 10000), + SPMI_VREG(LDO, P50, 0, INF, LDO, ldo, pldo, 5000), + SPMI_VREG(LDO, P150, 0, INF, LDO, ldo, pldo, 10000), + SPMI_VREG(LDO, P300, 0, INF, LDO, ldo, pldo, 10000), + SPMI_VREG(LDO, P600, 0, INF, LDO, ldo, pldo, 10000), + SPMI_VREG(LDO, P1200, 0, INF, LDO, ldo, pldo, 10000), + SPMI_VREG(LDO, LN, 0, INF, LN_LDO, ln_ldo, ln_ldo, 0), + SPMI_VREG(LDO, LV_P50, 0, INF, LDO, ldo, pldo, 5000), + SPMI_VREG(LDO, LV_P150, 0, INF, LDO, ldo, pldo, 10000), + SPMI_VREG(LDO, LV_P300, 0, INF, LDO, ldo, pldo, 10000), + SPMI_VREG(LDO, LV_P600, 0, INF, LDO, ldo, pldo, 10000), + SPMI_VREG(LDO, LV_P1200, 0, INF, LDO, ldo, pldo, 10000), + SPMI_VREG_VS(LV100, 0, INF), + SPMI_VREG_VS(LV300, 0, INF), + SPMI_VREG_VS(MV300, 0, INF), + SPMI_VREG_VS(MV500, 0, INF), + SPMI_VREG_VS(HDMI, 0, INF), + SPMI_VREG_VS(OTG, 0, INF), + SPMI_VREG(BOOST, 5V_BOOST, 0, INF, BOOST, boost, boost, 0), + SPMI_VREG(FTS, FTS_CTL, 0, INF, FTSMPS, ftsmps, ftsmps, 100000), + SPMI_VREG(FTS, FTS2p5_CTL, 0, INF, FTSMPS, ftsmps, ftsmps2p5, 100000), + SPMI_VREG(BOOST_BYP, BB_2A, 0, INF, BOOST_BYP, boost, boost_byp, 0), + SPMI_VREG(ULT_BUCK, ULT_HF_CTL1, 0, INF, ULT_LO_SMPS, ult_lo_smps, + ult_lo_smps, 100000), + SPMI_VREG(ULT_BUCK, ULT_HF_CTL2, 0, INF, ULT_LO_SMPS, ult_lo_smps, + ult_lo_smps, 100000), + SPMI_VREG(ULT_BUCK, ULT_HF_CTL3, 0, INF, ULT_LO_SMPS, ult_lo_smps, + ult_lo_smps, 100000), + SPMI_VREG(ULT_BUCK, ULT_HF_CTL4, 0, INF, ULT_HO_SMPS, ult_ho_smps, + ult_ho_smps, 100000), + SPMI_VREG(ULT_LDO, N300_ST, 0, INF, ULT_LDO, ult_ldo, ult_nldo, 10000), + SPMI_VREG(ULT_LDO, N600_ST, 0, INF, ULT_LDO, ult_ldo, ult_nldo, 10000), + SPMI_VREG(ULT_LDO, N900_ST, 0, INF, ULT_LDO, ult_ldo, ult_nldo, 10000), + SPMI_VREG(ULT_LDO, N1200_ST, 0, INF, ULT_LDO, ult_ldo, ult_nldo, 10000), + SPMI_VREG(ULT_LDO, LV_P150, 0, INF, ULT_LDO, ult_ldo, ult_pldo, 10000), + SPMI_VREG(ULT_LDO, LV_P300, 0, INF, ULT_LDO, ult_ldo, ult_pldo, 10000), + SPMI_VREG(ULT_LDO, LV_P450, 0, INF, ULT_LDO, ult_ldo, ult_pldo, 10000), + SPMI_VREG(ULT_LDO, P600, 0, INF, ULT_LDO, ult_ldo, ult_pldo, 10000), + SPMI_VREG(ULT_LDO, P150, 0, INF, ULT_LDO, ult_ldo, ult_pldo, 10000), + SPMI_VREG(ULT_LDO, P50, 0, INF, ULT_LDO, ult_ldo, ult_pldo, 5000), +}; + +static void spmi_calculate_num_voltages(struct spmi_voltage_set_points *points) +{ + unsigned int n; + struct spmi_voltage_range *range = points->range; + + for (; range < points->range + points->count; range++) { + n = 0; + if (range->set_point_max_uV) { + n = range->set_point_max_uV - range->set_point_min_uV; + n /= range->step_uV + 1; + } + range->n_voltages = n; + points->n_voltages += n; + } +} + +static int spmi_regulator_match(struct spmi_regulator *vreg, u16 force_type) +{ + const struct spmi_regulator_mapping *mapping; + int ret, i; + u32 dig_major_rev; + u8 version[SPMI_COMMON_REG_SUBTYPE - SPMI_COMMON_REG_DIG_MAJOR_REV + 1]; + u8 type, subtype; + + ret = spmi_vreg_read(vreg, SPMI_COMMON_REG_DIG_MAJOR_REV, version, + ARRAY_SIZE(version)); + if (ret) { + dev_err(vreg->dev, "could not read version registers\n"); + return ret; + } + dig_major_rev = version[SPMI_COMMON_REG_DIG_MAJOR_REV + - SPMI_COMMON_REG_DIG_MAJOR_REV]; + if (!force_type) { + type = version[SPMI_COMMON_REG_TYPE - + SPMI_COMMON_REG_DIG_MAJOR_REV]; + subtype = version[SPMI_COMMON_REG_SUBTYPE - + SPMI_COMMON_REG_DIG_MAJOR_REV]; + } else { + type = force_type >> 8; + subtype = force_type; + } + + for (i = 0; i < ARRAY_SIZE(supported_regulators); i++) { + mapping = &supported_regulators[i]; + if (mapping->type == type && mapping->subtype == subtype + && mapping->revision_min <= dig_major_rev + && mapping->revision_max >= dig_major_rev) + goto found; + } + + dev_err(vreg->dev, + "unsupported regulator: name=%s type=0x%02X, subtype=0x%02X, dig major rev=0x%02X\n", + vreg->desc.name, type, subtype, dig_major_rev); + + return -ENODEV; + +found: + vreg->logical_type = mapping->logical_type; + vreg->set_points = mapping->set_points; + vreg->hpm_min_load = mapping->hpm_min_load; + vreg->desc.ops = mapping->ops; + + if (mapping->set_points) { + if (!mapping->set_points->n_voltages) + spmi_calculate_num_voltages(mapping->set_points); + vreg->desc.n_voltages = mapping->set_points->n_voltages; + } + + return 0; +} + +static int spmi_regulator_ftsmps_init_slew_rate(struct spmi_regulator *vreg) +{ + int ret; + u8 reg = 0; + int step, delay, slew_rate; + const struct spmi_voltage_range *range; + + ret = spmi_vreg_read(vreg, SPMI_COMMON_REG_STEP_CTRL, ®, 1); + if (ret) { + dev_err(vreg->dev, "spmi read failed, ret=%d\n", ret); + return ret; + } + + range = spmi_regulator_find_range(vreg); + if (!range) + return -EINVAL; + + step = reg & SPMI_FTSMPS_STEP_CTRL_STEP_MASK; + step >>= SPMI_FTSMPS_STEP_CTRL_STEP_SHIFT; + + delay = reg & SPMI_FTSMPS_STEP_CTRL_DELAY_MASK; + delay >>= SPMI_FTSMPS_STEP_CTRL_DELAY_SHIFT; + + /* slew_rate has units of uV/us */ + slew_rate = SPMI_FTSMPS_CLOCK_RATE * range->step_uV * (1 << step); + slew_rate /= 1000 * (SPMI_FTSMPS_STEP_DELAY << delay); + slew_rate *= SPMI_FTSMPS_STEP_MARGIN_NUM; + slew_rate /= SPMI_FTSMPS_STEP_MARGIN_DEN; + + /* Ensure that the slew rate is greater than 0 */ + vreg->slew_rate = max(slew_rate, 1); + + return ret; +} + +static unsigned int spmi_regulator_of_map_mode(unsigned int mode) +{ + if (mode) + return REGULATOR_MODE_NORMAL; + + return REGULATOR_MODE_IDLE; +} + +static int spmi_regulator_of_parse(struct device_node *node, + const struct regulator_desc *desc, + struct regulator_config *config) +{ + struct spmi_regulator *vreg = config->driver_data; + struct device *dev = config->dev; + int ret; + + vreg->ocp_max_retries = SPMI_VS_OCP_DEFAULT_MAX_RETRIES; + vreg->ocp_retry_delay_ms = SPMI_VS_OCP_DEFAULT_RETRY_DELAY_MS; + + if (vreg->logical_type == SPMI_REGULATOR_LOGICAL_TYPE_FTSMPS) { + ret = spmi_regulator_ftsmps_init_slew_rate(vreg); + if (ret) + return ret; + } + + if (vreg->logical_type != SPMI_REGULATOR_LOGICAL_TYPE_VS) + vreg->ocp_irq = 0; + + if (vreg->ocp_irq) { + ret = devm_request_irq(dev, vreg->ocp_irq, + spmi_regulator_vs_ocp_isr, IRQF_TRIGGER_RISING, "ocp", + vreg); + if (ret < 0) { + dev_err(dev, "failed to request irq %d, ret=%d\n", + vreg->ocp_irq, ret); + return ret; + } + + INIT_DELAYED_WORK(&vreg->ocp_work, spmi_regulator_vs_ocp_work); + } + + return 0; +} + +static const struct spmi_regulator_data pm8941_regulators[] = { + { "s1", 0x1400, "vdd_s1", }, + { "s2", 0x1700, "vdd_s2", }, + { "s3", 0x1a00, "vdd_s3", }, + { "l1", 0x4000, "vdd_l1_l3", }, + { "l2", 0x4100, "vdd_l2_lvs_1_2_3", }, + { "l3", 0x4200, "vdd_l1_l3", }, + { "l4", 0x4300, "vdd_l4_l11", }, + { "l5", 0x4400, "vdd_l5_l7", NULL, 0x0410 }, + { "l6", 0x4500, "vdd_l6_l12_l14_l15", }, + { "l7", 0x4600, "vdd_l5_l7", NULL, 0x0410 }, + { "l8", 0x4700, "vdd_l8_l16_l18_19", }, + { "l9", 0x4800, "vdd_l9_l10_l17_l22", }, + { "l10", 0x4900, "vdd_l9_l10_l17_l22", }, + { "l11", 0x4a00, "vdd_l4_l11", }, + { "l12", 0x4b00, "vdd_l6_l12_l14_l15", }, + { "l13", 0x4c00, "vdd_l13_l20_l23_l24", }, + { "l14", 0x4d00, "vdd_l6_l12_l14_l15", }, + { "l15", 0x4e00, "vdd_l6_l12_l14_l15", }, + { "l16", 0x4f00, "vdd_l8_l16_l18_19", }, + { "l17", 0x5000, "vdd_l9_l10_l17_l22", }, + { "l18", 0x5100, "vdd_l8_l16_l18_19", }, + { "l19", 0x5200, "vdd_l8_l16_l18_19", }, + { "l20", 0x5300, "vdd_l13_l20_l23_l24", }, + { "l21", 0x5400, "vdd_l21", }, + { "l22", 0x5500, "vdd_l9_l10_l17_l22", }, + { "l23", 0x5600, "vdd_l13_l20_l23_l24", }, + { "l24", 0x5700, "vdd_l13_l20_l23_l24", }, + { "lvs1", 0x8000, "vdd_l2_lvs_1_2_3", }, + { "lvs2", 0x8100, "vdd_l2_lvs_1_2_3", }, + { "lvs3", 0x8200, "vdd_l2_lvs_1_2_3", }, + { "mvs1", 0x8300, "vin_5vs", }, + { "mvs2", 0x8400, "vin_5vs", }, + { } +}; + +static const struct spmi_regulator_data pm8841_regulators[] = { + { "s1", 0x1400, "vdd_s1", }, + { "s2", 0x1700, "vdd_s2", NULL, 0x1c08 }, + { "s3", 0x1a00, "vdd_s3", }, + { "s4", 0x1d00, "vdd_s4", NULL, 0x1c08 }, + { "s5", 0x2000, "vdd_s5", NULL, 0x1c08 }, + { "s6", 0x2300, "vdd_s6", NULL, 0x1c08 }, + { "s7", 0x2600, "vdd_s7", NULL, 0x1c08 }, + { "s8", 0x2900, "vdd_s8", NULL, 0x1c08 }, + { } +}; + +static const struct spmi_regulator_data pm8916_regulators[] = { + { "s1", 0x1400, "vdd_s1", }, + { "s2", 0x1700, "vdd_s2", }, + { "s3", 0x1a00, "vdd_s3", }, + { "s4", 0x1d00, "vdd_s4", }, + { "l1", 0x4000, "vdd_l1_l3", }, + { "l2", 0x4100, "vdd_l2", }, + { "l3", 0x4200, "vdd_l1_l3", }, + { "l4", 0x4300, "vdd_l4_l5_l6", }, + { "l5", 0x4400, "vdd_l4_l5_l6", }, + { "l6", 0x4500, "vdd_l4_l5_l6", }, + { "l7", 0x4600, "vdd_l7", }, + { "l8", 0x4700, "vdd_l8_l11_l14_l15_l16", }, + { "l9", 0x4800, "vdd_l9_l10_l12_l13_l17_l18", }, + { "l10", 0x4900, "vdd_l9_l10_l12_l13_l17_l18", }, + { "l11", 0x4a00, "vdd_l8_l11_l14_l15_l16", }, + { "l12", 0x4b00, "vdd_l9_l10_l12_l13_l17_l18", }, + { "l13", 0x4c00, "vdd_l9_l10_l12_l13_l17_l18", }, + { "l14", 0x4d00, "vdd_l8_l11_l14_l15_l16", }, + { "l15", 0x4e00, "vdd_l8_l11_l14_l15_l16", }, + { "l16", 0x4f00, "vdd_l8_l11_l14_l15_l16", }, + { "l17", 0x5000, "vdd_l9_l10_l12_l13_l17_l18", }, + { "l18", 0x5100, "vdd_l9_l10_l12_l13_l17_l18", }, + { } +}; + +static const struct of_device_id qcom_spmi_regulator_match[] = { + { .compatible = "qcom,pm8841-regulators", .data = &pm8841_regulators }, + { .compatible = "qcom,pm8916-regulators", .data = &pm8916_regulators }, + { .compatible = "qcom,pm8941-regulators", .data = &pm8941_regulators }, + { } +}; +MODULE_DEVICE_TABLE(of, qcom_spmi_regulator_match); + +static int qcom_spmi_regulator_probe(struct platform_device *pdev) +{ + const struct spmi_regulator_data *reg; + const struct of_device_id *match; + struct regulator_config config = { }; + struct regulator_dev *rdev; + struct spmi_regulator *vreg; + struct regmap *regmap; + const char *name; + struct device *dev = &pdev->dev; + int ret; + struct list_head *vreg_list; + + vreg_list = devm_kzalloc(dev, sizeof(*vreg_list), GFP_KERNEL); + if (!vreg_list) + return -ENOMEM; + INIT_LIST_HEAD(vreg_list); + platform_set_drvdata(pdev, vreg_list); + + regmap = dev_get_regmap(dev->parent, NULL); + if (!regmap) + return -ENODEV; + + match = of_match_device(qcom_spmi_regulator_match, &pdev->dev); + if (!match) + return -ENODEV; + + for (reg = match->data; reg->name; reg++) { + vreg = devm_kzalloc(dev, sizeof(*vreg), GFP_KERNEL); + if (!vreg) + return -ENOMEM; + + vreg->dev = dev; + vreg->base = reg->base; + vreg->regmap = regmap; + + if (reg->ocp) { + vreg->ocp_irq = platform_get_irq_byname(pdev, reg->ocp); + if (vreg->ocp_irq < 0) { + ret = vreg->ocp_irq; + goto err; + } + } + + vreg->desc.id = -1; + vreg->desc.owner = THIS_MODULE; + vreg->desc.type = REGULATOR_VOLTAGE; + vreg->desc.name = name = reg->name; + vreg->desc.supply_name = reg->supply; + vreg->desc.of_match = reg->name; + vreg->desc.of_parse_cb = spmi_regulator_of_parse; + vreg->desc.of_map_mode = spmi_regulator_of_map_mode; + + ret = spmi_regulator_match(vreg, reg->force_type); + if (ret) + goto err; + + config.dev = dev; + config.driver_data = vreg; + rdev = devm_regulator_register(dev, &vreg->desc, &config); + if (IS_ERR(rdev)) { + dev_err(dev, "failed to register %s\n", name); + ret = PTR_ERR(rdev); + goto err; + } + + INIT_LIST_HEAD(&vreg->node); + list_add(&vreg->node, vreg_list); + } + + return 0; + +err: + list_for_each_entry(vreg, vreg_list, node) + if (vreg->ocp_irq) + cancel_delayed_work_sync(&vreg->ocp_work); + return ret; +} + +static int qcom_spmi_regulator_remove(struct platform_device *pdev) +{ + struct spmi_regulator *vreg; + struct list_head *vreg_list = platform_get_drvdata(pdev); + + list_for_each_entry(vreg, vreg_list, node) + if (vreg->ocp_irq) + cancel_delayed_work_sync(&vreg->ocp_work); + + return 0; +} + +static struct platform_driver qcom_spmi_regulator_driver = { + .driver = { + .name = "qcom-spmi-regulator", + .of_match_table = qcom_spmi_regulator_match, + }, + .probe = qcom_spmi_regulator_probe, + .remove = qcom_spmi_regulator_remove, +}; +module_platform_driver(qcom_spmi_regulator_driver); + +MODULE_DESCRIPTION("Qualcomm SPMI PMIC regulator driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:qcom-spmi-regulator"); -- cgit v1.2.3