diff options
Diffstat (limited to 'drivers/regulator/hi6421v600-regulator.c')
-rw-r--r-- | drivers/regulator/hi6421v600-regulator.c | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/drivers/regulator/hi6421v600-regulator.c b/drivers/regulator/hi6421v600-regulator.c new file mode 100644 index 0000000000..4e10daa1e6 --- /dev/null +++ b/drivers/regulator/hi6421v600-regulator.c @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Device driver for regulators in Hisi IC +// +// Copyright (c) 2013 Linaro Ltd. +// Copyright (c) 2011 HiSilicon Ltd. +// Copyright (c) 2020-2021 Huawei Technologies Co., Ltd. +// +// Guodong Xu <guodong.xu@linaro.org> + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/spmi.h> + +struct hi6421_spmi_reg_priv { + /* Serialize regulator enable logic */ + struct mutex enable_mutex; +}; + +struct hi6421_spmi_reg_info { + struct regulator_desc desc; + u8 eco_mode_mask; + u32 eco_uA; +}; + +static const unsigned int range_1v5_to_2v0[] = { + 1500000, 1550000, 1600000, 1650000, + 1700000, 1725000, 1750000, 1775000, + 1800000, 1825000, 1850000, 1875000, + 1900000, 1925000, 1950000, 2000000 +}; + +static const unsigned int range_1v725_to_1v9[] = { + 1725000, 1750000, 1775000, 1800000, + 1825000, 1850000, 1875000, 1900000 +}; + +static const unsigned int range_1v75_to_3v3[] = { + 1750000, 1800000, 1825000, 2800000, + 2850000, 2950000, 3000000, 3300000 +}; + +static const unsigned int range_1v8_to_3v0[] = { + 1800000, 1850000, 2400000, 2600000, + 2700000, 2850000, 2950000, 3000000 +}; + +static const unsigned int range_2v5_to_3v3[] = { + 2500000, 2600000, 2700000, 2800000, + 3000000, 3100000, 3200000, 3300000 +}; + +static const unsigned int range_2v6_to_3v3[] = { + 2600000, 2700000, 2800000, 2900000, + 3000000, 3100000, 3200000, 3300000 +}; + +/** + * HI6421V600_LDO() - specify a LDO power line + * @_id: LDO id name string + * @vtable: voltage table + * @ereg: enable register + * @emask: enable mask + * @vreg: voltage select register + * @odelay: off/on delay time in uS + * @etime: enable time in uS + * @ecomask: eco mode mask + * @ecoamp: eco mode load uppler limit in uA + */ +#define HI6421V600_LDO(_id, vtable, ereg, emask, vreg, \ + odelay, etime, ecomask, ecoamp) \ + [hi6421v600_##_id] = { \ + .desc = { \ + .name = #_id, \ + .of_match = of_match_ptr(#_id), \ + .regulators_node = of_match_ptr("regulators"), \ + .ops = &hi6421_spmi_ldo_rops, \ + .type = REGULATOR_VOLTAGE, \ + .id = hi6421v600_##_id, \ + .owner = THIS_MODULE, \ + .volt_table = vtable, \ + .n_voltages = ARRAY_SIZE(vtable), \ + .vsel_mask = ARRAY_SIZE(vtable) - 1, \ + .vsel_reg = vreg, \ + .enable_reg = ereg, \ + .enable_mask = emask, \ + .enable_time = etime, \ + .ramp_delay = etime, \ + .off_on_delay = odelay, \ + }, \ + .eco_mode_mask = ecomask, \ + .eco_uA = ecoamp, \ + } + +static int hi6421_spmi_regulator_enable(struct regulator_dev *rdev) +{ + struct hi6421_spmi_reg_priv *priv = rdev_get_drvdata(rdev); + int ret; + + /* cannot enable more than one regulator at one time */ + mutex_lock(&priv->enable_mutex); + + ret = regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + rdev->desc->enable_mask, + rdev->desc->enable_mask); + + /* Avoid powering up multiple devices at the same time */ + usleep_range(rdev->desc->off_on_delay, rdev->desc->off_on_delay + 60); + + mutex_unlock(&priv->enable_mutex); + + return ret; +} + +static unsigned int hi6421_spmi_regulator_get_mode(struct regulator_dev *rdev) +{ + struct hi6421_spmi_reg_info *sreg; + unsigned int reg_val; + + sreg = container_of(rdev->desc, struct hi6421_spmi_reg_info, desc); + regmap_read(rdev->regmap, rdev->desc->enable_reg, ®_val); + + if (reg_val & sreg->eco_mode_mask) + return REGULATOR_MODE_IDLE; + + return REGULATOR_MODE_NORMAL; +} + +static int hi6421_spmi_regulator_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct hi6421_spmi_reg_info *sreg; + unsigned int val; + + sreg = container_of(rdev->desc, struct hi6421_spmi_reg_info, desc); + switch (mode) { + case REGULATOR_MODE_NORMAL: + val = 0; + break; + case REGULATOR_MODE_IDLE: + if (!sreg->eco_mode_mask) + return -EINVAL; + + val = sreg->eco_mode_mask; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + sreg->eco_mode_mask, val); +} + +static unsigned int +hi6421_spmi_regulator_get_optimum_mode(struct regulator_dev *rdev, + int input_uV, int output_uV, + int load_uA) +{ + struct hi6421_spmi_reg_info *sreg; + + sreg = container_of(rdev->desc, struct hi6421_spmi_reg_info, desc); + + if (!sreg->eco_uA || ((unsigned int)load_uA > sreg->eco_uA)) + return REGULATOR_MODE_NORMAL; + + return REGULATOR_MODE_IDLE; +} + +static const struct regulator_ops hi6421_spmi_ldo_rops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = hi6421_spmi_regulator_enable, + .disable = regulator_disable_regmap, + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_ascend, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_mode = hi6421_spmi_regulator_get_mode, + .set_mode = hi6421_spmi_regulator_set_mode, + .get_optimum_mode = hi6421_spmi_regulator_get_optimum_mode, +}; + +/* HI6421v600 regulators with known registers */ +enum hi6421_spmi_regulator_id { + hi6421v600_ldo3, + hi6421v600_ldo4, + hi6421v600_ldo9, + hi6421v600_ldo15, + hi6421v600_ldo16, + hi6421v600_ldo17, + hi6421v600_ldo33, + hi6421v600_ldo34, +}; + +static struct hi6421_spmi_reg_info regulator_info[] = { + HI6421V600_LDO(ldo3, range_1v5_to_2v0, + 0x16, 0x01, 0x51, + 20000, 120, + 0, 0), + HI6421V600_LDO(ldo4, range_1v725_to_1v9, + 0x17, 0x01, 0x52, + 20000, 120, + 0x10, 10000), + HI6421V600_LDO(ldo9, range_1v75_to_3v3, + 0x1c, 0x01, 0x57, + 20000, 360, + 0x10, 10000), + HI6421V600_LDO(ldo15, range_1v8_to_3v0, + 0x21, 0x01, 0x5c, + 20000, 360, + 0x10, 10000), + HI6421V600_LDO(ldo16, range_1v8_to_3v0, + 0x22, 0x01, 0x5d, + 20000, 360, + 0x10, 10000), + HI6421V600_LDO(ldo17, range_2v5_to_3v3, + 0x23, 0x01, 0x5e, + 20000, 120, + 0x10, 10000), + HI6421V600_LDO(ldo33, range_2v5_to_3v3, + 0x32, 0x01, 0x6d, + 20000, 120, + 0, 0), + HI6421V600_LDO(ldo34, range_2v6_to_3v3, + 0x33, 0x01, 0x6e, + 20000, 120, + 0, 0), +}; + +static int hi6421_spmi_regulator_probe(struct platform_device *pdev) +{ + struct device *pmic_dev = pdev->dev.parent; + struct regulator_config config = { }; + struct hi6421_spmi_reg_priv *priv; + struct hi6421_spmi_reg_info *info; + struct device *dev = &pdev->dev; + struct regmap *regmap; + struct regulator_dev *rdev; + int i; + + /* + * This driver is meant to be called by hi6421-spmi-core, + * which should first set drvdata. If this doesn't happen, hit + * a warn on and return. + */ + regmap = dev_get_drvdata(pmic_dev); + if (WARN_ON(!regmap)) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_init(&priv->enable_mutex); + + for (i = 0; i < ARRAY_SIZE(regulator_info); i++) { + info = ®ulator_info[i]; + + config.dev = pdev->dev.parent; + config.driver_data = priv; + config.regmap = regmap; + + rdev = devm_regulator_register(dev, &info->desc, &config); + if (IS_ERR(rdev)) { + dev_err(dev, "failed to register %s\n", + info->desc.name); + return PTR_ERR(rdev); + } + } + + return 0; +} + +static const struct platform_device_id hi6421_spmi_regulator_table[] = { + { .name = "hi6421v600-regulator" }, + {}, +}; +MODULE_DEVICE_TABLE(platform, hi6421_spmi_regulator_table); + +static struct platform_driver hi6421_spmi_regulator_driver = { + .id_table = hi6421_spmi_regulator_table, + .driver = { + .name = "hi6421v600-regulator", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .probe = hi6421_spmi_regulator_probe, +}; +module_platform_driver(hi6421_spmi_regulator_driver); + +MODULE_DESCRIPTION("Hi6421v600 SPMI regulator driver"); +MODULE_LICENSE("GPL v2"); + |