Initial regulator driver for ldos on the pm8921 controlled by RPM. Signed-off-by: Bjorn Andersson <bjorn.andersson@xxxxxxxxxxxxxx> --- drivers/regulator/Kconfig | 7 + drivers/regulator/Makefile | 1 + drivers/regulator/msm_rpm-regulator.c | 467 ++++++++++++++++++++++++++++++++++ 3 files changed, 475 insertions(+) create mode 100644 drivers/regulator/msm_rpm-regulator.c diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 6a79328..a1d35316 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -372,6 +372,13 @@ config REGULATOR_MC13892 Say y here to support the regulators found on the Freescale MC13892 PMIC. +config REGULATOR_MSM_RPM + tristate "Qualcomm RPM regulator driver" + depends on MFD_MSM_RPM + help + This driver provides support for regulators controlled by the + Qualcomm RPM. + config REGULATOR_PALMAS tristate "TI Palmas PMIC Regulators" depends on MFD_PALMAS diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 979f9dd..c1cb250 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -51,6 +51,7 @@ obj-$(CONFIG_REGULATOR_MAX77693) += max77693.o obj-$(CONFIG_REGULATOR_MC13783) += mc13783-regulator.o obj-$(CONFIG_REGULATOR_MC13892) += mc13892-regulator.o obj-$(CONFIG_REGULATOR_MC13XXX_CORE) += mc13xxx-regulator-core.o +obj-$(CONFIG_REGULATOR_MSM_RPM) += msm_rpm-regulator.o obj-$(CONFIG_REGULATOR_PALMAS) += palmas-regulator.o obj-$(CONFIG_REGULATOR_PFUZE100) += pfuze100-regulator.o obj-$(CONFIG_REGULATOR_TPS51632) += tps51632-regulator.o diff --git a/drivers/regulator/msm_rpm-regulator.c b/drivers/regulator/msm_rpm-regulator.c new file mode 100644 index 0000000..4c1cd6a --- /dev/null +++ b/drivers/regulator/msm_rpm-regulator.c @@ -0,0 +1,467 @@ +/* + * Copyright (c) 2013, Sony Mobile Communications AB. + * + * 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/platform_device.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> + +#include <linux/mfd/msm_rpm.h> + +struct rpm_vreg_parts; +struct vreg_range; + +struct msm_rpm_reg { + struct mutex lock; + struct device *dev; + struct regulator_desc desc; + + const struct rpm_vreg_parts *parts; + const struct vreg_range *ranges; + int n_ranges; + + const int resource; + + int is_enabled; + + u32 val[2]; + + int uV; + const int hpm_min_load; +}; + +#define REQUEST_MEMBER(_word, _mask, _shift) \ +{ \ + .word = _word, \ + .mask = _mask, \ + .shift = _shift, \ +} + +struct request_member { + int word; + unsigned int mask; + int shift; +}; + +/* Possible RPM regulator request members */ +struct rpm_vreg_parts { + struct request_member mV; /* voltage: used if voltage is in mV */ + struct request_member uV; /* voltage: used if voltage is in uV */ + struct request_member ip; /* peak current in mA */ + struct request_member pd; /* pull down enable */ + struct request_member ia; /* average current in mA */ + struct request_member fm; /* force mode */ + struct request_member pm; /* power mode */ + struct request_member pc; /* pin control */ + struct request_member pf; /* pin function */ + struct request_member enable_state; /* NCP and switch */ + struct request_member comp_mode; /* NCP */ + struct request_member freq; /* frequency: NCP and SMPS */ + struct request_member freq_clk_src; /* clock source: SMPS */ + struct request_member hpm; /* switch: control OCP and SS */ + int request_len; +}; + +struct vreg_range { + int min_uV; + int max_uV; + int step_uV; +}; + +#define VOLTAGE_RANGE(_min_uV, _max_uV, _step_uV) \ +{ \ + .min_uV = _min_uV, \ + .max_uV = _max_uV, \ + .step_uV = _step_uV, \ +} + +#define SET_POINTS(_ranges) \ +{ \ + .range = _ranges, \ + .count = ARRAY_SIZE(_ranges), \ +}; + +/* Minimum high power mode loads in uA. */ +#define RPM_VREG_8960_LDO_50_HPM_MIN_LOAD 5000 +#define RPM_VREG_8960_LDO_150_HPM_MIN_LOAD 10000 +#define RPM_VREG_8960_LDO_300_HPM_MIN_LOAD 10000 +#define RPM_VREG_8960_LDO_600_HPM_MIN_LOAD 10000 +#define RPM_VREG_8960_LDO_1200_HPM_MIN_LOAD 10000 +#define RPM_VREG_8960_SMPS_1500_HPM_MIN_LOAD 100000 +#define RPM_VREG_8960_SMPS_2000_HPM_MIN_LOAD 100000 + +/* + * Physically available PMIC regulator voltage setpoint ranges + */ +static const struct vreg_range pldo_ranges[] = { + VOLTAGE_RANGE( 750000, 1487500, 12500), + VOLTAGE_RANGE(1500000, 3075000, 25000), + VOLTAGE_RANGE(3100000, 4900000, 50000), +}; + +static const struct vreg_range nldo_ranges[] = { + VOLTAGE_RANGE( 750000, 1537500, 12500), +}; + +static const struct vreg_range nldo1200_ranges[] = { + VOLTAGE_RANGE( 375000, 743750, 6250), + VOLTAGE_RANGE( 750000, 1537500, 12500), +}; + +static const struct vreg_range ln_ldo_ranges[] = { + VOLTAGE_RANGE( 690000, 1110000, 60000), + VOLTAGE_RANGE(1380000, 2220000, 120000), +}; + +static const struct vreg_range smps_ranges[] = { + VOLTAGE_RANGE( 375000, 737500, 12500), + VOLTAGE_RANGE( 750000, 1487500, 12500), + VOLTAGE_RANGE(1500000, 3075000, 25000), +}; + +static const struct vreg_range ftsmps_ranges[] = { + VOLTAGE_RANGE( 350000, 650000, 50000), + VOLTAGE_RANGE( 700000, 1400000, 12500), + VOLTAGE_RANGE(1500000, 3300000, 50000), +}; + +static const struct vreg_range ncp_ranges[] = { + VOLTAGE_RANGE(1500000, 3050000, 50000), +}; + +/* + * RPM regulator request formats for MSM8660 + */ +static const struct rpm_vreg_parts ldo_parts_8660 = { + .request_len = 2, + .mV = REQUEST_MEMBER(0, 0x00000FFF, 0), + .ip = REQUEST_MEMBER(0, 0x00FFF000, 12), + .fm = REQUEST_MEMBER(0, 0x03000000, 24), + .pc = REQUEST_MEMBER(0, 0x3C000000, 26), + .pf = REQUEST_MEMBER(0, 0xC0000000, 30), + .pd = REQUEST_MEMBER(1, 0x00000001, 0), + .ia = REQUEST_MEMBER(1, 0x00001FFE, 1), +}; + +static const struct rpm_vreg_parts smps_parts_8660 = { + .request_len = 2, + .mV = REQUEST_MEMBER(0, 0x00000FFF, 0), + .ip = REQUEST_MEMBER(0, 0x00FFF000, 12), + .fm = REQUEST_MEMBER(0, 0x03000000, 24), + .pc = REQUEST_MEMBER(0, 0x3C000000, 26), + .pf = REQUEST_MEMBER(0, 0xC0000000, 30), + .pd = REQUEST_MEMBER(1, 0x00000001, 0), + .ia = REQUEST_MEMBER(1, 0x00001FFE, 1), + .freq = REQUEST_MEMBER(1, 0x001FE000, 13), + .freq_clk_src = REQUEST_MEMBER(1, 0x00600000, 21), +}; + +static const struct rpm_vreg_parts switch_parts_8660 = { + .request_len = 1, + .enable_state = REQUEST_MEMBER(0, 0x00000001, 0), + .pd = REQUEST_MEMBER(0, 0x00000002, 1), + .pc = REQUEST_MEMBER(0, 0x0000003C, 2), + .pf = REQUEST_MEMBER(0, 0x000000C0, 6), + .hpm = REQUEST_MEMBER(0, 0x00000300, 8), +}; + +static const struct rpm_vreg_parts ncp_parts_8660 = { + .request_len = 1, + .mV = REQUEST_MEMBER(0, 0x00000FFF, 0), + .enable_state = REQUEST_MEMBER(0, 0x00001000, 12), + .comp_mode = REQUEST_MEMBER(0, 0x00002000, 13), + .freq = REQUEST_MEMBER(0, 0x003FC000, 14), +}; + +/* + * RPM regulator request formats for MSM8960 and APQ8064 + */ +static const struct rpm_vreg_parts ldo_parts_8960 = { + .request_len = 2, + .uV = REQUEST_MEMBER(0, 0x007FFFFF, 0), + .pd = REQUEST_MEMBER(0, 0x00800000, 23), + .pc = REQUEST_MEMBER(0, 0x0F000000, 24), + .pf = REQUEST_MEMBER(0, 0xF0000000, 28), + .ip = REQUEST_MEMBER(1, 0x000003FF, 0), + .ia = REQUEST_MEMBER(1, 0x000FFC00, 10), + .fm = REQUEST_MEMBER(1, 0x00700000, 20), +}; + +static const struct rpm_vreg_parts smps_parts_8960 = { + .request_len = 2, + .uV = REQUEST_MEMBER(0, 0x007FFFFF, 0), + .pd = REQUEST_MEMBER(0, 0x00800000, 23), + .pc = REQUEST_MEMBER(0, 0x0F000000, 24), + .pf = REQUEST_MEMBER(0, 0xF0000000, 28), + .ip = REQUEST_MEMBER(1, 0x000003FF, 0), + .ia = REQUEST_MEMBER(1, 0x000FFC00, 10), + .fm = REQUEST_MEMBER(1, 0x00700000, 20), + .pm = REQUEST_MEMBER(1, 0x00800000, 23), + .freq = REQUEST_MEMBER(1, 0x1F000000, 24), + .freq_clk_src = REQUEST_MEMBER(1, 0x60000000, 29), +}; + +static const struct rpm_vreg_parts switch_parts_8960 = { + .request_len = 1, + .enable_state = REQUEST_MEMBER(0, 0x00000001, 0), + .pd = REQUEST_MEMBER(0, 0x00000002, 1), + .pc = REQUEST_MEMBER(0, 0x0000003C, 2), + .pf = REQUEST_MEMBER(0, 0x000003C0, 6), + .hpm = REQUEST_MEMBER(0, 0x00000C00, 10), +}; + +static const struct rpm_vreg_parts ncp_parts_8960 = { + .request_len = 1, + .uV = REQUEST_MEMBER(0, 0x007FFFFF, 0), + .enable_state = REQUEST_MEMBER(0, 0x00800000, 23), + .comp_mode = REQUEST_MEMBER(0, 0x01000000, 24), + .freq = REQUEST_MEMBER(0, 0x3E000000, 25), +}; + +static const struct msm_rpm_reg msm8960_ldo16 = { + .resource = MSM_RPM_PM8921_L16, + + .desc.name = "pm8921_ldo16", + .ranges = pldo_ranges, + .n_ranges = ARRAY_SIZE(pldo_ranges), + .parts = &ldo_parts_8960, + .hpm_min_load = RPM_VREG_8960_LDO_300_HPM_MIN_LOAD, +}; + +static const struct msm_rpm_reg msm8960_ldo17 = { + .resource = MSM_RPM_PM8921_L17, + + .desc.name = "pm8921_ldo17", + .ranges = pldo_ranges, + .n_ranges = ARRAY_SIZE(pldo_ranges), + .parts = &ldo_parts_8960, + .hpm_min_load = RPM_VREG_8960_LDO_300_HPM_MIN_LOAD, +}; + +static const struct msm_rpm_reg msm8960_ldo29 = { + .resource = MSM_RPM_PM8921_L29, + + .desc.name = "pm8921_ldo29", + .ranges = pldo_ranges, + .n_ranges = ARRAY_SIZE(pldo_ranges), + .parts = &ldo_parts_8960, + .hpm_min_load = RPM_VREG_8960_LDO_300_HPM_MIN_LOAD, +}; + +static const struct of_device_id rpm_of_match[] = { + { .compatible = "qcom,msm8960-regulator-l16", .data = &msm8960_ldo16 }, + { .compatible = "qcom,apq8064-regulator-l17", .data = &msm8960_ldo17 }, + { .compatible = "qcom,apq8064-regulator-l29", .data = &msm8960_ldo29 }, +}; +MODULE_DEVICE_TABLE(of, rpm_of_match); + +static int rpm_reg_write(struct msm_rpm_reg *vreg, + const struct request_member *req, + const int value) +{ + vreg->val[req->word] &= ~req->mask; + vreg->val[req->word] |= value << req->shift; + + return msm_rpm_write(vreg->dev->parent, + vreg->resource, + vreg->val, + vreg->parts->request_len); +} + +static int rpm_reg_enable(struct regulator_dev *rdev) +{ + struct msm_rpm_reg *vreg = rdev_get_drvdata(rdev); + const struct rpm_vreg_parts *parts = vreg->parts; + int ret; + + mutex_lock(&vreg->lock); + ret = rpm_reg_write(vreg, &parts->uV, vreg->uV); + if (!ret) + vreg->is_enabled = 1; + mutex_unlock(&vreg->lock); + + return ret; +} + +static int rpm_reg_is_enabled(struct regulator_dev *rdev) +{ + struct msm_rpm_reg *vreg = rdev_get_drvdata(rdev); + + return vreg->is_enabled; +} + +static int rpm_reg_disable(struct regulator_dev *rdev) +{ + struct msm_rpm_reg *vreg = rdev_get_drvdata(rdev); + const struct rpm_vreg_parts *parts = vreg->parts; + int ret; + + mutex_lock(&vreg->lock); + ret = rpm_reg_write(vreg, &parts->uV, 0); + if (!ret) + vreg->is_enabled = 0; + mutex_unlock(&vreg->lock); + + return ret; +} + +static int rpm_reg_set_voltage(struct regulator_dev *rdev, int min_uV, int max_uV, + unsigned *selector) +{ + struct msm_rpm_reg *vreg = rdev_get_drvdata(rdev); + const struct rpm_vreg_parts *parts = vreg->parts; + const struct vreg_range *range = NULL; + int ret = 0; + int uV; + int i; + + dev_dbg(vreg->dev, "set_voltage(%d, %d)\n", min_uV, max_uV); + + /* + * Snap to the voltage to a supported level. + */ + for (i = 0; i < vreg->n_ranges; i++) { + range = &vreg->ranges[i]; + if (min_uV <= range->max_uV && max_uV >= range->min_uV) + break; + } + + if (i == vreg->n_ranges) { + dev_err(vreg->dev, + "requested voltage %d-%d outside possible ranges\n", + min_uV, max_uV); + return -EINVAL; + } + + if (min_uV < range->min_uV) + uV = range->min_uV; + else + uV = min_uV; + + uV = roundup(uV, range->step_uV); + + dev_dbg(vreg->dev, "snapped voltage %duV\n", uV); + + /* + * Update the register. + */ + mutex_lock(&vreg->lock); + vreg->uV = uV; + if (vreg->is_enabled) + ret = rpm_reg_write(vreg, &parts->uV, vreg->uV); + mutex_unlock(&vreg->lock); + + return ret; +} + +static int rpm_reg_get_voltage(struct regulator_dev *rdev) +{ + struct msm_rpm_reg *vreg = rdev_get_drvdata(rdev); + return vreg->uV; +} + +static struct regulator_ops ldo_ops = { + .enable = rpm_reg_enable, + .disable = rpm_reg_disable, + .is_enabled = rpm_reg_is_enabled, + + .set_voltage = rpm_reg_set_voltage, + .get_voltage = rpm_reg_get_voltage, +}; + +static int rpm_reg_probe(struct platform_device *pdev) +{ + struct regulator_init_data *initdata; + const struct msm_rpm_reg *template; + const struct of_device_id *match; + struct regulator_config config = { }; + struct regulator_desc *desc; + struct regulator_dev *rdev; + struct msm_rpm_reg *vreg; + + match = of_match_device(rpm_of_match, &pdev->dev); + template = match->data; + + initdata = of_get_regulator_init_data(&pdev->dev, pdev->dev.of_node); + if (!initdata) + return -EINVAL; + + vreg = devm_kmalloc(&pdev->dev, sizeof(*vreg), GFP_KERNEL); + if (!vreg) { + dev_err(&pdev->dev, "failed to allocate vreg\n"); + return -ENOMEM; + } + memcpy(vreg, template, sizeof(*vreg)); + + mutex_init(&vreg->lock); + vreg->dev = &pdev->dev; + + /* XXX: + * tighten constraints of initdata + * round min_uV and max_uV to step values + */ + + desc = &vreg->desc; + desc->id = -1; + desc->ops = &ldo_ops; + desc->owner = THIS_MODULE; + desc->type = REGULATOR_VOLTAGE; + + config.dev = &pdev->dev; + config.init_data = initdata; + config.driver_data = vreg; + config.of_node = pdev->dev.of_node; + rdev = devm_regulator_register(&pdev->dev, desc, &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "can't register regulator\n"); + return PTR_ERR(rdev); + } + + platform_set_drvdata(pdev, rdev); + + return 0; +} + +static int rpm_reg_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver rpm_reg_driver = { + .probe = rpm_reg_probe, + .remove = rpm_reg_remove, + .driver = { + .name = "msm_rpm_reg", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(rpm_of_match), + }, +}; + +static int __init rpm_reg_init(void) +{ + return platform_driver_register(&rpm_reg_driver); +} +arch_initcall(rpm_reg_init); + +static void __exit rpm_reg_exit(void) +{ + platform_driver_unregister(&rpm_reg_driver); +} +module_exit(rpm_reg_exit) + +MODULE_DESCRIPTION("MSM RPM regulator driver"); +MODULE_LICENSE("GPLv2"); + -- 1.8.2.2 -- To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html