All of lore.kernel.org
 help / color / mirror / Atom feed
From: Wenyou Yang <wenyou.yang@atmel.com>
To: <lgirdwood@gmail.com>
Cc: <broonie@kernel.org>, <grant.likely@linaro.org>,
	<rob.herring@calxeda.com>, <vpalatin@chromium.org>,
	<devicetree@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
	<linux-doc@vger.kernel.org>,
	<linux-arm-kernel@lists.infradead.org>, <plagnioj@jcrosoft.com>,
	<nicolas.ferre@atmel.com>, <wenyou.yang@atmel.com>
Subject: [PATCH 1/3] regulator: act8865: add PMIC(Power Management IC) driver
Date: Thu, 12 Dec 2013 09:18:49 +0800	[thread overview]
Message-ID: <1386811131-2720-2-git-send-email-wenyou.yang@atmel.com> (raw)
In-Reply-To: <1386811131-2720-1-git-send-email-wenyou.yang@atmel.com>

Signed-off-by: Wenyou Yang <wenyou.yang@atmel.com>
---
 drivers/regulator/Kconfig             |    7 +
 drivers/regulator/Makefile            |    1 +
 drivers/regulator/act8865-regulator.c |  516 +++++++++++++++++++++++++++++++++
 include/linux/regulator/act8865.h     |   56 ++++
 4 files changed, 580 insertions(+)
 create mode 100644 drivers/regulator/act8865-regulator.c
 create mode 100644 include/linux/regulator/act8865.h

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index ce785f4..e13bcf1 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -577,5 +577,12 @@ config REGULATOR_WM8994
 	  This driver provides support for the voltage regulators on the
 	  WM8994 CODEC.
 
+config REGULATOR_ACT8865
+	tristate "Active-semi act8865 voltage regulator"
+	depends on I2C
+	help
+	  This driver controls a active-semi act8865 voltage output
+	  regulator via I2C bus.
+
 endif
 
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 01c597e..8bc485f 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -78,6 +78,7 @@ obj-$(CONFIG_REGULATOR_WM831X) += wm831x-ldo.o
 obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o
 obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o
 obj-$(CONFIG_REGULATOR_WM8994) += wm8994-regulator.o
+obj-$(CONFIG_REGULATOR_ACT8865) += act8865-regulator.o
 
 
 ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG
