All of lore.kernel.org
 help / color / mirror / Atom feed
From: Krystian Garbaciak <krystian.garbaciak@diasemi.com>
To: linux-kernel@vger.kernel.org, rtc-linux@googlegroups.com,
	lm-sensors@lm-sensors.org, linux-input@vger.kernel.org,
	linux-watchdog@vger.kernel.org, linux-leds@vger.kernel.org
Cc: Alessandro Zummo <a.zummo@towertech.it>,
	Andrew Jones <drjones@redhat.com>,
	Dmitry Torokhov <dmitry.torokhov@gmail.com>,
	Samuel Ortiz <sameo@linux.intel.com>,
	Ashish Jangam <ashish.jangam@kpitcummins.com>,
	Mark Brown <broonie@opensource.wolfsonmicro.com>,
	Donggeun Kim <dg77.kim@samsung.com>,
	Wim Van Sebroeck <wim@iguana.be>,
	"Richard Purdie <rpurdie@rpsys.net> Anthony Olech"
	<anthony.olech@diasemi.com>, Bryan Wu <bryan.wu@canonical.com>,
	Liam Girdwood <lrg@ti.com>
Subject: [RFC PATCH 2/8] regulator: Add Dialog DA906x voltage regulators support.
Date: Fri, 24 Aug 2012 14:55:00 +0100	[thread overview]
Message-ID: <201208241455@sw-eng-lt-dc-vm2> (raw)
In-Reply-To: <201208241450@sw-eng-lt-dc-vm2>

The driver adds support for the following DA9063 PMIC regulators:
 - 11x LDOs (named LDO1 - LDO11),
 - 6x buck converters (BCORE1, BCORE2, BPRO, BMEM, BIO, BPERI),
 - 1x power switch to switch on/off 32 KHz oscilator output (32K_OUT).

Regulators provide following operations:
 - REGULATOR_CHANGE_STATUS for all regulators,
 - REGULATOR_CHANGE_VOLTAGE for LDOs and buck converters,
 - REGULATOR_CHANGE_MODE for LDOs and buck converters, where:
     - LDOs allow REGULATOR_MODE_NORMAL and REGULATOR_MODE_STANDBY,
     - buck converters allow REGULATOR_MODE_FAST, REGULATOR_MODE_NORMAL
       and REGULATOR_MODE_STANDBY,
 - REGULATOR_CHANGE_CURRENT for buck converters (current limits).

The driver generates REGULATOR_EVENT_OVER_CURRENT for LDO3, LDO4, LDO7, LDO8
and LDO11.

Internally, PMIC provides two voltage configurations for normal and suspend
system state for each regulator. The driver switches between those on
suspend/wake-up to provide quick and fluent output voltage change.

This driver requires MFD core driver for operation.

Signed-off-by: Krystian Garbaciak <krystian.garbaciak@diasemi.com>
---
 drivers/regulator/Kconfig            |    6 +
 drivers/regulator/Makefile           |    1 +
 drivers/regulator/da906x-regulator.c | 1018 ++++++++++++++++++++++++++++++++++
 3 files changed, 1025 insertions(+), 0 deletions(-)
 create mode 100644 drivers/regulator/da906x-regulator.c

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 4e932cc..b57b6c6 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -110,6 +110,12 @@ config REGULATOR_DA9052
 	  This driver supports the voltage regulators of DA9052-BC and
 	  DA9053-AA/Bx PMIC.
 
+config REGULATOR_DA906X
+	bool "Dialog DA906X Regulator family chip"
+	depends on MFD_DA906X
+	help
+	  Support for Dialog Semiconductor DA906x chip.
+
 config REGULATOR_ANATOP
 	tristate "Freescale i.MX on-chip ANATOP LDO regulators"
 	depends on MFD_ANATOP
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 3342615..46a503a 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_REGULATOR_ANATOP) += anatop-regulator.o
 obj-$(CONFIG_REGULATOR_ARIZONA) += arizona-micsupp.o arizona-ldo1.o
 obj-$(CONFIG_REGULATOR_DA903X)	+= da903x.o
 obj-$(CONFIG_REGULATOR_DA9052)	+= da9052-regulator.o
+obj-$(CONFIG_REGULATOR_DA906X) += da906x-regulator.o
 obj-$(CONFIG_REGULATOR_DBX500_PRCMU) += dbx500-prcmu.o
 obj-$(CONFIG_REGULATOR_DB8500_PRCMU) += db8500-prcmu.o
 obj-$(CONFIG_REGULATOR_GPIO) += gpio-regulator.o
