[PATCH 3/3] regulator: mcp16502: add regulator driver for MCP16502

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

 



This patch adds a regulator driver for the MCP16502 PMIC.
This drivers supports basic operations through the
regulator interface such as:
- setting/reading voltage
- setting/reading operating mode
- reading current status
- transitioning to/from suspend-to-ram and standby
  power states

Signed-off-by: Andrei Stefanescu <andrei.stefanescu@xxxxxxxxxxxxx>
---
 drivers/regulator/Kconfig    |   9 +
 drivers/regulator/Makefile   |   1 +
 drivers/regulator/mcp16502.c | 524 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 534 insertions(+)
 create mode 100644 drivers/regulator/mcp16502.c

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 926cee0..719d9d6 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -567,6 +567,15 @@ config REGULATOR_MC13892
 	  Say y here to support the regulators found on the Freescale MC13892
 	  PMIC.
 
+config REGULATOR_MCP16502
+	tristate "Microchip MCP16502 PMIC"
+	depends on I2C && OF
+	help
+	  Say y here to support the MCP16502 PMIC. This driver supports
+	  basic operations (get/set voltage, get/set operating mode)
+	  through the regulator interface. In addition it enables
+	  suspend-to-ram/standby transition.
+
 config REGULATOR_MT6311
 	tristate "MediaTek MT6311 PMIC"
 	depends on I2C
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 72488ef..b12e1c9 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -74,6 +74,7 @@ obj-$(CONFIG_REGULATOR_MAX77802) += max77802-regulator.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_MCP16502) += mcp16502.o
 obj-$(CONFIG_REGULATOR_MT6311) += mt6311-regulator.o
 obj-$(CONFIG_REGULATOR_MT6323)	+= mt6323-regulator.o
 obj-$(CONFIG_REGULATOR_MT6380)	+= mt6380-regulator.o
