diff options
author | Guodong Xu <guodong.xu@linaro.org> | 2013-08-16 16:28:51 +0800 |
---|---|---|
committer | Zhangfei Gao <zhangfei.gao@linaro.org> | 2013-08-19 17:14:20 +0800 |
commit | 5ed366b3739224be33f95d9a08753887b384f418 (patch) | |
tree | c0348817e0d02cdaab7b36a54bc0d66bf4a6f5fa | |
parent | 2903e34ac7e8860dec26ec2e9f7d03c9351e72ef (diff) |
regulator: hi6421: Add support to hi6421 regulators
Add support to hi6421 regulators. Hi6421 is a multi-functional PMIC
manufactured by HiSilicon Inc.
Signed-off-by: Guodong Xu <guodong.xu@linaro.org>
Acked-by: Haojian Zhuang <haojian.zhuang@linaro.org>
-rw-r--r-- | drivers/mfd/hi6421-pmic-core.c | 2 | ||||
-rw-r--r-- | drivers/regulator/Kconfig | 7 | ||||
-rw-r--r-- | drivers/regulator/Makefile | 2 | ||||
-rw-r--r-- | drivers/regulator/hi6421-regulator.c | 557 | ||||
-rw-r--r-- | include/linux/mfd/hi6421-pmic.h | 15 |
5 files changed, 582 insertions, 1 deletions
diff --git a/drivers/mfd/hi6421-pmic-core.c b/drivers/mfd/hi6421-pmic-core.c index f7fd7965dca1..b2d7c4e82bd2 100644 --- a/drivers/mfd/hi6421-pmic-core.c +++ b/drivers/mfd/hi6421-pmic-core.c @@ -27,6 +27,7 @@ #include <linux/err.h> #include <linux/interrupt.h> #include <linux/io.h> +#include <linux/mutex.h> #include <linux/platform_device.h> #include <linux/of.h> #include <linux/of_address.h> @@ -203,6 +204,7 @@ static int hi6421_pmic_probe(struct platform_device *pdev) return -ENOMEM; } + mutex_init(&pmic->enable_mutex); /* get resources */ pmic->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!pmic->res) { diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 8bb26446037e..08774c02a840 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -514,5 +514,12 @@ config REGULATOR_AS3711 This driver provides support for the voltage regulators on the AS3711 PMIC +config REGULATOR_HI6421 + tristate "HiSilicon Hi6421 PMIC regulators" + depends on MFD_HI6421_PMIC + help + This driver provides support for the voltage regulators on the + HiSilicon Hi6421 PMU / Codec IC. + endif diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 47a34ff88f98..5a67225c8973 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -70,6 +70,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_HI6421) += hi6421-regulator.o ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG diff --git a/drivers/regulator/hi6421-regulator.c b/drivers/regulator/hi6421-regulator.c new file mode 100644 index 000000000000..a48c9501030a --- /dev/null +++ b/drivers/regulator/hi6421-regulator.c @@ -0,0 +1,557 @@ +/* + * Device driver for regulators in Hi6421 IC + * + * Copyright (c) 2013 Linaro Ltd. + * Copyright (c) 2011 Hisilicon. + * + * Guodong Xu <guodong.xu@linaro.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_address.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/mfd/hi6421-pmic.h> +#include <linux/delay.h> +#include <linux/time.h> + +struct hi6421_regulator_register_info { + u32 ctrl_reg; + u32 enable_mask; + u32 eco_mode_mask; + u32 vset_reg; + u32 vset_mask; +}; + +struct hi6421_regulator { + const char *name; + struct hi6421_regulator_register_info register_info; + struct timeval last_off_time; + u32 off_on_delay; + u32 eco_uA; + struct regulator_desc rdesc; + int (*dt_parse)(struct hi6421_regulator *, struct platform_device *); +}; + +static inline struct hi6421_pmic *rdev_to_pmic(struct regulator_dev *dev) +{ + /* regulator_dev parent to-> + * hi6421 regulator platform device_dev parent to-> + * hi6421 pmic platform device_dev + */ + return dev_get_drvdata(rdev_get_dev(dev)->parent->parent); +} + +/* helper function to ensure when it returns it is at least 'delay_us' + * microseconds after 'since'. + */ +static void ensured_time_after(struct timeval since, u32 delay_us) +{ + struct timeval now; + u64 elapsed_ns64, delay_ns64; + u32 actual_us32; + + delay_ns64 = delay_us * NSEC_PER_USEC; + do_gettimeofday(&now); + elapsed_ns64 = timeval_to_ns(&now) - timeval_to_ns(&since); + if (delay_ns64 > elapsed_ns64) { + actual_us32 = ((u32)(delay_ns64 - elapsed_ns64) / + NSEC_PER_USEC); + if (actual_us32 >= 1000) { + mdelay(actual_us32 / 1000); + udelay(actual_us32 % 1000); + } else if (actual_us32 > 0) { + udelay(actual_us32); + } + } + return; +} + +static int hi6421_regulator_is_enabled(struct regulator_dev *dev) +{ + u32 reg_val; + struct hi6421_regulator *sreg = rdev_get_drvdata(dev); + struct hi6421_pmic *pmic = rdev_to_pmic(dev); + + reg_val = hi6421_pmic_read(pmic, sreg->register_info.ctrl_reg); + + return ((reg_val & sreg->register_info.enable_mask) != 0); +} + +static int hi6421_regulator_enable(struct regulator_dev *dev) +{ + struct hi6421_regulator *sreg = rdev_get_drvdata(dev); + struct hi6421_pmic *pmic = rdev_to_pmic(dev); + + /* keep a distance of off_on_delay from last time disabled */ + ensured_time_after(sreg->last_off_time, sreg->off_on_delay); + + /* cannot enable more than one regulator at one time */ + mutex_lock(&pmic->enable_mutex); + ensured_time_after(pmic->last_enabled, HI6421_REGS_ENA_PROTECT_TIME); + + /* set enable register */ + hi6421_pmic_rmw(pmic, sreg->register_info.ctrl_reg, + sreg->register_info.enable_mask, + sreg->register_info.enable_mask); + + do_gettimeofday(&pmic->last_enabled); + mutex_unlock(&pmic->enable_mutex); + + return 0; +} + +static int hi6421_regulator_disable(struct regulator_dev *dev) +{ + struct hi6421_regulator *sreg = rdev_get_drvdata(dev); + struct hi6421_pmic *pmic = rdev_to_pmic(dev); + + /* set enable register to 0 */ + hi6421_pmic_rmw(pmic, sreg->register_info.ctrl_reg, + sreg->register_info.enable_mask, 0); + + do_gettimeofday(&sreg->last_off_time); + + return 0; +} + +static int hi6421_regulator_get_voltage(struct regulator_dev *dev) +{ + struct hi6421_regulator *sreg = rdev_get_drvdata(dev); + struct hi6421_pmic *pmic = rdev_to_pmic(dev); + u32 reg_val, selector; + + /* get voltage selector */ + reg_val = hi6421_pmic_read(pmic, sreg->register_info.vset_reg); + selector = (reg_val & sreg->register_info.vset_mask) >> + (ffs(sreg->register_info.vset_mask) - 1); + + return sreg->rdesc.ops->list_voltage(dev, selector); +} + +static int hi6421_regulator_ldo_set_voltage(struct regulator_dev *dev, + int min_uV, int max_uV, unsigned *selector) +{ + struct hi6421_regulator *sreg = rdev_get_drvdata(dev); + struct hi6421_pmic *pmic = rdev_to_pmic(dev); + u32 vsel; + int ret = 0; + + for (vsel = 0; vsel < sreg->rdesc.n_voltages; vsel++) { + int uV = sreg->rdesc.volt_table[vsel]; + /* Break at the first in-range value */ + if (min_uV <= uV && uV <= max_uV) + break; + } + + /* unlikely to happen. sanity test done by regulator core */ + if (unlikely(vsel == sreg->rdesc.n_voltages)) + return -EINVAL; + + *selector = vsel; + /* set voltage selector */ + hi6421_pmic_rmw(pmic, sreg->register_info.vset_reg, + sreg->register_info.vset_mask, + vsel << (ffs(sreg->register_info.vset_mask) - 1)); + + return ret; +} + +static int hi6421_regulator_buck012_set_voltage(struct regulator_dev *dev, + int min_uV, int max_uV, unsigned *selector) +{ + struct hi6421_regulator *sreg = rdev_get_drvdata(dev); + struct hi6421_pmic *pmic = rdev_to_pmic(dev); + u32 vsel; + int ret = 0; + + vsel = DIV_ROUND_UP((max_uV - sreg->rdesc.min_uV), + sreg->rdesc.uV_step); + + *selector = vsel; + /* set voltage selector */ + hi6421_pmic_rmw(pmic, sreg->register_info.vset_reg, + sreg->register_info.vset_mask, + vsel << (ffs(sreg->register_info.vset_mask) - 1)); + + return ret; +} + +static unsigned int hi6421_regulator_get_mode(struct regulator_dev *dev) +{ + struct hi6421_regulator *sreg = rdev_get_drvdata(dev); + struct hi6421_pmic *pmic = rdev_to_pmic(dev); + u32 reg_val; + + reg_val = hi6421_pmic_read(pmic, sreg->register_info.ctrl_reg); + if (reg_val & sreg->register_info.eco_mode_mask) + return REGULATOR_MODE_IDLE; + else + return REGULATOR_MODE_NORMAL; +} + +static int hi6421_regulator_set_mode(struct regulator_dev *dev, + unsigned int mode) +{ + struct hi6421_regulator *sreg = rdev_get_drvdata(dev); + struct hi6421_pmic *pmic = rdev_to_pmic(dev); + u32 eco_mode; + + switch (mode) { + case REGULATOR_MODE_NORMAL: + eco_mode = HI6421_ECO_MODE_DISABLE; + break; + case REGULATOR_MODE_IDLE: + eco_mode = HI6421_ECO_MODE_ENABLE; + break; + default: + return -EINVAL; + } + + /* set mode */ + hi6421_pmic_rmw(pmic, sreg->register_info.ctrl_reg, + sreg->register_info.eco_mode_mask, + eco_mode << (ffs(sreg->register_info.eco_mode_mask) - 1)); + + return 0; +} + + +unsigned int hi6421_regulator_get_optimum_mode(struct regulator_dev *dev, + int input_uV, int output_uV, int load_uA) +{ + struct hi6421_regulator *sreg = rdev_get_drvdata(dev); + + if ((load_uA == 0) || (load_uA > sreg->eco_uA)) + return REGULATOR_MODE_NORMAL; + else + return REGULATOR_MODE_IDLE; +} + +static int hi6421_dt_parse_common(struct hi6421_regulator *sreg, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct regulator_desc *rdesc = &sreg->rdesc; + unsigned int register_info[3]; + int ret = 0; + + /* parse .register_info.ctrl_reg */ + ret = of_property_read_u32_array(np, "hisilicon,hi6421-ctrl", + register_info, 3); + if (ret) { + dev_err(dev, "no hisilicon,hi6421-ctrl property set\n"); + goto dt_parse_common_end; + } + sreg->register_info.ctrl_reg = register_info[0]; + sreg->register_info.enable_mask = register_info[1]; + sreg->register_info.eco_mode_mask = register_info[2]; + + /* parse .register_info.vset_reg */ + ret = of_property_read_u32_array(np, "hisilicon,hi6421-vset", + register_info, 2); + if (ret) { + dev_err(dev, "no hisilicon,hi6421-vset property set\n"); + goto dt_parse_common_end; + } + sreg->register_info.vset_reg = register_info[0]; + sreg->register_info.vset_mask = register_info[1]; + + /* parse .off-on-delay */ + ret = of_property_read_u32(np, "hisilicon,hi6421-off-on-delay-us", + &sreg->off_on_delay); + if (ret) { + dev_err(dev, "no hisilicon,hi6421-off-on-delay-us property set\n"); + goto dt_parse_common_end; + } + + /* parse .enable_time */ + ret = of_property_read_u32(np, "hisilicon,hi6421-enable-time-us", + &rdesc->enable_time); + if (ret) { + dev_err(dev, "no hisilicon,hi6421-enable-time-us property set\n"); + goto dt_parse_common_end; + } + + /* parse .eco_uA */ + ret = of_property_read_u32(np, "hisilicon,hi6421-eco-microamp", + &sreg->eco_uA); + if (ret) { + sreg->eco_uA = 0; + ret = 0; + } + +dt_parse_common_end: + return ret; +} + +static int hi6421_dt_parse_ldo(struct hi6421_regulator *sreg, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct regulator_desc *rdesc = &sreg->rdesc; + unsigned int *v_table; + int ret = 0; + + /* parse .n_voltages, and .volt_table */ + ret = of_property_read_u32(np, "hisilicon,hi6421-n-voltages", + &rdesc->n_voltages); + if (ret) { + dev_err(dev, "no hisilicon,hi6421-n-voltages property set\n"); + goto dt_parse_ldo_end; + } + + /* alloc space for .volt_table */ + v_table = devm_kzalloc(dev, sizeof(unsigned int) * rdesc->n_voltages, + GFP_KERNEL); + if (unlikely(!v_table)) { + ret = -ENOMEM; + dev_err(dev, "no memory for .volt_table\n"); + goto dt_parse_ldo_end; + } + + ret = of_property_read_u32_array(np, "hisilicon,hi6421-vset-table", + v_table, rdesc->n_voltages); + if (ret) { + dev_err(dev, "no hisilicon,hi6421-vset-table property set\n"); + goto dt_parse_ldo_end; + } + rdesc->volt_table = v_table; + + /* parse hi6421 regulator's dt common part */ + ret = hi6421_dt_parse_common(sreg, pdev); + if (ret) { + dev_err(dev, "failure in hi6421_dt_parse_common\n"); + goto dt_parse_ldo_end; + } + +dt_parse_ldo_end: + return ret; +} + +static int hi6421_dt_parse_buck012(struct hi6421_regulator *sreg, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct regulator_desc *rdesc = &sreg->rdesc; + int ret = 0; + + /* parse .n_voltages, and .uV_step */ + ret = of_property_read_u32(np, "hisilicon,hi6421-n-voltages", + &rdesc->n_voltages); + if (ret) { + dev_err(dev, "no hisilicon,hi6421-n-voltages property set\n"); + goto dt_parse_buck012_end; + } + ret = of_property_read_u32(np, "hisilicon,hi6421-uv-step", + &rdesc->uV_step); + if (ret) { + dev_err(dev, "no hisilicon,hi6421-uv-step property set\n"); + goto dt_parse_buck012_end; + } + + /* parse hi6421 regulator's dt common part */ + ret = hi6421_dt_parse_common(sreg, pdev); + if (ret) { + dev_err(dev, "failure in hi6421_dt_parse_common\n"); + goto dt_parse_buck012_end; + } + +dt_parse_buck012_end: + return ret; +} + +static struct regulator_ops hi6421_ldo_rops = { + .is_enabled = hi6421_regulator_is_enabled, + .enable = hi6421_regulator_enable, + .disable = hi6421_regulator_disable, + .list_voltage = regulator_list_voltage_table, + .get_voltage = hi6421_regulator_get_voltage, + .set_voltage = hi6421_regulator_ldo_set_voltage, + .get_mode = hi6421_regulator_get_mode, + .set_mode = hi6421_regulator_set_mode, + .get_optimum_mode = hi6421_regulator_get_optimum_mode, +}; + +static struct regulator_ops hi6421_buck012_rops = { + .is_enabled = hi6421_regulator_is_enabled, + .enable = hi6421_regulator_enable, + .disable = hi6421_regulator_disable, + .list_voltage = regulator_list_voltage_linear, + .get_voltage = hi6421_regulator_get_voltage, + .set_voltage = hi6421_regulator_buck012_set_voltage, + .get_mode = hi6421_regulator_get_mode, + .set_mode = hi6421_regulator_set_mode, + .get_optimum_mode = hi6421_regulator_get_optimum_mode, +}; + +static struct regulator_ops hi6421_buck345_rops = { + .is_enabled = hi6421_regulator_is_enabled, + .enable = hi6421_regulator_enable, + .disable = hi6421_regulator_disable, + .list_voltage = regulator_list_voltage_table, + .get_voltage = hi6421_regulator_get_voltage, + .set_voltage = hi6421_regulator_ldo_set_voltage, + .get_mode = hi6421_regulator_get_mode, + .set_mode = hi6421_regulator_set_mode, + .get_optimum_mode = hi6421_regulator_get_optimum_mode, +}; + +static const struct hi6421_regulator hi6421_regulator_ldo = { + .rdesc = { + .ops = &hi6421_ldo_rops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .dt_parse = hi6421_dt_parse_ldo, +}; + +static const struct hi6421_regulator hi6421_regulator_buck012 = { + .rdesc = { + .ops = &hi6421_buck012_rops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .dt_parse = hi6421_dt_parse_buck012, +}; + +static const struct hi6421_regulator hi6421_regulator_buck345 = { + .rdesc = { + .ops = &hi6421_buck345_rops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .dt_parse = hi6421_dt_parse_ldo, +}; + +static struct of_device_id of_hi6421_regulator_match_tbl[] = { + { + .compatible = "hisilicon,hi6421-ldo", + .data = &hi6421_regulator_ldo, + }, + { + .compatible = "hisilicon,hi6421-buck012", + .data = &hi6421_regulator_buck012, + }, + { + .compatible = "hisilicon,hi6421-buck345", + .data = &hi6421_regulator_buck345, + }, + { /* end */ } +}; + +static int hi6421_regulator_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct regulator_desc *rdesc; + struct regulator_dev *rdev; + struct hi6421_regulator *sreg = NULL; + struct regulator_init_data *initdata; + struct regulation_constraints *c; + struct regulator_config config = { }; + const struct of_device_id *match; + const struct hi6421_regulator *template = NULL; + int ret = 0; + + /* to check which type of regulator this is */ + match = of_match_device(of_hi6421_regulator_match_tbl, &pdev->dev); + if (match) + template = match->data; + else + return -EINVAL; + + initdata = of_get_regulator_init_data(dev, np); + + /* hi6421 regulator supports two modes */ + c = &initdata->constraints; + c->valid_modes_mask = REGULATOR_MODE_NORMAL | REGULATOR_MODE_IDLE; + c->valid_ops_mask |= (REGULATOR_CHANGE_MODE | REGULATOR_CHANGE_DRMS); + c->input_uV = c->min_uV; + + sreg = devm_kzalloc(dev, sizeof(*sreg), GFP_KERNEL); + if (sreg == NULL) + return -ENOMEM; + memcpy(sreg, template, sizeof(*sreg)); + + sreg->name = initdata->constraints.name; + rdesc = &sreg->rdesc; + rdesc->name = sreg->name; + rdesc->min_uV = initdata->constraints.min_uV; + + /* to parse device tree data for regulator specific */ + ret = sreg->dt_parse(sreg, pdev); + if (ret) { + dev_err(dev, "device tree parameter parse error!\n"); + goto hi6421_probe_end; + } + + config.dev = &pdev->dev; + config.init_data = initdata; + config.driver_data = sreg; + config.of_node = pdev->dev.of_node; + + /* register regulator */ + rdev = regulator_register(rdesc, &config); + if (IS_ERR(rdev)) { + dev_err(dev, "failed to register %s\n", + rdesc->name); + ret = PTR_ERR(rdev); + goto hi6421_probe_end; + } + + platform_set_drvdata(pdev, rdev); + +hi6421_probe_end: + return ret; +} + +static int hi6421_regulator_remove(struct platform_device *pdev) +{ + struct regulator_dev *rdev = platform_get_drvdata(pdev); + struct hi6421_regulator *sreg = rdev_get_drvdata(rdev); + + regulator_unregister(rdev); + + return 0; +} + +static struct platform_driver hi6421_regulator_driver = { + .driver = { + .name = "hi6421_regulator", + .owner = THIS_MODULE, + .of_match_table = of_hi6421_regulator_match_tbl, + }, + .probe = hi6421_regulator_probe, + .remove = hi6421_regulator_remove, +}; +module_platform_driver(hi6421_regulator_driver); + +MODULE_AUTHOR("Guodong Xu <guodong.xu@linaro.org>"); +MODULE_DESCRIPTION("Hi6421 regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/mfd/hi6421-pmic.h b/include/linux/mfd/hi6421-pmic.h index 2f875cf52ae9..0b60a9b2a666 100644 --- a/include/linux/mfd/hi6421-pmic.h +++ b/include/linux/mfd/hi6421-pmic.h @@ -24,6 +24,8 @@ #define __HI6421_PMIC_H #include <linux/irqdomain.h> +#include <linux/mutex.h> +#include <linux/time.h> #define OCP_DEB_CTRL_REG (0x51) #define OCP_DEB_SEL_MASK (0x0C) @@ -66,6 +68,17 @@ #define HI6421_IRQ_BATTERY_ON 20 /* Battery inserted */ #define HI6421_IRQ_RESET 21 /* Reset */ +/** + * struct hi6421_pmic - Hi6421 PMIC chip-level data structure. + * + * This struct describes Hi6421 PMIC chip-level data. + * + * @enable_mutex: Used by regulators, to make sure that only one regulator is + * in the turning-on phase at any time. See also @last_enabled + * and hi6421_regulator_enable(). + * @last_enabled: Used by regulators, to make sure enough distance in time + * between enabling of any of two regulators. + */ struct hi6421_pmic { struct resource *res; struct device *dev; @@ -74,6 +87,8 @@ struct hi6421_pmic { struct irq_domain *domain; int irq; int gpio; + struct mutex enable_mutex; + struct timeval last_enabled; }; /* Register Access Helpers */ |