diff --git a/drivers/regulator/da906x-regulator.c b/drivers/regulator/da906x-regulator.c
new file mode 100644
index 0000000..1b68de0
--- /dev/null
+++ b/drivers/regulator/da906x-regulator.c
@@ -0,0 +1,1018 @@
+/*
+ * Regulator driver for DA906x PMIC series
+ *
+ * Copyright 2012 Dialog Semiconductors Ltd.
+ *
+ * Author: Krystian Garbaciak <krystian.garbaciak@diasemi.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.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/mfd/da906x/core.h>
+#include <linux/mfd/da906x/pdata.h>
+#include <linux/mfd/da906x/registers.h>
+
+
+/* Definition for registering bit fields */
+struct bfield {
+	unsigned short	addr;
+	unsigned char	mask;
+};
+#define BFIELD(_addr, _mask) \
+	{ .addr = _addr, .mask = _mask }
+
+struct field {
+	unsigned short	addr;
+	unsigned char	mask;
+	unsigned char	shift;
+	unsigned char	offset;
+};
+#define FIELD(_addr, _mask, _shift, _offset) \
+	{ .addr = _addr, .mask = _mask, .shift = _shift, .offset = _offset }
+
+/* Regulator capabilities and registers description */
+struct da906x_regulator_info {
+	int			id;
+	char			*name;
+	struct regulator_ops	*ops;
+
+	/* Voltage adjust range */
+	int		min_uV;
+	int		max_uV;
+	unsigned	step_uV;
+	unsigned	n_steps;
+
+	/* Current limiting */
+	unsigned	n_current_limits;
+	const int	*current_limits;
+
+	/* DA906x main register fields */
+	struct bfield	enable;		/* bit used to enable regulator,
+					   it returns actual state when read */
+	struct field	mode;		/* buck mode of operation */
+	struct bfield	suspend;
+	struct bfield	sleep;
+	struct bfield	suspend_sleep;
+	struct field	voltage;
+	struct field	suspend_voltage;
+	struct field	ilimit;
+
+	/* DA906x event detection bit */
+	struct bfield	oc_event;
+};
+
+/* Macro for switch regulator */
+#define DA906X_SWITCH(chip, regl_name) \
+	.id = chip##_ID_##regl_name, \
+	.name = __stringify(chip##_##regl_name), \
+	.ops = &da906x_switch_ops, \
+	.n_steps = 0
+
+/* Macros for LDO */
+#define DA906X_LDO(chip, regl_name, min_mV, step_mV, max_mV) \
+	.id = chip##_ID_##regl_name, \
+	.name = __stringify(chip##_##regl_name), \
+	.ops = &da906x_ldo_ops, \
+	.min_uV = (min_mV) * 1000, \
+	.max_uV = (max_mV) * 1000, \
+	.step_uV = (step_mV) * 1000, \
+	.n_steps = (((max_mV) - (min_mV))/(step_mV) + 1)
+
+#define DA906X_LDO_COMMON_FIELDS(regl_name) \
+	.enable = BFIELD(DA906X_REG_##regl_name##_CONT, DA906X_LDO_EN), \
+	.sleep = BFIELD(DA906X_REG_V##regl_name##_A, DA906X_LDO_SL), \
+	.suspend_sleep = BFIELD(DA906X_REG_V##regl_name##_B, DA906X_LDO_SL), \
+	.voltage = FIELD(DA906X_REG_V##regl_name##_A, \
+			DA906X_V##regl_name##_MASK, \
+			DA906X_V##regl_name##_SHIFT, \
+			DA906X_V##regl_name##_BIAS), \
+	.suspend_voltage = FIELD(DA906X_REG_V##regl_name##_B, \
+			DA906X_V##regl_name##_MASK,\
+			DA906X_V##regl_name##_SHIFT, \
+			DA906X_V##regl_name##_BIAS)
+
+/* Macros for voltage DC/DC converters (BUCKs) */
+#define DA906X_BUCK(chip, regl_name, min_mV, step_mV, max_mV, limits_array) \
+	.id = chip##_ID_##regl_name, \
+	.name = __stringify(chip##_##regl_name), \
+	.ops = &da906x_buck_ops, \
+	.min_uV = (min_mV) * 1000, \
+	.max_uV = (max_mV) * 1000, \
+	.step_uV = (step_mV) * 1000, \
+	.n_steps = ((max_mV) - (min_mV))/(step_mV) + 1, \
+	.current_limits = limits_array, \
+	.n_current_limits = ARRAY_SIZE(limits_array)
+
+#define DA906X_BUCK_COMMON_FIELDS(regl_name) \
+	.enable = BFIELD(DA906X_REG_##regl_name##_CONT, DA906X_BUCK_EN), \
+	.sleep = BFIELD(DA906X_REG_V##regl_name##_A, DA906X_BUCK_SL), \
+	.suspend_sleep = BFIELD(DA906X_REG_V##regl_name##_B, DA906X_BUCK_SL), \
+	.voltage = FIELD(DA906X_REG_V##regl_name##_A, \
+			 DA906X_VBUCK_MASK, \
+			 DA906X_VBUCK_SHIFT, \
+			 DA906X_VBUCK_BIAS), \
+	.suspend_voltage = FIELD(DA906X_REG_V##regl_name##_B, \
+				 DA906X_VBUCK_MASK,\
+				 DA906X_VBUCK_SHIFT, \
+				 DA906X_VBUCK_BIAS), \
+	.mode = FIELD(DA906X_REG_##regl_name##_CFG, DA906X_BUCK_MODE_MASK, \
+		      DA906X_BUCK_MODE_SHIFT, 0)
+
+/* Defines asignment of regulators info table to chip model */
+struct da906x_dev_model {
+	const struct da906x_regulator_info	*regulator_info;
+	unsigned				n_regulators;
+	unsigned				dev_model;
+};
+
+/* Single regulator settings */
+struct da906x_regulator {
+	struct regulator_desc			desc;
+	struct regulator_dev			*rdev;
+	struct da906x				*hw;
+	const struct da906x_regulator_info	*info;
+
+	unsigned				mode;
+	unsigned				suspend_mode;
+};
+
+/* Encapsulates all information for the regulators driver */
+struct da906x_regulators {
+	int					irq_ldo_lim;
+	int					irq_uvov;
+
+	unsigned				n_regulators;
+	/* Array size to be defined during init. Keep at end. */
+	struct da906x_regulator			regulator[0];
+};
+
+/* System states for da906x_update_mode_internal()
+   and for da906x_get_mode_internal() */
+enum {
+	SYS_STATE_NORMAL,
+	SYS_STATE_SUSPEND,
+	SYS_STATE_CURRENT
+};
+
+/* BUCK modes for DA906x */
+enum {
+	BUCK_MODE_MANUAL,	/* 0 */
+	BUCK_MODE_SLEEP,	/* 1 */
+	BUCK_MODE_SYNC,		/* 2 */
+	BUCK_MODE_AUTO		/* 3 */
+};
+
+/* Regulator operations */
+static int da906x_set_voltage(struct regulator_dev *rdev, int min_uV,
+			      int max_uV, unsigned *selector);
+static int da906x_get_voltage_sel(struct regulator_dev *rdev);
+static int da906x_set_current_limit(struct regulator_dev *rdev,
+				    int min_uA, int max_uA);
+static int da906x_get_current_limit(struct regulator_dev *rdev);
+static int da906x_enable(struct regulator_dev *rdev);
+static int da906x_set_mode(struct regulator_dev *rdev, unsigned int mode);
+static unsigned da906x_get_mode(struct regulator_dev *rdev);
+static int da906x_get_status(struct regulator_dev *rdev);
+static int da906x_set_suspend_voltage(struct regulator_dev *rdev, int uV);
+static int da906x_suspend_enable(struct regulator_dev *rdev);
+static int da906x_set_suspend_mode(struct regulator_dev *rdev, unsigned mode);
+
+static struct regulator_ops da906x_switch_ops = {
+	.enable			= da906x_enable,
+	.disable		= regulator_disable_regmap,
+	.is_enabled		= regulator_is_enabled_regmap,
+	.set_suspend_enable	= da906x_enable,
+	.set_suspend_disable	= regulator_disable_regmap,
+};
+
+static struct regulator_ops da906x_ldo_ops = {
+	.enable			= da906x_enable,
+	.disable		= regulator_disable_regmap,
+	.is_enabled		= regulator_is_enabled_regmap,
+	.set_voltage		= da906x_set_voltage,
+	.get_voltage_sel	= da906x_get_voltage_sel,
+	.list_voltage		= regulator_list_voltage_linear,
+	.set_mode		= da906x_set_mode,
+	.get_mode		= da906x_get_mode,
+	.get_status		= da906x_get_status,
+	.set_suspend_voltage	= da906x_set_suspend_voltage,
+	.set_suspend_enable	= da906x_suspend_enable,
+	.set_suspend_disable	= regulator_disable_regmap,
+	.set_suspend_mode	= da906x_set_suspend_mode,
+};
+
+static struct regulator_ops da906x_buck_ops = {
+	.enable			= da906x_enable,
+	.disable		= regulator_disable_regmap,
+	.is_enabled		= regulator_is_enabled_regmap,
+	.set_voltage		= da906x_set_voltage,
+	.get_voltage_sel	= da906x_get_voltage_sel,
+	.list_voltage		= regulator_list_voltage_linear,
+	.set_current_limit	= da906x_set_current_limit,
+	.get_current_limit	= da906x_get_current_limit,
+	.set_mode		= da906x_set_mode,
+	.get_mode		= da906x_get_mode,
+	.get_status		= da906x_get_status,
+	.set_suspend_voltage	= da906x_set_suspend_voltage,
+	.set_suspend_enable	= da906x_suspend_enable,
+	.set_suspend_disable	= regulator_disable_regmap,
+	.set_suspend_mode	= da906x_set_suspend_mode,
+};
+
+/* Current limits array (in uA) for BCORE1, BCORE2, BPRO.
+   Entry indexes corresponds to register values. */
+static const int da9063_buck_a_limits[] = {
+	 500000,  600000,  700000,  800000,  900000, 1000000, 1100000, 1200000,
+	1300000, 1400000, 1500000, 1600000, 1700000, 1800000, 1900000, 2000000
+};
+
+/* Current limits array (in uA) for BMEM, BIO, BPERI.
+   Entry indexes corresponds to register values. */
+static const int da9063_buck_b_limits[] = {
+	1500000, 1600000, 1700000, 1800000, 1900000, 2000000, 2100000, 2200000,
+	2300000, 2400000, 2500000, 2600000, 2700000, 2800000, 2900000, 3000000
+};
+
+/* Current limits array (in uA) for merged BCORE1 and BCORE2.
+   Entry indexes corresponds to register values. */
+static const int da9063_bcores_merged_limits[] = {
+	1000000, 1200000, 1400000, 1600000, 1800000, 2000000, 2200000, 2400000,
+	2600000, 2800000, 3000000, 3200000, 3400000, 3600000, 3800000, 4000000
+};
+
+/* Current limits array (in uA) for merged BMEM and BIO.
+   Entry indexes corresponds to register values. */
+static const int da9063_bmem_bio_merged_limits[] = {
+	3000000, 3200000, 3400000, 3600000, 3800000, 4000000, 4200000, 4400000,
+	4600000, 4800000, 5000000, 5200000, 5400000, 5600000, 5800000, 6000000
+};
+
+/* Info of regulators for DA9063 */
+static const struct da906x_regulator_info da9063_regulator_info[] = {
+	{
+		DA906X_BUCK(DA9063, BCORE1, 300, 10, 1570,
+			    da9063_buck_a_limits),
+		DA906X_BUCK_COMMON_FIELDS(BCORE1),
+		.suspend = BFIELD(DA906X_REG_DVC_1, DA906X_VBCORE1_SEL),
+		.ilimit = FIELD(DA906X_REG_BUCK_ILIM_C, DA906X_BCORE1_ILIM_MASK,
+				DA906X_BCORE1_ILIM_SHIFT, 0),
+	},
+	{
+		DA906X_BUCK(DA9063, BCORE2, 300, 10, 1570,
+			    da9063_buck_a_limits),
+		DA906X_BUCK_COMMON_FIELDS(BCORE2),
+		.suspend = BFIELD(DA906X_REG_DVC_1, DA906X_VBCORE2_SEL),
+
+		.ilimit = FIELD(DA906X_REG_BUCK_ILIM_C,
+				DA906X_BCORE2_ILIM_MASK,
+				DA906X_BCORE2_ILIM_SHIFT,
+				0),
+	},
+	{
+		DA906X_BUCK(DA9063, BPRO, 530, 10, 1800,
+			    da9063_buck_a_limits),
+		DA906X_BUCK_COMMON_FIELDS(BPRO),
+		.suspend = BFIELD(DA906X_REG_DVC_1, DA906X_VBPRO_SEL),
+		.ilimit = FIELD(DA906X_REG_BUCK_ILIM_B, DA906X_BPRO_ILIM_MASK,
+				DA906X_BPRO_ILIM_SHIFT, 0),
+	},
+	{
+		DA906X_BUCK(DA9063, BMEM, 800, 20, 3340,
+			    da9063_buck_b_limits),
+		DA906X_BUCK_COMMON_FIELDS(BMEM),
+		.suspend = BFIELD(DA906X_REG_DVC_1, DA906X_VBMEM_SEL),
+		.ilimit = FIELD(DA906X_REG_BUCK_ILIM_A, DA906X_BMEM_ILIM_MASK,
+				DA906X_BMEM_ILIM_SHIFT, 0),
+	},
+	{
+		DA906X_BUCK(DA9063, BIO, 800, 20, 3340,
+			    da9063_buck_b_limits),
+		DA906X_BUCK_COMMON_FIELDS(BIO),
+		.suspend = BFIELD(DA906X_REG_DVC_2, DA906X_VBIO_SEL),
+		.ilimit = FIELD(DA906X_REG_BUCK_ILIM_A, DA906X_BIO_ILIM_MASK,
+				DA906X_BIO_ILIM_SHIFT, 0),
+	},
+	{
+		DA906X_BUCK(DA9063, BPERI, 800, 20, 3340,
+			    da9063_buck_b_limits),
+		DA906X_BUCK_COMMON_FIELDS(BPERI),
+		.suspend = BFIELD(DA906X_REG_DVC_1, DA906X_VBPERI_SEL),
+		.ilimit = FIELD(DA906X_REG_BUCK_ILIM_B, DA906X_BPERI_ILIM_MASK,
+				DA906X_BPERI_ILIM_SHIFT, 0),
+	},
+	{
+		DA906X_BUCK(DA9063, BCORES_MERGED, 300, 10, 1570,
+			    da9063_bcores_merged_limits),
+		/* BCORES_MERGED uses the same register fields as BCORE1 */
+		DA906X_BUCK_COMMON_FIELDS(BCORE1),
+		.suspend = BFIELD(DA906X_REG_DVC_1, DA906X_VBCORE1_SEL),
+		.ilimit = FIELD(DA906X_REG_BUCK_ILIM_C, DA906X_BCORE1_ILIM_MASK,
+				DA906X_BCORE1_ILIM_SHIFT, 0),
+	},
+	{
+		DA906X_BUCK(DA9063, BMEM_BIO_MERGED, 800, 20, 3340,
+			    da9063_bmem_bio_merged_limits),
+		/* BMEM_BIO_MERGED uses the same register fields as BMEM */
+		DA906X_BUCK_COMMON_FIELDS(BMEM),
+		.suspend = BFIELD(DA906X_REG_DVC_1, DA906X_VBMEM_SEL),
+		.ilimit = FIELD(DA906X_REG_BUCK_ILIM_A, DA906X_BMEM_ILIM_MASK,
+				DA906X_BMEM_ILIM_SHIFT, 0),
+	},
+	{
+		DA906X_LDO(DA9063, LDO1, 600, 20, 1860),
+		DA906X_LDO_COMMON_FIELDS(LDO1),
+		.suspend = BFIELD(DA906X_REG_DVC_1, DA906X_VLDO1_SEL),
+	},
+	{
+		DA906X_LDO(DA9063, LDO2, 600, 20, 1860),
+		DA906X_LDO_COMMON_FIELDS(LDO2),
+		.suspend = BFIELD(DA906X_REG_DVC_1, DA906X_VLDO2_SEL),
+	},
+	{
+		DA906X_LDO(DA9063, LDO3, 900, 20, 3440),
+		DA906X_LDO_COMMON_FIELDS(LDO3),
+		.suspend = BFIELD(DA906X_REG_DVC_1, DA906X_VLDO3_SEL),
+		.oc_event = BFIELD(DA906X_REG_STATUS_D, DA906X_LDO3_LIM),
+	},
+	{
+		DA906X_LDO(DA9063, LDO4, 900, 20, 3440),
+		DA906X_LDO_COMMON_FIELDS(LDO4),
+		.suspend = BFIELD(DA906X_REG_DVC_2, DA906X_VLDO4_SEL),
+		.oc_event = BFIELD(DA906X_REG_STATUS_D, DA906X_LDO4_LIM),
+	},
+	{
+		DA906X_LDO(DA9063, LDO5, 900, 50, 3600),
+		DA906X_LDO_COMMON_FIELDS(LDO5),
+		.suspend = BFIELD(DA906X_REG_LDO5_CONT, DA906X_VLDO5_SEL),
+	},
+	{
+		DA906X_LDO(DA9063, LDO6, 900, 50, 3600),
+		DA906X_LDO_COMMON_FIELDS(LDO6),
+		.suspend = BFIELD(DA906X_REG_LDO6_CONT, DA906X_VLDO6_SEL),
+	},
+	{
+		DA906X_LDO(DA9063, LDO7, 900, 50, 3600),
+		DA906X_LDO_COMMON_FIELDS(LDO7),
+		.suspend = BFIELD(DA906X_REG_LDO7_CONT, DA906X_VLDO7_SEL),
+		.oc_event = BFIELD(DA906X_REG_STATUS_D, DA906X_LDO7_LIM),
+	},
+	{
+		DA906X_LDO(DA9063, LDO8, 900, 50, 3600),
+		DA906X_LDO_COMMON_FIELDS(LDO8),
+		.suspend = BFIELD(DA906X_REG_LDO8_CONT, DA906X_VLDO8_SEL),
+		.oc_event = BFIELD(DA906X_REG_STATUS_D, DA906X_LDO8_LIM),
+	},
+	{
+		DA906X_LDO(DA9063, LDO9, 950, 50, 3600),
+		DA906X_LDO_COMMON_FIELDS(LDO9),
+		.suspend = BFIELD(DA906X_REG_LDO9_CONT, DA906X_VLDO9_SEL),
+	},
+	{
+		DA906X_LDO(DA9063, LDO10, 900, 50, 3600),
+		DA906X_LDO_COMMON_FIELDS(LDO10),
+		.suspend = BFIELD(DA906X_REG_LDO10_CONT, DA906X_VLDO10_SEL),
+	},
+	{
+		DA906X_LDO(DA9063, LDO11, 900, 50, 3600),
+		DA906X_LDO_COMMON_FIELDS(LDO11),
+		.suspend = BFIELD(DA906X_REG_LDO11_CONT, DA906X_VLDO11_SEL),
+		.oc_event = BFIELD(DA906X_REG_STATUS_D, DA906X_LDO11_LIM),
+	},
+	{
+		DA906X_SWITCH(DA9063, 32K_OUT),
+		.enable = BFIELD(DA906X_REG_EN_32K, DA906X_OUT_32K_EN),
+	},
+};
+
+/* Link chip model with regulators info table */
+static struct da906x_dev_model regulators_models[] = {
+	{
+		.regulator_info = da9063_regulator_info,
+		.n_regulators = ARRAY_SIZE(da9063_regulator_info),
+		.dev_model = PMIC_DA9063,
+	},
+	{NULL, 0, 0}	/* End of list */
+};
+
+
+/*
+ * Regulator internal functions
+ */
+static int da906x_update_mode_internal(struct da906x_regulator *regl,
+				       int sys_state)
+{
+	const struct da906x_regulator_info *rinfo = regl->info;
+	unsigned val;
+	unsigned mode;
+	int ret;
+
+	if (sys_state == SYS_STATE_SUSPEND)
+		/* Get mode for regulator in suspend state */
+		mode = regl->suspend_mode;
+	else
+		/* Get mode for regulator in normal operation */
+		mode = regl->mode;
+
+	/* LDOs use sleep flags - one for normal and one for suspend state.
+	   For BUCKs single mode register field is used in normal and
+	   suspend state. */
+	if (rinfo->mode.addr) {
+		/* Set mode for BUCK - 3 modes are supported */
+		switch (mode) {
+		case REGULATOR_MODE_FAST:
+			val = BUCK_MODE_SYNC;
+			break;
+		case REGULATOR_MODE_NORMAL:
+			val = BUCK_MODE_AUTO;
+			break;
+		case REGULATOR_MODE_STANDBY:
+			val = BUCK_MODE_SLEEP;
+			break;
+		default:
+			return -EINVAL;
+		}
+		val = val << rinfo->mode.shift;
+
+		ret = da906x_reg_update(regl->hw, rinfo->mode.addr,
+					rinfo->mode.mask, val);
+	} else {
+		/* Set mode for LDO - 2 modes are supported */
+		switch (mode) {
+		case REGULATOR_MODE_NORMAL:
+			val = 0;
+			break;
+		case REGULATOR_MODE_STANDBY:
+			val = DA906X_LDO_SL;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		if (sys_state == SYS_STATE_SUSPEND) {
+			if (!rinfo->suspend_sleep.addr)
+				return -EINVAL;
+			ret = da906x_reg_update(regl->hw,
+						rinfo->suspend_sleep.addr,
+						rinfo->suspend_sleep.mask,
+						val);
+		} else {
+			if (!rinfo->sleep.addr)
+				return -EINVAL;
+			ret = da906x_reg_update(regl->hw,
+						rinfo->sleep.addr,
+						rinfo->sleep.mask, val);
+		}
+	}
+
+	return ret;
+}
+
+static unsigned da906x_get_mode_internal(struct da906x_regulator *regl,
+					 int sys_state)
+{
+	const struct da906x_regulator_info *rinfo = regl->info;
+	int val;
+	int addr;
+	int mask;
+	unsigned mode = 0;
+
+	/* Bucks use single mode register field for normal operation
+	   and suspend state. LDOs use sleep flags - one for normal
+	   and one for suspend state. */
+	if (rinfo->mode.addr) {
+		/* For BUCKs, there are 3 modes to map to */
+		val = da906x_reg_read(regl->hw, rinfo->mode.addr);
+		if (val < 0)
+			return val;
+
+		val = (val & rinfo->mode.mask) >> rinfo->mode.shift;
+		switch (val) {
+		default:
+		case BUCK_MODE_MANUAL:
+			mode = REGULATOR_MODE_FAST | REGULATOR_MODE_STANDBY;
+			/* Sleep flag bit decides the mode */
+			break;
+		case BUCK_MODE_SLEEP:
+			return REGULATOR_MODE_STANDBY;
+		case BUCK_MODE_SYNC:
+			return REGULATOR_MODE_FAST;
+		case BUCK_MODE_AUTO:
+			return REGULATOR_MODE_NORMAL;
+		}
+	} else if (rinfo->sleep.addr) {
+		/* For LDOs there are 2 modes to map to */
+		mode = REGULATOR_MODE_NORMAL | REGULATOR_MODE_STANDBY;
+		/* Sleep flag bit decides the mode */
+	} else {
+		/* No support */
+		return 0;
+	}
+
+	/* If sys_state == SYS_STATE_CURRENT, current regulator state
+	   is detected. */
+	if (sys_state == SYS_STATE_CURRENT && rinfo->suspend.addr) {
+		val = da906x_reg_read(regl->hw,
+					  rinfo->suspend.addr);
+		if (val < 0)
+			return val;
+
+		if (val & rinfo->suspend.mask)
+			sys_state = SYS_STATE_SUSPEND;
+		else
+			sys_state = SYS_STATE_NORMAL;
+	}
+
+	/* Read regulator mode from proper register,
+	   depending on selected system state */
+	if (sys_state == SYS_STATE_SUSPEND && rinfo->suspend_sleep.addr) {
+		addr = rinfo->suspend_sleep.addr;
+		mask = rinfo->suspend_sleep.mask;
+	} else {
+		addr = rinfo->sleep.addr;
+		mask = rinfo->sleep.mask;
+	}
+
+	val = da906x_reg_read(regl->hw, addr);
+	if (val < 0)
+		return val;
+
+	if (val & mask)
+		mode &= REGULATOR_MODE_STANDBY;
+	else
+		mode &= REGULATOR_MODE_NORMAL | REGULATOR_MODE_FAST;
+
+	return mode;
+}
+
+static int da906x_set_voltage(struct regulator_dev *rdev,
+				int min_uV, int max_uV, unsigned *selector)
+{
+	struct da906x_regulator *regl = rdev_get_drvdata(rdev);
+	const struct field *fvol = &regl->info->voltage;
+	int ret;
+	unsigned val;
+
+	val = regulator_map_voltage_linear(rdev, min_uV, max_uV);
+	if (val < 0)
+		return -EINVAL;
+
+	val = (val + fvol->offset) << fvol->shift;
+	ret = da906x_reg_update(regl->hw, fvol->addr, fvol->mask, val);
+	if (ret >= 0)
+		*selector = val;
+
+	return ret;
+}
+
+static int da906x_get_voltage_sel(struct regulator_dev *rdev)
+{
+	struct da906x_regulator *regl = rdev_get_drvdata(rdev);
+	const struct da906x_regulator_info *rinfo = regl->info;
+	int sel;
+
+	sel = da906x_reg_read(regl->hw, rinfo->voltage.addr);
+	if (sel < 0)
+		return sel;
+
+	sel = (sel & rinfo->voltage.mask) >> rinfo->voltage.shift;
+	sel -= rinfo->voltage.offset;
+	if (sel < 0)
+		sel = 0;
+	if (sel >= rinfo->n_steps)
+		sel = rinfo->n_steps - 1;
+
+	return sel;
+}
+
+static int da906x_set_current_limit(struct regulator_dev *rdev,
+							int min_uA, int max_uA)
+{
+	struct da906x_regulator *regl = rdev_get_drvdata(rdev);
+	const struct da906x_regulator_info *rinfo = regl->info;
+	int val = INT_MAX;
+	unsigned sel = 0;
+	int n;
+	int tval;
+
+	if (!rinfo->current_limits)
+		return -EINVAL;
+
+	for (n = 0; n < rinfo->n_current_limits; n++) {
+		tval = rinfo->current_limits[n];
+		if (tval >= min_uA && tval <= max_uA && val > tval) {
+			val = tval;
+			sel = n;
+		}
+	}
+	if (val == INT_MAX)
+		return -EINVAL;
+
+	sel = (sel + rinfo->ilimit.offset) << rinfo->ilimit.shift;
+	return da906x_reg_update(regl->hw, rinfo->ilimit.addr,
+						rinfo->ilimit.mask, sel);
+}
+
+static int da906x_get_current_limit(struct regulator_dev *rdev)
+{
+	struct da906x_regulator *regl = rdev_get_drvdata(rdev);
+	const struct da906x_regulator_info *rinfo = regl->info;
+	int sel;
+
+	sel = da906x_reg_read(regl->hw, rinfo->ilimit.addr);
+	if (sel < 0)
+		return sel;
+
+	sel = (sel & rinfo->ilimit.mask) >> rinfo->ilimit.shift;
+	sel -= rinfo->ilimit.offset;
+	if (sel < 0)
+		sel = 0;
+	if (sel >= rinfo->n_current_limits)
+		sel = rinfo->n_current_limits - 1;
+
+	return rinfo->current_limits[sel];
+}
+
+static int da906x_enable(struct regulator_dev *rdev)
+{
+	struct da906x_regulator *regl = rdev_get_drvdata(rdev);
+	int ret;
+
+	if (regl->info->suspend.mask) {
+		/* Make sure to exit from suspend mode on enable */
+		ret = da906x_reg_clear_bits(regl->hw, regl->info->suspend.addr,
+					    regl->info->suspend.mask);
+		if (ret < 0)
+			return ret;
+
+		/* BUCKs need mode update after wake-up from suspend state. */
+		ret = da906x_update_mode_internal(regl, SYS_STATE_NORMAL);
+		if (ret < 0)
+			return ret;
+	}
+
+	return regulator_enable_regmap(rdev);
+}
+
+static int da906x_set_mode(struct regulator_dev *rdev, unsigned mode)
+{
+	struct da906x_regulator *regl = rdev_get_drvdata(rdev);
+	unsigned orig_mode = regl->mode;
+	int ret;
+
+	regl->mode = mode;
+	ret = da906x_update_mode_internal(regl, SYS_STATE_NORMAL);
+	if (ret)
+		regl->mode = orig_mode;
+
+	return ret;
+}
+
+static unsigned da906x_get_mode(struct regulator_dev *rdev)
+{
+	struct da906x_regulator *regl = rdev_get_drvdata(rdev);
+
+	return da906x_get_mode_internal(regl, SYS_STATE_CURRENT);
+}
+
+static int da906x_get_status(struct regulator_dev *rdev)
+{
+	int ret = regulator_is_enabled_regmap(rdev);
+
+	if (ret == 0) {
+		ret = REGULATOR_STATUS_OFF;
+	} else if (ret > 0) {
+		ret = da906x_get_mode(rdev);
+		if (ret > 0)
+			ret = regulator_mode_to_status(ret);
+		else if (ret == 0)
+			ret = -EIO;
+	}
+
+	return ret;
+}
+
+static int da906x_set_suspend_voltage(struct regulator_dev *rdev, int uV)
+{
+	struct da906x_regulator *regl = rdev_get_drvdata(rdev);
+	const struct da906x_regulator_info *rinfo = regl->info;
+	const struct field *fsusvol = &rinfo->suspend_voltage;
+	int val;
+	int ret;
+
+	val = regulator_map_voltage_linear(rdev, uV, uV);
+	if (val < 0)
+		return -EINVAL;
+
+	val = (val + fsusvol->offset) << fsusvol->shift;
+	ret = da906x_reg_update(regl->hw, fsusvol->addr, fsusvol->mask, val);
+
+	return ret;
+}
+
+static int da906x_suspend_enable(struct regulator_dev *rdev)
+{
+	int ret;
+	struct da906x_regulator *regl = rdev_get_drvdata(rdev);
+	const struct bfield *bsuspend = &regl->info->suspend;
+
+	ret = da906x_reg_set_bits(regl->hw, bsuspend->addr, bsuspend->mask);
+	return ret;
+}
+
+static int da906x_set_suspend_mode(struct regulator_dev *rdev, unsigned mode)
+{
+	struct da906x_regulator *regl = rdev_get_drvdata(rdev);
+	int ret;
+
+	regl->suspend_mode = mode;
+	ret = da906x_update_mode_internal(regl, SYS_STATE_SUSPEND);
+	return ret;
+}
+
+/* Regulator event handlers */
+irqreturn_t da906x_ldo_lim_event(int irq, void *data)
+{
+	struct da906x_regulators *regulators = data;
+	struct da906x *hw = regulators->regulator[0].hw;
+	struct da906x_regulator *regl;
+	int bits;
+	int i;
+
+	bits = da906x_reg_read(hw, DA906X_REG_STATUS_D);
+	if (bits < 0)
+		return IRQ_HANDLED;
+
+	for (i = regulators->n_regulators - 1; i >= 0; i--) {
+		regl = &regulators->regulator[i];
+		if (regl->info->oc_event.addr != DA906X_REG_STATUS_D)
+			continue;
+
+		if (regl->info->oc_event.mask & bits)
+			regulator_notifier_call_chain(regl->rdev,
+					REGULATOR_EVENT_OVER_CURRENT, NULL);
+	}
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Probing and Initialisation functions
+ */
+static __devinit const struct da906x_regulator_info *
+da906x_get_regl_info(const struct da906x *da906x,
+		     const struct da906x_dev_model *model, int id)
+{
+	int m;
+
+	for (m = model->n_regulators - 1;
+	     model->regulator_info[m].id != id; m--) {
+		if (m <= 0)
+			return NULL;
+	}
+	return &model->regulator_info[m];
+}
+
+static __devinit int da906x_regulator_probe(struct platform_device *pdev)
+{
+	struct da906x *da906x = dev_get_drvdata(pdev->dev.parent);
+	struct da906x_pdata *da906x_pdata = dev_get_platdata(da906x->dev);
+	struct da906x_regulators_pdata *regl_pdata;
+	struct da906x_regulator_data *rdata;
+	const struct da906x_dev_model *model;
+	struct da906x_regulators *regulators;
+	struct da906x_regulator *regl;
+	struct regulator_config config;
+	bool bcores_merged, bmem_bio_merged;
+	size_t size;
+	int n;
+	int ret;
+
+	if (!da906x_pdata) {
+		dev_err(&pdev->dev, "No platform init data supplied\n");
+		return -ENODEV;
+	}
+	regl_pdata = da906x_pdata->regulators_pdata;
+
+	if (!regl_pdata || regl_pdata->n_regulators == 0) {
+		dev_err(&pdev->dev,
+			"No regulators defined for the platform\n");
+		return -ENODEV;
+	}
+
+	/* Find regulators set for particular device model */
+	for (model = regulators_models; model->regulator_info; model++) {
+		if (model->dev_model == da906x_model(da906x))
+			break;
+	}
+	if (!model->regulator_info) {
+		dev_err(&pdev->dev, "Chip model not recognised (%u)\n",
+			da906x_model(da906x));
+		return -ENODEV;
+	}
+
+	ret = da906x_reg_read(da906x, DA906X_REG_CONFIG_H);
+	if (ret < 0) {
+		dev_err(&pdev->dev,
+			"Error while reading BUCKs configuration\n");
+		return -EIO;
+	}
+	bcores_merged = (ret & DA906X_BCORE_MERGE) ? true : false;
+	bmem_bio_merged = (ret & DA906X_BUCK_MERGE) ? true : false;
+
+	/* Allocate memory required by usable regulators */
+	size = sizeof(struct da906x_regulators) +
+		regl_pdata->n_regulators * sizeof(struct da906x_regulator);
+	regulators = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
+	if (!regulators) {
+		dev_err(&pdev->dev, "No memory for regulators\n");
+		return -ENOMEM;
+	}
+
+	regulators->n_regulators = regl_pdata->n_regulators;
+	platform_set_drvdata(pdev, regulators);
+
+	/* Register all regulators declared in platform information */
+	n = 0;
+	while (n < regulators->n_regulators) {
+		rdata = &regl_pdata->regulator_data[n];
+
+		/* Check regulator ID against merge mode configuration */
+		switch (rdata->id) {
+		case DA9063_ID_BCORE1:
+		case DA9063_ID_BCORE2:
+			if (bcores_merged) {
+				dev_err(&pdev->dev,
+					"Cannot use BCORE1 and BCORE2 separately, when in merge mode\n");
+				ret = -EINVAL;
+				goto err;
+			}
+			break;
+		case DA9063_ID_BMEM:
+		case DA9063_ID_BIO:
+			if (bmem_bio_merged) {
+				dev_err(&pdev->dev,
+					"Cannot use BMEM and BIO separately, when in merge mode\n");
+				ret = -EINVAL;
+				goto err;
+			}
+			break;
+		case DA9063_ID_BCORES_MERGED:
+			if (!bcores_merged) {
+				dev_err(&pdev->dev,
+					"BCORE1 and BCORE2 are unavailable in merge mode\n");
+				ret = -EINVAL;
+				goto err;
+			}
+			break;
+		case DA9063_ID_BMEM_BIO_MERGED:
+			if (!bmem_bio_merged) {
+				dev_err(&pdev->dev,
+					"BMEM and BIO are unavailable in merge mode\n");
+				ret = -EINVAL;
+				goto err;
+			}
+			break;
+		}
+
+		/* Initialise regulator structure */
+		regl = &regulators->regulator[n];
+		regl->hw = da906x;
+		regl->info = da906x_get_regl_info(da906x, model, rdata->id);
+		if (!regl->info) {
+			dev_err(&pdev->dev,
+				"Invalid regulator ID in platform data\n");
+			ret = -EINVAL;
+			goto err;
+		}
+		regl->desc.type = REGULATOR_VOLTAGE;
+		regl->desc.owner = THIS_MODULE;
+		regl->desc.name = regl->info->name;
+		regl->desc.id = rdata->id;
+		regl->desc.ops = regl->info->ops;
+		regl->desc.n_voltages = regl->info->n_steps;
+		regl->desc.min_uV = regl->info->min_uV;
+		regl->desc.uV_step = regl->info->step_uV;
+		regl->desc.enable_reg = regl->info->enable.addr +
+					DA906X_MAPPING_BASE;
+		regl->desc.enable_mask = regl->info->enable.mask;
+
+		/* Register regulator */
+		config.dev = &pdev->dev;
+		config.init_data = rdata->initdata;
+		config.driver_data = regl;
+		config.regmap = da906x->regmap;
+		regl->rdev = regulator_register(&regl->desc, &config);
+		if (IS_ERR_OR_NULL(regl->rdev)) {
+			dev_err(&pdev->dev,
+				"Failed to register %s regulator\n",
+				regl->info->name);
+			ret = PTR_ERR(regl->rdev);
+			goto err;
+		}
+		n++;
+
+		/* Get current modes of operation (A/B voltage selection)
+		   for normal and suspend states */
+		ret = da906x_get_mode_internal(regl, SYS_STATE_NORMAL);
+		if (ret < 0) {
+			dev_err(&pdev->dev,
+				"Failed to read %s regulator's mode\n",
+				regl->info->name);
+			goto err;
+		}
+		if (ret == 0)
+			regl->mode = REGULATOR_MODE_NORMAL;
+		else
+			regl->mode = ret;
+
+		ret = da906x_get_mode_internal(regl, SYS_STATE_SUSPEND);
+		if (ret < 0) {
+			dev_err(&pdev->dev,
+				"Failed to read %s regulator's mode\n",
+				regl->info->name);
+			goto err;
+		}
+		if (ret == 0)
+			regl->suspend_mode = REGULATOR_MODE_NORMAL;
+		else
+			regl->mode = ret;
+	}
+
+	/* LDOs overcurrent event support */
+	regulators->irq_ldo_lim = platform_get_irq_byname(pdev, "LDO_LIM");
+	if (regulators->irq_ldo_lim >= 0) {
+		ret = request_threaded_irq(regulators->irq_ldo_lim,
+					   NULL, da906x_ldo_lim_event,
+					   IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					   "LDO_LIM", regulators);
+		if (ret) {
+			dev_err(&pdev->dev,
+					"Failed to request LDO_LIM IRQ.\n");
+			regulators->irq_ldo_lim = -ENXIO;
+		}
+	}
+
+	return 0;
+
+err:
+	/* Wind back regulators registeration */
+	while (--n >= 0) {
+		regulator_unregister(regulators->regulator[n].rdev);
+	}
+
+	return ret;
+}
+
+static int __devexit da906x_regulator_remove(struct platform_device *pdev)
+{
+	struct da906x_regulators *regulators = platform_get_drvdata(pdev);
+	struct da906x_regulator *regl;
+
+	free_irq(regulators->irq_ldo_lim, regulators);
+	free_irq(regulators->irq_uvov, regulators);
+
+	for (regl = &regulators->regulator[regulators->n_regulators - 1];
+	     regl >= &regulators->regulator[0]; regl--)
+		regulator_unregister(regl->rdev);
+
+	return 0;
+}
+
+static struct platform_driver da906x_regulator_driver = {
+	.driver = {
+		.name = DA906X_DRVNAME_REGULATORS,
+		.owner = THIS_MODULE,
+	},
+	.probe = da906x_regulator_probe,
+	.remove = __devexit_p(da906x_regulator_remove),
+};
+
+static int __init da906x_regulator_init(void)
+{
+	return platform_driver_register(&da906x_regulator_driver);
+}
+subsys_initcall(da906x_regulator_init);
+
+static void __exit da906x_regulator_cleanup(void)
+{
+	platform_driver_unregister(&da906x_regulator_driver);
+}
+module_exit(da906x_regulator_cleanup);
+
+
+/* Module information */
+MODULE_AUTHOR("Krystian Garbaciak <krystian.garbaciak@diasemi.com>");
+MODULE_DESCRIPTION("DA906x regulators driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("paltform:" DA906X_DRVNAME_REGULATORS);
-- 
1.7.0.4


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

WARNING: multiple messages have this Message-ID (diff)
From: Krystian Garbaciak <krystian.garbaciak@diasemi.com>
To: linux-kernel@vger.kernel.org, rtc-linux@googlegroups.com,
	lm-sensors@lm-sensors.org, linux-input@vger.kernel.org,
	linux-watchdog@vger.kernel.org, linux-leds@vger.kernel.org
Cc: Alessandro Zummo <a.zummo@towertech.it>,
	Andrew Jones <drjones@redhat.com>,
	Dmitry Torokhov <dmitry.torokhov@gmail.com>,
	Samuel Ortiz <sameo@linux.intel.com>,
	Ashish Jangam <ashish.jangam@kpitcummins.com>,
	Mark Brown <broonie@opensource.wolfsonmicro.com>,
	Donggeun Kim <dg77.kim@samsung.com>,
	Wim Van Sebroeck <wim@iguana.be>,
	"Richard Purdie <rpurdie@rpsys.net> Anthony Olech"
	<anthony.olech@diasemi.com>, Bryan Wu <bryan.wu@canonical.com>,
	Liam Girdwood <lrg@ti.com>
Subject: [lm-sensors] [RFC PATCH 2/8] regulator: Add Dialog DA906x voltage regulators support.
Date: Fri, 24 Aug 2012 13:55:00 +0000	[thread overview]
Message-ID: <201208241455@sw-eng-lt-dc-vm2> (raw)
In-Reply-To: <201208241450@sw-eng-lt-dc-vm2>

The driver adds support for the following DA9063 PMIC regulators:
 - 11x LDOs (named LDO1 - LDO11),
 - 6x buck converters (BCORE1, BCORE2, BPRO, BMEM, BIO, BPERI),
 - 1x power switch to switch on/off 32 KHz oscilator output (32K_OUT).

Regulators provide following operations:
 - REGULATOR_CHANGE_STATUS for all regulators,
 - REGULATOR_CHANGE_VOLTAGE for LDOs and buck converters,
 - REGULATOR_CHANGE_MODE for LDOs and buck converters, where:
     - LDOs allow REGULATOR_MODE_NORMAL and REGULATOR_MODE_STANDBY,
     - buck converters allow REGULATOR_MODE_FAST, REGULATOR_MODE_NORMAL
       and REGULATOR_MODE_STANDBY,
 - REGULATOR_CHANGE_CURRENT for buck converters (current limits).

The driver generates REGULATOR_EVENT_OVER_CURRENT for LDO3, LDO4, LDO7, LDO8
and LDO11.

Internally, PMIC provides two voltage configurations for normal and suspend
system state for each regulator. The driver switches between those on
suspend/wake-up to provide quick and fluent output voltage change.

This driver requires MFD core driver for operation.

Signed-off-by: Krystian Garbaciak <krystian.garbaciak@diasemi.com>
---
 drivers/regulator/Kconfig            |    6 +
 drivers/regulator/Makefile           |    1 +
 drivers/regulator/da906x-regulator.c | 1018 ++++++++++++++++++++++++++++++++++
 3 files changed, 1025 insertions(+), 0 deletions(-)
 create mode 100644 drivers/regulator/da906x-regulator.c

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 4e932cc..b57b6c6 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -110,6 +110,12 @@ config REGULATOR_DA9052
 	  This driver supports the voltage regulators of DA9052-BC and
 	  DA9053-AA/Bx PMIC.
 
+config REGULATOR_DA906X
+	bool "Dialog DA906X Regulator family chip"
+	depends on MFD_DA906X
+	help
+	  Support for Dialog Semiconductor DA906x chip.
+
 config REGULATOR_ANATOP
 	tristate "Freescale i.MX on-chip ANATOP LDO regulators"
 	depends on MFD_ANATOP
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 3342615..46a503a 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_REGULATOR_ANATOP) += anatop-regulator.o
 obj-$(CONFIG_REGULATOR_ARIZONA) += arizona-micsupp.o arizona-ldo1.o
 obj-$(CONFIG_REGULATOR_DA903X)	+= da903x.o
 obj-$(CONFIG_REGULATOR_DA9052)	+= da9052-regulator.o
+obj-$(CONFIG_REGULATOR_DA906X) += da906x-regulator.o
 obj-$(CONFIG_REGULATOR_DBX500_PRCMU) += dbx500-prcmu.o
 obj-$(CONFIG_REGULATOR_DB8500_PRCMU) += db8500-prcmu.o
 obj-$(CONFIG_REGULATOR_GPIO) += gpio-regulator.o
diff --git a/drivers/regulator/da906x-regulator.c b/drivers/regulator/da906x-regulator.c
new file mode 100644
index 0000000..1b68de0
--- /dev/null
+++ b/drivers/regulator/da906x-regulator.c
@@ -0,0 +1,1018 @@
+/*
+ * Regulator driver for DA906x PMIC series
+ *
+ * Copyright 2012 Dialog Semiconductors Ltd.
+ *
+ * Author: Krystian Garbaciak <krystian.garbaciak@diasemi.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.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/mfd/da906x/core.h>
+#include <linux/mfd/da906x/pdata.h>
+#include <linux/mfd/da906x/registers.h>
+
+
+/* Definition for registering bit fields */
+struct bfield {
+	unsigned short	addr;
+	unsigned char	mask;
+};
+#define BFIELD(_addr, _mask) \
+	{ .addr = _addr, .mask = _mask }
+
+struct field {
+	unsigned short	addr;
+	unsigned char	mask;
+	unsigned char	shift;
+	unsigned char	offset;
+};
+#define FIELD(_addr, _mask, _shift, _offset) \
+	{ .addr = _addr, .mask = _mask, .shift = _shift, .offset = _offset }
+
+/* Regulator capabilities and registers description */
+struct da906x_regulator_info {
+	int			id;
+	char			*name;
+	struct regulator_ops	*ops;
+
+	/* Voltage adjust range */
+	int		min_uV;
+	int		max_uV;
+	unsigned	step_uV;
+	unsigned	n_steps;
+
+	/* Current limiting */
+	unsigned	n_current_limits;
+	const int	*current_limits;
+
+	/* DA906x main register fields */
+	struct bfield	enable;		/* bit used to enable regulator,
+					   it returns actual state when read */
+	struct field	mode;		/* buck mode of operation */
+	struct bfield	suspend;
+	struct bfield	sleep;
+	struct bfield	suspend_sleep;
+	struct field	voltage;
+	struct field	suspend_voltage;
+	struct field	ilimit;
+
+	/* DA906x event detection bit */
+	struct bfield	oc_event;
+};
+
+/* Macro for switch regulator */
+#define DA906X_SWITCH(chip, regl_name) \
+	.id = chip##_ID_##regl_name, \
+	.name = __stringify(chip##_##regl_name), \
+	.ops = &da906x_switch_ops, \
+	.n_steps = 0
+
+/* Macros for LDO */
+#define DA906X_LDO(chip, regl_name, min_mV, step_mV, max_mV) \
+	.id = chip##_ID_##regl_name, \
+	.name = __stringify(chip##_##regl_name), \
+	.ops = &da906x_ldo_ops, \
+	.min_uV = (min_mV) * 1000, \
+	.max_uV = (max_mV) * 1000, \
+	.step_uV = (step_mV) * 1000, \
+	.n_steps = (((max_mV) - (min_mV))/(step_mV) + 1)
+
+#define DA906X_LDO_COMMON_FIELDS(regl_name) \
+	.enable = BFIELD(DA906X_REG_##regl_name##_CONT, DA906X_LDO_EN), \
+	.sleep = BFIELD(DA906X_REG_V##regl_name##_A, DA906X_LDO_SL), \
+	.suspend_sleep = BFIELD(DA906X_REG_V##regl_name##_B, DA906X_LDO_SL), \
+	.voltage = FIELD(DA906X_REG_V##regl_name##_A, \
+			DA906X_V##regl_name##_MASK, \
+			DA906X_V##regl_name##_SHIFT, \
+			DA906X_V##regl_name##_BIAS), \
+	.suspend_voltage = FIELD(DA906X_REG_V##regl_name##_B, \
+			DA906X_V##regl_name##_MASK,\
+			DA906X_V##regl_name##_SHIFT, \
+			DA906X_V##regl_name##_BIAS)
+
+/* Macros for voltage DC/DC converters (BUCKs) */
+#define DA906X_BUCK(chip, regl_name, min_mV, step_mV, max_mV, limits_array) \
+	.id = chip##_ID_##regl_name, \
+	.name = __stringify(chip##_##regl_name), \
+	.ops = &da906x_buck_ops, \
+	.min_uV = (min_mV) * 1000, \
+	.max_uV = (max_mV) * 1000, \
+	.step_uV = (step_mV) * 1000, \
+	.n_steps = ((max_mV) - (min_mV))/(step_mV) + 1, \
+	.current_limits = limits_array, \
+	.n_current_limits = ARRAY_SIZE(limits_array)
+
+#define DA906X_BUCK_COMMON_FIELDS(regl_name) \
+	.enable = BFIELD(DA906X_REG_##regl_name##_CONT, DA906X_BUCK_EN), \
+	.sleep = BFIELD(DA906X_REG_V##regl_name##_A, DA906X_BUCK_SL), \
+	.suspend_sleep = BFIELD(DA906X_REG_V##regl_name##_B, DA906X_BUCK_SL), \
+	.voltage = FIELD(DA906X_REG_V##regl_name##_A, \
+			 DA906X_VBUCK_MASK, \
+			 DA906X_VBUCK_SHIFT, \
+			 DA906X_VBUCK_BIAS), \
+	.suspend_voltage = FIELD(DA906X_REG_V##regl_name##_B, \
+				 DA906X_VBUCK_MASK,\
+				 DA906X_VBUCK_SHIFT, \
+				 DA906X_VBUCK_BIAS), \
+	.mode = FIELD(DA906X_REG_##regl_name##_CFG, DA906X_BUCK_MODE_MASK, \
+		      DA906X_BUCK_MODE_SHIFT, 0)
+
+/* Defines asignment of regulators info table to chip model */
+struct da906x_dev_model {
+	const struct da906x_regulator_info	*regulator_info;
+	unsigned				n_regulators;
+	unsigned				dev_model;
+};
+
+/* Single regulator settings */
+struct da906x_regulator {
+	struct regulator_desc			desc;
+	struct regulator_dev			*rdev;
+	struct da906x				*hw;
+	const struct da906x_regulator_info	*info;
+
+	unsigned				mode;
+	unsigned				suspend_mode;
+};
+
+/* Encapsulates all information for the regulators driver */
+struct da906x_regulators {
+	int					irq_ldo_lim;
+	int					irq_uvov;
+
+	unsigned				n_regulators;
+	/* Array size to be defined during init. Keep at end. */
+	struct da906x_regulator			regulator[0];
+};
+
+/* System states for da906x_update_mode_internal()
+   and for da906x_get_mode_internal() */
+enum {
+	SYS_STATE_NORMAL,
+	SYS_STATE_SUSPEND,
+	SYS_STATE_CURRENT
+};
+
+/* BUCK modes for DA906x */
+enum {
+	BUCK_MODE_MANUAL,	/* 0 */
+	BUCK_MODE_SLEEP,	/* 1 */
+	BUCK_MODE_SYNC,		/* 2 */
+	BUCK_MODE_AUTO		/* 3 */
+};
+
+/* Regulator operations */
+static int da906x_set_voltage(struct regulator_dev *rdev, int min_uV,
+			      int max_uV, unsigned *selector);
+static int da906x_get_voltage_sel(struct regulator_dev *rdev);
+static int da906x_set_current_limit(struct regulator_dev *rdev,
+				    int min_uA, int max_uA);
+static int da906x_get_current_limit(struct regulator_dev *rdev);
+static int da906x_enable(struct regulator_dev *rdev);
+static int da906x_set_mode(struct regulator_dev *rdev, unsigned int mode);
+static unsigned da906x_get_mode(struct regulator_dev *rdev);
+static int da906x_get_status(struct regulator_dev *rdev);
+static int da906x_set_suspend_voltage(struct regulator_dev *rdev, int uV);
+static int da906x_suspend_enable(struct regulator_dev *rdev);
+static int da906x_set_suspend_mode(struct regulator_dev *rdev, unsigned mode);
+
+static struct regulator_ops da906x_switch_ops = {
+	.enable			= da906x_enable,
+	.disable		= regulator_disable_regmap,
+	.is_enabled		= regulator_is_enabled_regmap,
+	.set_suspend_enable	= da906x_enable,
+	.set_suspend_disable	= regulator_disable_regmap,
+};
+
+static struct regulator_ops da906x_ldo_ops = {
+	.enable			= da906x_enable,
+	.disable		= regulator_disable_regmap,
+	.is_enabled		= regulator_is_enabled_regmap,
+	.set_voltage		= da906x_set_voltage,
+	.get_voltage_sel	= da906x_get_voltage_sel,
+	.list_voltage		= regulator_list_voltage_linear,
+	.set_mode		= da906x_set_mode,
+	.get_mode		= da906x_get_mode,
+	.get_status		= da906x_get_status,
+	.set_suspend_voltage	= da906x_set_suspend_voltage,
+	.set_suspend_enable	= da906x_suspend_enable,
+	.set_suspend_disable	= regulator_disable_regmap,
+	.set_suspend_mode	= da906x_set_suspend_mode,
+};
+
+static struct regulator_ops da906x_buck_ops = {
+	.enable			= da906x_enable,
+	.disable		= regulator_disable_regmap,
+	.is_enabled		= regulator_is_enabled_regmap,
+	.set_voltage		= da906x_set_voltage,
+	.get_voltage_sel	= da906x_get_voltage_sel,
+	.list_voltage		= regulator_list_voltage_linear,
+	.set_current_limit	= da906x_set_current_limit,
+	.get_current_limit	= da906x_get_current_limit,
+	.set_mode		= da906x_set_mode,
+	.get_mode		= da906x_get_mode,
+	.get_status		= da906x_get_status,
+	.set_suspend_voltage	= da906x_set_suspend_voltage,
+	.set_suspend_enable	= da906x_suspend_enable,
+	.set_suspend_disable	= regulator_disable_regmap,
+	.set_suspend_mode	= da906x_set_suspend_mode,
+};
+
+/* Current limits array (in uA) for BCORE1, BCORE2, BPRO.
+   Entry indexes corresponds to register values. */
+static const int da9063_buck_a_limits[] = {
+	 500000,  600000,  700000,  800000,  900000, 1000000, 1100000, 1200000,
+	1300000, 1400000, 1500000, 1600000, 1700000, 1800000, 1900000, 2000000
+};
+
+/* Current limits array (in uA) for BMEM, BIO, BPERI.
+   Entry indexes corresponds to register values. */
+static const int da9063_buck_b_limits[] = {
+	1500000, 1600000, 1700000, 1800000, 1900000, 2000000, 2100000, 2200000,
+	2300000, 2400000, 2500000, 2600000, 2700000, 2800000, 2900000, 3000000
+};
+
+/* Current limits array (in uA) for merged BCORE1 and BCORE2.
+   Entry indexes corresponds to register values. */
+static const int da9063_bcores_merged_limits[] = {
+	1000000, 1200000, 1400000, 1600000, 1800000, 2000000, 2200000, 2400000,
+	2600000, 2800000, 3000000, 3200000, 3400000, 3600000, 3800000, 4000000
+};
+
+/* Current limits array (in uA) for merged BMEM and BIO.
+   Entry indexes corresponds to register values. */
+static const int da9063_bmem_bio_merged_limits[] = {
+	3000000, 3200000, 3400000, 3600000, 3800000, 4000000, 4200000, 4400000,
+	4600000, 4800000, 5000000, 5200000, 5400000, 5600000, 5800000, 6000000
+};
+
+/* Info of regulators for DA9063 */
+static const struct da906x_regulator_info da9063_regulator_info[] = {
+	{
+		DA906X_BUCK(DA9063, BCORE1, 300, 10, 1570,
+			    da9063_buck_a_limits),
+		DA906X_BUCK_COMMON_FIELDS(BCORE1),
+		.suspend = BFIELD(DA906X_REG_DVC_1, DA906X_VBCORE1_SEL),
+		.ilimit = FIELD(DA906X_REG_BUCK_ILIM_C, DA906X_BCORE1_ILIM_MASK,
+				DA906X_BCORE1_ILIM_SHIFT, 0),
+	},
+	{
+		DA906X_BUCK(DA9063, BCORE2, 300, 10, 1570,
+			    da9063_buck_a_limits),
+		DA906X_BUCK_COMMON_FIELDS(BCORE2),
+		.suspend = BFIELD(DA906X_REG_DVC_1, DA906X_VBCORE2_SEL),
+
+		.ilimit = FIELD(DA906X_REG_BUCK_ILIM_C,
+				DA906X_BCORE2_ILIM_MASK,
+				DA906X_BCORE2_ILIM_SHIFT,
+				0),
+	},
+	{
+		DA906X_BUCK(DA9063, BPRO, 530, 10, 1800,
+			    da9063_buck_a_limits),
+		DA906X_BUCK_COMMON_FIELDS(BPRO),
+		.suspend = BFIELD(DA906X_REG_DVC_1, DA906X_VBPRO_SEL),
+		.ilimit = FIELD(DA906X_REG_BUCK_ILIM_B, DA906X_BPRO_ILIM_MASK,
+				DA906X_BPRO_ILIM_SHIFT, 0),
+	},
+	{
+		DA906X_BUCK(DA9063, BMEM, 800, 20, 3340,
+			    da9063_buck_b_limits),
+		DA906X_BUCK_COMMON_FIELDS(BMEM),
+		.suspend = BFIELD(DA906X_REG_DVC_1, DA906X_VBMEM_SEL),
+		.ilimit = FIELD(DA906X_REG_BUCK_ILIM_A, DA906X_BMEM_ILIM_MASK,
+				DA906X_BMEM_ILIM_SHIFT, 0),
+	},
+	{
+		DA906X_BUCK(DA9063, BIO, 800, 20, 3340,
+			    da9063_buck_b_limits),
+		DA906X_BUCK_COMMON_FIELDS(BIO),
+		.suspend = BFIELD(DA906X_REG_DVC_2, DA906X_VBIO_SEL),
+		.ilimit = FIELD(DA906X_REG_BUCK_ILIM_A, DA906X_BIO_ILIM_MASK,
+				DA906X_BIO_ILIM_SHIFT, 0),
+	},
+	{
+		DA906X_BUCK(DA9063, BPERI, 800, 20, 3340,
+			    da9063_buck_b_limits),
+		DA906X_BUCK_COMMON_FIELDS(BPERI),
+		.suspend = BFIELD(DA906X_REG_DVC_1, DA906X_VBPERI_SEL),
+		.ilimit = FIELD(DA906X_REG_BUCK_ILIM_B, DA906X_BPERI_ILIM_MASK,
+				DA906X_BPERI_ILIM_SHIFT, 0),
+	},
+	{
+		DA906X_BUCK(DA9063, BCORES_MERGED, 300, 10, 1570,
+			    da9063_bcores_merged_limits),
+		/* BCORES_MERGED uses the same register fields as BCORE1 */
+		DA906X_BUCK_COMMON_FIELDS(BCORE1),
+		.suspend = BFIELD(DA906X_REG_DVC_1, DA906X_VBCORE1_SEL),
+		.ilimit = FIELD(DA906X_REG_BUCK_ILIM_C, DA906X_BCORE1_ILIM_MASK,
+				DA906X_BCORE1_ILIM_SHIFT, 0),
+	},
+	{
+		DA906X_BUCK(DA9063, BMEM_BIO_MERGED, 800, 20, 3340,
+			    da9063_bmem_bio_merged_limits),
+		/* BMEM_BIO_MERGED uses the same register fields as BMEM */
+		DA906X_BUCK_COMMON_FIELDS(BMEM),
+		.suspend = BFIELD(DA906X_REG_DVC_1, DA906X_VBMEM_SEL),
+		.ilimit = FIELD(DA906X_REG_BUCK_ILIM_A, DA906X_BMEM_ILIM_MASK,
+				DA906X_BMEM_ILIM_SHIFT, 0),
+	},
+	{
+		DA906X_LDO(DA9063, LDO1, 600, 20, 1860),
+		DA906X_LDO_COMMON_FIELDS(LDO1),
+		.suspend = BFIELD(DA906X_REG_DVC_1, DA906X_VLDO1_SEL),
+	},
+	{
+		DA906X_LDO(DA9063, LDO2, 600, 20, 1860),
+		DA906X_LDO_COMMON_FIELDS(LDO2),
+		.suspend = BFIELD(DA906X_REG_DVC_1, DA906X_VLDO2_SEL),
+	},
+	{
+		DA906X_LDO(DA9063, LDO3, 900, 20, 3440),
+		DA906X_LDO_COMMON_FIELDS(LDO3),
+		.suspend = BFIELD(DA906X_REG_DVC_1, DA906X_VLDO3_SEL),
+		.oc_event = BFIELD(DA906X_REG_STATUS_D, DA906X_LDO3_LIM),
+	},
+	{
+		DA906X_LDO(DA9063, LDO4, 900, 20, 3440),
+		DA906X_LDO_COMMON_FIELDS(LDO4),
+		.suspend = BFIELD(DA906X_REG_DVC_2, DA906X_VLDO4_SEL),
+		.oc_event = BFIELD(DA906X_REG_STATUS_D, DA906X_LDO4_LIM),
+	},
+	{
+		DA906X_LDO(DA9063, LDO5, 900, 50, 3600),
+		DA906X_LDO_COMMON_FIELDS(LDO5),
+		.suspend = BFIELD(DA906X_REG_LDO5_CONT, DA906X_VLDO5_SEL),
+	},
+	{
+		DA906X_LDO(DA9063, LDO6, 900, 50, 3600),
+		DA906X_LDO_COMMON_FIELDS(LDO6),
+		.suspend = BFIELD(DA906X_REG_LDO6_CONT, DA906X_VLDO6_SEL),
+	},
+	{
+		DA906X_LDO(DA9063, LDO7, 900, 50, 3600),
+		DA906X_LDO_COMMON_FIELDS(LDO7),
+		.suspend = BFIELD(DA906X_REG_LDO7_CONT, DA906X_VLDO7_SEL),
+		.oc_event = BFIELD(DA906X_REG_STATUS_D, DA906X_LDO7_LIM),
+	},
+	{
+		DA906X_LDO(DA9063, LDO8, 900, 50, 3600),
+		DA906X_LDO_COMMON_FIELDS(LDO8),
+		.suspend = BFIELD(DA906X_REG_LDO8_CONT, DA906X_VLDO8_SEL),
+		.oc_event = BFIELD(DA906X_REG_STATUS_D, DA906X_LDO8_LIM),
+	},
+	{
+		DA906X_LDO(DA9063, LDO9, 950, 50, 3600),
+		DA906X_LDO_COMMON_FIELDS(LDO9),
+		.suspend = BFIELD(DA906X_REG_LDO9_CONT, DA906X_VLDO9_SEL),
+	},
+	{
+		DA906X_LDO(DA9063, LDO10, 900, 50, 3600),
+		DA906X_LDO_COMMON_FIELDS(LDO10),
+		.suspend = BFIELD(DA906X_REG_LDO10_CONT, DA906X_VLDO10_SEL),
+	},
+	{
+		DA906X_LDO(DA9063, LDO11, 900, 50, 3600),
+		DA906X_LDO_COMMON_FIELDS(LDO11),
+		.suspend = BFIELD(DA906X_REG_LDO11_CONT, DA906X_VLDO11_SEL),
+		.oc_event = BFIELD(DA906X_REG_STATUS_D, DA906X_LDO11_LIM),
+	},
+	{
+		DA906X_SWITCH(DA9063, 32K_OUT),
+		.enable = BFIELD(DA906X_REG_EN_32K, DA906X_OUT_32K_EN),
+	},
+};
+
+/* Link chip model with regulators info table */
+static struct da906x_dev_model regulators_models[] = {
+	{
+		.regulator_info = da9063_regulator_info,
+		.n_regulators = ARRAY_SIZE(da9063_regulator_info),
+		.dev_model = PMIC_DA9063,
+	},
+	{NULL, 0, 0}	/* End of list */
+};
+
+
+/*
+ * Regulator internal functions
+ */
+static int da906x_update_mode_internal(struct da906x_regulator *regl,
+				       int sys_state)
+{
+	const struct da906x_regulator_info *rinfo = regl->info;
+	unsigned val;
+	unsigned mode;
+	int ret;
+
+	if (sys_state = SYS_STATE_SUSPEND)
+		/* Get mode for regulator in suspend state */
+		mode = regl->suspend_mode;
+	else
+		/* Get mode for regulator in normal operation */
+		mode = regl->mode;
+
+	/* LDOs use sleep flags - one for normal and one for suspend state.
+	   For BUCKs single mode register field is used in normal and
+	   suspend state. */
+	if (rinfo->mode.addr) {
+		/* Set mode for BUCK - 3 modes are supported */
+		switch (mode) {
+		case REGULATOR_MODE_FAST:
+			val = BUCK_MODE_SYNC;
+			break;
+		case REGULATOR_MODE_NORMAL:
+			val = BUCK_MODE_AUTO;
+			break;
+		case REGULATOR_MODE_STANDBY:
+			val = BUCK_MODE_SLEEP;
+			break;
+		default:
+			return -EINVAL;
+		}
+		val = val << rinfo->mode.shift;
+
+		ret = da906x_reg_update(regl->hw, rinfo->mode.addr,
+					rinfo->mode.mask, val);
+	} else {
+		/* Set mode for LDO - 2 modes are supported */
+		switch (mode) {
+		case REGULATOR_MODE_NORMAL:
+			val = 0;
+			break;
+		case REGULATOR_MODE_STANDBY:
+			val = DA906X_LDO_SL;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		if (sys_state = SYS_STATE_SUSPEND) {
+			if (!rinfo->suspend_sleep.addr)
+				return -EINVAL;
+			ret = da906x_reg_update(regl->hw,
+						rinfo->suspend_sleep.addr,
+						rinfo->suspend_sleep.mask,
+						val);
+		} else {
+			if (!rinfo->sleep.addr)
+				return -EINVAL;
+			ret = da906x_reg_update(regl->hw,
+						rinfo->sleep.addr,
+						rinfo->sleep.mask, val);
+		}
+	}
+
+	return ret;
+}
+
+static unsigned da906x_get_mode_internal(struct da906x_regulator *regl,
+					 int sys_state)
+{
+	const struct da906x_regulator_info *rinfo = regl->info;
+	int val;
+	int addr;
+	int mask;
+	unsigned mode = 0;
+
+	/* Bucks use single mode register field for normal operation
+	   and suspend state. LDOs use sleep flags - one for normal
+	   and one for suspend state. */
+	if (rinfo->mode.addr) {
+		/* For BUCKs, there are 3 modes to map to */
+		val = da906x_reg_read(regl->hw, rinfo->mode.addr);
+		if (val < 0)
+			return val;
+
+		val = (val & rinfo->mode.mask) >> rinfo->mode.shift;
+		switch (val) {
+		default:
+		case BUCK_MODE_MANUAL:
+			mode = REGULATOR_MODE_FAST | REGULATOR_MODE_STANDBY;
+			/* Sleep flag bit decides the mode */
+			break;
+		case BUCK_MODE_SLEEP:
+			return REGULATOR_MODE_STANDBY;
+		case BUCK_MODE_SYNC:
+			return REGULATOR_MODE_FAST;
+		case BUCK_MODE_AUTO:
+			return REGULATOR_MODE_NORMAL;
+		}
+	} else if (rinfo->sleep.addr) {
+		/* For LDOs there are 2 modes to map to */
+		mode = REGULATOR_MODE_NORMAL | REGULATOR_MODE_STANDBY;
+		/* Sleep flag bit decides the mode */
+	} else {
+		/* No support */
+		return 0;
+	}
+
+	/* If sys_state = SYS_STATE_CURRENT, current regulator state
+	   is detected. */
+	if (sys_state = SYS_STATE_CURRENT && rinfo->suspend.addr) {
+		val = da906x_reg_read(regl->hw,
+					  rinfo->suspend.addr);
+		if (val < 0)
+			return val;
+
+		if (val & rinfo->suspend.mask)
+			sys_state = SYS_STATE_SUSPEND;
+		else
+			sys_state = SYS_STATE_NORMAL;
+	}
+
+	/* Read regulator mode from proper register,
+	   depending on selected system state */
+	if (sys_state = SYS_STATE_SUSPEND && rinfo->suspend_sleep.addr) {
+		addr = rinfo->suspend_sleep.addr;
+		mask = rinfo->suspend_sleep.mask;
+	} else {
+		addr = rinfo->sleep.addr;
+		mask = rinfo->sleep.mask;
+	}
+
+	val = da906x_reg_read(regl->hw, addr);
+	if (val < 0)
+		return val;
+
+	if (val & mask)
+		mode &= REGULATOR_MODE_STANDBY;
+	else
+		mode &= REGULATOR_MODE_NORMAL | REGULATOR_MODE_FAST;
+
+	return mode;
+}
+
+static int da906x_set_voltage(struct regulator_dev *rdev,
+				int min_uV, int max_uV, unsigned *selector)
+{
+	struct da906x_regulator *regl = rdev_get_drvdata(rdev);
+	const struct field *fvol = &regl->info->voltage;
+	int ret;
+	unsigned val;
+
+	val = regulator_map_voltage_linear(rdev, min_uV, max_uV);
+	if (val < 0)
+		return -EINVAL;
+
+	val = (val + fvol->offset) << fvol->shift;
+	ret = da906x_reg_update(regl->hw, fvol->addr, fvol->mask, val);
+	if (ret >= 0)
+		*selector = val;
+
+	return ret;
+}
+
+static int da906x_get_voltage_sel(struct regulator_dev *rdev)
+{
+	struct da906x_regulator *regl = rdev_get_drvdata(rdev);
+	const struct da906x_regulator_info *rinfo = regl->info;
+	int sel;
+
+	sel = da906x_reg_read(regl->hw, rinfo->voltage.addr);
+	if (sel < 0)
+		return sel;
+
+	sel = (sel & rinfo->voltage.mask) >> rinfo->voltage.shift;
+	sel -= rinfo->voltage.offset;
+	if (sel < 0)
+		sel = 0;
+	if (sel >= rinfo->n_steps)
+		sel = rinfo->n_steps - 1;
+
+	return sel;
+}
+
+static int da906x_set_current_limit(struct regulator_dev *rdev,
+							int min_uA, int max_uA)
+{
+	struct da906x_regulator *regl = rdev_get_drvdata(rdev);
+	const struct da906x_regulator_info *rinfo = regl->info;
+	int val = INT_MAX;
+	unsigned sel = 0;
+	int n;
+	int tval;
+
+	if (!rinfo->current_limits)
+		return -EINVAL;
+
+	for (n = 0; n < rinfo->n_current_limits; n++) {
+		tval = rinfo->current_limits[n];
+		if (tval >= min_uA && tval <= max_uA && val > tval) {
+			val = tval;
+			sel = n;
+		}
+	}
+	if (val = INT_MAX)
+		return -EINVAL;
+
+	sel = (sel + rinfo->ilimit.offset) << rinfo->ilimit.shift;
+	return da906x_reg_update(regl->hw, rinfo->ilimit.addr,
+						rinfo->ilimit.mask, sel);
+}
+
+static int da906x_get_current_limit(struct regulator_dev *rdev)
+{
+	struct da906x_regulator *regl = rdev_get_drvdata(rdev);
+	const struct da906x_regulator_info *rinfo = regl->info;
+	int sel;
+
+	sel = da906x_reg_read(regl->hw, rinfo->ilimit.addr);
+	if (sel < 0)
+		return sel;
+
+	sel = (sel & rinfo->ilimit.mask) >> rinfo->ilimit.shift;
+	sel -= rinfo->ilimit.offset;
+	if (sel < 0)
+		sel = 0;
+	if (sel >= rinfo->n_current_limits)
+		sel = rinfo->n_current_limits - 1;
+
+	return rinfo->current_limits[sel];
+}
+
+static int da906x_enable(struct regulator_dev *rdev)
+{
+	struct da906x_regulator *regl = rdev_get_drvdata(rdev);
+	int ret;
+
+	if (regl->info->suspend.mask) {
+		/* Make sure to exit from suspend mode on enable */
+		ret = da906x_reg_clear_bits(regl->hw, regl->info->suspend.addr,
+					    regl->info->suspend.mask);
+		if (ret < 0)
+			return ret;
+
+		/* BUCKs need mode update after wake-up from suspend state. */
+		ret = da906x_update_mode_internal(regl, SYS_STATE_NORMAL);
+		if (ret < 0)
+			return ret;
+	}
+
+	return regulator_enable_regmap(rdev);
+}
+
+static int da906x_set_mode(struct regulator_dev *rdev, unsigned mode)
+{
+	struct da906x_regulator *regl = rdev_get_drvdata(rdev);
+	unsigned orig_mode = regl->mode;
+	int ret;
+
+	regl->mode = mode;
+	ret = da906x_update_mode_internal(regl, SYS_STATE_NORMAL);
+	if (ret)
+		regl->mode = orig_mode;
+
+	return ret;
+}
+
+static unsigned da906x_get_mode(struct regulator_dev *rdev)
+{
+	struct da906x_regulator *regl = rdev_get_drvdata(rdev);
+
+	return da906x_get_mode_internal(regl, SYS_STATE_CURRENT);
+}
+
+static int da906x_get_status(struct regulator_dev *rdev)
+{
+	int ret = regulator_is_enabled_regmap(rdev);
+
+	if (ret = 0) {
+		ret = REGULATOR_STATUS_OFF;
+	} else if (ret > 0) {
+		ret = da906x_get_mode(rdev);
+		if (ret > 0)
+			ret = regulator_mode_to_status(ret);
+		else if (ret = 0)
+			ret = -EIO;
+	}
+
+	return ret;
+}
+
+static int da906x_set_suspend_voltage(struct regulator_dev *rdev, int uV)
+{
+	struct da906x_regulator *regl = rdev_get_drvdata(rdev);
+	const struct da906x_regulator_info *rinfo = regl->info;
+	const struct field *fsusvol = &rinfo->suspend_voltage;
+	int val;
+	int ret;
+
+	val = regulator_map_voltage_linear(rdev, uV, uV);
+	if (val < 0)
+		return -EINVAL;
+
+	val = (val + fsusvol->offset) << fsusvol->shift;
+	ret = da906x_reg_update(regl->hw, fsusvol->addr, fsusvol->mask, val);
+
+	return ret;
+}
+
+static int da906x_suspend_enable(struct regulator_dev *rdev)
+{
+	int ret;
+	struct da906x_regulator *regl = rdev_get_drvdata(rdev);
+	const struct bfield *bsuspend = &regl->info->suspend;
+
+	ret = da906x_reg_set_bits(regl->hw, bsuspend->addr, bsuspend->mask);
+	return ret;
+}
+
+static int da906x_set_suspend_mode(struct regulator_dev *rdev, unsigned mode)
+{
+	struct da906x_regulator *regl = rdev_get_drvdata(rdev);
+	int ret;
+
+	regl->suspend_mode = mode;
+	ret = da906x_update_mode_internal(regl, SYS_STATE_SUSPEND);
+	return ret;
+}
+
+/* Regulator event handlers */
+irqreturn_t da906x_ldo_lim_event(int irq, void *data)
+{
+	struct da906x_regulators *regulators = data;
+	struct da906x *hw = regulators->regulator[0].hw;
+	struct da906x_regulator *regl;
+	int bits;
+	int i;
+
+	bits = da906x_reg_read(hw, DA906X_REG_STATUS_D);
+	if (bits < 0)
+		return IRQ_HANDLED;
+
+	for (i = regulators->n_regulators - 1; i >= 0; i--) {
+		regl = &regulators->regulator[i];
+		if (regl->info->oc_event.addr != DA906X_REG_STATUS_D)
+			continue;
+
+		if (regl->info->oc_event.mask & bits)
+			regulator_notifier_call_chain(regl->rdev,
+					REGULATOR_EVENT_OVER_CURRENT, NULL);
+	}
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Probing and Initialisation functions
+ */
+static __devinit const struct da906x_regulator_info *
+da906x_get_regl_info(const struct da906x *da906x,
+		     const struct da906x_dev_model *model, int id)
+{
+	int m;
+
+	for (m = model->n_regulators - 1;
+	     model->regulator_info[m].id != id; m--) {
+		if (m <= 0)
+			return NULL;
+	}
+	return &model->regulator_info[m];
+}
+
+static __devinit int da906x_regulator_probe(struct platform_device *pdev)
+{
+	struct da906x *da906x = dev_get_drvdata(pdev->dev.parent);
+	struct da906x_pdata *da906x_pdata = dev_get_platdata(da906x->dev);
+	struct da906x_regulators_pdata *regl_pdata;
+	struct da906x_regulator_data *rdata;
+	const struct da906x_dev_model *model;
+	struct da906x_regulators *regulators;
+	struct da906x_regulator *regl;
+	struct regulator_config config;
+	bool bcores_merged, bmem_bio_merged;
+	size_t size;
+	int n;
+	int ret;
+
+	if (!da906x_pdata) {
+		dev_err(&pdev->dev, "No platform init data supplied\n");
+		return -ENODEV;
+	}
+	regl_pdata = da906x_pdata->regulators_pdata;
+
+	if (!regl_pdata || regl_pdata->n_regulators = 0) {
+		dev_err(&pdev->dev,
+			"No regulators defined for the platform\n");
+		return -ENODEV;
+	}
+
+	/* Find regulators set for particular device model */
+	for (model = regulators_models; model->regulator_info; model++) {
+		if (model->dev_model = da906x_model(da906x))
+			break;
+	}
+	if (!model->regulator_info) {
+		dev_err(&pdev->dev, "Chip model not recognised (%u)\n",
+			da906x_model(da906x));
+		return -ENODEV;
+	}
+
+	ret = da906x_reg_read(da906x, DA906X_REG_CONFIG_H);
+	if (ret < 0) {
+		dev_err(&pdev->dev,
+			"Error while reading BUCKs configuration\n");
+		return -EIO;
+	}
+	bcores_merged = (ret & DA906X_BCORE_MERGE) ? true : false;
+	bmem_bio_merged = (ret & DA906X_BUCK_MERGE) ? true : false;
+
+	/* Allocate memory required by usable regulators */
+	size = sizeof(struct da906x_regulators) +
+		regl_pdata->n_regulators * sizeof(struct da906x_regulator);
+	regulators = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
+	if (!regulators) {
+		dev_err(&pdev->dev, "No memory for regulators\n");
+		return -ENOMEM;
+	}
+
+	regulators->n_regulators = regl_pdata->n_regulators;
+	platform_set_drvdata(pdev, regulators);
+
+	/* Register all regulators declared in platform information */
+	n = 0;
+	while (n < regulators->n_regulators) {
+		rdata = &regl_pdata->regulator_data[n];
+
+		/* Check regulator ID against merge mode configuration */
+		switch (rdata->id) {
+		case DA9063_ID_BCORE1:
+		case DA9063_ID_BCORE2:
+			if (bcores_merged) {
+				dev_err(&pdev->dev,
+					"Cannot use BCORE1 and BCORE2 separately, when in merge mode\n");
+				ret = -EINVAL;
+				goto err;
+			}
+			break;
+		case DA9063_ID_BMEM:
+		case DA9063_ID_BIO:
+			if (bmem_bio_merged) {
+				dev_err(&pdev->dev,
+					"Cannot use BMEM and BIO separately, when in merge mode\n");
+				ret = -EINVAL;
+				goto err;
+			}
+			break;
+		case DA9063_ID_BCORES_MERGED:
+			if (!bcores_merged) {
+				dev_err(&pdev->dev,
+					"BCORE1 and BCORE2 are unavailable in merge mode\n");
+				ret = -EINVAL;
+				goto err;
+			}
+			break;
+		case DA9063_ID_BMEM_BIO_MERGED:
+			if (!bmem_bio_merged) {
+				dev_err(&pdev->dev,
+					"BMEM and BIO are unavailable in merge mode\n");
+				ret = -EINVAL;
+				goto err;
+			}
+			break;
+		}
+
+		/* Initialise regulator structure */
+		regl = &regulators->regulator[n];
+		regl->hw = da906x;
+		regl->info = da906x_get_regl_info(da906x, model, rdata->id);
+		if (!regl->info) {
+			dev_err(&pdev->dev,
+				"Invalid regulator ID in platform data\n");
+			ret = -EINVAL;
+			goto err;
+		}
+		regl->desc.type = REGULATOR_VOLTAGE;
+		regl->desc.owner = THIS_MODULE;
+		regl->desc.name = regl->info->name;
+		regl->desc.id = rdata->id;
+		regl->desc.ops = regl->info->ops;
+		regl->desc.n_voltages = regl->info->n_steps;
+		regl->desc.min_uV = regl->info->min_uV;
+		regl->desc.uV_step = regl->info->step_uV;
+		regl->desc.enable_reg = regl->info->enable.addr +
+					DA906X_MAPPING_BASE;
+		regl->desc.enable_mask = regl->info->enable.mask;
+
+		/* Register regulator */
+		config.dev = &pdev->dev;
+		config.init_data = rdata->initdata;
+		config.driver_data = regl;
+		config.regmap = da906x->regmap;
+		regl->rdev = regulator_register(&regl->desc, &config);
+		if (IS_ERR_OR_NULL(regl->rdev)) {
+			dev_err(&pdev->dev,
+				"Failed to register %s regulator\n",
+				regl->info->name);
+			ret = PTR_ERR(regl->rdev);
+			goto err;
+		}
+		n++;
+
+		/* Get current modes of operation (A/B voltage selection)
+		   for normal and suspend states */
+		ret = da906x_get_mode_internal(regl, SYS_STATE_NORMAL);
+		if (ret < 0) {
+			dev_err(&pdev->dev,
+				"Failed to read %s regulator's mode\n",
+				regl->info->name);
+			goto err;
+		}
+		if (ret = 0)
+			regl->mode = REGULATOR_MODE_NORMAL;
+		else
+			regl->mode = ret;
+
+		ret = da906x_get_mode_internal(regl, SYS_STATE_SUSPEND);
+		if (ret < 0) {
+			dev_err(&pdev->dev,
+				"Failed to read %s regulator's mode\n",
+				regl->info->name);
+			goto err;
+		}
+		if (ret = 0)
+			regl->suspend_mode = REGULATOR_MODE_NORMAL;
+		else
+			regl->mode = ret;
+	}
+
+	/* LDOs overcurrent event support */
+	regulators->irq_ldo_lim = platform_get_irq_byname(pdev, "LDO_LIM");
+	if (regulators->irq_ldo_lim >= 0) {
+		ret = request_threaded_irq(regulators->irq_ldo_lim,
+					   NULL, da906x_ldo_lim_event,
+					   IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					   "LDO_LIM", regulators);
+		if (ret) {
+			dev_err(&pdev->dev,
+					"Failed to request LDO_LIM IRQ.\n");
+			regulators->irq_ldo_lim = -ENXIO;
+		}
+	}
+
+	return 0;
+
+err:
+	/* Wind back regulators registeration */
+	while (--n >= 0) {
+		regulator_unregister(regulators->regulator[n].rdev);
+	}
+
+	return ret;
+}
+
+static int __devexit da906x_regulator_remove(struct platform_device *pdev)
+{
+	struct da906x_regulators *regulators = platform_get_drvdata(pdev);
+	struct da906x_regulator *regl;
+
+	free_irq(regulators->irq_ldo_lim, regulators);
+	free_irq(regulators->irq_uvov, regulators);
+
+	for (regl = &regulators->regulator[regulators->n_regulators - 1];
+	     regl >= &regulators->regulator[0]; regl--)
+		regulator_unregister(regl->rdev);
+
+	return 0;
+}
+
+static struct platform_driver da906x_regulator_driver = {
+	.driver = {
+		.name = DA906X_DRVNAME_REGULATORS,
+		.owner = THIS_MODULE,
+	},
+	.probe = da906x_regulator_probe,
+	.remove = __devexit_p(da906x_regulator_remove),
+};
+
+static int __init da906x_regulator_init(void)
+{
+	return platform_driver_register(&da906x_regulator_driver);
+}
+subsys_initcall(da906x_regulator_init);
+
+static void __exit da906x_regulator_cleanup(void)
+{
+	platform_driver_unregister(&da906x_regulator_driver);
+}
+module_exit(da906x_regulator_cleanup);
+
+
+/* Module information */
+MODULE_AUTHOR("Krystian Garbaciak <krystian.garbaciak@diasemi.com>");
+MODULE_DESCRIPTION("DA906x regulators driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("paltform:" DA906X_DRVNAME_REGULATORS);
-- 
1.7.0.4


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

  reply	other threads:[~2012-08-24 13:55 UTC|newest]

Thread overview: 60+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2012-08-24 13:45 [RFC PATCH 0/8] DA906x PMIC driver Krystian Garbaciak
2012-08-24 13:45 ` [lm-sensors] " Krystian Garbaciak
2012-08-24 13:50 ` [PATCH 1/8] mfd: Add Dialog DA906x core driver Krystian Garbaciak
2012-08-24 13:50   ` [lm-sensors] " Krystian Garbaciak
2012-08-24 13:55   ` Krystian Garbaciak [this message]
2012-08-24 13:55     ` [lm-sensors] [RFC PATCH 2/8] regulator: Add Dialog DA906x voltage regulators support Krystian Garbaciak
2012-08-24 14:00     ` [RFC PATCH 3/8] rtc: Add RTC driver for DA906x PMIC Krystian Garbaciak
2012-08-24 14:00       ` [lm-sensors] " Krystian Garbaciak
2012-08-24 14:05       ` [RFC PATCH 4/8] hwmon: Add DA906x hardware monitoring support Krystian Garbaciak
2012-08-24 14:05         ` [lm-sensors] " Krystian Garbaciak
2012-08-24 14:10         ` [RFC PATCH 5/8] input: Add support for DA906x PMIC OnKey detection Krystian Garbaciak
2012-08-24 14:10           ` [lm-sensors] " Krystian Garbaciak
2012-08-24 14:15           ` [RFC PATCH 6/8] input: Add support for DA906x vibration motor driver Krystian Garbaciak
2012-08-24 14:15             ` [lm-sensors] " Krystian Garbaciak
2012-08-24 14:20             ` [RFC PATCH 7/8] watchdog: Add DA906x PMIC watchdog driver Krystian Garbaciak
2012-08-24 14:20               ` [lm-sensors] " Krystian Garbaciak
2012-08-24 14:25               ` [RFC PATCH 8/8] leds: Add DA906x PMIC LED driver Krystian Garbaciak
2012-08-24 14:25                 ` [lm-sensors] " Krystian Garbaciak
2012-08-24 18:45         ` [RFC PATCH 4/8] hwmon: Add DA906x hardware monitoring support Guenter Roeck
2012-08-24 18:45           ` [lm-sensors] " Guenter Roeck
2012-08-29 13:25           ` [PATCH] regulator: Fix bug in regulator_mode_to_status() core function Krystian Garbaciak
2012-08-29 13:25             ` [lm-sensors] " Krystian Garbaciak
2012-08-25 15:10     ` [RFC PATCH 2/8] regulator: Add Dialog DA906x voltage regulators support Mark Brown
2012-08-25 15:10       ` [lm-sensors] " Mark Brown
2012-08-29 14:50       ` Krystian Garbaciak
2012-08-29 14:50         ` [lm-sensors] " Krystian Garbaciak
2012-08-29 14:50         ` Krystian Garbaciak
2012-08-30 17:47         ` Mark Brown
2012-08-30 17:47           ` [lm-sensors] " Mark Brown
2012-08-31 10:00           ` Krystian Garbaciak
2012-08-31 10:00             ` [lm-sensors] " Krystian Garbaciak
2012-08-31 10:00             ` Krystian Garbaciak
2013-05-09 14:05             ` Guennadi Liakhovetski
2013-05-09 14:18               ` Anthony Olech
2013-05-09 14:28                 ` Guennadi Liakhovetski
2013-05-09 14:42                   ` Anthony Olech
2013-05-09 14:50                     ` Guennadi Liakhovetski
2012-08-25 18:31   ` [PATCH 1/8] mfd: Add Dialog DA906x core driver Mark Brown
2012-08-25 18:31     ` [lm-sensors] " Mark Brown
2012-08-31 11:20     ` Krystian Garbaciak
2012-08-31 11:20       ` [lm-sensors] " Krystian Garbaciak
2012-08-31 11:20       ` Krystian Garbaciak
2012-08-31 11:37       ` Philippe Rétornaz
2012-08-31 11:37         ` [lm-sensors] " Philippe Rétornaz
2012-08-31 11:37         ` Philippe Rétornaz
2012-08-31 11:37         ` Philippe Rétornaz
2012-08-31 17:16       ` Mark Brown
2012-08-31 17:16         ` [lm-sensors] " Mark Brown
  -- strict thread matches above, loose matches on Subject: below --
2012-08-24  8:32 [RFC PATCH 0/8] DA906x PMIC driver Krystian Garbaciak
2012-08-24  8:32 ` [lm-sensors] " Krystian Garbaciak
2012-08-24  8:32 ` [PATCH 1/8] mfd: Add Dialog DA906x core driver Krystian Garbaciak
2012-08-24  8:32   ` [lm-sensors] " Krystian Garbaciak
2012-08-24  8:32   ` [PATCH 2/8] regulator: Add Dialog DA906x voltage regulators support Krystian Garbaciak
2012-08-24  8:32     ` [lm-sensors] " Krystian Garbaciak
2012-08-24  8:32     ` [PATCH 3/8] rtc: Add RTC driver for DA906x PMIC Krystian Garbaciak
2012-08-24  8:32       ` [lm-sensors] " Krystian Garbaciak
2012-08-24  8:32       ` [PATCH 4/8] hwmon: Add DA906x hardware monitoring support Krystian Garbaciak
2012-08-24  8:32         ` [lm-sensors] " Krystian Garbaciak
2012-08-24  8:32         ` [PATCH 5/8] input: Add support for DA906x PMIC OnKey detection Krystian Garbaciak
2012-08-24  8:32           ` [lm-sensors] " Krystian Garbaciak

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=201208241455@sw-eng-lt-dc-vm2 \
    --to=krystian.garbaciak@diasemi.com \
    --cc=a.zummo@towertech.it \
    --cc=anthony.olech@diasemi.com \
    --cc=ashish.jangam@kpitcummins.com \
    --cc=broonie@opensource.wolfsonmicro.com \
    --cc=bryan.wu@canonical.com \
    --cc=dg77.kim@samsung.com \
    --cc=dmitry.torokhov@gmail.com \
    --cc=drjones@redhat.com \
    --cc=linux-input@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-leds@vger.kernel.org \
    --cc=linux-watchdog@vger.kernel.org \
    --cc=lm-sensors@lm-sensors.org \
    --cc=lrg@ti.com \
    --cc=rtc-linux@googlegroups.com \
    --cc=sameo@linux.intel.com \
    --cc=wim@iguana.be \
    /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.