diff --git a/drivers/regulator/mcp16502.c b/drivers/regulator/mcp16502.c
new file mode 100644
index 0000000..29c72d3
--- /dev/null
+++ b/drivers/regulator/mcp16502.c
@@ -0,0 +1,524 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * MCP16502 PMIC driver
+ *
+ * Copyright (C) 2018 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Andrei Stefanescu <andrei.stefanescu@xxxxxxxxxxxxx>
+ *
+ * Inspired from tps65086-regulator.c
+ */
+
+#include  <linux/gpio.h>
+#include  <linux/i2c.h>
+#include  <linux/init.h>
+#include  <linux/kernel.h>
+#include  <linux/module.h>
+#include  <linux/of.h>
+#include  <linux/regmap.h>
+#include  <linux/regulator/driver.h>
+
+#define VDD_LOW_SEL 0x0D
+#define VDD_HIGH_SEL 0x3F
+
+#define MCP16502_VOL_SEL_MASK 0x3F
+#define MCP16502_EN_MASK BIT(7)
+#define MCP16502_MODE_MASK BIT(6)
+
+/*
+ *
+ * The PMIC has four sets of registers corresponding to four power modes:
+ * Performance, Active, Low-power, Hibernate.
+ *
+ * Registers:
+ * Each regulator has a register for each power mode. To access a register
+ * for a specific regulator and mode BASE_* and OFFSET_* need to be added.
+ *
+ * Operating modes:
+ * In order for the PMIC to transition to operating modes it has to be
+ * controlled via GPIO lines called LPM and HPM.
+ *
+ * The registers are fully configurable such that you can put all regulators in
+ * a low-power state while the PMIC is in Active mode. They are supposed to be
+ * configured at startup and then simply transition to/from a global low-power
+ * state by setting the GPIO lpm pin high/low.
+ *
+ * This driver keeps the PMIC in Active mode, Low-power state is set for the
+ * regulators by enabling/disabling operating mode (FPWM or Auto PFM).
+ *
+ * The PMIC's Low-power and Hibernate modes are used during standby/suspend.
+ * To enter standby/suspend the PMIC will go to Low-power mode. From there, it
+ * will transition to Hibernate when the PWRHLD line is set to low by the MPU.
+ */
+
+/*
+ * This macro is useful for iterating over all regulators and accessing their
+ * registers in a generic way or accessing a regulator device by its id.
+ */
+#define BASE(i) (((i) + 1) << 4)
+
+#define OFFSET_MODE_A 0
+#define OFFSET_MODE_HIB 2
+
+#define MCP16502_MODE_ACTIVE REGULATOR_MODE_NORMAL
+#define MCP16502_MODE_HIB REGULATOR_MODE_STANDBY
+
+#define MCP16502_MODE_AUTO_PFM 0
+#define MCP16502_MODE_FPWM BIT(6)
+
+#define MIN_REG 0x0
+#define MAX_REG 0x65
+
+static unsigned int mcp16502_of_map_mode(unsigned int mode)
+{
+	if (mode == REGULATOR_MODE_NORMAL || mode == REGULATOR_MODE_IDLE)
+		return mode;
+
+	return REGULATOR_MODE_INVALID;
+}
+
+#define MCP16502_REGULATOR(_name, _id, _ranges, _ops)			\
+	[_id] = {							\
+		.name			= _name,			\
+		.regulators_node	= of_match_ptr("regulators"),	\
+		.id			= _id,				\
+		.ops			= &(_ops),			\
+		.type			= REGULATOR_VOLTAGE,		\
+		.owner			= THIS_MODULE,			\
+		.linear_ranges		= _ranges,			\
+		.n_linear_ranges	= ARRAY_SIZE(_ranges),		\
+		.of_match		= of_match_ptr(_name),		\
+		.of_map_mode		= mcp16502_of_map_mode,		\
+	}
+
+enum {
+	BUCK1 = 0,
+	BUCK2,
+	BUCK3,
+	BUCK4,
+	LDO1,
+	LDO2,
+	NUM_REGULATORS
+};
+
+/*
+ * struct mcp16502 - PMIC representation
+ * @rdev: the regulators belonging to this chip
+ * @rmap: regmap to be used for I2C communication
+ * @lpm: LPM GPIO descriptor
+ */
+struct mcp16502 {
+	struct regulator_dev *rdev[NUM_REGULATORS];
+	struct regmap *rmap;
+	struct gpio_desc *lpm;
+};
+
+/*
+ * mcp16502_update_regulator - regmap_update_bits called for registers
+ *			       Active or Low-power depending on hibernate
+ *			       parameter
+ * @hibernate - use the registers for hibernate mode
+ *
+ */
+static int mcp16502_update_regulator(struct regulator_dev *rdev,
+				     bool hibernate,
+				     unsigned int mask, unsigned int val)
+{
+	struct mcp16502 *mcp = rdev_get_drvdata(rdev);
+	unsigned int reg = BASE(rdev_get_id(rdev));
+
+	reg += (hibernate) ? OFFSET_MODE_HIB : OFFSET_MODE_A;
+
+	return regmap_update_bits(mcp->rmap, reg, mask, val);
+}
+
+/*
+ * mcp16502_read_regulator - read from Active or Low-power registers depending
+ *			     on the hibernate parameter
+ *
+ * @hibernate - use the registers for hibernate mode
+ *
+ */
+static int mcp16502_read(struct regulator_dev *rdev, bool hibernate,
+			 unsigned int mask)
+{
+	struct mcp16502 *mcp = rdev_get_drvdata(rdev);
+	unsigned int reg = BASE(rdev_get_id(rdev));
+	int ret, val;
+
+	reg += (hibernate) ? OFFSET_MODE_HIB : OFFSET_MODE_A;
+
+	ret = regmap_read(mcp->rmap, reg, &val);
+	if (ret)
+		return ret;
+
+	return val & mask;
+}
+
+/*
+ * mcp16502_gpio_set_mode - set the GPIO corresponding value
+ *
+ * Used to prepare transitioning into hibernate or resuming from it.
+ */
+static void mcp16502_gpio_set_mode(struct mcp16502 *mcp, int mode)
+{
+	int lpm = (mode == MCP16502_MODE_HIB);
+
+	gpiod_set_value(mcp->lpm, lpm);
+}
+
+/*
+ * mcp16502_get_mode - return the current operating mode of a regulator
+ *
+ * Note: all functions that are not part of entering/exiting standby/suspend
+ *	 use the Active mode registers.
+ *
+ * Note: this is different from the PMIC's operatig mode, it is the
+ *	 MODE bit from the regulator's register.
+ */
+static unsigned int mcp16502_get_mode(struct regulator_dev *rdev)
+{
+	int val;
+
+	val = mcp16502_read(rdev, false, MCP16502_MODE_MASK);
+	if (val < 0)
+		return val;
+
+	if (val == MCP16502_MODE_FPWM)
+		return REGULATOR_MODE_NORMAL;
+	else if (val == MCP16502_MODE_AUTO_PFM)
+		return REGULATOR_MODE_IDLE;
+
+	return REGULATOR_MODE_INVALID;
+}
+
+/*
+ * mcp16502_is_enabled - regulator_ops is_enabled
+ */
+static int mcp16502_is_enabled(struct regulator_dev *rdev)
+{
+	int val;
+
+	val = mcp16502_read(rdev, false, MCP16502_EN_MASK);
+	if (val < 0)
+		return val;
+
+	return !!val;
+}
+
+/*
+ * mcp16502_disable - regulator_ops disable
+ */
+static int mcp16502_disable(struct regulator_dev *rdev)
+{
+	return mcp16502_update_regulator(rdev, false, MCP16502_EN_MASK, 0);
+}
+
+/*
+ * _mcp16502_set_mode - helper for set_mode and set_suspend_mode
+ */
+static int _mcp16502_set_mode(struct regulator_dev *rdev, bool hibernate,
+			      unsigned int mode)
+{
+	int val;
+
+	if (mode != REGULATOR_MODE_NORMAL && mode != REGULATOR_MODE_IDLE)
+		return -EINVAL;
+
+	val = (mode == REGULATOR_MODE_NORMAL) ? MCP16502_MODE_FPWM :
+						MCP16502_MODE_AUTO_PFM;
+
+	return mcp16502_update_regulator(rdev, hibernate, MCP16502_MODE_MASK,
+					 val);
+}
+
+/*
+ * mcp16502_set_mode - regulator_ops set_mode
+ */
+static int mcp16502_set_mode(struct regulator_dev *rdev, unsigned int mode)
+{
+	return _mcp16502_set_mode(rdev, false, mode);
+}
+
+/*
+ * mcp16502_enable - regulator_ops enable
+ */
+static int mcp16502_enable(struct regulator_dev *rdev)
+{
+	return mcp16502_update_regulator(rdev, false, MCP16502_EN_MASK,
+					 MCP16502_EN_MASK);
+}
+
+/*
+ * mcp16502_get_voltage_sel - regulator_ops get_voltage_sel
+ */
+static int mcp16502_get_voltage_sel(struct regulator_dev *rdev)
+{
+	return mcp16502_read(rdev, false, MCP16502_VOL_SEL_MASK);
+}
+
+/*
+ * mcp16502_set_voltage_sel - regulator_ops set_voltage_sel
+ */
+static int mcp16502_set_voltage_sel(struct regulator_dev *rdev,
+				    unsigned int selector)
+{
+	return mcp16502_update_regulator(rdev, false, MCP16502_VOL_SEL_MASK,
+					 selector);
+}
+
+/*
+ * mcp16502_get_status - regulator_ops get_status
+ */
+static int mcp16502_get_status(struct regulator_dev *rdev)
+{
+	int mode = mcp16502_get_mode(rdev);
+
+	if (!mcp16502_is_enabled(rdev))
+		return REGULATOR_STATUS_OFF;
+	else if (mode == REGULATOR_MODE_NORMAL)
+		return REGULATOR_STATUS_NORMAL;
+	else if (mode == REGULATOR_MODE_IDLE)
+		return REGULATOR_STATUS_IDLE;
+
+	return REGULATOR_STATUS_UNDEFINED;
+}
+
+/*
+ * mcp16502_set_suspend_voltage - regulator_ops set_suspend_voltage
+ */
+int mcp16502_set_suspend_voltage(struct regulator_dev *rdev, int uV)
+{
+	int sel = regulator_map_voltage_linear_range(rdev, uV, uV);
+
+	if (sel < 0)
+		return sel;
+
+	return mcp16502_update_regulator(rdev, true, MCP16502_VOL_SEL_MASK,
+					 sel);
+}
+
+/*
+ * mcp16502_set_suspend_mode - regulator_ops set_suspend_mode
+ */
+int mcp16502_set_suspend_mode(struct regulator_dev *rdev, unsigned int mode)
+{
+	return _mcp16502_set_mode(rdev, true, mode);
+}
+
+/*
+ * mcp16502_set_suspend_enable - regulator_ops set_suspend_enable
+ */
+static int mcp16502_set_suspend_enable(struct regulator_dev *rdev)
+{
+	return mcp16502_update_regulator(rdev, true, MCP16502_EN_MASK,
+					 MCP16502_EN_MASK);
+}
+
+/*
+ * mcp16502_set_suspend_disable - regulator_ops set_suspend_disable
+ */
+static int mcp16502_set_suspend_disable(struct regulator_dev *rdev)
+{
+	return mcp16502_update_regulator(rdev, true, MCP16502_EN_MASK, 0);
+}
+
+static const struct regulator_ops mcp16502_buck_ops = {
+	.list_voltage			= regulator_list_voltage_linear_range,
+	.map_voltage			= regulator_map_voltage_linear_range,
+	.get_voltage_sel		= mcp16502_get_voltage_sel,
+	.set_voltage_sel		= mcp16502_set_voltage_sel,
+	.enable				= mcp16502_enable,
+	.disable			= mcp16502_disable,
+	.is_enabled			= mcp16502_is_enabled,
+	.set_mode			= mcp16502_set_mode,
+	.get_mode			= mcp16502_get_mode,
+	.get_status			= mcp16502_get_status,
+	.set_suspend_voltage		= mcp16502_set_suspend_voltage,
+	.set_suspend_mode		= mcp16502_set_suspend_mode,
+	.set_suspend_enable		= mcp16502_set_suspend_enable,
+	.set_suspend_disable		= mcp16502_set_suspend_disable,
+};
+
+/*
+ * LDOs cannot change operating modes.
+ */
+static const struct regulator_ops mcp16502_ldo_ops = {
+	.list_voltage			= regulator_list_voltage_linear_range,
+	.map_voltage			= regulator_map_voltage_linear_range,
+	.get_voltage_sel		= mcp16502_get_voltage_sel,
+	.set_voltage_sel		= mcp16502_set_voltage_sel,
+	.enable				= mcp16502_enable,
+	.disable			= mcp16502_disable,
+	.is_enabled			= mcp16502_is_enabled,
+	.get_status			= mcp16502_get_status,
+	.set_suspend_voltage		= mcp16502_set_suspend_voltage,
+	.set_suspend_enable		= mcp16502_set_suspend_enable,
+	.set_suspend_disable		= mcp16502_set_suspend_disable,
+};
+
+static const struct of_device_id mcp16502_ids[] = {
+	{ .compatible = "microchip,mcp16502", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, mcp16502_ids);
+
+static const struct regulator_linear_range b1l12_ranges[] = {
+	REGULATOR_LINEAR_RANGE(1200000, VDD_LOW_SEL, VDD_HIGH_SEL, 50000),
+};
+
+static const struct regulator_linear_range b234_ranges[] = {
+	REGULATOR_LINEAR_RANGE(600000, VDD_LOW_SEL, VDD_HIGH_SEL, 25000),
+};
+
+static const struct regulator_desc mcp16502_desc[] = {
+	/* MCP16502_REGULATOR(_name, _id, ranges, regulator_ops) */
+	MCP16502_REGULATOR("VDD_IO", BUCK1, b1l12_ranges, mcp16502_buck_ops),
+	MCP16502_REGULATOR("VDD_DDR", BUCK2, b234_ranges, mcp16502_buck_ops),
+	MCP16502_REGULATOR("VDD_CORE", BUCK3, b234_ranges, mcp16502_buck_ops),
+	MCP16502_REGULATOR("VDD_OTHER", BUCK4, b234_ranges, mcp16502_buck_ops),
+	MCP16502_REGULATOR("LDO1", LDO1, b1l12_ranges, mcp16502_ldo_ops),
+	MCP16502_REGULATOR("LDO2", LDO2, b1l12_ranges, mcp16502_ldo_ops)
+};
+
+static const struct regmap_range mcp16502_ranges[] = {
+	regmap_reg_range(MIN_REG, MAX_REG)
+};
+
+static const struct regmap_access_table mcp16502_yes_reg_table = {
+	.yes_ranges = mcp16502_ranges,
+	.n_yes_ranges = ARRAY_SIZE(mcp16502_ranges),
+};
+
+static const struct regmap_config mcp16502_regmap_config = {
+	.reg_bits	= 8,
+	.val_bits	= 8,
+	.max_register	= MAX_REG,
+	.cache_type	= REGCACHE_NONE,
+	.rd_table	= &mcp16502_yes_reg_table,
+	.wr_table	= &mcp16502_yes_reg_table,
+};
+
+/*
+ * set_up_regulators - initialize all regulators
+ */
+static int setup_regulators(struct mcp16502 *mcp, struct device *dev,
+			    struct regulator_config config)
+{
+	int i;
+
+	for (i = 0; i < NUM_REGULATORS; i++) {
+		mcp->rdev[i] = devm_regulator_register(dev,
+						       &mcp16502_desc[i],
+						       &config);
+		if (IS_ERR(mcp->rdev[i])) {
+			dev_err(dev,
+				"failed to register %s regulator %ld\n",
+				mcp16502_desc[i].name, PTR_ERR(mcp->rdev[i]));
+			return PTR_ERR(mcp->rdev[i]);
+		}
+	}
+
+	return 0;
+}
+
+static int mcp16502_probe(struct i2c_client *client,
+			  const struct i2c_device_id *id)
+{
+	struct regulator_config config = { };
+	struct device *dev;
+	struct mcp16502 *mcp;
+	int ret = 0;
+
+	dev = &client->dev;
+	config.dev = dev;
+
+	mcp = devm_kzalloc(dev, sizeof(*mcp), GFP_KERNEL);
+	if (!mcp)
+		return -ENOMEM;
+
+	mcp->rmap = devm_regmap_init_i2c(client, &mcp16502_regmap_config);
+	if (IS_ERR(mcp->rmap)) {
+		ret = PTR_ERR(mcp->rmap);
+		dev_err(dev, "regmap init failed: %d\n", ret);
+		return ret;
+	}
+
+	i2c_set_clientdata(client, mcp);
+	config.regmap = mcp->rmap;
+	config.driver_data = mcp;
+
+	mcp->lpm = devm_gpiod_get(dev, "lpm", GPIOD_OUT_LOW);
+	if (IS_ERR(mcp->lpm)) {
+		dev_err(dev, "failed to get lpm pin: %ld\n", PTR_ERR(mcp->lpm));
+		return PTR_ERR(mcp->lpm);
+	}
+
+	ret = setup_regulators(mcp, dev, config);
+	if (ret != 0)
+		return ret;
+
+	mcp16502_gpio_set_mode(mcp, MCP16502_MODE_ACTIVE);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int mcp16502_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct mcp16502 *mcp = i2c_get_clientdata(client);
+
+	mcp16502_gpio_set_mode(mcp, MCP16502_MODE_HIB);
+
+	return 0;
+}
+
+static int mcp16502_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct mcp16502 *mcp = i2c_get_clientdata(client);
+
+	mcp16502_gpio_set_mode(mcp, MCP16502_MODE_ACTIVE);
+
+	return 0;
+}
+#endif
+
+static const struct dev_pm_ops mcp16502_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(mcp16502_suspend, mcp16502_resume)
+};
+
+static const struct i2c_device_id mcp16502_i2c_id[] = {
+	{ "mcp16502", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, mcp16502_i2c_id);
+
+static struct i2c_driver mcp16502_drv = {
+	.probe		= mcp16502_probe,
+	.driver		= {
+		.name	= "mcp16502-regulator",
+		.owner		= THIS_MODULE,
+		.of_match_table	= of_match_ptr(mcp16502_ids),
+		.pm = &mcp16502_pm_ops,
+	},
+	.id_table	= mcp16502_i2c_id,
+};
+
+static int __init mcp16502_init(void)
+{
+	return i2c_add_driver(&mcp16502_drv);
+}
+
+static void __exit mcp16502_exit(void)
+{
+}
+
+module_init(mcp16502_init);
+module_exit(mcp16502_exit);
+
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MCP16502 PMIC driver");
+MODULE_AUTHOR("Andrei Stefanescu andrei.stefanescu@xxxxxxxxxxxxx");
-- 
2.7.4





[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux