[PATCH V2 5/6] regulator: add mxs on-chip regulator driver

Stefan Wahren stefan.wahren at i2se.com
Wed Apr 29 15:32:26 PDT 2015


This patch adds driver support for Freescale i.MX23, i.MX28
on-chip regulators. The driver supports the following components:
dcdc, vddd, vdda and vddio.

Signed-off-by: Stefan Wahren <stefan.wahren at i2se.com>
---
 drivers/regulator/Kconfig         |    8 +
 drivers/regulator/Makefile        |    1 +
 drivers/regulator/mxs-regulator.c |  540 +++++++++++++++++++++++++++++++++++++
 3 files changed, 549 insertions(+)
 create mode 100644 drivers/regulator/mxs-regulator.c

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index a6f116a..7b525f5 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -450,6 +450,14 @@ config REGULATOR_MT6397
 	  This driver supports the control of different power rails of device
 	  through regulator interface.
 
+config REGULATOR_MXS
+	tristate "Freescale MXS on-chip regulators"
+	depends on (MXS_POWER || COMPILE_TEST)
+	help
+	  Say y here to support Freescale MXS on-chip regulators.
+	  It is recommended that this option be enabled on i.MX23,
+	  i.MX28 platform.
+
 config REGULATOR_PALMAS
 	tristate "TI Palmas PMIC Regulators"
 	depends on MFD_PALMAS
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 2c4da15..a3ebf23 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -60,6 +60,7 @@ 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_MT6397)	+= mt6397-regulator.o
+obj-$(CONFIG_REGULATOR_MXS) += mxs-regulator.o
 obj-$(CONFIG_REGULATOR_QCOM_RPM) += qcom_rpm-regulator.o
 obj-$(CONFIG_REGULATOR_PALMAS) += palmas-regulator.o
 obj-$(CONFIG_REGULATOR_PFUZE100) += pfuze100-regulator.o
