[RFC WIP 2/2] regulator: msm_rpm: Initial regulator driver for Qualcomm RPM

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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




[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [Linux for Sparc]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]

  Powered by Linux