diff --git a/drivers/regulator/act8865-regulator.c b/drivers/regulator/act8865-regulator.c
new file mode 100644
index 0000000..d616a28
--- /dev/null
+++ b/drivers/regulator/act8865-regulator.c
@@ -0,0 +1,516 @@
+/*
+ * act8865-regulator.c - Voltage regulation for the active-semi ACT8865
+ * http://www.active-semi.com/sheets/ACT8865_Datasheet.pdf
+ *
+ * Copyright (C) 2013 Atmel Corporation
+ * Wenyou Yang <wenyou.yang@atmel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/init.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/act8865.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/regulator/of_regulator.h>
+
+/*
+ * ACT8865 Global Register Map
+ */
+#define	ACT8865_SYS_MODE	0x00
+#define ACT8865_SYS_CTRL	0x01
+#define ACT8865_REG1_VSET1	0x20
+#define	ACT8865_REG1_VSET2	0x21
+#define	ACT8865_REG1_CTRL	0x22
+#define	ACT8865_REG2_VSET1	0x30
+#define	ACT8865_REG2_VSET2	0x31
+#define	ACT8865_REG2_CTRL	0x32
+#define	ACT8865_REG3_VSET1	0x40
+#define	ACT8865_REG3_VSET2	0x41
+#define	ACT8865_REG3_CTRL	0x42
+#define	ACT8865_REG4_VSET	0x50
+#define	ACT8865_REG4_CTRL	0x51
+#define	ACT8865_REG5_VSET	0x54
+#define	ACT8865_REG5_CTRL	0x55
+#define	ACT8865_REG6_VSET	0x60
+#define	ACT8865_REG6_CTRL	0x61
+#define	ACT8865_REG7_VSET	0x64
+#define	ACT8865_REG7_CTRL	0x65
+
+struct act8865_data {
+	u8	vsel_is_low:1;
+	struct i2c_client	*client;
+	struct regulator_dev	*rdev[];
+};
+
+/* ACt8865 voltage table */
+static const u32 act8865_voltages_table[] = {
+	600000,		625000,		650000,		675000,
+	700000,		725000,		750000,		775000,
+	800000,		825000,		850000,		875000,
+	900000,		925000,		950000,		975000,
+	1000000,	1025000,	1050000,	1075000,
+	1100000,	1125000,	1150000,	1175000,
+	1200000,	1250000,	1300000,	1350000,
+	1400000,	1450000,	1500000,	1550000,
+	1600000,	1650000,	1700000,	1750000,
+	1800000,	1850000,	1900000,	1950000,
+	2000000,	2050000,	2010000,	2150000,
+	2200000,	2250000,	2300000,	2350000,
+	2400000,	2500000,	2600000,	2700000,
+	2800000,	2900000,	3000000,	3100000,
+	3200000,	3300000,	3400000,	3500000,
+	3600000,	3700000,	3800000,	3900000,
+};
+
+static int act8865_read_reg(struct act8865_data *act8865, u8 reg)
+{
+	int ret = i2c_smbus_read_byte_data(act8865->client, reg);
+	if (ret > 0)
+		ret &= 0xff;
+
+	return ret;
+}
+
+static int act8865_write_reg(struct act8865_data *act8865, u8 reg, u8 value)
+{
+	return i2c_smbus_write_byte_data(act8865->client, reg, value);
+}
+
+/*
+ * operation functions
+ */
+
+static u32 act8865_ctrl_reg_addr(int id)
+{
+	u32	reg = 0;
+
+	switch (id) {
+	case ACT8865_ID_REG1:
+		reg = ACT8865_REG1_CTRL;
+		break;
+
+	case ACT8865_ID_REG2:
+		reg = ACT8865_REG2_CTRL;
+		break;
+
+	case ACT8865_ID_REG3:
+		reg = ACT8865_REG3_CTRL;
+		break;
+
+	case ACT8865_ID_REG4:
+		reg = ACT8865_REG4_CTRL;
+		break;
+
+	case ACT8865_ID_REG5:
+		reg = ACT8865_REG5_CTRL;
+		break;
+
+	case ACT8865_ID_REG6:
+		reg = ACT8865_REG6_CTRL;
+		break;
+
+	case ACT8865_ID_REG7:
+		reg = ACT8865_REG7_CTRL;
+		break;
+	};
+
+	return reg;
+}
+
+static u32 act8865_vset_reg_addr(struct act8865_data *act8865, int id)
+{
+	u32	reg = 0;
+
+	switch (id) {
+	case ACT8865_ID_REG1:
+		if (act8865->vsel_is_low)
+			reg = ACT8865_REG1_VSET1;
+		else
+			reg = ACT8865_REG1_VSET2;
+		break;
+
+	case ACT8865_ID_REG2:
+		if (act8865->vsel_is_low)
+			reg = ACT8865_REG2_VSET1;
+		else
+			reg = ACT8865_REG2_VSET2;
+		break;
+
+	case ACT8865_ID_REG3:
+		if (act8865->vsel_is_low)
+			reg = ACT8865_REG3_VSET1;
+		else
+			reg = ACT8865_REG3_VSET2;
+		break;
+
+	case ACT8865_ID_REG4:
+		reg = ACT8865_REG4_VSET;
+		break;
+
+	case ACT8865_ID_REG5:
+		reg = ACT8865_REG5_VSET;
+		break;
+
+	case ACT8865_ID_REG6:
+		reg = ACT8865_REG6_VSET;
+		break;
+
+	case ACT8865_ID_REG7:
+		reg = ACT8865_REG7_VSET;
+		break;
+	};
+
+	return reg;
+}
+
+static int act88665_is_enabled(struct regulator_dev *rdev)
+{
+	struct act8865_data *act8865 = rdev_get_drvdata(rdev);
+	int id = rdev_get_id(rdev);
+	u32	reg;
+	u8	mask = 0x80;
+
+	reg = act8865_ctrl_reg_addr(id);
+
+	return (act8865_read_reg(act8865, reg) & mask) == mask;
+}
+
+static int act8865_enable(struct regulator_dev *rdev)
+{
+	struct act8865_data *act8865 = rdev_get_drvdata(rdev);
+	int id = rdev_get_id(rdev);
+	u32	reg;
+	u8	val;
+	u8	mask = 0x80;
+
+	reg = act8865_ctrl_reg_addr(id);
+	val = act8865_read_reg(act8865, reg);
+	val |= mask;
+
+	return act8865_write_reg(act8865, reg, val);
+}
+
+static int act8865_disable(struct regulator_dev *rdev)
+{
+	struct act8865_data *act8865 = rdev_get_drvdata(rdev);
+	int id = rdev_get_id(rdev);
+	u32	reg;
+	u8	val;
+	u8	mask = 0x80;
+
+	reg = act8865_ctrl_reg_addr(id);
+	val = act8865_read_reg(act8865, reg);
+	val &= ~mask;
+
+	return act8865_write_reg(act8865, reg, val);
+}
+
+static int act8865_get_voltage_sel(struct regulator_dev *rdev)
+{
+	struct act8865_data *act8865 = rdev_get_drvdata(rdev);
+	int id = rdev_get_id(rdev);
+	u32	reg = act8865_vset_reg_addr(act8865, id);
+
+	return act8865_read_reg(act8865, reg);
+}
+
+static int act8865_set_voltage_sel(struct regulator_dev *rdev, u32 selector)
+{
+	struct act8865_data *act8865 = rdev_get_drvdata(rdev);
+	int id = rdev_get_id(rdev);
+	u32	reg = act8865_vset_reg_addr(act8865, id);
+
+	return act8865_write_reg(act8865, reg, selector);
+}
+
+static int act8865_set_suspend_voltage(struct regulator_dev *rdev, int uV)
+{
+	struct act8865_data *act8865 = rdev_get_drvdata(rdev);
+	int id = rdev_get_id(rdev);
+	u32	reg = act8865_vset_reg_addr(act8865, id);
+	u32	selector;
+
+	pr_info("%s: suspend voltage %dmV\n", rdev->desc->name, uV / 1000);
+
+	selector = regulator_map_voltage_iterate(rdev, uV, uV);
+
+	return act8865_write_reg(act8865, reg, selector);
+}
+
+static struct regulator_ops act8865_ops = {
+	.list_voltage		= regulator_list_voltage_table,
+	.set_voltage_sel	= act8865_set_voltage_sel,
+	.get_voltage_sel	= act8865_get_voltage_sel,
+	.enable			= act8865_enable,
+	.disable		= act8865_disable,
+	.is_enabled		= act88665_is_enabled,
+	.set_suspend_voltage	= act8865_set_suspend_voltage,
+	.set_suspend_enable	= act8865_enable,
+	.set_suspend_disable	= act8865_disable,
+};
+
+static const struct regulator_desc act8865_reg[] = {
+	{
+		.name = "DCDC_REG1",
+		.id = ACT8865_ID_REG1,
+		.ops = &act8865_ops,
+		.type = REGULATOR_VOLTAGE,
+		.n_voltages = ARRAY_SIZE(act8865_voltages_table),
+		.volt_table = act8865_voltages_table,
+		.owner = THIS_MODULE,
+	},
+	{
+		.name = "DCDC_REG2",
+		.id = ACT8865_ID_REG2,
+		.ops = &act8865_ops,
+		.type = REGULATOR_VOLTAGE,
+		.n_voltages = ARRAY_SIZE(act8865_voltages_table),
+		.volt_table = act8865_voltages_table,
+		.owner = THIS_MODULE,
+	},
+	{
+		.name = "DCDC_REG3",
+		.id = ACT8865_ID_REG3,
+		.ops = &act8865_ops,
+		.type = REGULATOR_VOLTAGE,
+		.n_voltages = ARRAY_SIZE(act8865_voltages_table),
+		.volt_table = act8865_voltages_table,
+		.owner = THIS_MODULE,
+	},
+	{
+		.name = "LDO_REG4",
+		.id = ACT8865_ID_REG4,
+		.ops = &act8865_ops,
+		.type = REGULATOR_VOLTAGE,
+		.n_voltages = ARRAY_SIZE(act8865_voltages_table),
+		.volt_table = act8865_voltages_table,
+		.owner = THIS_MODULE,
+	},
+	{
+		.name = "LDO_REG5",
+		.id = ACT8865_ID_REG5,
+		.ops = &act8865_ops,
+		.type = REGULATOR_VOLTAGE,
+		.n_voltages = ARRAY_SIZE(act8865_voltages_table),
+		.volt_table = act8865_voltages_table,
+		.owner = THIS_MODULE,
+	},
+	{
+		.name = "LDO_REG6",
+		.id = ACT8865_ID_REG6,
+		.ops = &act8865_ops,
+		.type = REGULATOR_VOLTAGE,
+		.n_voltages = ARRAY_SIZE(act8865_voltages_table),
+		.volt_table = act8865_voltages_table,
+		.owner = THIS_MODULE,
+	},
+	{
+		.name = "LDO_REG7",
+		.id = ACT8865_ID_REG7,
+		.ops = &act8865_ops,
+		.type = REGULATOR_VOLTAGE,
+		.n_voltages = ARRAY_SIZE(act8865_voltages_table),
+		.volt_table = act8865_voltages_table,
+		.owner = THIS_MODULE,
+	},
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id act8865_dt_ids[] = {
+	{ .compatible = "active-semi,act8865" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, act8865_dt_ids);
+
+static int act8865_pdata_from_dt(struct device *dev,
+				 struct device_node **of_node,
+				 struct act8865_platform_data *pdata)
+{
+	int matched, i;
+	struct device_node *np;
+	struct act8865_regulator_data *regulator;
+	struct of_regulator_match rmatch[ARRAY_SIZE(act8865_reg)];
+
+	if (of_find_property(dev->of_node, "vsel-state-low", NULL))
+		pdata->vsel_is_low = 1;
+
+	np = of_find_node_by_name(dev->of_node, "regulators");
+	if (!np) {
+		dev_err(dev, "missing 'regulators' subnode in DT\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(rmatch); i++)
+		rmatch[i].name = act8865_reg[i].name;
+
+	matched = of_regulator_match(dev, np, rmatch, ARRAY_SIZE(rmatch));
+	if (matched <= 0)
+		return matched;
+
+	pdata->regulators = devm_kzalloc(dev,
+				sizeof(struct act8865_regulator_data) * matched,
+				GFP_KERNEL);
+	if (!pdata->regulators) {
+		dev_err(dev, "%s: failed to allocate act8865 registor\n",
+						__func__);
+		return -ENOMEM;
+	}
+
+	pdata->num_regulators = matched;
+	regulator = pdata->regulators;
+
+	for (i = 0; i < matched; i++) {
+		regulator->id = i;
+		regulator->name = rmatch[i].name;
+		regulator->platform_data = rmatch[i].init_data;
+		of_node[i] = rmatch[i].of_node;
+		regulator++;
+	}
+
+	return 0;
+}
+
+#else
+
+static inline int act8865_pdata_from_dt(struct device *dev,
+					struct device_node **of_node,
+					struct act8865_platform_data *pdata)
+{
+	return 0;
+}
+#endif
+
+static int act8865_pmu_probe(struct i2c_client *client,
+			   const struct i2c_device_id *i2c_id)
+{
+	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+	struct regulator_dev **rdev;
+	struct device *dev = &client->dev;
+	struct act8865_platform_data *pdata = dev_get_platdata(dev);
+	struct regulator_config config = { };
+	struct act8865_data *act8865;
+	int i, id;
+	int ret = -EINVAL;
+	struct device_node *of_node[ACT8865_REG_NUM];
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+		return -EIO;
+
+	if (dev->of_node && !pdata) {
+		const struct of_device_id *id;
+		struct act8865_platform_data pdata_of;
+
+		id = of_match_device(of_match_ptr(act8865_dt_ids), dev);
+		if (!id)
+			return -ENODEV;
+
+		ret = act8865_pdata_from_dt(dev, of_node, &pdata_of);
+		if (ret < 0)
+			return ret;
+
+		pdata = &pdata_of;
+	} else {
+		memset(of_node, 0, sizeof(of_node));
+	}
+
+	if (pdata->num_regulators > ACT8865_REG_NUM) {
+		dev_err(dev, "Too many regulators found!\n");
+		return -EINVAL;
+	}
+
+	act8865 = devm_kzalloc(dev, sizeof(struct act8865_data) +
+			sizeof(struct regulator_dev *) * ACT8865_REG_NUM,
+			GFP_KERNEL);
+	if (!act8865)
+		return -ENOMEM;
+
+	act8865->client = client;
+	act8865->vsel_is_low = pdata->vsel_is_low;
+	rdev = act8865->rdev;
+
+	/* Finally register devices */
+	for (i = 0; i < pdata->num_regulators; i++) {
+
+		id = pdata->regulators[i].id;
+
+		config.dev = dev;
+		config.init_data = pdata->regulators[i].platform_data;
+		config.of_node = of_node[i];
+		config.driver_data = act8865;
+
+		rdev[i] = regulator_register(&act8865_reg[id], &config);
+		if (IS_ERR(rdev[i])) {
+			ret = PTR_ERR(rdev[i]);
+			dev_err(dev, "failed to register %s\n",
+				act8865_reg[id].name);
+			goto err_unregister;
+		}
+	}
+
+	i2c_set_clientdata(client, act8865);
+
+	return 0;
+
+err_unregister:
+	while (--i >= 0)
+		regulator_unregister(rdev[i]);
+
+	return ret;
+}
+
+static int act8865_pmu_remove(struct i2c_client *client)
+{
+	struct act8865_data *act8865 = i2c_get_clientdata(client);
+	int i;
+
+	for (i = 0; i < ACT8865_REG_NUM; i++)
+		regulator_unregister(act8865->rdev[i]);
+
+	return 0;
+}
+
+static const struct i2c_device_id act8865_ids[] = {
+	{ "act8865", 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(i2c, act8865_ids);
+
+static struct i2c_driver act8865_pmu_driver = {
+	.probe		= act8865_pmu_probe,
+	.remove		= act8865_pmu_remove,
+	.driver		= {
+		.name	= "act8865",
+		.owner	= THIS_MODULE,
+	},
+	.id_table	= act8865_ids,
+};
+
+static int __init act8865_pmu_init(void)
+{
+	return i2c_add_driver(&act8865_pmu_driver);
+}
+subsys_initcall(act8865_pmu_init);
+
+static void __exit act8865_pmu_exit(void)
+{
+	i2c_del_driver(&act8865_pmu_driver);
+}
+module_exit(act8865_pmu_exit);
+
+MODULE_DESCRIPTION("active-semi act8865 voltage regulator driver");
+MODULE_AUTHOR("Wenyou Yang <wenyou.yang@atmel.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/regulator/act8865.h b/include/linux/regulator/act8865.h
new file mode 100644
index 0000000..202544f
--- /dev/null
+++ b/include/linux/regulator/act8865.h
@@ -0,0 +1,56 @@
+/*
+ * act8865.h  --  Voltage regulation for the active-semi act8865
+ *
+ * Copyright (C) 2013 Atmel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * 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.
+ */
+
+#ifndef __LINUX_REGULATOR_ACT8865_H
+#define __LINUX_REGULATOR_ACT8865_H
+
+#include <linux/regulator/machine.h>
+
+enum {
+	ACT8865_ID_REG1,
+	ACT8865_ID_REG2,
+	ACT8865_ID_REG3,
+	ACT8865_ID_REG4,
+	ACT8865_ID_REG5,
+	ACT8865_ID_REG6,
+	ACT8865_ID_REG7,
+	ACT8865_REG_NUM,
+};
+
+/**
+ * act8865_regulator_data - regulator data
+ * @id: regulator id
+ * @name: regulator name
+ * @platform_data: regulator init data
+ */
+struct act8865_regulator_data {
+	int				id;
+	const char				*name;
+	struct regulator_init_data	*platform_data;
+};
+
+/**
+ * act8865_platform_data - platform data for act8865
+ * @num_regulators: number of regulators used
+ * @regulators: pointer to regulators used
+ * @vsel_is_low: VSEL pin, drive to logic low to select default output voltage,
+ *			drive to logic high to select secondary output voltage.
+ */
+struct act8865_platform_data {
+	int num_regulators;
+	struct act8865_regulator_data *regulators;
+	unsigned vsel_is_low:1;
+};
+#endif
-- 
1.7.9.5


WARNING: multiple messages have this Message-ID (diff)
From: Wenyou Yang <wenyou.yang@atmel.com>
To: lgirdwood@gmail.com
Cc: devicetree@vger.kernel.org, vpalatin@chromium.org,
	linux-doc@vger.kernel.org, nicolas.ferre@atmel.com,
	linux-kernel@vger.kernel.org, rob.herring@calxeda.com,
	wenyou.yang@atmel.com, broonie@kernel.org,
	grant.likely@linaro.org, plagnioj@jcrosoft.com,
	linux-arm-kernel@lists.infradead.org
Subject: [PATCH 1/3] regulator: act8865: add PMIC(Power Management IC) driver
Date: Thu, 12 Dec 2013 09:18:49 +0800	[thread overview]
Message-ID: <1386811131-2720-2-git-send-email-wenyou.yang@atmel.com> (raw)
In-Reply-To: <1386811131-2720-1-git-send-email-wenyou.yang@atmel.com>

Signed-off-by: Wenyou Yang <wenyou.yang@atmel.com>
---
 drivers/regulator/Kconfig             |    7 +
 drivers/regulator/Makefile            |    1 +
 drivers/regulator/act8865-regulator.c |  516 +++++++++++++++++++++++++++++++++
 include/linux/regulator/act8865.h     |   56 ++++
 4 files changed, 580 insertions(+)
 create mode 100644 drivers/regulator/act8865-regulator.c
 create mode 100644 include/linux/regulator/act8865.h

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index ce785f4..e13bcf1 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -577,5 +577,12 @@ config REGULATOR_WM8994
 	  This driver provides support for the voltage regulators on the
 	  WM8994 CODEC.
 
+config REGULATOR_ACT8865
+	tristate "Active-semi act8865 voltage regulator"
+	depends on I2C
+	help
+	  This driver controls a active-semi act8865 voltage output
+	  regulator via I2C bus.
+
 endif
 
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 01c597e..8bc485f 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -78,6 +78,7 @@ obj-$(CONFIG_REGULATOR_WM831X) += wm831x-ldo.o
 obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o
 obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o
 obj-$(CONFIG_REGULATOR_WM8994) += wm8994-regulator.o
+obj-$(CONFIG_REGULATOR_ACT8865) += act8865-regulator.o
 
 
 ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG
diff --git a/drivers/regulator/act8865-regulator.c b/drivers/regulator/act8865-regulator.c
new file mode 100644
index 0000000..d616a28
--- /dev/null
+++ b/drivers/regulator/act8865-regulator.c
@@ -0,0 +1,516 @@
+/*
+ * act8865-regulator.c - Voltage regulation for the active-semi ACT8865
+ * http://www.active-semi.com/sheets/ACT8865_Datasheet.pdf
+ *
+ * Copyright (C) 2013 Atmel Corporation
+ * Wenyou Yang <wenyou.yang@atmel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/init.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/act8865.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/regulator/of_regulator.h>
+
+/*
+ * ACT8865 Global Register Map
+ */
+#define	ACT8865_SYS_MODE	0x00
+#define ACT8865_SYS_CTRL	0x01
+#define ACT8865_REG1_VSET1	0x20
+#define	ACT8865_REG1_VSET2	0x21
+#define	ACT8865_REG1_CTRL	0x22
+#define	ACT8865_REG2_VSET1	0x30
+#define	ACT8865_REG2_VSET2	0x31
+#define	ACT8865_REG2_CTRL	0x32
+#define	ACT8865_REG3_VSET1	0x40
+#define	ACT8865_REG3_VSET2	0x41
+#define	ACT8865_REG3_CTRL	0x42
+#define	ACT8865_REG4_VSET	0x50
+#define	ACT8865_REG4_CTRL	0x51
+#define	ACT8865_REG5_VSET	0x54
+#define	ACT8865_REG5_CTRL	0x55
+#define	ACT8865_REG6_VSET	0x60
+#define	ACT8865_REG6_CTRL	0x61
+#define	ACT8865_REG7_VSET	0x64
+#define	ACT8865_REG7_CTRL	0x65
+
+struct act8865_data {
+	u8	vsel_is_low:1;
+	struct i2c_client	*client;
+	struct regulator_dev	*rdev[];
+};
+
+/* ACt8865 voltage table */
+static const u32 act8865_voltages_table[] = {
+	600000,		625000,		650000,		675000,
+	700000,		725000,		750000,		775000,
+	800000,		825000,		850000,		875000,
+	900000,		925000,		950000,		975000,
+	1000000,	1025000,	1050000,	1075000,
+	1100000,	1125000,	1150000,	1175000,
+	1200000,	1250000,	1300000,	1350000,
+	1400000,	1450000,	1500000,	1550000,
+	1600000,	1650000,	1700000,	1750000,
+	1800000,	1850000,	1900000,	1950000,
+	2000000,	2050000,	2010000,	2150000,
+	2200000,	2250000,	2300000,	2350000,
+	2400000,	2500000,	2600000,	2700000,
+	2800000,	2900000,	3000000,	3100000,
+	3200000,	3300000,	3400000,	3500000,
+	3600000,	3700000,	3800000,	3900000,
+};
+
+static int act8865_read_reg(struct act8865_data *act8865, u8 reg)
+{
+	int ret = i2c_smbus_read_byte_data(act8865->client, reg);
+	if (ret > 0)
+		ret &= 0xff;
+
+	return ret;
+}
+
+static int act8865_write_reg(struct act8865_data *act8865, u8 reg, u8 value)
+{
+	return i2c_smbus_write_byte_data(act8865->client, reg, value);
+}
+
+/*
+ * operation functions
+ */
+
+static u32 act8865_ctrl_reg_addr(int id)
+{
+	u32	reg = 0;
+
+	switch (id) {
+	case ACT8865_ID_REG1:
+		reg = ACT8865_REG1_CTRL;
+		break;
+
+	case ACT8865_ID_REG2:
+		reg = ACT8865_REG2_CTRL;
+		break;
+
+	case ACT8865_ID_REG3:
+		reg = ACT8865_REG3_CTRL;
+		break;
+
+	case ACT8865_ID_REG4:
+		reg = ACT8865_REG4_CTRL;
+		break;
+
+	case ACT8865_ID_REG5:
+		reg = ACT8865_REG5_CTRL;
+		break;
+
+	case ACT8865_ID_REG6:
+		reg = ACT8865_REG6_CTRL;
+		break;
+
+	case ACT8865_ID_REG7:
+		reg = ACT8865_REG7_CTRL;
+		break;
+	};
+
+	return reg;
+}
+
+static u32 act8865_vset_reg_addr(struct act8865_data *act8865, int id)
+{
+	u32	reg = 0;
+
+	switch (id) {
+	case ACT8865_ID_REG1:
+		if (act8865->vsel_is_low)
+			reg = ACT8865_REG1_VSET1;
+		else
+			reg = ACT8865_REG1_VSET2;
+		break;
+
+	case ACT8865_ID_REG2:
+		if (act8865->vsel_is_low)
+			reg = ACT8865_REG2_VSET1;
+		else
+			reg = ACT8865_REG2_VSET2;
+		break;
+
+	case ACT8865_ID_REG3:
+		if (act8865->vsel_is_low)
+			reg = ACT8865_REG3_VSET1;
+		else
+			reg = ACT8865_REG3_VSET2;
+		break;
+
+	case ACT8865_ID_REG4:
+		reg = ACT8865_REG4_VSET;
+		break;
+
+	case ACT8865_ID_REG5:
+		reg = ACT8865_REG5_VSET;
+		break;
+
+	case ACT8865_ID_REG6:
+		reg = ACT8865_REG6_VSET;
+		break;
+
+	case ACT8865_ID_REG7:
+		reg = ACT8865_REG7_VSET;
+		break;
+	};
+
+	return reg;
+}
+
+static int act88665_is_enabled(struct regulator_dev *rdev)
+{
+	struct act8865_data *act8865 = rdev_get_drvdata(rdev);
+	int id = rdev_get_id(rdev);
+	u32	reg;
+	u8	mask = 0x80;
+
+	reg = act8865_ctrl_reg_addr(id);
+
+	return (act8865_read_reg(act8865, reg) & mask) == mask;
+}
+
+static int act8865_enable(struct regulator_dev *rdev)
+{
+	struct act8865_data *act8865 = rdev_get_drvdata(rdev);
+	int id = rdev_get_id(rdev);
+	u32	reg;
+	u8	val;
+	u8	mask = 0x80;
+
+	reg = act8865_ctrl_reg_addr(id);
+	val = act8865_read_reg(act8865, reg);
+	val |= mask;
+
+	return act8865_write_reg(act8865, reg, val);
+}
+
+static int act8865_disable(struct regulator_dev *rdev)
+{
+	struct act8865_data *act8865 = rdev_get_drvdata(rdev);
+	int id = rdev_get_id(rdev);
+	u32	reg;
+	u8	val;
+	u8	mask = 0x80;
+
+	reg = act8865_ctrl_reg_addr(id);
+	val = act8865_read_reg(act8865, reg);
+	val &= ~mask;
+
+	return act8865_write_reg(act8865, reg, val);
+}
+
+static int act8865_get_voltage_sel(struct regulator_dev *rdev)
+{
+	struct act8865_data *act8865 = rdev_get_drvdata(rdev);
+	int id = rdev_get_id(rdev);
+	u32	reg = act8865_vset_reg_addr(act8865, id);
+
+	return act8865_read_reg(act8865, reg);
+}
+
+static int act8865_set_voltage_sel(struct regulator_dev *rdev, u32 selector)
+{
+	struct act8865_data *act8865 = rdev_get_drvdata(rdev);
+	int id = rdev_get_id(rdev);
+	u32	reg = act8865_vset_reg_addr(act8865, id);
+
+	return act8865_write_reg(act8865, reg, selector);
+}
+
+static int act8865_set_suspend_voltage(struct regulator_dev *rdev, int uV)
+{
+	struct act8865_data *act8865 = rdev_get_drvdata(rdev);
+	int id = rdev_get_id(rdev);
+	u32	reg = act8865_vset_reg_addr(act8865, id);
+	u32	selector;
+
+	pr_info("%s: suspend voltage %dmV\n", rdev->desc->name, uV / 1000);
+
+	selector = regulator_map_voltage_iterate(rdev, uV, uV);
+
+	return act8865_write_reg(act8865, reg, selector);
+}
+
+static struct regulator_ops act8865_ops = {
+	.list_voltage		= regulator_list_voltage_table,
+	.set_voltage_sel	= act8865_set_voltage_sel,
+	.get_voltage_sel	= act8865_get_voltage_sel,
+	.enable			= act8865_enable,
+	.disable		= act8865_disable,
+	.is_enabled		= act88665_is_enabled,
+	.set_suspend_voltage	= act8865_set_suspend_voltage,
+	.set_suspend_enable	= act8865_enable,
+	.set_suspend_disable	= act8865_disable,
+};
+
+static const struct regulator_desc act8865_reg[] = {
+	{
+		.name = "DCDC_REG1",
+		.id = ACT8865_ID_REG1,
+		.ops = &act8865_ops,
+		.type = REGULATOR_VOLTAGE,
+		.n_voltages = ARRAY_SIZE(act8865_voltages_table),
+		.volt_table = act8865_voltages_table,
+		.owner = THIS_MODULE,
+	},
+	{
+		.name = "DCDC_REG2",
+		.id = ACT8865_ID_REG2,
+		.ops = &act8865_ops,
+		.type = REGULATOR_VOLTAGE,
+		.n_voltages = ARRAY_SIZE(act8865_voltages_table),
+		.volt_table = act8865_voltages_table,
+		.owner = THIS_MODULE,
+	},
+	{
+		.name = "DCDC_REG3",
+		.id = ACT8865_ID_REG3,
+		.ops = &act8865_ops,
+		.type = REGULATOR_VOLTAGE,
+		.n_voltages = ARRAY_SIZE(act8865_voltages_table),
+		.volt_table = act8865_voltages_table,
+		.owner = THIS_MODULE,
+	},
+	{
+		.name = "LDO_REG4",
+		.id = ACT8865_ID_REG4,
+		.ops = &act8865_ops,
+		.type = REGULATOR_VOLTAGE,
+		.n_voltages = ARRAY_SIZE(act8865_voltages_table),
+		.volt_table = act8865_voltages_table,
+		.owner = THIS_MODULE,
+	},
+	{
+		.name = "LDO_REG5",
+		.id = ACT8865_ID_REG5,
+		.ops = &act8865_ops,
+		.type = REGULATOR_VOLTAGE,
+		.n_voltages = ARRAY_SIZE(act8865_voltages_table),
+		.volt_table = act8865_voltages_table,
+		.owner = THIS_MODULE,
+	},
+	{
+		.name = "LDO_REG6",
+		.id = ACT8865_ID_REG6,
+		.ops = &act8865_ops,
+		.type = REGULATOR_VOLTAGE,
+		.n_voltages = ARRAY_SIZE(act8865_voltages_table),
+		.volt_table = act8865_voltages_table,
+		.owner = THIS_MODULE,
+	},
+	{
+		.name = "LDO_REG7",
+		.id = ACT8865_ID_REG7,
+		.ops = &act8865_ops,
+		.type = REGULATOR_VOLTAGE,
+		.n_voltages = ARRAY_SIZE(act8865_voltages_table),
+		.volt_table = act8865_voltages_table,
+		.owner = THIS_MODULE,
+	},
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id act8865_dt_ids[] = {
+	{ .compatible = "active-semi,act8865" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, act8865_dt_ids);
+
+static int act8865_pdata_from_dt(struct device *dev,
+				 struct device_node **of_node,
+				 struct act8865_platform_data *pdata)
+{
+	int matched, i;
+	struct device_node *np;
+	struct act8865_regulator_data *regulator;
+	struct of_regulator_match rmatch[ARRAY_SIZE(act8865_reg)];
+
+	if (of_find_property(dev->of_node, "vsel-state-low", NULL))
+		pdata->vsel_is_low = 1;
+
+	np = of_find_node_by_name(dev->of_node, "regulators");
+	if (!np) {
+		dev_err(dev, "missing 'regulators' subnode in DT\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(rmatch); i++)
+		rmatch[i].name = act8865_reg[i].name;
+
+	matched = of_regulator_match(dev, np, rmatch, ARRAY_SIZE(rmatch));
+	if (matched <= 0)
+		return matched;
+
+	pdata->regulators = devm_kzalloc(dev,
+				sizeof(struct act8865_regulator_data) * matched,
+				GFP_KERNEL);
+	if (!pdata->regulators) {
+		dev_err(dev, "%s: failed to allocate act8865 registor\n",
+						__func__);
+		return -ENOMEM;
+	}
+
+	pdata->num_regulators = matched;
+	regulator = pdata->regulators;
+
+	for (i = 0; i < matched; i++) {
+		regulator->id = i;
+		regulator->name = rmatch[i].name;
+		regulator->platform_data = rmatch[i].init_data;
+		of_node[i] = rmatch[i].of_node;
+		regulator++;
+	}
+
+	return 0;
+}
+
+#else
+
+static inline int act8865_pdata_from_dt(struct device *dev,
+					struct device_node **of_node,
+					struct act8865_platform_data *pdata)
+{
+	return 0;
+}
+#endif
+
+static int act8865_pmu_probe(struct i2c_client *client,
+			   const struct i2c_device_id *i2c_id)
+{
+	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+	struct regulator_dev **rdev;
+	struct device *dev = &client->dev;
+	struct act8865_platform_data *pdata = dev_get_platdata(dev);
+	struct regulator_config config = { };
+	struct act8865_data *act8865;
+	int i, id;
+	int ret = -EINVAL;
+	struct device_node *of_node[ACT8865_REG_NUM];
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+		return -EIO;
+
+	if (dev->of_node && !pdata) {
+		const struct of_device_id *id;
+		struct act8865_platform_data pdata_of;
+
+		id = of_match_device(of_match_ptr(act8865_dt_ids), dev);
+		if (!id)
+			return -ENODEV;
+
+		ret = act8865_pdata_from_dt(dev, of_node, &pdata_of);
+		if (ret < 0)
+			return ret;
+
+		pdata = &pdata_of;
+	} else {
+		memset(of_node, 0, sizeof(of_node));
+	}
+
+	if (pdata->num_regulators > ACT8865_REG_NUM) {
+		dev_err(dev, "Too many regulators found!\n");
+		return -EINVAL;
+	}
+
+	act8865 = devm_kzalloc(dev, sizeof(struct act8865_data) +
+			sizeof(struct regulator_dev *) * ACT8865_REG_NUM,
+			GFP_KERNEL);
+	if (!act8865)
+		return -ENOMEM;
+
+	act8865->client = client;
+	act8865->vsel_is_low = pdata->vsel_is_low;
+	rdev = act8865->rdev;
+
+	/* Finally register devices */
+	for (i = 0; i < pdata->num_regulators; i++) {
+
+		id = pdata->regulators[i].id;
+
+		config.dev = dev;
+		config.init_data = pdata->regulators[i].platform_data;
+		config.of_node = of_node[i];
+		config.driver_data = act8865;
+
+		rdev[i] = regulator_register(&act8865_reg[id], &config);
+		if (IS_ERR(rdev[i])) {
+			ret = PTR_ERR(rdev[i]);
+			dev_err(dev, "failed to register %s\n",
+				act8865_reg[id].name);
+			goto err_unregister;
+		}
+	}
+
+	i2c_set_clientdata(client, act8865);
+
+	return 0;
+
+err_unregister:
+	while (--i >= 0)
+		regulator_unregister(rdev[i]);
+
+	return ret;
+}
+
+static int act8865_pmu_remove(struct i2c_client *client)
+{
+	struct act8865_data *act8865 = i2c_get_clientdata(client);
+	int i;
+
+	for (i = 0; i < ACT8865_REG_NUM; i++)
+		regulator_unregister(act8865->rdev[i]);
+
+	return 0;
+}
+
+static const struct i2c_device_id act8865_ids[] = {
+	{ "act8865", 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(i2c, act8865_ids);
+
+static struct i2c_driver act8865_pmu_driver = {
+	.probe		= act8865_pmu_probe,
+	.remove		= act8865_pmu_remove,
+	.driver		= {
+		.name	= "act8865",
+		.owner	= THIS_MODULE,
+	},
+	.id_table	= act8865_ids,
+};
+
+static int __init act8865_pmu_init(void)
+{
+	return i2c_add_driver(&act8865_pmu_driver);
+}
+subsys_initcall(act8865_pmu_init);
+
+static void __exit act8865_pmu_exit(void)
+{
+	i2c_del_driver(&act8865_pmu_driver);
+}
+module_exit(act8865_pmu_exit);
+
+MODULE_DESCRIPTION("active-semi act8865 voltage regulator driver");
+MODULE_AUTHOR("Wenyou Yang <wenyou.yang@atmel.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/regulator/act8865.h b/include/linux/regulator/act8865.h
new file mode 100644
index 0000000..202544f
--- /dev/null
+++ b/include/linux/regulator/act8865.h
@@ -0,0 +1,56 @@
+/*
+ * act8865.h  --  Voltage regulation for the active-semi act8865
+ *
+ * Copyright (C) 2013 Atmel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * 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.
+ */
+
+#ifndef __LINUX_REGULATOR_ACT8865_H
+#define __LINUX_REGULATOR_ACT8865_H
+
+#include <linux/regulator/machine.h>
+
+enum {
+	ACT8865_ID_REG1,
+	ACT8865_ID_REG2,
+	ACT8865_ID_REG3,
+	ACT8865_ID_REG4,
+	ACT8865_ID_REG5,
+	ACT8865_ID_REG6,
+	ACT8865_ID_REG7,
+	ACT8865_REG_NUM,
+};
+
+/**
+ * act8865_regulator_data - regulator data
+ * @id: regulator id
+ * @name: regulator name
+ * @platform_data: regulator init data
+ */
+struct act8865_regulator_data {
+	int				id;
+	const char				*name;
+	struct regulator_init_data	*platform_data;
+};
+
+/**
+ * act8865_platform_data - platform data for act8865
+ * @num_regulators: number of regulators used
+ * @regulators: pointer to regulators used
+ * @vsel_is_low: VSEL pin, drive to logic low to select default output voltage,
+ *			drive to logic high to select secondary output voltage.
+ */
+struct act8865_platform_data {
+	int num_regulators;
+	struct act8865_regulator_data *regulators;
+	unsigned vsel_is_low:1;
+};
+#endif
-- 
1.7.9.5

WARNING: multiple messages have this Message-ID (diff)
From: wenyou.yang@atmel.com (Wenyou Yang)
To: linux-arm-kernel@lists.infradead.org
Subject: [PATCH 1/3] regulator: act8865: add PMIC(Power Management IC) driver
Date: Thu, 12 Dec 2013 09:18:49 +0800	[thread overview]
Message-ID: <1386811131-2720-2-git-send-email-wenyou.yang@atmel.com> (raw)
In-Reply-To: <1386811131-2720-1-git-send-email-wenyou.yang@atmel.com>

Signed-off-by: Wenyou Yang <wenyou.yang@atmel.com>
---
 drivers/regulator/Kconfig             |    7 +
 drivers/regulator/Makefile            |    1 +
 drivers/regulator/act8865-regulator.c |  516 +++++++++++++++++++++++++++++++++
 include/linux/regulator/act8865.h     |   56 ++++
 4 files changed, 580 insertions(+)
 create mode 100644 drivers/regulator/act8865-regulator.c
 create mode 100644 include/linux/regulator/act8865.h

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index ce785f4..e13bcf1 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -577,5 +577,12 @@ config REGULATOR_WM8994
 	  This driver provides support for the voltage regulators on the
 	  WM8994 CODEC.
 
+config REGULATOR_ACT8865
+	tristate "Active-semi act8865 voltage regulator"
+	depends on I2C
+	help
+	  This driver controls a active-semi act8865 voltage output
+	  regulator via I2C bus.
+
 endif
 
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 01c597e..8bc485f 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -78,6 +78,7 @@ obj-$(CONFIG_REGULATOR_WM831X) += wm831x-ldo.o
 obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o
 obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o
 obj-$(CONFIG_REGULATOR_WM8994) += wm8994-regulator.o
+obj-$(CONFIG_REGULATOR_ACT8865) += act8865-regulator.o
 
 
 ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG
diff --git a/drivers/regulator/act8865-regulator.c b/drivers/regulator/act8865-regulator.c
new file mode 100644
index 0000000..d616a28
--- /dev/null
+++ b/drivers/regulator/act8865-regulator.c
@@ -0,0 +1,516 @@
+/*
+ * act8865-regulator.c - Voltage regulation for the active-semi ACT8865
+ * http://www.active-semi.com/sheets/ACT8865_Datasheet.pdf
+ *
+ * Copyright (C) 2013 Atmel Corporation
+ * Wenyou Yang <wenyou.yang@atmel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/init.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/act8865.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/regulator/of_regulator.h>
+
+/*
+ * ACT8865 Global Register Map
+ */
+#define	ACT8865_SYS_MODE	0x00
+#define ACT8865_SYS_CTRL	0x01
+#define ACT8865_REG1_VSET1	0x20
+#define	ACT8865_REG1_VSET2	0x21
+#define	ACT8865_REG1_CTRL	0x22
+#define	ACT8865_REG2_VSET1	0x30
+#define	ACT8865_REG2_VSET2	0x31
+#define	ACT8865_REG2_CTRL	0x32
+#define	ACT8865_REG3_VSET1	0x40
+#define	ACT8865_REG3_VSET2	0x41
+#define	ACT8865_REG3_CTRL	0x42
+#define	ACT8865_REG4_VSET	0x50
+#define	ACT8865_REG4_CTRL	0x51
+#define	ACT8865_REG5_VSET	0x54
+#define	ACT8865_REG5_CTRL	0x55
+#define	ACT8865_REG6_VSET	0x60
+#define	ACT8865_REG6_CTRL	0x61
+#define	ACT8865_REG7_VSET	0x64
+#define	ACT8865_REG7_CTRL	0x65
+
+struct act8865_data {
+	u8	vsel_is_low:1;
+	struct i2c_client	*client;
+	struct regulator_dev	*rdev[];
+};
+
+/* ACt8865 voltage table */
+static const u32 act8865_voltages_table[] = {
+	600000,		625000,		650000,		675000,
+	700000,		725000,		750000,		775000,
+	800000,		825000,		850000,		875000,
+	900000,		925000,		950000,		975000,
+	1000000,	1025000,	1050000,	1075000,
+	1100000,	1125000,	1150000,	1175000,
+	1200000,	1250000,	1300000,	1350000,
+	1400000,	1450000,	1500000,	1550000,
+	1600000,	1650000,	1700000,	1750000,
+	1800000,	1850000,	1900000,	1950000,
+	2000000,	2050000,	2010000,	2150000,
+	2200000,	2250000,	2300000,	2350000,
+	2400000,	2500000,	2600000,	2700000,
+	2800000,	2900000,	3000000,	3100000,
+	3200000,	3300000,	3400000,	3500000,
+	3600000,	3700000,	3800000,	3900000,
+};
+
+static int act8865_read_reg(struct act8865_data *act8865, u8 reg)
+{
+	int ret = i2c_smbus_read_byte_data(act8865->client, reg);
+	if (ret > 0)
+		ret &= 0xff;
+
+	return ret;
+}
+
+static int act8865_write_reg(struct act8865_data *act8865, u8 reg, u8 value)
+{
+	return i2c_smbus_write_byte_data(act8865->client, reg, value);
+}
+
+/*
+ * operation functions
+ */
+
+static u32 act8865_ctrl_reg_addr(int id)
+{
+	u32	reg = 0;
+
+	switch (id) {
+	case ACT8865_ID_REG1:
+		reg = ACT8865_REG1_CTRL;
+		break;
+
+	case ACT8865_ID_REG2:
+		reg = ACT8865_REG2_CTRL;
+		break;
+
+	case ACT8865_ID_REG3:
+		reg = ACT8865_REG3_CTRL;
+		break;
+
+	case ACT8865_ID_REG4:
+		reg = ACT8865_REG4_CTRL;
+		break;
+
+	case ACT8865_ID_REG5:
+		reg = ACT8865_REG5_CTRL;
+		break;
+
+	case ACT8865_ID_REG6:
+		reg = ACT8865_REG6_CTRL;
+		break;
+
+	case ACT8865_ID_REG7:
+		reg = ACT8865_REG7_CTRL;
+		break;
+	};
+
+	return reg;
+}
+
+static u32 act8865_vset_reg_addr(struct act8865_data *act8865, int id)
+{
+	u32	reg = 0;
+
+	switch (id) {
+	case ACT8865_ID_REG1:
+		if (act8865->vsel_is_low)
+			reg = ACT8865_REG1_VSET1;
+		else
+			reg = ACT8865_REG1_VSET2;
+		break;
+
+	case ACT8865_ID_REG2:
+		if (act8865->vsel_is_low)
+			reg = ACT8865_REG2_VSET1;
+		else
+			reg = ACT8865_REG2_VSET2;
+		break;
+
+	case ACT8865_ID_REG3:
+		if (act8865->vsel_is_low)
+			reg = ACT8865_REG3_VSET1;
+		else
+			reg = ACT8865_REG3_VSET2;
+		break;
+
+	case ACT8865_ID_REG4:
+		reg = ACT8865_REG4_VSET;
+		break;
+
+	case ACT8865_ID_REG5:
+		reg = ACT8865_REG5_VSET;
+		break;
+
+	case ACT8865_ID_REG6:
+		reg = ACT8865_REG6_VSET;
+		break;
+
+	case ACT8865_ID_REG7:
+		reg = ACT8865_REG7_VSET;
+		break;
+	};
+
+	return reg;
+}
+
+static int act88665_is_enabled(struct regulator_dev *rdev)
+{
+	struct act8865_data *act8865 = rdev_get_drvdata(rdev);
+	int id = rdev_get_id(rdev);
+	u32	reg;
+	u8	mask = 0x80;
+
+	reg = act8865_ctrl_reg_addr(id);
+
+	return (act8865_read_reg(act8865, reg) & mask) == mask;
+}
+
+static int act8865_enable(struct regulator_dev *rdev)
+{
+	struct act8865_data *act8865 = rdev_get_drvdata(rdev);
+	int id = rdev_get_id(rdev);
+	u32	reg;
+	u8	val;
+	u8	mask = 0x80;
+
+	reg = act8865_ctrl_reg_addr(id);
+	val = act8865_read_reg(act8865, reg);
+	val |= mask;
+
+	return act8865_write_reg(act8865, reg, val);
+}
+
+static int act8865_disable(struct regulator_dev *rdev)
+{
+	struct act8865_data *act8865 = rdev_get_drvdata(rdev);
+	int id = rdev_get_id(rdev);
+	u32	reg;
+	u8	val;
+	u8	mask = 0x80;
+
+	reg = act8865_ctrl_reg_addr(id);
+	val = act8865_read_reg(act8865, reg);
+	val &= ~mask;
+
+	return act8865_write_reg(act8865, reg, val);
+}
+
+static int act8865_get_voltage_sel(struct regulator_dev *rdev)
+{
+	struct act8865_data *act8865 = rdev_get_drvdata(rdev);
+	int id = rdev_get_id(rdev);
+	u32	reg = act8865_vset_reg_addr(act8865, id);
+
+	return act8865_read_reg(act8865, reg);
+}
+
+static int act8865_set_voltage_sel(struct regulator_dev *rdev, u32 selector)
+{
+	struct act8865_data *act8865 = rdev_get_drvdata(rdev);
+	int id = rdev_get_id(rdev);
+	u32	reg = act8865_vset_reg_addr(act8865, id);
+
+	return act8865_write_reg(act8865, reg, selector);
+}
+
+static int act8865_set_suspend_voltage(struct regulator_dev *rdev, int uV)
+{
+	struct act8865_data *act8865 = rdev_get_drvdata(rdev);
+	int id = rdev_get_id(rdev);
+	u32	reg = act8865_vset_reg_addr(act8865, id);
+	u32	selector;
+
+	pr_info("%s: suspend voltage %dmV\n", rdev->desc->name, uV / 1000);
+
+	selector = regulator_map_voltage_iterate(rdev, uV, uV);
+
+	return act8865_write_reg(act8865, reg, selector);
+}
+
+static struct regulator_ops act8865_ops = {
+	.list_voltage		= regulator_list_voltage_table,
+	.set_voltage_sel	= act8865_set_voltage_sel,
+	.get_voltage_sel	= act8865_get_voltage_sel,
+	.enable			= act8865_enable,
+	.disable		= act8865_disable,
+	.is_enabled		= act88665_is_enabled,
+	.set_suspend_voltage	= act8865_set_suspend_voltage,
+	.set_suspend_enable	= act8865_enable,
+	.set_suspend_disable	= act8865_disable,
+};
+
+static const struct regulator_desc act8865_reg[] = {
+	{
+		.name = "DCDC_REG1",
+		.id = ACT8865_ID_REG1,
+		.ops = &act8865_ops,
+		.type = REGULATOR_VOLTAGE,
+		.n_voltages = ARRAY_SIZE(act8865_voltages_table),
+		.volt_table = act8865_voltages_table,
+		.owner = THIS_MODULE,
+	},
+	{
+		.name = "DCDC_REG2",
+		.id = ACT8865_ID_REG2,
+		.ops = &act8865_ops,
+		.type = REGULATOR_VOLTAGE,
+		.n_voltages = ARRAY_SIZE(act8865_voltages_table),
+		.volt_table = act8865_voltages_table,
+		.owner = THIS_MODULE,
+	},
+	{
+		.name = "DCDC_REG3",
+		.id = ACT8865_ID_REG3,
+		.ops = &act8865_ops,
+		.type = REGULATOR_VOLTAGE,
+		.n_voltages = ARRAY_SIZE(act8865_voltages_table),
+		.volt_table = act8865_voltages_table,
+		.owner = THIS_MODULE,
+	},
+	{
+		.name = "LDO_REG4",
+		.id = ACT8865_ID_REG4,
+		.ops = &act8865_ops,
+		.type = REGULATOR_VOLTAGE,
+		.n_voltages = ARRAY_SIZE(act8865_voltages_table),
+		.volt_table = act8865_voltages_table,
+		.owner = THIS_MODULE,
+	},
+	{
+		.name = "LDO_REG5",
+		.id = ACT8865_ID_REG5,
+		.ops = &act8865_ops,
+		.type = REGULATOR_VOLTAGE,
+		.n_voltages = ARRAY_SIZE(act8865_voltages_table),
+		.volt_table = act8865_voltages_table,
+		.owner = THIS_MODULE,
+	},
+	{
+		.name = "LDO_REG6",
+		.id = ACT8865_ID_REG6,
+		.ops = &act8865_ops,
+		.type = REGULATOR_VOLTAGE,
+		.n_voltages = ARRAY_SIZE(act8865_voltages_table),
+		.volt_table = act8865_voltages_table,
+		.owner = THIS_MODULE,
+	},
+	{
+		.name = "LDO_REG7",
+		.id = ACT8865_ID_REG7,
+		.ops = &act8865_ops,
+		.type = REGULATOR_VOLTAGE,
+		.n_voltages = ARRAY_SIZE(act8865_voltages_table),
+		.volt_table = act8865_voltages_table,
+		.owner = THIS_MODULE,
+	},
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id act8865_dt_ids[] = {
+	{ .compatible = "active-semi,act8865" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, act8865_dt_ids);
+
+static int act8865_pdata_from_dt(struct device *dev,
+				 struct device_node **of_node,
+				 struct act8865_platform_data *pdata)
+{
+	int matched, i;
+	struct device_node *np;
+	struct act8865_regulator_data *regulator;
+	struct of_regulator_match rmatch[ARRAY_SIZE(act8865_reg)];
+
+	if (of_find_property(dev->of_node, "vsel-state-low", NULL))
+		pdata->vsel_is_low = 1;
+
+	np = of_find_node_by_name(dev->of_node, "regulators");
+	if (!np) {
+		dev_err(dev, "missing 'regulators' subnode in DT\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(rmatch); i++)
+		rmatch[i].name = act8865_reg[i].name;
+
+	matched = of_regulator_match(dev, np, rmatch, ARRAY_SIZE(rmatch));
+	if (matched <= 0)
+		return matched;
+
+	pdata->regulators = devm_kzalloc(dev,
+				sizeof(struct act8865_regulator_data) * matched,
+				GFP_KERNEL);
+	if (!pdata->regulators) {
+		dev_err(dev, "%s: failed to allocate act8865 registor\n",
+						__func__);
+		return -ENOMEM;
+	}
+
+	pdata->num_regulators = matched;
+	regulator = pdata->regulators;
+
+	for (i = 0; i < matched; i++) {
+		regulator->id = i;
+		regulator->name = rmatch[i].name;
+		regulator->platform_data = rmatch[i].init_data;
+		of_node[i] = rmatch[i].of_node;
+		regulator++;
+	}
+
+	return 0;
+}
+
+#else
+
+static inline int act8865_pdata_from_dt(struct device *dev,
+					struct device_node **of_node,
+					struct act8865_platform_data *pdata)
+{
+	return 0;
+}
+#endif
+
+static int act8865_pmu_probe(struct i2c_client *client,
+			   const struct i2c_device_id *i2c_id)
+{
+	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+	struct regulator_dev **rdev;
+	struct device *dev = &client->dev;
+	struct act8865_platform_data *pdata = dev_get_platdata(dev);
+	struct regulator_config config = { };
+	struct act8865_data *act8865;
+	int i, id;
+	int ret = -EINVAL;
+	struct device_node *of_node[ACT8865_REG_NUM];
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+		return -EIO;
+
+	if (dev->of_node && !pdata) {
+		const struct of_device_id *id;
+		struct act8865_platform_data pdata_of;
+
+		id = of_match_device(of_match_ptr(act8865_dt_ids), dev);
+		if (!id)
+			return -ENODEV;
+
+		ret = act8865_pdata_from_dt(dev, of_node, &pdata_of);
+		if (ret < 0)
+			return ret;
+
+		pdata = &pdata_of;
+	} else {
+		memset(of_node, 0, sizeof(of_node));
+	}
+
+	if (pdata->num_regulators > ACT8865_REG_NUM) {
+		dev_err(dev, "Too many regulators found!\n");
+		return -EINVAL;
+	}
+
+	act8865 = devm_kzalloc(dev, sizeof(struct act8865_data) +
+			sizeof(struct regulator_dev *) * ACT8865_REG_NUM,
+			GFP_KERNEL);
+	if (!act8865)
+		return -ENOMEM;
+
+	act8865->client = client;
+	act8865->vsel_is_low = pdata->vsel_is_low;
+	rdev = act8865->rdev;
+
+	/* Finally register devices */
+	for (i = 0; i < pdata->num_regulators; i++) {
+
+		id = pdata->regulators[i].id;
+
+		config.dev = dev;
+		config.init_data = pdata->regulators[i].platform_data;
+		config.of_node = of_node[i];
+		config.driver_data = act8865;
+
+		rdev[i] = regulator_register(&act8865_reg[id], &config);
+		if (IS_ERR(rdev[i])) {
+			ret = PTR_ERR(rdev[i]);
+			dev_err(dev, "failed to register %s\n",
+				act8865_reg[id].name);
+			goto err_unregister;
+		}
+	}
+
+	i2c_set_clientdata(client, act8865);
+
+	return 0;
+
+err_unregister:
+	while (--i >= 0)
+		regulator_unregister(rdev[i]);
+
+	return ret;
+}
+
+static int act8865_pmu_remove(struct i2c_client *client)
+{
+	struct act8865_data *act8865 = i2c_get_clientdata(client);
+	int i;
+
+	for (i = 0; i < ACT8865_REG_NUM; i++)
+		regulator_unregister(act8865->rdev[i]);
+
+	return 0;
+}
+
+static const struct i2c_device_id act8865_ids[] = {
+	{ "act8865", 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(i2c, act8865_ids);
+
+static struct i2c_driver act8865_pmu_driver = {
+	.probe		= act8865_pmu_probe,
+	.remove		= act8865_pmu_remove,
+	.driver		= {
+		.name	= "act8865",
+		.owner	= THIS_MODULE,
+	},
+	.id_table	= act8865_ids,
+};
+
+static int __init act8865_pmu_init(void)
+{
+	return i2c_add_driver(&act8865_pmu_driver);
+}
+subsys_initcall(act8865_pmu_init);
+
+static void __exit act8865_pmu_exit(void)
+{
+	i2c_del_driver(&act8865_pmu_driver);
+}
+module_exit(act8865_pmu_exit);
+
+MODULE_DESCRIPTION("active-semi act8865 voltage regulator driver");
+MODULE_AUTHOR("Wenyou Yang <wenyou.yang@atmel.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/regulator/act8865.h b/include/linux/regulator/act8865.h
new file mode 100644
index 0000000..202544f
--- /dev/null
+++ b/include/linux/regulator/act8865.h
@@ -0,0 +1,56 @@
+/*
+ * act8865.h  --  Voltage regulation for the active-semi act8865
+ *
+ * Copyright (C) 2013 Atmel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * 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.
+ */
+
+#ifndef __LINUX_REGULATOR_ACT8865_H
+#define __LINUX_REGULATOR_ACT8865_H
+
+#include <linux/regulator/machine.h>
+
+enum {
+	ACT8865_ID_REG1,
+	ACT8865_ID_REG2,
+	ACT8865_ID_REG3,
+	ACT8865_ID_REG4,
+	ACT8865_ID_REG5,
+	ACT8865_ID_REG6,
+	ACT8865_ID_REG7,
+	ACT8865_REG_NUM,
+};
+
+/**
+ * act8865_regulator_data - regulator data
+ * @id: regulator id
+ * @name: regulator name
+ * @platform_data: regulator init data
+ */
+struct act8865_regulator_data {
+	int				id;
+	const char				*name;
+	struct regulator_init_data	*platform_data;
+};
+
+/**
+ * act8865_platform_data - platform data for act8865
+ * @num_regulators: number of regulators used
+ * @regulators: pointer to regulators used
+ * @vsel_is_low: VSEL pin, drive to logic low to select default output voltage,
+ *			drive to logic high to select secondary output voltage.
+ */
+struct act8865_platform_data {
+	int num_regulators;
+	struct act8865_regulator_data *regulators;
+	unsigned vsel_is_low:1;
+};
+#endif
-- 
1.7.9.5

  reply	other threads:[~2013-12-12  1:25 UTC|newest]

Thread overview: 22+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2013-12-12  1:18 [PATCH 0/3] regulator: act8865: add PMIC driver Wenyou Yang
2013-12-12  1:18 ` Wenyou Yang
2013-12-12  1:18 ` Wenyou Yang
2013-12-12  1:18 ` Wenyou Yang [this message]
2013-12-12  1:18   ` [PATCH 1/3] regulator: act8865: add PMIC(Power Management IC) driver Wenyou Yang
2013-12-12  1:18   ` Wenyou Yang
2013-12-12 16:51   ` Mark Brown
2013-12-12 16:51     ` Mark Brown
2013-12-13  7:10     ` Yang, Wenyou
2013-12-13  7:10       ` Yang, Wenyou
2013-12-13  7:10       ` Yang, Wenyou
2013-12-13 10:45       ` Mark Brown
2013-12-13 10:45         ` Mark Brown
2013-12-13 10:45         ` Mark Brown
2013-12-12  1:18 ` [PATCH 2/3] regulator: act8865: add device tree binding doc Wenyou Yang
2013-12-12  1:18   ` Wenyou Yang
2013-12-12  1:18   ` Wenyou Yang
2013-12-12 16:52   ` Mark Brown
2013-12-12 16:52     ` Mark Brown
2013-12-12  1:18 ` [PATCH 3/3] ARM: dts: sama5d3xcm: add the regulator device node Wenyou Yang
2013-12-12  1:18   ` Wenyou Yang
2013-12-12  1:18   ` Wenyou Yang

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1386811131-2720-2-git-send-email-wenyou.yang@atmel.com \
    --to=wenyou.yang@atmel.com \
    --cc=broonie@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=grant.likely@linaro.org \
    --cc=lgirdwood@gmail.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=nicolas.ferre@atmel.com \
    --cc=plagnioj@jcrosoft.com \
    --cc=rob.herring@calxeda.com \
    --cc=vpalatin@chromium.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.