diff --git a/drivers/regulator/mxs-regulator.c b/drivers/regulator/mxs-regulator.c
new file mode 100644
index 0000000..e53707b
--- /dev/null
+++ b/drivers/regulator/mxs-regulator.c
@@ -0,0 +1,540 @@
+/*
+ * Freescale MXS on-chip regulators
+ *
+ * Embedded Alley Solutions, Inc <source at embeddedalley.com>
+ *
+ * Copyright (C) 2014 Stefan Wahren
+ * Copyright (C) 2010 Freescale Semiconductor, Inc.
+ * Copyright (C) 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ *
+ * Inspired by imx-bootlets
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/slab.h>
+
+/* Powered by linear regulator.  DCDC output is gated off and
+   the linreg output is equal to the target. */
+#define HW_POWER_LINREG_DCDC_OFF		1
+
+/* Powered by linear regulator.  DCDC output is not gated off
+   and is ready for the automatic hardware transistion after a 5V
+   event.  The converters are not enabled when 5V is present. LinReg output
+   is 25mV below target. */
+#define HW_POWER_LINREG_DCDC_READY		2
+
+/* Powered by DCDC converter and the LinReg is on. LinReg output
+   is 25mV below target. */
+#define HW_POWER_DCDC_LINREG_ON			3
+
+/* Powered by DCDC converter and the LinReg is off. LinReg output
+   is 25mV below target. */
+#define HW_POWER_DCDC_LINREG_OFF		4
+
+/* Powered by DCDC converter and the LinReg is ready for the
+   automatic hardware transfer.  The LinReg output is not enabled and
+   depends on the 5V presence to enable the LinRegs.  LinReg offset is 25mV
+   below target. */
+#define HW_POWER_DCDC_LINREG_READY		5
+
+/* Powered by an external source when 5V is present.  This does not
+   necessarily mean the external source is powered by 5V,but the chip needs
+   to be aware that 5V is present. */
+#define HW_POWER_EXTERNAL_SOURCE_5V		6
+
+/* Powered by an external source when 5V is not present.This doesn't
+   necessarily mean the external source is powered by the battery, but the
+   chip needs to be aware that the battery is present */
+#define HW_POWER_EXTERNAL_SOURCE_BATTERY	7
+
+/* Unknown configuration.  This is an error. */
+#define HW_POWER_UNKNOWN_SOURCE			8
+
+/* TODO: Move power register offsets into header file */
+#define HW_POWER_5VCTRL		0x00000010
+#define HW_POWER_VDDDCTRL	0x00000040
+#define HW_POWER_VDDACTRL	0x00000050
+#define HW_POWER_VDDIOCTRL	0x00000060
+#define HW_POWER_MISC		0x00000090
+#define HW_POWER_STS		0x000000c0
+
+#define BM_POWER_STS_VBUSVALID0_STATUS	BIT(15)
+#define BM_POWER_STS_DC_OK		BIT(9)
+
+#define BM_POWER_5VCTRL_ILIMIT_EQ_ZERO	BIT(2)
+#define BM_POWER_5VCTRL_ENABLE_DCDC	BIT(0)
+
+#define BM_POWER_LINREG_OFFSET_DCDC_MODE	BIT(1)
+
+#define SHIFT_FREQSEL			4
+
+#define BM_POWER_MISC_FREQSEL		(7 << SHIFT_FREQSEL)
+
+/* Recommended DC-DC clock source values */
+#define HW_POWER_MISC_FREQSEL_20000_KHZ	1
+#define HW_POWER_MISC_FREQSEL_24000_KHZ	2
+#define HW_POWER_MISC_FREQSEL_19200_KHZ	3
+
+#define HW_POWER_MISC_SEL_PLLCLK	BIT(0)
+
+/* Regulator IDs */
+#define MXS_DCDC	1
+#define MXS_VDDIO	2
+#define MXS_VDDA	3
+#define MXS_VDDD	4
+
+struct mxs_reg_info {
+	/* regulator descriptor */
+	struct regulator_desc desc;
+
+	/* regulator control register */
+	int ctrl_reg;
+
+	/* disable DC-DC output */
+	unsigned int disable_fet_mask;
+
+	/* steps between linreg output and DC-DC target */
+	unsigned int linreg_offset_mask;
+	u8 linreg_offset_shift;
+
+	/* function which determine power source */
+	u8 (*get_power_source)(struct regulator_dev *);
+};
+
+static inline u8 get_linreg_offset(struct mxs_reg_info *ldo, u32 regs)
+{
+	return (regs & ldo->linreg_offset_mask) >> ldo->linreg_offset_shift;
+}
+
+static u8 get_vddio_power_source(struct regulator_dev *reg)
+{
+	struct mxs_reg_info *ldo = rdev_get_drvdata(reg);
+	u32 v5ctrl, status, base;
+	u8 offset;
+
+	if (regmap_read(reg->regmap, HW_POWER_5VCTRL, &v5ctrl))
+		return HW_POWER_UNKNOWN_SOURCE;
+
+	if (regmap_read(reg->regmap, HW_POWER_STS, &status))
+		return HW_POWER_UNKNOWN_SOURCE;
+
+	if (regmap_read(reg->regmap, ldo->ctrl_reg, &base))
+		return HW_POWER_UNKNOWN_SOURCE;
+
+	offset = get_linreg_offset(ldo, base);
+
+	/* If VBUS valid then 5 V power supply present */
+	if (status & BM_POWER_STS_VBUSVALID0_STATUS) {
+		/* Powered by Linreg, DC-DC is off */
+		if ((base & ldo->disable_fet_mask) &&
+		    !(offset & BM_POWER_LINREG_OFFSET_DCDC_MODE)) {
+			return HW_POWER_LINREG_DCDC_OFF;
+		}
+
+		if (v5ctrl & BM_POWER_5VCTRL_ENABLE_DCDC) {
+			/* Powered by DC-DC, Linreg is on */
+			if (offset & BM_POWER_LINREG_OFFSET_DCDC_MODE)
+				return HW_POWER_DCDC_LINREG_ON;
+		} else {
+			/* Powered by Linreg, DC-DC is off */
+			if (!(offset & BM_POWER_LINREG_OFFSET_DCDC_MODE))
+				return HW_POWER_LINREG_DCDC_OFF;
+		}
+	} else {
+		/* Powered by DC-DC, Linreg is on */
+		if (offset & BM_POWER_LINREG_OFFSET_DCDC_MODE)
+			return HW_POWER_DCDC_LINREG_ON;
+	}
+
+	return HW_POWER_UNKNOWN_SOURCE;
+}
+
+static u8 get_vdda_vddd_power_source(struct regulator_dev *reg)
+{
+	struct mxs_reg_info *ldo = rdev_get_drvdata(reg);
+	struct regulator_desc *desc = &ldo->desc;
+	u32 v5ctrl, status, base;
+	u8 offset;
+
+	if (regmap_read(reg->regmap, HW_POWER_5VCTRL, &v5ctrl))
+		return HW_POWER_UNKNOWN_SOURCE;
+
+	if (regmap_read(reg->regmap, HW_POWER_STS, &status))
+		return HW_POWER_UNKNOWN_SOURCE;
+
+	if (regmap_read(reg->regmap, ldo->ctrl_reg, &base))
+		return HW_POWER_UNKNOWN_SOURCE;
+
+	offset = get_linreg_offset(ldo, base);
+
+	/* DC-DC output is disabled */
+	if (base & ldo->disable_fet_mask) {
+		/* Powered by 5 V supply */
+		if (status & BM_POWER_STS_VBUSVALID0_STATUS)
+			return HW_POWER_EXTERNAL_SOURCE_5V;
+
+		/* Powered by Linreg, DC-DC is off */
+		if (!(offset & BM_POWER_LINREG_OFFSET_DCDC_MODE))
+			return HW_POWER_LINREG_DCDC_OFF;
+	}
+
+	/* If VBUS valid then 5 V power supply present */
+	if (status & BM_POWER_STS_VBUSVALID0_STATUS) {
+		/* Powered by DC-DC, Linreg is on */
+		if (v5ctrl & BM_POWER_5VCTRL_ENABLE_DCDC)
+			return HW_POWER_DCDC_LINREG_ON;
+
+		/* Powered by Linreg, DC-DC is off */
+		return HW_POWER_LINREG_DCDC_OFF;
+	}
+
+	/* DC-DC is on */
+	if (offset & BM_POWER_LINREG_OFFSET_DCDC_MODE) {
+		/* Powered by DC-DC, Linreg is on */
+		if (base & desc->enable_mask)
+			return HW_POWER_DCDC_LINREG_ON;
+
+		/* Powered by DC-DC, Linreg is off */
+		return HW_POWER_DCDC_LINREG_OFF;
+	}
+
+	return HW_POWER_UNKNOWN_SOURCE;
+}
+
+static int mxs_set_dcdc_freq(struct regulator_dev *reg, u32 hz)
+{
+	struct mxs_reg_info *dcdc = rdev_get_drvdata(reg);
+	u32 val;
+	int ret;
+
+	if (dcdc->desc.id != MXS_DCDC) {
+		dev_warn(&reg->dev, "Setting switching freq is not supported\n");
+		return -EINVAL;
+	}
+
+	ret = regmap_read(reg->regmap, HW_POWER_MISC, &val);
+	if (ret)
+		return ret;
+
+	val &= ~BM_POWER_MISC_FREQSEL;
+	val &= ~HW_POWER_MISC_SEL_PLLCLK;
+
+	/*
+	 * Select the PLL/PFD based frequency that the DC-DC converter uses.
+	 * The actual switching frequency driving the power inductor is
+	 * DCDC_CLK/16. Accept only values recommend by Freescale.
+	 */
+	switch (hz) {
+	case 1200000:
+		val |= HW_POWER_MISC_FREQSEL_19200_KHZ << SHIFT_FREQSEL;
+		break;
+	case 1250000:
+		val |= HW_POWER_MISC_FREQSEL_20000_KHZ << SHIFT_FREQSEL;
+		break;
+	case 1500000:
+		val |= HW_POWER_MISC_FREQSEL_24000_KHZ << SHIFT_FREQSEL;
+		break;
+	default:
+		dev_warn(&reg->dev, "Switching freq: %u Hz not supported\n",
+			 hz);
+		return -EINVAL;
+	}
+
+	/* First program FREQSEL */
+	ret = regmap_write(reg->regmap, HW_POWER_MISC, val);
+	if (ret)
+		return ret;
+
+	/* then set PLL as clock for DC-DC converter */
+	val |= HW_POWER_MISC_SEL_PLLCLK;
+
+	return regmap_write(reg->regmap, HW_POWER_MISC, val);
+}
+
+static int mxs_ldo_set_voltage_sel(struct regulator_dev *reg, unsigned sel)
+{
+	struct mxs_reg_info *ldo = rdev_get_drvdata(reg);
+	struct regulator_desc *desc = &ldo->desc;
+	u32 status = 0;
+	int timeout;
+	int ret;
+
+	ret = regmap_update_bits(reg->regmap, desc->vsel_reg, desc->vsel_mask,
+				 sel);
+	if (ret)
+		return ret;
+
+	if (ldo->get_power_source) {
+		switch (ldo->get_power_source(reg)) {
+		case HW_POWER_LINREG_DCDC_OFF:
+		case HW_POWER_LINREG_DCDC_READY:
+		case HW_POWER_EXTERNAL_SOURCE_5V:
+			/*
+			 * Since the DC-DC converter is off we can't
+			 * trigger on DC_OK. So wait at least 1 ms
+			 * for stabilization.
+			 */
+			usleep_range(1000, 2000);
+			return 0;
+		}
+	}
+
+	/* Make sure DC_OK has changed */
+	usleep_range(15, 20);
+
+	for (timeout = 0; timeout < 20; timeout++) {
+		ret = regmap_read(reg->regmap, HW_POWER_STS, &status);
+
+		if (ret)
+			break;
+
+		/* DC-DC converter control loop has stabilized */
+		if (status & BM_POWER_STS_DC_OK)
+			return 0;
+
+		udelay(1);
+	}
+
+	if (!ret)
+		dev_warn_ratelimited(&reg->dev, "%s: timeout status=0x%08x\n",
+				     __func__, status);
+
+	msleep(20);
+
+	return -ETIMEDOUT;
+}
+
+static int mxs_ldo_is_enabled(struct regulator_dev *reg)
+{
+	struct mxs_reg_info *ldo = rdev_get_drvdata(reg);
+
+	if (ldo->get_power_source) {
+		switch (ldo->get_power_source(reg)) {
+		case HW_POWER_LINREG_DCDC_OFF:
+		case HW_POWER_LINREG_DCDC_READY:
+		case HW_POWER_DCDC_LINREG_ON:
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+static struct regulator_ops mxs_dcdc_ops = {
+	.is_enabled		= regulator_is_enabled_regmap,
+};
+
+static struct regulator_ops mxs_ldo_ops = {
+	.list_voltage		= regulator_list_voltage_linear,
+	.map_voltage		= regulator_map_voltage_linear,
+	.set_voltage_sel	= mxs_ldo_set_voltage_sel,
+	.get_voltage_sel	= regulator_get_voltage_sel_regmap,
+	.is_enabled		= mxs_ldo_is_enabled,
+};
+
+static const struct mxs_reg_info mxs_info_dcdc = {
+	.desc = {
+		.name = "dcdc",
+		.id = MXS_DCDC,
+		.type = REGULATOR_VOLTAGE,
+		.owner = THIS_MODULE,
+		.ops = &mxs_dcdc_ops,
+		.enable_reg = HW_POWER_STS,
+		.enable_mask = (1 << 0),
+	},
+};
+
+static const struct mxs_reg_info imx23_info_vddio = {
+	.desc = {
+		.name = "vddio",
+		.id = MXS_VDDIO,
+		.type = REGULATOR_VOLTAGE,
+		.owner = THIS_MODULE,
+		.n_voltages = 0x20,
+		.uV_step = 25000,
+		.linear_min_sel = 0,
+		.min_uV = 2800000,
+		.vsel_reg = HW_POWER_VDDIOCTRL,
+		.vsel_mask = 0x1f,
+		.ops = &mxs_ldo_ops,
+	},
+	.ctrl_reg = HW_POWER_VDDIOCTRL,
+	.disable_fet_mask = 1 << 16,
+	.linreg_offset_mask = 3 << 12,
+	.linreg_offset_shift = 12,
+	.get_power_source = get_vddio_power_source,
+};
+
+static const struct mxs_reg_info imx28_info_vddio = {
+	.desc = {
+		.name = "vddio",
+		.id = MXS_VDDIO,
+		.type = REGULATOR_VOLTAGE,
+		.owner = THIS_MODULE,
+		.n_voltages = 0x11,
+		.uV_step = 50000,
+		.linear_min_sel = 0,
+		.min_uV = 2800000,
+		.vsel_reg = HW_POWER_VDDIOCTRL,
+		.vsel_mask = 0x1f,
+		.ops = &mxs_ldo_ops,
+	},
+	.ctrl_reg = HW_POWER_VDDIOCTRL,
+	.disable_fet_mask = 1 << 16,
+	.linreg_offset_mask = 3 << 12,
+	.linreg_offset_shift = 12,
+	.get_power_source = get_vddio_power_source,
+};
+
+static const struct mxs_reg_info mxs_info_vdda = {
+	.desc = {
+		.name = "vdda",
+		.id = MXS_VDDA,
+		.type = REGULATOR_VOLTAGE,
+		.owner = THIS_MODULE,
+		.n_voltages = 0x20,
+		.uV_step = 25000,
+		.linear_min_sel = 0,
+		.min_uV = 1500000,
+		.vsel_reg = HW_POWER_VDDACTRL,
+		.vsel_mask = 0x1f,
+		.ops = &mxs_ldo_ops,
+		.enable_mask = (1 << 17),
+	},
+	.ctrl_reg = HW_POWER_VDDACTRL,
+	.disable_fet_mask = 1 << 16,
+	.linreg_offset_mask = 3 << 12,
+	.linreg_offset_shift = 12,
+	.get_power_source = get_vdda_vddd_power_source,
+};
+
+static const struct mxs_reg_info mxs_info_vddd = {
+	.desc = {
+		.name = "vddd",
+		.id = MXS_VDDD,
+		.type = REGULATOR_VOLTAGE,
+		.owner = THIS_MODULE,
+		.n_voltages = 0x20,
+		.uV_step = 25000,
+		.linear_min_sel = 0,
+		.min_uV = 800000,
+		.vsel_reg = HW_POWER_VDDDCTRL,
+		.vsel_mask = 0x1f,
+		.ops = &mxs_ldo_ops,
+		.enable_mask = (1 << 21),
+	},
+	.ctrl_reg = HW_POWER_VDDDCTRL,
+	.disable_fet_mask = 1 << 20,
+	.linreg_offset_mask = 3 << 16,
+	.linreg_offset_shift = 16,
+	.get_power_source = get_vdda_vddd_power_source,
+};
+
+static const struct of_device_id of_mxs_regulator_match[] = {
+	{ .compatible = "fsl,imx23-dcdc",  .data = &mxs_info_dcdc },
+	{ .compatible = "fsl,imx28-dcdc",  .data = &mxs_info_dcdc },
+	{ .compatible = "fsl,imx23-vddio", .data = &imx23_info_vddio },
+	{ .compatible = "fsl,imx23-vdda",  .data = &mxs_info_vdda },
+	{ .compatible = "fsl,imx23-vddd",  .data = &mxs_info_vddd },
+	{ .compatible = "fsl,imx28-vddio", .data = &imx28_info_vddio },
+	{ .compatible = "fsl,imx28-vdda",  .data = &mxs_info_vdda },
+	{ .compatible = "fsl,imx28-vddd",  .data = &mxs_info_vddd },
+	{ /* end */ }
+};
+MODULE_DEVICE_TABLE(of, of_mxs_regulator_match);
+
+static int mxs_regulator_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	const struct of_device_id *match;
+	struct device_node *parent_np;
+	struct regulator_dev *rdev = NULL;
+	struct mxs_reg_info *info;
+	struct regulator_init_data *initdata;
+	struct regulator_config config = { };
+	u32 switch_freq;
+
+	match = of_match_device(of_mxs_regulator_match, dev);
+	if (!match) {
+		/* We do not expect this to happen */
+		dev_err(dev, "%s: Unable to match device\n", __func__);
+		return -ENODEV;
+	}
+
+	info = devm_kmemdup(dev, match->data, sizeof(struct mxs_reg_info),
+			    GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	initdata = of_get_regulator_init_data(dev, dev->of_node, &info->desc);
+	if (!initdata) {
+		dev_err(dev, "missing regulator init data\n");
+		return -EINVAL;
+	}
+
+	parent_np = of_get_parent(dev->of_node);
+	if (!parent_np)
+		return -ENODEV;
+	config.regmap = syscon_node_to_regmap(parent_np);
+	of_node_put(parent_np);
+	if (IS_ERR(config.regmap))
+		return PTR_ERR(config.regmap);
+
+	config.dev = dev;
+	config.init_data = initdata;
+	config.driver_data = info;
+	config.of_node = dev->of_node;
+
+	rdev = devm_regulator_register(dev, &info->desc, &config);
+	if (IS_ERR(rdev)) {
+		int ret = PTR_ERR(rdev);
+
+		dev_err(dev, "%s: failed to register regulator(%d)\n",
+			__func__, ret);
+		return ret;
+	}
+
+	if (!of_property_read_u32(dev->of_node, "switching-frequency",
+				  &switch_freq))
+		mxs_set_dcdc_freq(rdev, switch_freq);
+
+	platform_set_drvdata(pdev, rdev);
+
+	return 0;
+}
+
+static struct platform_driver mxs_regulator_driver = {
+	.driver = {
+		.name	= "mxs_regulator",
+		.of_match_table = of_mxs_regulator_match,
+	},
+	.probe	= mxs_regulator_probe,
+};
+
+module_platform_driver(mxs_regulator_driver);
+
+MODULE_AUTHOR("Stefan Wahren <stefan.wahren at i2se.com>");
+MODULE_DESCRIPTION("Freescale MXS regulators");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:mxs_regulator");
-- 
1.7.9.5




More information about the linux-arm-kernel mailing list