diff options
author | Jun Nie <jun.nie@linaro.org> | 2020-03-09 16:44:53 +0800 |
---|---|---|
committer | Fabien Parent <fabien.parent@linaro.org> | 2022-10-30 13:41:38 +0100 |
commit | 1147e078494846baf5c7e2466387fb7915089213 (patch) | |
tree | d61b182c143bc0ffc20120465b0a655dd3f4094f | |
parent | eea08979ce1615a5ff3c2fa53215c5ed7d4f6133 (diff) |
regulator: onsemi-ncp6335d: add support for device tree
Port from kernel 3.10
Signed-off-by: Jun Nie <jun.nie@linaro.org>
-rw-r--r-- | Documentation/devicetree/bindings/regulator/onsemi-ncp6335d.txt | 72 | ||||
-rw-r--r-- | drivers/regulator/Kconfig | 9 | ||||
-rw-r--r-- | drivers/regulator/Makefile | 1 | ||||
-rw-r--r-- | drivers/regulator/onsemi-ncp6335d.c | 864 |
4 files changed, 946 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/regulator/onsemi-ncp6335d.txt b/Documentation/devicetree/bindings/regulator/onsemi-ncp6335d.txt new file mode 100644 index 000000000000..7d492f5d7e25 --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/onsemi-ncp6335d.txt @@ -0,0 +1,72 @@ +ON Semiconductor NCP6335d regulator + +NCP6335d is able to deliver up to 5.0 A, with programmable output voltage from +0.6 V to 1.87 V in 10mV steps, with synchronous rectification and automatic PWM/ +PFM transitions, enable pins and power good/fail signaling. + +The NCP6335d interface is via I2C bus. + +Required Properties: +- compatible: Must be "onnn,ncp6335d-regulator". +- reg: The device 8-bit I2C address. +- regulator-min-microvolt: Minimum voltage in microvolts supported by this + regulator. +- regulator-max-microvolt: Maximum voltage in microvolts supported by this + regulator. +- onnn,min-setpoint: Minimum setpoint voltage in microvolts supported + by this regulator. +- onnn,step-size: The step size of the regulator, in uV. +- onnn,min-slew-ns: Minimum time in ns needed to change voltage by + one step size. This value corresponds to DVS + mode bit of 00b in command register. +- onnn,max-slew-ns: Maximum time in ns needed to change voltage by + one step size. This value corresponds to DVS + mode bit of 11b in command register. +- onnn,vsel: Working vsel register. Supported value are 0 + or 1. +- onnn,slew-ns: Time in ns needed to change voltage by one step + size. Supported value are 333, 666, 1333, 2666. + +Optional Properties: +- onnn,discharge-enable: Present: discharge enabled. + Not Present: discharge disabled. +- onnn,restore-reg: Present: Restore vsel register from backup register. + Not Present: No restore. +- onnn,vsel-gpio: Present: GPIO connects to the VSEL pin and set the + VSEL pin according to device tree flag. + Not Present: No GPIO is connected to vsel pin. +- pinctrl-names: The state name of the VSEL pin configuration. + Only support: "default" +- pinctrl-0: The phandles of the pin configuration node in + pinctrl for VSEL pin. + For details of pinctrl properties, please refer to: + "Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt" +- onnn,sleep-enable: Present: Forced in sleep mode when EN and VSEL + pins are low. + Not Present: Low quiescent current mode when EN and VSEL + pins are low. +- onnn,mode: A string which specifies the initial mode to use for the regulator. + Supported values are "pwm" and "auto". PWM mode is more + robust, but draws more current than auto mode. If this propery + is not specified, then the regulator will be in the hardware default mode. + +Example: + i2c_0 { + ncp6335d-regulator@1c { + compatible = "onnn,ncp6335d-regulator"; + reg = <0x1c>; + onnn,vsel = <0>; + onnn,slew-rate-ns = <2666>; + onnn,discharge-enable; + onnn,step-size = <10000>; + onnn,min-slew-ns = <333>; + onnn,max-slew-ns = <2666>; + pintrl-names = "default"; + pinctrl-0 = <&ext_buck_vsel_default>; + + regulator-min-microvolt = <1050000>; + regulator-max-microvolt = <1350000>; + onnn,min-setpoint = <600000>; + onnn,vsel-gpio = <&msmgpio 2 1>; + }; + }; diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 070e4403c6c2..61b6b0d9e1f7 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -1524,4 +1524,13 @@ config REGULATOR_QCOM_LABIBB boost regulator and IBB can be used as a negative boost regulator for LCD display panel. +config REGULATOR_ONSEMI_NCP6335D + tristate "OnSemi NCP6335D regulator support" + depends on I2C + help + This driver supports the OnSemi NCP6335D switching voltage regulator + (buck convertor). The regulator is controlled using an I2C interface + and supports a programmable voltage range from 0.6V to 1.4V in steps + of 6.25mV. + endif diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 5962307e1130..91518cf134e4 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -182,5 +182,6 @@ obj-$(CONFIG_REGULATOR_WM831X) += wm831x-ldo.o obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o obj-$(CONFIG_REGULATOR_WM8994) += wm8994-regulator.o +obj-$(CONFIG_REGULATOR_ONSEMI_NCP6335D) += onsemi-ncp6335d.o ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG diff --git a/drivers/regulator/onsemi-ncp6335d.c b/drivers/regulator/onsemi-ncp6335d.c new file mode 100644 index 000000000000..c823fb46bd25 --- /dev/null +++ b/drivers/regulator/onsemi-ncp6335d.c @@ -0,0 +1,864 @@ +/* Copyright (c) 2012-2014, 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 <linux/module.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/log2.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/regmap.h> +#include <linux/string.h> + +/* registers */ +#define REG_NCP6335D_PID 0x03 +#define REG_NCP6335D_PROGVSEL1 0x10 +#define REG_NCP6335D_PROGVSEL0 0x11 +#define REG_NCP6335D_PGOOD 0x12 +#define REG_NCP6335D_TIMING 0x13 +#define REG_NCP6335D_COMMAND 0x14 +#define REG_NCP6335D_MODULE 0x15 +#define REG_NCP6335D_LIMCONF 0x16 + +/* constraints */ +#define NCP6335D_MIN_VOLTAGE_UV 600000 +#define NCP6335D_STEP_VOLTAGE_UV 6250 +#define NCP6335D_VOLTAGE_STEPS 128 +#define NCP6335D_MIN_SLEW_NS 166 +#define NCP6335D_MAX_SLEW_NS 1333 + +/* bits */ +#define NCP6335D_ENABLE BIT(7) +#define NCP6335D_DVS_PWM_MODE BIT(5) +#define NCP6335D_PGOOD_PGDCDC BIT(0) +#define NCP6335D_PGOOD_PGDVS BIT(1) +#define NCP6335D_PGOOD_TOR GENMASK(3, 2) +#define NCP6335D_PGOOD_DISCHG BIT(4) +#define NCP6335D_TIMING_DBN_TIME GENMASK(1, 0) +#define NCP6335D_TIMING_DBN_TIME_1_US BIT(0) +#define NCP6335D_TIMING_DELAY GENMASK(7, 5) +#define NCP6335D_COMMAND_VSELGT BIT(0) +#define NCP6335D_COMMAND_SLEEP_MODE BIT(4) +#define NCP6335D_COMMAND_DVSMODE BIT(5) +#define NCP6335D_COMMAND_PWMVSEL1 BIT(6) +#define NCP6335D_COMMAND_PWMVSEL0 BIT(7) +#define NCP6335D_MODULE_NUMMODULES GENMASK(7, 4) +#define NCP6335D_MODULE_NUMMODULES_9 BIT(7) +#define NCP6335D_LIMCONF_TSD_REARM BIT(0) +#define NCP6335D_LIMCONF_TPWTH GENMASK(5, 4) +#define NCP6335D_LIMCONF_TPWTH_105_C BIT(5) +#define NCP6335D_LIMCONF_IPEAK GENMASK(7, 6) +#define NCP6335D_LIMCONF_IPEAK_5_A GENMASK(7, 6) + +#define NCP6335D_VOUT_SEL_MASK 0x7F +#define NCP6335D_SLEW_MASK 0x18 +#define NCP6335D_SLEW_SHIFT 0x3 + +enum { + NCP6335D_VSEL0, + NCP6335D_VSEL1, +}; + +struct ncp6335d_platform_data { + struct regulator_init_data *init_data; + int default_vsel; + int slew_rate_ns; + int discharge_enable; + bool sleep_enable; +}; + +struct ncp6335d_info { + struct regulator_dev *regulator; + struct regulator_init_data *init_data; + struct regmap *regmap; + struct device *dev; + unsigned int vsel_reg; + unsigned int vsel_backup_reg; + unsigned int mode_bit; + int curr_voltage; + int slew_rate; + + unsigned int step_size; + unsigned int min_voltage; + unsigned int min_slew_ns; + unsigned int max_slew_ns; + unsigned int peek_poke_address; + + bool set_en_always; +}; + +static int delay_array[] = {10, 20, 30, 40, 50}; + +static int ncp6335x_read(struct ncp6335d_info *dd, unsigned int reg, + unsigned int *val) +{ + int i = 0, rc = 0; + + rc = regmap_read(dd->regmap, reg, val); + + pr_debug(" %s: reg=0x%x, val=0x%x\n", + __func__, reg, *val); + for (i = 0; rc && i < ARRAY_SIZE(delay_array); i++) { + pr_warn("%s: failed reading reg=0x%x val=%u rc=%d - retry(%d)\n", + __func__, reg, *val, rc, i); + msleep(delay_array[i]); + rc = regmap_read(dd->regmap, reg, val); + } + + if (rc) + pr_err("%s: failed reading reg=0x%x rc=%d\n", __func__, reg, rc); + else + pr_debug("%s: reg=0x%x, val=0x%x\n", + __func__, reg, *val); + + return rc; +} + +static int ncp6335x_write(struct ncp6335d_info *dd, unsigned int reg, + unsigned int val) +{ + int i = 0, rc = 0; + int read_val = 0, read_rc = 0; + + rc = regmap_write(dd->regmap, reg, val); + + read_rc = regmap_read(dd->regmap, reg, &read_val); + + for (i = 0; (rc || read_rc || val != read_val) + && i < ARRAY_SIZE(delay_array); i++) { + pr_warn("%s: failed writing reg=0x%x val=0x%x rc=%d " + "read_rc=%d read_val=0x%x - retry(%d)\n", __func__, reg, + val, rc, read_rc, read_val, i); + msleep(delay_array[i]); + rc = regmap_write(dd->regmap, reg, val); + read_rc = regmap_read(dd->regmap, reg, &read_val); + } + + if (rc || read_rc || val != read_val) + pr_warn("%s: failed writing reg=0x%x val=0x%x rc=%d " + "read_rc=%d read_val=0x%x\n", __func__, reg, val, rc, + read_rc, read_val); + else + pr_debug("%s: reg=0x%x, val=0x%x, read_val=0x%x\n", + __func__, reg, val, read_val); + + return rc; +} + +static int ncp6335x_update_bits(struct ncp6335d_info *dd, unsigned int reg, + unsigned int mask, unsigned int val) +{ + int i = 0, rc = 0; + int read_val = 0, read_rc = 0; + + rc = regmap_update_bits(dd->regmap, reg, mask, val); + + read_rc = regmap_read(dd->regmap, reg, &read_val); + + pr_debug(" %s: reg=0x%x, mask=0x%x, val=0x%x, read_val=0x%x\n", + __func__, reg, mask, val, read_val); + + for (i = 0; (rc || read_rc || (val & mask) != (read_val & mask)) + && i < ARRAY_SIZE(delay_array); i++) { + pr_warn("%s: failed updating reg=0x%x mask=0x%x val=0x%x " + "rc=%d read_rc=%d read_val=%u - retry(%d)\n", __func__, + reg, mask, val, rc, read_rc, read_val, i); + msleep(delay_array[i]); + rc = regmap_update_bits(dd->regmap, reg, mask, val); + read_rc = regmap_read(dd->regmap, reg, &read_val); + } + + if (rc || read_rc || (val & mask) != (read_val & mask)) + pr_err("%s: failed updating reg=0x%x mask=0x%x val=0x%x rc=%d " + "read_rc=%d read_val=0x%x\n", __func__, reg, mask, val, rc, + read_rc, read_val); + else + pr_debug("%s: reg=0x%x, mask=0x%x, val=0x%x, read_val=0x%x\n", + __func__, reg, mask, val, read_val); + + return rc; +} + +static void ncp633d_slew_delay(struct ncp6335d_info *dd, + int prev_uV, int new_uV) +{ + u8 val; + int delay; + + val = abs(prev_uV - new_uV) / dd->step_size; + delay = ((val * dd->slew_rate) / 1000) + 1; + + dev_dbg(dd->dev, "Slew Delay = %d\n", delay); + + udelay(delay); +} + +static int ncp6335d_enable(struct regulator_dev *rdev) +{ + int rc; + struct ncp6335d_info *dd = rdev_get_drvdata(rdev); + + rc = ncp6335x_update_bits(dd, dd->vsel_reg, + NCP6335D_ENABLE, NCP6335D_ENABLE); + if (rc) + dev_err(dd->dev, "Unable to enable regualtor rc(%d)", rc); + + return rc; +} + +static int ncp6335d_disable(struct regulator_dev *rdev) +{ + int rc; + struct ncp6335d_info *dd = rdev_get_drvdata(rdev); + + rc = ncp6335x_update_bits(dd, dd->vsel_reg, + NCP6335D_ENABLE, 0); + if (rc) + dev_err(dd->dev, "Unable to disable regualtor rc(%d)", rc); + + return rc; +} + +static int ncp6335d_get_voltage(struct regulator_dev *rdev) +{ + unsigned int val; + int rc; + struct ncp6335d_info *dd = rdev_get_drvdata(rdev); + + rc = ncp6335x_read(dd, dd->vsel_reg, &val); + if (rc) { + dev_err(dd->dev, "Unable to get volatge rc(%d)", rc); + return rc; + } + dd->curr_voltage = ((val & NCP6335D_VOUT_SEL_MASK) * dd->step_size) + + dd->min_voltage; + + return dd->curr_voltage; +} + +static int ncp6335d_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV, unsigned *selector) +{ + int rc, set_val, new_uV; + struct ncp6335d_info *dd = rdev_get_drvdata(rdev); + + set_val = DIV_ROUND_UP(min_uV - dd->min_voltage, dd->step_size); + new_uV = (set_val * dd->step_size) + dd->min_voltage; + if (new_uV > max_uV) { + dev_err(dd->dev, "Unable to set volatge (%d %d)\n", + min_uV, max_uV); + return -EINVAL; + } + + if (dd->set_en_always) { + rc = ncp6335x_write(dd, dd->vsel_reg, + NCP6335D_ENABLE | (set_val & NCP6335D_VOUT_SEL_MASK)); + } else { + rc = ncp6335x_update_bits(dd, dd->vsel_reg, + NCP6335D_VOUT_SEL_MASK, + (set_val & NCP6335D_VOUT_SEL_MASK)); + } + + if (rc) { + dev_err(dd->dev, "Unable to set volatge (%d %d)\n", + min_uV, max_uV); + } else { + ncp633d_slew_delay(dd, dd->curr_voltage, new_uV); + dd->curr_voltage = new_uV; + } + + return rc; +} + +static int ncp6335d_list_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + struct ncp6335d_info *dd = rdev_get_drvdata(rdev); + + if (selector >= NCP6335D_VOLTAGE_STEPS) + return 0; + + return selector * dd->step_size + dd->min_voltage; +} + +static int ncp6335d_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + int rc; + struct ncp6335d_info *dd = rdev_get_drvdata(rdev); + + /* only FAST and NORMAL mode types are supported */ + if (mode != REGULATOR_MODE_FAST && mode != REGULATOR_MODE_NORMAL) { + dev_err(dd->dev, "Mode %d not supported\n", mode); + return -EINVAL; + } + + rc = ncp6335x_update_bits(dd, REG_NCP6335D_COMMAND, dd->mode_bit, + (mode == REGULATOR_MODE_FAST) ? dd->mode_bit : 0); + if (rc) { + dev_err(dd->dev, "Unable to set operating mode rc(%d)", rc); + return rc; + } + + rc = ncp6335x_update_bits(dd, REG_NCP6335D_COMMAND, + NCP6335D_DVS_PWM_MODE, + (mode == REGULATOR_MODE_FAST) ? + NCP6335D_DVS_PWM_MODE : 0); + if (rc) + dev_err(dd->dev, "Unable to set DVS trans. mode rc(%d)", rc); + + return rc; +} + +static unsigned int ncp6335d_get_mode(struct regulator_dev *rdev) +{ + unsigned int val; + int rc; + struct ncp6335d_info *dd = rdev_get_drvdata(rdev); + + rc = ncp6335x_read(dd, REG_NCP6335D_COMMAND, &val); + if (rc) { + dev_err(dd->dev, "Unable to get regulator mode rc(%d)\n", rc); + return rc; + } + + if (val & dd->mode_bit) + return REGULATOR_MODE_FAST; + + return REGULATOR_MODE_NORMAL; +} + +static struct regulator_ops ncp6335d_ops = { + .set_voltage = ncp6335d_set_voltage, + .get_voltage = ncp6335d_get_voltage, + .list_voltage = ncp6335d_list_voltage, + .enable = ncp6335d_enable, + .disable = ncp6335d_disable, + .set_mode = ncp6335d_set_mode, + .get_mode = ncp6335d_get_mode, +}; + +static struct regulator_desc rdesc = { + .name = "ncp6335d", + .owner = THIS_MODULE, + .n_voltages = NCP6335D_VOLTAGE_STEPS, + .ops = &ncp6335d_ops, +}; + +static int ncp6335d_restore_working_reg(struct device_node *node, + struct ncp6335d_info *dd) +{ + int ret; + unsigned int val; + + /* Restore register from back up register */ + ret = ncp6335x_read(dd, dd->vsel_backup_reg, &val); + if (ret < 0) { + dev_err(dd->dev, "Failed to get backup data from reg %d, ret = %d\n", + dd->vsel_backup_reg, ret); + return ret; + } + + ret = ncp6335x_update_bits(dd, dd->vsel_reg, + NCP6335D_VOUT_SEL_MASK, val); + if (ret < 0) { + dev_err(dd->dev, "Failed to update working reg %d, ret = %d\n", + dd->vsel_reg, ret); + return ret; + } + + return ret; +} + +static int ncp6335d_parse_gpio(struct device_node *node, + struct ncp6335d_info *dd) +{ + int ret = 0, gpio; + enum of_gpio_flags flags; + + if (!of_find_property(node, "onnn,vsel-gpio", NULL)) + return ret; + + /* Get GPIO connected to vsel and set its output */ + gpio = of_get_named_gpio_flags(node, + "onnn,vsel-gpio", 0, &flags); + if (!gpio_is_valid(gpio)) { + if (gpio != -EPROBE_DEFER) + dev_err(dd->dev, "Could not get vsel, ret = %d\n", + gpio); + return gpio; + } + + ret = devm_gpio_request(dd->dev, gpio, "ncp6335d_vsel"); + if (ret) { + dev_err(dd->dev, "Failed to obtain gpio %d ret = %d\n", + gpio, ret); + return ret; + } + + ret = gpio_direction_output(gpio, flags & OF_GPIO_ACTIVE_LOW ? 0 : 1); + if (ret) { + dev_err(dd->dev, "Failed to set GPIO %d to: %s, ret = %d", + gpio, flags & OF_GPIO_ACTIVE_LOW ? + "GPIO_LOW" : "GPIO_HIGH", ret); + return ret; + } + + return ret; +} + +static int ncp6335d_init(struct i2c_client *client, struct ncp6335d_info *dd, + const struct ncp6335d_platform_data *pdata) +{ + int rc; + unsigned int val; + + switch (pdata->default_vsel) { + case NCP6335D_VSEL0: + dd->vsel_reg = REG_NCP6335D_PROGVSEL0; + dd->vsel_backup_reg = REG_NCP6335D_PROGVSEL1; + dd->mode_bit = NCP6335D_COMMAND_PWMVSEL0; + break; + case NCP6335D_VSEL1: + dd->vsel_reg = REG_NCP6335D_PROGVSEL1; + dd->vsel_backup_reg = REG_NCP6335D_PROGVSEL0; + dd->mode_bit = NCP6335D_COMMAND_PWMVSEL1; + break; + default: + dev_err(dd->dev, "Invalid VSEL ID %d\n", pdata->default_vsel); + return -EINVAL; + } + + if (of_property_read_bool(client->dev.of_node, "onnn,restore-reg")) { + rc = ncp6335d_restore_working_reg(client->dev.of_node, dd); + if (rc) + return rc; + } + + rc = ncp6335d_parse_gpio(client->dev.of_node, dd); + if (rc) + return rc; + + /* get the current programmed voltage */ + rc = ncp6335x_read(dd, dd->vsel_reg, &val); + if (rc) { + dev_err(dd->dev, "Unable to get volatge rc(%d)", rc); + return rc; + } + dd->curr_voltage = ((val & NCP6335D_VOUT_SEL_MASK) * + dd->step_size) + dd->min_voltage; + + /* set discharge */ + rc = ncp6335x_update_bits(dd, REG_NCP6335D_PGOOD, + NCP6335D_PGOOD_DISCHG, + (pdata->discharge_enable ? + NCP6335D_PGOOD_DISCHG : 0)); + if (rc) { + dev_err(dd->dev, "Unable to set Active Discharge rc(%d)\n", rc); + return -EINVAL; + } + + /* set slew rate */ + if (pdata->slew_rate_ns < dd->min_slew_ns || + pdata->slew_rate_ns > dd->max_slew_ns) { + dev_err(dd->dev, "Invalid slew rate %d\n", pdata->slew_rate_ns); + return -EINVAL; + } + + dd->slew_rate = pdata->slew_rate_ns; + val = DIV_ROUND_UP(pdata->slew_rate_ns, dd->min_slew_ns); + val = ilog2(val); + + rc = ncp6335x_update_bits(dd, REG_NCP6335D_TIMING, + NCP6335D_SLEW_MASK, val << NCP6335D_SLEW_SHIFT); + if (rc) + dev_err(dd->dev, "Unable to set slew rate rc(%d)\n", rc); + + /* Set Sleep mode bit */ + rc = ncp6335x_update_bits(dd, REG_NCP6335D_COMMAND, + NCP6335D_COMMAND_SLEEP_MODE, pdata->sleep_enable ? + NCP6335D_COMMAND_SLEEP_MODE : 0); + if (rc) + dev_err(dd->dev, "Unable to set sleep mode (%d)\n", rc); + + if (of_property_read_bool(client->dev.of_node, "onnn,register-corruption-workaround")) { + /* The following registers are otherwise untouched by this driver, + * which expects they remain at their power-on default values. + * + * However, there may be factors outside our control (OnSemi design + * error, host SoC I2C bus error, non-HLOS firmware) causing these + * registers to be corrupted or set to different values. Even in the + * face of these factors, we need to be able to bring back the IC to + * a known state during system reset. + * + * Due to the fact that a SoC/PMIC reset does not cause this external + * regulator to reset, here we forcibly overwrite the register values + * with their known power-on defaults. + */ + dev_info(dd->dev, "Applying register corruption workaround.\n"); + + /* set power good settings */ + rc = ncp6335x_update_bits(dd, REG_NCP6335D_PGOOD, + NCP6335D_PGOOD_PGDCDC, 0); + if (rc) { + dev_err(dd->dev, "Unable to set PGOOD_PGDCDC (%d)\n", rc); + return -EINVAL; + } + rc = ncp6335x_update_bits(dd, REG_NCP6335D_PGOOD, + NCP6335D_PGOOD_PGDVS, 0); + if (rc) { + dev_err(dd->dev, "Unable to set PGOOD_PGDVS (%d)\n", rc); + return -EINVAL; + } + rc = ncp6335x_update_bits(dd, REG_NCP6335D_PGOOD, + NCP6335D_PGOOD_TOR, 0); + if (rc) { + dev_err(dd->dev, "Unable to set PGOOD_TOR (%d)\n", rc); + return -EINVAL; + } + + /* set EN and VSEL debounce time */ + rc = ncp6335x_update_bits(dd, REG_NCP6335D_TIMING, + NCP6335D_TIMING_DBN_TIME, NCP6335D_TIMING_DBN_TIME_1_US); + if (rc) { + dev_err(dd->dev, "Unable to set TIMING_DBN_TIME (%d)\n", rc); + return -EINVAL; + } + + /* set delay upon enabling */ + rc = ncp6335x_update_bits(dd, REG_NCP6335D_TIMING, + NCP6335D_TIMING_DELAY, 0); + if (rc) { + dev_err(dd->dev, "Unable to set TIMING_DELAY (%d)\n", rc); + return -EINVAL; + } + + /* set command settings */ + rc = ncp6335x_update_bits(dd, REG_NCP6335D_COMMAND, + NCP6335D_COMMAND_VSELGT, NCP6335D_COMMAND_VSELGT); + if (rc) { + dev_err(dd->dev, "Unable to set COMMAND_VSELGT (%d)\n", rc); + return -EINVAL; + } + rc = ncp6335x_update_bits(dd, REG_NCP6335D_COMMAND, + NCP6335D_COMMAND_DVSMODE, 0); + if (rc) { + dev_err(dd->dev, "Unable to set COMMAND_DVSMODE (%d)\n", rc); + return -EINVAL; + } + rc = ncp6335x_update_bits(dd, REG_NCP6335D_COMMAND, + NCP6335D_COMMAND_PWMVSEL1, 0); + if (rc) { + dev_err(dd->dev, "Unable to set COMMAND_PWMVSEL1 (%d)\n", rc); + return -EINVAL; + } + rc = ncp6335x_update_bits(dd, REG_NCP6335D_COMMAND, + NCP6335D_COMMAND_PWMVSEL0, 0); + if (rc) { + dev_err(dd->dev, "Unable to set COMMAND_PWMVSEL0 (%d)\n", rc); + return -EINVAL; + } + + /* set number of output stage modules */ + rc = ncp6335x_update_bits(dd, REG_NCP6335D_MODULE, + NCP6335D_MODULE_NUMMODULES, NCP6335D_MODULE_NUMMODULES_9); + if (rc) { + dev_err(dd->dev, "Unable to set MODULE_NUMMODULES (%d)\n", rc); + return -EINVAL; + } + + /* set limit configuration */ + rc = ncp6335x_update_bits(dd, REG_NCP6335D_LIMCONF, + NCP6335D_LIMCONF_TSD_REARM, NCP6335D_LIMCONF_TSD_REARM); + if (rc) { + dev_err(dd->dev, "Unable to set LIMCONF_TSD_REARM (%d)\n", rc); + return -EINVAL; + } + rc = ncp6335x_update_bits(dd, REG_NCP6335D_LIMCONF, + NCP6335D_LIMCONF_TPWTH, NCP6335D_LIMCONF_TPWTH_105_C); + if (rc) { + dev_err(dd->dev, "Unable to set LIMCONF_TPWTH (%d)\n", rc); + return -EINVAL; + } + rc = ncp6335x_update_bits(dd, REG_NCP6335D_LIMCONF, + NCP6335D_LIMCONF_IPEAK, NCP6335D_LIMCONF_IPEAK_5_A); + if (rc) { + dev_err(dd->dev, "Unable to set LIMCONF_IPEAK (%d)\n", rc); + return -EINVAL; + } + } + + return rc; +} + +static struct regmap_config ncp6335d_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int ncp6335d_parse_dt(struct i2c_client *client, + struct ncp6335d_info *dd) +{ + int rc; + + rc = of_property_read_u32(client->dev.of_node, + "onnn,step-size", &dd->step_size); + if (rc < 0) { + dev_err(&client->dev, "step size missing: rc = %d.\n", rc); + return rc; + } + + rc = of_property_read_u32(client->dev.of_node, + "onnn,min-slew-ns", &dd->min_slew_ns); + if (rc < 0) { + dev_err(&client->dev, "min slew us missing: rc = %d.\n", rc); + return rc; + } + + rc = of_property_read_u32(client->dev.of_node, + "onnn,max-slew-ns", &dd->max_slew_ns); + if (rc < 0) { + dev_err(&client->dev, "max slew us missing: rc = %d.\n", rc); + return rc; + } + + rc = of_property_read_u32(client->dev.of_node, + "onnn,min-setpoint", &dd->min_voltage); + if (rc < 0) { + dev_err(&client->dev, "min set point missing: rc = %d.\n", rc); + return rc; + } + + dd->set_en_always = of_property_read_bool(client->dev.of_node, + "onnn,set-en-always"); + + return rc; +} + +static struct ncp6335d_platform_data * + ncp6335d_get_of_platform_data(struct i2c_client *client) +{ + struct ncp6335d_platform_data *pdata = NULL; + struct regulator_init_data *init_data; + const char *mode_name; + int rc; + + init_data = of_get_regulator_init_data(&client->dev, + client->dev.of_node, &rdesc); + if (!init_data) { + dev_err(&client->dev, "regulator init data is missing\n"); + return pdata; + } + + pdata = devm_kzalloc(&client->dev, + sizeof(struct ncp6335d_platform_data), GFP_KERNEL); + if (!pdata) { + dev_err(&client->dev, "ncp6335d_platform_data allocation failed.\n"); + return pdata; + } + + rc = of_property_read_u32(client->dev.of_node, + "onnn,vsel", &pdata->default_vsel); + if (rc < 0) { + dev_err(&client->dev, "onnn,vsel property missing: rc = %d.\n", + rc); + return NULL; + } + + rc = of_property_read_u32(client->dev.of_node, + "onnn,slew-ns", &pdata->slew_rate_ns); + if (rc < 0) { + dev_err(&client->dev, "onnn,slew-ns property missing: rc = %d.\n", + rc); + return NULL; + } + + pdata->discharge_enable = of_property_read_bool(client->dev.of_node, + "onnn,discharge-enable"); + + pdata->sleep_enable = of_property_read_bool(client->dev.of_node, + "onnn,sleep-enable"); + + pdata->init_data = init_data; + + init_data->constraints.input_uV = init_data->constraints.max_uV; + init_data->constraints.valid_ops_mask = + REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_STATUS | + REGULATOR_CHANGE_MODE; + init_data->constraints.valid_modes_mask = + REGULATOR_MODE_NORMAL | + REGULATOR_MODE_FAST; + + rc = of_property_read_string(client->dev.of_node, "onnn,mode", + &mode_name); + if (!rc) { + if (strcmp("pwm", mode_name) == 0) { + init_data->constraints.initial_mode = + REGULATOR_MODE_FAST; + } else if (strcmp("auto", mode_name) == 0) { + init_data->constraints.initial_mode = + REGULATOR_MODE_NORMAL; + } else { + dev_err(&client->dev, "onnn,mode, unknown regulator mode: %s\n", + mode_name); + return NULL; + } + } + + return pdata; +} + +static int ncp6335d_regulator_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc; + unsigned int val = 0; + struct ncp6335d_info *dd; + const struct ncp6335d_platform_data *pdata; + struct regulator_config config = { }; + + if (client->dev.of_node) + pdata = ncp6335d_get_of_platform_data(client); + else + pdata = client->dev.platform_data; + + if (!pdata) { + dev_err(&client->dev, "Platform data not specified\n"); + return -EINVAL; + } + + dd = devm_kzalloc(&client->dev, sizeof(*dd), GFP_KERNEL); + if (!dd) { + dev_err(&client->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + if (client->dev.of_node) { + rc = ncp6335d_parse_dt(client, dd); + if (rc) + return rc; + } else { + dd->step_size = NCP6335D_STEP_VOLTAGE_UV; + dd->min_voltage = NCP6335D_MIN_VOLTAGE_UV; + dd->min_slew_ns = NCP6335D_MIN_SLEW_NS; + dd->max_slew_ns = NCP6335D_MAX_SLEW_NS; + } + rdesc.uV_step = dd->step_size; + + dd->regmap = devm_regmap_init_i2c(client, &ncp6335d_regmap_config); + if (IS_ERR(dd->regmap)) { + dev_err(&client->dev, "Error allocating regmap\n"); + return PTR_ERR(dd->regmap); + } + + rc = ncp6335x_read(dd, REG_NCP6335D_PID, &val); + if (rc) { + dev_err(&client->dev, "Unable to identify NCP6335D, rc(%d)\n", + rc); + return rc; + } + dev_info(&client->dev, "Detected Regulator NCP6335D PID = %d\n", val); + + dd->init_data = pdata->init_data; + dd->dev = &client->dev; + i2c_set_clientdata(client, dd); + + rc = ncp6335d_init(client, dd, pdata); + if (rc) { + dev_err(&client->dev, "Unable to intialize the regulator\n"); + return -EINVAL; + } + + config.dev = &client->dev; + config.init_data = dd->init_data; + config.regmap = dd->regmap; + config.driver_data = dd; + config.of_node = client->dev.of_node; + + dd->regulator = regulator_register(&rdesc, &config); + + if (IS_ERR(dd->regulator)) { + dev_err(&client->dev, "Unable to register regulator rc(%ld)", + PTR_ERR(dd->regulator)); + + return PTR_ERR(dd->regulator); + } + + return 0; +} + +static void ncp6335d_regulator_remove(struct i2c_client *client) +{ + struct ncp6335d_info *dd = i2c_get_clientdata(client); + + regulator_unregister(dd->regulator); +} + +static struct of_device_id ncp6335d_match_table[] = { + { .compatible = "onnn,ncp6335d-regulator", }, + {}, +}; +MODULE_DEVICE_TABLE(of, ncp6335d_match_table); + +static const struct i2c_device_id ncp6335d_id[] = { + {"ncp6335d", -1}, + { }, +}; + +static struct i2c_driver ncp6335d_regulator_driver = { + .driver = { + .name = "ncp6335d-regulator", + .owner = THIS_MODULE, + .of_match_table = ncp6335d_match_table, + }, + .probe = ncp6335d_regulator_probe, + .remove = ncp6335d_regulator_remove, + .id_table = ncp6335d_id, +}; + +/** + * ncp6335d_regulator_init() - initialized ncp6335d regulator driver + * This function registers the ncp6335d regulator platform driver. + * + * Returns 0 on success or errno on failure. + */ +int __init ncp6335d_regulator_init(void) +{ + int ret; + static bool initialized; + + if (initialized) + return 0; + + ret = i2c_add_driver(&ncp6335d_regulator_driver); + if (ret) + return ret; + + initialized = true; + return 0; +} +arch_initcall(ncp6335d_regulator_init); + +static void __exit ncp6335d_regulator_exit(void) +{ + i2c_del_driver(&ncp6335d_regulator_driver); +} +module_exit(ncp6335d_regulator_exit); +MODULE_DESCRIPTION("OnSemi-NCP6335D regulator driver"); +MODULE_LICENSE("GPL v2"); |