lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Date:	Fri, 21 Aug 2009 00:11:11 +0200
From:	Linus Walleij <linus.walleij@...ricsson.com>
To:	Liam Girdwood <lrg@...mlogic.co.uk>,
	Mark Brown <broonie@...nsource.wolfsonmicro.com>,
	linux-kernel@...r.kernel.org
Cc:	Samuel Ortiz <sameo@...ux.intel.com>,
	Russell King <linux@....linux.org.uk>,
	linux-arm-kernel@...ts.arm.linux.org.uk,
	Linus Walleij <linus.walleij@...ricsson.com>,
	Liam Girdwood <lrg@...mlogic.co.uk>,
	Mark Brown <broonie@...nsource.wolfsonmicro.com>
Subject: [PATCH] AB3100 regulator support v1

This adds support for the regulators found in the AB3100
Mixed-Signal IC.

It further also defines platform data for the ST-Ericsson
U300 platform and extends the AB3100 MFD driver so that
platform/board data with regulation constraints and an init
function can be passed down all the way from the board to
the regulators.

Signed-off-by: Linus Walleij <linus.walleij@...ricsson.com>
Cc: Liam Girdwood <lrg@...mlogic.co.uk>
Cc: Mark Brown <broonie@...nsource.wolfsonmicro.com>
Cc: Samuel Ortiz <sameo@...ux.intel.com>
Cc: Russell King <linux@....linux.org.uk>
---
This is pending some changes that are queued in Samuels
for-next tree (accessor names) and Russells next tree (I2C
board init file), so it will have to come in some time
later in the 2.6.32-rc cycle so mainly reviewing for now
I guess.
---
 arch/arm/mach-u300/core.c  |    2 +-
 arch/arm/mach-u300/core.h  |   13 +
 arch/arm/mach-u300/i2c.c   |  204 +++++++++
 drivers/mfd/ab3100-core.c  |    4 +
 drivers/regulator/Kconfig  |    9 +
 drivers/regulator/Makefile |    1 +
 drivers/regulator/ab3100.c | 1012 ++++++++++++++++++++++++++++++++++++++++++++
 include/linux/mfd/ab3100.h |   19 +
 8 files changed, 1263 insertions(+), 1 deletions(-)
 create mode 100644 arch/arm/mach-u300/core.h
 create mode 100644 drivers/regulator/ab3100.c

diff --git a/arch/arm/mach-u300/core.c b/arch/arm/mach-u300/core.c
index d4cca25..2a1dd78 100644
--- a/arch/arm/mach-u300/core.c
+++ b/arch/arm/mach-u300/core.c
@@ -153,7 +153,7 @@ static struct amba_device pl022_device = {
 	 */
 };
 
-static struct amba_device mmcsd_device = {
+struct amba_device mmcsd_device = {
 	.dev = {
 		.init_name = "mmci", /* Fast device at 0x1000 offset */
 		.platform_data = NULL, /* Added later */
diff --git a/arch/arm/mach-u300/core.h b/arch/arm/mach-u300/core.h
new file mode 100644
index 0000000..40082df
--- /dev/null
+++ b/arch/arm/mach-u300/core.h
@@ -0,0 +1,13 @@
+/*
+ * arch/arm/mach-u300/core.h
+ *
+ * Copyright (C) 2007-2009 ST-Ericsson AB
+ * License terms: GNU General Public License (GPL) version 2
+ * Stuff the core expose to the outside
+ * Author: Linus Walleij <linus.walleij@...ricsson.com>
+ */
+
+/*
+ * Needed to connect the regulator to this device
+ */
+extern struct amba_device mmcsd_device;
diff --git a/arch/arm/mach-u300/i2c.c b/arch/arm/mach-u300/i2c.c
index 10be1f8..750b6cb 100644
--- a/arch/arm/mach-u300/i2c.c
+++ b/arch/arm/mach-u300/i2c.c
@@ -10,12 +10,216 @@
 #include <linux/kernel.h>
 #include <linux/i2c.h>
 #include <mach/irqs.h>
+#include <linux/mfd/ab3100.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/amba/bus.h>
+#include "core.h"
+
+/* Preset (hardware defined) voltages for these regulators */
+#define LDO_A_VOLTAGE 2750000
+#define LDO_C_VOLTAGE 2650000
+#define LDO_D_VOLTAGE 2650000
+/* Not defined by ab3100 so we return 0 */
+#define LDO_EXT_VOLTAGE 0
+
+/*
+ * A local pointer to regulator LDO D which is used for
+ * shutting down the system.
+ */
+static struct regulator_dev *local_ldo_d;
+
+/*
+ * This function is used from pm.h to shut down the system by
+ * resetting all regulators in turn and then disable regulator
+ * LDO D (main power).
+ */
+void u300_pm_poweroff(void)
+{
+	sigset_t old, all;
+
+	sigfillset(&all);
+	if (!sigprocmask(SIG_BLOCK, &all, &old)) {
+		/* Disable LDO D to shut down the system */
+		if (local_ldo_d)
+			(void) local_ldo_d->desc->ops->disable(local_ldo_d);
+		else
+			pr_err("LDO D not available to shut down system\n");
+		(void) sigprocmask(SIG_SETMASK, &old, NULL);
+	}
+	return;
+}
+
+static int u300_ab3100_regulator_init(void *data)
+{
+	struct regulator_dev *rdev = data;
+
+	if (!strcmp(rdev->desc->name, "LDO_D")) {
+		/*
+		 * On U300 a special system controller register pulls up the DC
+		 * until the LDO_D regulator comes up. At this point, all
+		 * regulators are set and we do not need power control via
+		 * DC ON anymore.
+		 */
+		dev_info(&rdev->dev, "disable system controller pull-up\n");
+		/* err = syscon_dc_on(0); */
+		/* Set local regulator device pointer */
+		local_ldo_d = rdev;
+		/* Register globally exported PM poweroff hook */
+		pm_power_off = u300_pm_poweroff;
+	}
+	return 0;
+}
+
+static struct regulator_consumer_supply supply_LDO_G[] = {
+	{
+		.dev = &mmcsd_device.dev,
+		.supply = "VCARD",
+	},
+};
+
+static struct regulator_consumer_supply supply_LDO_H[] = {
+	{
+		.dev = NULL,
+		.supply = "VDISP",
+	},
+};
+
+static struct regulator_consumer_supply supply_LDO_C[] = {
+	{
+		.dev = NULL,
+		.supply = "VAUDIO",
+	},
+};
+
+static const struct ab3100_platform_data ab3100_plf_data = {
+	.reg_constraints = {
+		{
+			.constraints = {
+				.name = "LDO_A",
+				.min_uV = LDO_A_VOLTAGE,
+				.max_uV = LDO_A_VOLTAGE,
+				.valid_modes_mask = REGULATOR_MODE_NORMAL,
+			},
+		},
+		{
+			.constraints = {
+				.name = "LDO_C",
+				.min_uV = LDO_C_VOLTAGE,
+				.max_uV = LDO_C_VOLTAGE,
+				.valid_modes_mask = REGULATOR_MODE_NORMAL,
+			},
+			.num_consumer_supplies = ARRAY_SIZE(supply_LDO_C),
+			.consumer_supplies = supply_LDO_C,
+		},
+		{
+		.constraints = {
+				.name = "LDO_D",
+				.min_uV = LDO_D_VOLTAGE,
+				.max_uV = LDO_D_VOLTAGE,
+				.valid_modes_mask = REGULATOR_MODE_NORMAL,
+				/* Used for powering DB chip */
+				.always_on = 1,
+			},
+		},
+		{
+			.constraints = {
+				.name = "LDO_E",
+				.min_uV = 0,
+				.max_uV = 1800000,
+				.valid_modes_mask = REGULATOR_MODE_NORMAL,
+				.valid_ops_mask =
+				REGULATOR_CHANGE_VOLTAGE |
+				REGULATOR_CHANGE_STATUS,
+				/* Used for powering DB chip */
+				.always_on = 1,
+			},
+		},
+		{
+			.constraints = {
+				.name = "LDO_F",
+				.min_uV = 0,
+				.max_uV = 2650000,
+				.valid_modes_mask = REGULATOR_MODE_NORMAL,
+				.valid_ops_mask =
+				REGULATOR_CHANGE_VOLTAGE |
+				REGULATOR_CHANGE_STATUS,
+				/* Used for powering DB chip */
+				.always_on = 1,
+			},
+		},
+		{
+			.constraints = {
+				.name = "LDO_G",
+				.min_uV = 0,
+				.max_uV = 2850000,
+				.valid_modes_mask = REGULATOR_MODE_NORMAL,
+				.valid_ops_mask =
+				REGULATOR_CHANGE_VOLTAGE |
+				REGULATOR_CHANGE_STATUS,
+			},
+			.num_consumer_supplies = ARRAY_SIZE(supply_LDO_G),
+			.consumer_supplies = supply_LDO_G,
+		},
+		{
+			.constraints = {
+				.name = "LDO_H",
+				.min_uV = 0,
+				.max_uV = 2750000,
+				.valid_modes_mask = REGULATOR_MODE_NORMAL,
+				.valid_ops_mask =
+				REGULATOR_CHANGE_VOLTAGE |
+				REGULATOR_CHANGE_STATUS,
+			},
+			.num_consumer_supplies = ARRAY_SIZE(supply_LDO_H),
+			.consumer_supplies = supply_LDO_H,
+		},
+		{
+			.constraints = {
+				.name = "LDO_K",
+				.min_uV = 0,
+				.max_uV = 2750000,
+				.valid_modes_mask = REGULATOR_MODE_NORMAL,
+				.valid_ops_mask =
+				REGULATOR_CHANGE_VOLTAGE |
+				REGULATOR_CHANGE_STATUS,
+			},
+		},
+		/* External regulator interface. No fixed voltage specified */
+		{
+			.constraints = {
+				.name = "LDO_EXT",
+				.min_uV = 0,
+				.max_uV = 0,
+				.valid_modes_mask = REGULATOR_MODE_NORMAL,
+				.valid_ops_mask =
+				REGULATOR_CHANGE_STATUS,
+			},
+		},
+		{
+			.constraints = {
+				.name = "LDO_BUCK",
+				.min_uV = 0,
+				.max_uV = 1800000,
+				.valid_modes_mask = REGULATOR_MODE_NORMAL,
+				.valid_ops_mask =
+				REGULATOR_CHANGE_VOLTAGE |
+				REGULATOR_CHANGE_STATUS,
+				/* Used for powering DB chip */
+				.always_on = 1,
+			},
+		},
+	},
+	.regulator_init = u300_ab3100_regulator_init,
+};
+
 
 static struct i2c_board_info __initdata bus0_i2c_board_info[] = {
 	{
 		.type = "ab3100",
 		.addr = 0x48,
 		.irq = IRQ_U300_IRQ0_EXT,
+		.platform_data = &ab3100_plf_data,
 	},
 };
 
diff --git a/drivers/mfd/ab3100-core.c b/drivers/mfd/ab3100-core.c
index 098e825..54d3052 100644
--- a/drivers/mfd/ab3100-core.c
+++ b/drivers/mfd/ab3100-core.c
@@ -838,6 +838,8 @@ static int __init ab3100_probe(struct i2c_client *client,
 			const struct i2c_device_id *id)
 {
 	struct ab3100 *ab3100;
+	struct ab3100_platform_data *ab3100_plf_data =
+		client->dev.platform_data;
 	int err;
 	int i;
 
@@ -921,6 +923,8 @@ static int __init ab3100_probe(struct i2c_client *client,
 	for (i = 0; i < ARRAY_SIZE(ab3100_platform_devs); i++) {
 		ab3100_platform_devs[i]->dev.parent =
 			&client->dev;
+		ab3100_platform_devs[i]->dev.platform_data =
+			ab3100_plf_data;
 		platform_set_drvdata(ab3100_platform_devs[i], ab3100);
 	}
 
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index f431779..ebe7380 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -117,4 +117,13 @@ config REGULATOR_LP3971
 	 Say Y here to support the voltage regulators and convertors
 	 on National Semiconductors LP3971 PMIC
 
+config REGULATOR_AB3100
+	tristate "ST-Ericsson AB3100 Regulator functions"
+	depends on AB3100_CORE
+	default y if AB3100_CORE
+	help
+	 These regulators correspond to functionality in the
+	 AB3100 analog baseband dealing with power regulators
+	 for the system.
+
 endif
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 4d762c4..181555d 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -16,5 +16,6 @@ obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o
 obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o
 obj-$(CONFIG_REGULATOR_DA903X)	+= da903x.o
 obj-$(CONFIG_REGULATOR_PCF50633) += pcf50633-regulator.o
+obj-$(CONFIG_REGULATOR_AB3100) += ab3100.o
 
 ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG
diff --git a/drivers/regulator/ab3100.c b/drivers/regulator/ab3100.c
new file mode 100644
index 0000000..1defb33
--- /dev/null
+++ b/drivers/regulator/ab3100.c
@@ -0,0 +1,1012 @@
+/*
+ * drivers/regulator/ab3100.c
+ *
+ * Copyright (C) 2008-2009 ST-Ericsson AB
+ * License terms: GNU General Public License (GPL) version 2
+ * Low-level control of the AB3100 IC Low Dropout (LDO) regulators
+ * Author: Mattias Wallin <mattias.wallin@...ricsson.com>
+ * Author: Linus Walleij <linus.walleij@...ricsson.com>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/mfd/ab3100.h>
+
+/* LDO registers and some handy masking definitions for AB3100 */
+#define AB3100_LDO_A		0x40
+#define AB3100_LDO_C		0x41
+#define AB3100_LDO_D		0x42
+#define AB3100_LDO_E		0x43
+#define AB3100_LDO_ESLP		0x44
+#define AB3100_LDO_F		0x45
+#define AB3100_LDO_G		0x46
+#define AB3100_LDO_H		0x47
+#define AB3100_LDO_H_SLEEP_MODE	0
+#define AB3100_LDO_H_SLEEP_EN	2
+#define AB3100_LDO_ON		4
+#define AB3100_LDO_H_VSEL_AC	5
+#define AB3100_LDO_K		0x48
+#define AB3100_LDO_EXT		0x49
+#define AB3100_LDO_BUCK		0x4A
+#define AB3100_LDO_BUCK_SLEEP	0x4B
+#define AB3100_LDO_ON_MASK	0x10
+
+/*
+ * Initial settings of ab3100 registers.
+ * Common for below LDO regulator settings are that
+ * bit 7-5 controls voltage. Bit 4 turns regulator ON(1) or OFF(0).
+ * Bit 3-2 controls sleep enable and bit 1-0 controls sleep mode.
+ * See spec for more detailed info.
+ */
+
+/* LDO_A 0x16: 2.75V, ON, SLEEP_A, SLEEP OFF GND */
+#define LDO_A_SETTING		0x16
+/* LDO_C 0x10: 2.65V, ON, SLEEP_A or B, SLEEP full power */
+#define LDO_C_SETTING		0x10
+/* LDO_D 0x10: 2.65V, ON, sleep mode not used */
+#define LDO_D_SETTING		0x10
+/* LDO_E 0x10: 1.8V, ON, SLEEP_A or B, SLEEP full power */
+#define LDO_E_SETTING		0x10
+/* LDO_E SLEEP 0x00: 1.8V, not used, SLEEP_A or B, not used */
+#define LDO_ESLP_SETTING	0x00
+/* LDO_F 0xD0: 2.5V, ON, SLEEP_A or B, SLEEP full power */
+#define LDO_F_SETTING		0xD0
+/* LDO_G 0x00: 2.85V, OFF, SLEEP_A or B, SLEEP full power */
+#define LDO_G_SETTING		0x00
+/* LDO_H 0x18: 2.75V, ON, SLEEP_B, SLEEP full power */
+#define LDO_H_SETTING		0x18
+/* LDO_K 0x00: 2.75V, OFF, SLEEP_A or B, SLEEP full power */
+#define LDO_K_SETTING		0x00
+/* LDO_EXT 0x00: Voltage not set, OFF, not used, not used */
+#define LDO_EXT_SETTING		0x00
+/* LDO_BUCK 0x7D: 1.2V, ON, SLEEP_A and B, SLEEP low power */
+#define LDO_BUCK_SETTING	0x7D
+/* LDO_BUCK SLEEP 0xAC: 1.05V, Not used, SLEEP_A and B, Not used */
+#define LDO_BUCK_SLEEP_SETTING	0xAC
+
+/* Preset (hardware defined) voltages for these regulators */
+#define LDO_A_VOLTAGE 2750000
+#define LDO_C_VOLTAGE 2650000
+#define LDO_D_VOLTAGE 2650000
+/* Not defined by ab3100 so we return 0 */
+#define LDO_EXT_VOLTAGE 0
+
+struct ab3100_regulator {
+	struct platform_device *pdev;
+	struct ab3100 *ab3100;
+	struct ab3100_platform_data *plfdata;
+};
+
+static inline int ab3100_get_regid(struct regulator_dev *reg, u8 *regid)
+{
+	int ldo = rdev_get_id(reg);
+
+	if (ldo < AB3100_LDO_A || ldo > AB3100_LDO_BUCK_SLEEP) {
+		dev_err(&reg->dev, "wrong regulator id 0x%x\n", ldo);
+		return -EINVAL;
+	}
+	/* typecast int -> u8 should be ok with above check */
+	*regid = (u8)ldo;
+	return 0;
+}
+/*
+ * General functions for enable, disable and is_enabled used for
+ * LDO: A,C,E,F,G,H,K,EXT and BUCK
+ */
+static int ab3100_enable_regulator_LDO(struct regulator_dev *reg)
+{
+	struct ab3100 *ab3100 = reg->reg_data;
+	int err;
+	u8 regid, regval;
+
+	if (ab3100_get_regid(reg, &regid))
+		return -EINVAL;
+
+	err = ab3100_get_register_interruptible(ab3100, regid, &regval);
+	if (err) {
+		dev_warn(&reg->dev, "failed to get regid %d "
+			 "value\n", regid);
+		return err;
+	}
+
+	/* The regulator is already on, no reason to go further */
+	if (regval | AB3100_LDO_ON_MASK)
+		return 0;
+
+	regval |= AB3100_LDO_ON_MASK;
+
+	err = ab3100_set_register_interruptible(ab3100, regid, regval);
+	if (err) {
+		dev_warn(&reg->dev, "failed to set regid %d "
+			 "value\n", regid);
+		return err;
+	}
+
+	/* Per-regulator power on delay from spec */
+	switch (regid) {
+	case AB3100_LDO_A: /* Fallthrough */
+	case AB3100_LDO_C: /* Fallthrough */
+	case AB3100_LDO_D: /* Fallthrough */
+	case AB3100_LDO_E: /* Fallthrough */
+	case AB3100_LDO_H: /* Fallthrough */
+	case AB3100_LDO_K:
+		udelay(200);
+		break;
+	case AB3100_LDO_F:
+		udelay(600);
+		break;
+	case AB3100_LDO_G:
+		udelay(400);
+		break;
+	case AB3100_LDO_BUCK:
+		/* This supplies LEDs and is not time critical */
+		/* mdelay(1); */
+		break;
+	default:
+		break;
+	}
+
+	return err;
+}
+
+static int ab3100_disable_regulator_LDO(struct regulator_dev *reg)
+{
+	struct ab3100 *ab3100 = reg->reg_data;
+	int err;
+	u8 regid, regval;
+
+	if (ab3100_get_regid(reg, &regid))
+		return -EINVAL;
+
+	err = ab3100_get_register_interruptible(ab3100, regid, &regval);
+	if (err) {
+		if (err != -ERESTARTSYS)
+			dev_err(&reg->dev, "unable to get register 0x%x\n",
+				regid);
+		else
+			dev_info(&reg->dev, "iterrupted "
+				 "while getting register 0x%x\n", regid);
+		return err;
+	}
+	regval &= ~AB3100_LDO_ON_MASK;
+	return ab3100_set_register_interruptible(ab3100, regid, regval);
+}
+
+static int ab3100_is_enabled_regulator_LDO(struct regulator_dev *reg)
+{
+	struct ab3100 *ab3100 = reg->reg_data;
+	int err;
+	u8 regid, regval;
+
+	if (ab3100_get_regid(reg, &regid))
+		return -EINVAL;
+
+	err = ab3100_get_register_interruptible(ab3100, regid, &regval);
+	if (err) {
+		if (err != -ERESTARTSYS)
+			dev_err(&reg->dev, "unable to get register 0x%x\n",
+				regid);
+		else
+			dev_info(&reg->dev, "interrupted "
+				 "while getting register 0x%x\n", regid);
+		return err;
+	}
+
+	return regval & AB3100_LDO_ON_MASK;
+}
+
+/* Used for arrays of settings */
+struct ldo_setting {
+	u8 abreg;
+	u8 setting;
+};
+
+static const struct ldo_setting
+ldo_init_settings[] = {
+	{
+		.abreg = AB3100_LDO_A,
+		.setting = LDO_A_SETTING
+	}, {
+		.abreg = AB3100_LDO_C,
+		.setting = LDO_C_SETTING
+	}, {
+		.abreg = AB3100_LDO_E,
+		.setting = LDO_E_SETTING
+	}, {
+		.abreg = AB3100_LDO_ESLP,
+		.setting = LDO_ESLP_SETTING
+	}, {
+		.abreg = AB3100_LDO_F,
+		.setting = LDO_F_SETTING
+	}, {
+		.abreg = AB3100_LDO_G,
+		.setting = LDO_G_SETTING
+	}, {
+		.abreg = AB3100_LDO_H,
+		.setting = LDO_H_SETTING
+	}, {
+		.abreg = AB3100_LDO_K,
+		.setting = LDO_K_SETTING
+	}, {
+		.abreg = AB3100_LDO_EXT,
+		.setting = LDO_EXT_SETTING
+	}, {
+		.abreg = AB3100_LDO_BUCK,
+		.setting = LDO_BUCK_SETTING
+	}, {
+		.abreg = AB3100_LDO_BUCK_SLEEP,
+		.setting = LDO_BUCK_SLEEP_SETTING
+	}, {
+		/* LDO D must be initialized last. */
+		.abreg = AB3100_LDO_D,
+		.setting = LDO_D_SETTING
+	},
+};
+
+static int ab3100_disable_regulator_LDO_D(struct regulator_dev *reg)
+{
+	struct ab3100 *ab3100 = reg->reg_data;
+	int i;
+
+	/*
+	 * Set regulators to default values, ignore any errors,
+	 * we're going DOWN
+	 */
+	for (i = 0; i < ARRAY_SIZE(ldo_init_settings); i++)
+		(void) ab3100_set_register_interruptible(ab3100,
+					  ldo_init_settings[i].abreg,
+					  ldo_init_settings[i].setting);
+
+	/* Setting LDO D to 0x00 cuts the power to the CPU */
+	return ab3100_set_register_interruptible(ab3100,
+						 AB3100_LDO_D, 0x00U);
+}
+
+static int ab3100_get_voltage_regulator_LDO_ACD(struct regulator_dev *reg)
+{
+	u8 regid;
+	if (ab3100_get_regid(reg, &regid))
+		return -EINVAL;
+
+	switch (regid) {
+	case(AB3100_LDO_A):
+		return LDO_A_VOLTAGE;
+	case(AB3100_LDO_C):
+		return LDO_C_VOLTAGE;
+	case(AB3100_LDO_D):
+		return LDO_D_VOLTAGE;
+	default:
+		printk(KERN_ERR "AB3100: get voltage for " \
+		       "unknown regulator 0x%x\n", regid);
+		return 0;
+	}
+}
+
+static int ab3100_get_voltage_regulator_LDO_E_BUCK(struct regulator_dev *reg)
+{
+	struct ab3100 *ab3100 = reg->reg_data;
+	u8 regval;
+	int err;
+
+	err = ab3100_get_register_interruptible(ab3100,
+						AB3100_LDO_E, &regval);
+	if (err) {
+		dev_warn(&reg->dev, "failed to get LDO_EB regulator "
+			 "value\n");
+		return err;
+	}
+
+	regval &= 0xE0;
+	switch (regval >> 5) {
+	case 0:
+		return 1800000;
+	case 1:
+		return 1400000;
+	case 2:
+		return 1300000;
+	case 3:
+		return 1200000;
+	case 4:
+		return 1100000;
+	case 5:
+		return 1050000;
+	case 6:
+		return 900000;
+	case 7:
+		return 0;
+	default:
+		dev_warn(&reg->dev, "erroneous voltage in "
+			 "register LDO_EB %u\n", regval);
+		break;
+	}
+
+	return 0;
+}
+
+static int ab3100_set_voltage_regulator_LDO_E_BUCK(struct regulator_dev *reg,
+						   int min_uV, int max_uV)
+{
+	struct ab3100 *ab3100 = reg->reg_data;
+	int err, v = 0;
+	u8 regval;
+
+	err = ab3100_get_register_interruptible(ab3100,
+						AB3100_LDO_E, &regval);
+	if (err) {
+		dev_warn(&reg->dev, "failed to get LDO_EB "
+			 "regulator value\n");
+		return err;
+	}
+
+	if (min_uV < 900000)
+		v = 7;
+	else if (min_uV < 1050000)
+		v = 6;
+	else if (min_uV < 1100000)
+		v = 5;
+	else if (min_uV < 1200000)
+		v = 4;
+	else if (min_uV < 1300000)
+		v = 3;
+	else if (min_uV < 1400000)
+		v = 2;
+	else if (min_uV < 1800000)
+		v = 1;
+	else
+		v = 0;
+
+	regval &= ~0xE0;
+	regval |= (v << 5);
+	err = ab3100_set_register_interruptible(ab3100,
+						AB3100_LDO_E, regval);
+	if (err)
+		dev_warn(&reg->dev, "failed to set LDO_EB regulator "
+			 "value\n");
+	return err;
+}
+
+static int ab3100_get_voltage_regulator_LDO_F(struct regulator_dev *reg)
+{
+	struct ab3100 *ab3100 = reg->reg_data;
+	u8 regval;
+	int err;
+
+	err = ab3100_get_register_interruptible(ab3100,
+						AB3100_LDO_F, &regval);
+	if (err) {
+		dev_warn(&reg->dev, "failed to get LDO_F regulator value\n");
+		return err;
+	}
+
+	regval &= 0xE0;
+	switch (regval >> 5) {
+	case 0:
+		return 1800000;
+	case 1:
+		return 1400000;
+	case 2:
+		return 1300000;
+	case 3:
+		return 1200000;
+	case 4:
+		return 1100000;
+	case 5:
+		return 1050000;
+	case 6:
+		return 2500000;
+	case 7:
+		return 0;
+	default:
+		dev_warn(&reg->dev, "erroneous voltage in regulator "
+			 "LDO F %u\n", regval);
+		break;
+	}
+
+	return 0;
+}
+
+static int ab3100_set_voltage_regulator_LDO_F(struct regulator_dev *reg,
+					      int min_uV, int max_uV)
+{
+	struct ab3100 *ab3100 = reg->reg_data;
+	int err, v = 0;
+	u8 regval;
+
+	err = ab3100_get_register_interruptible(ab3100,
+						AB3100_LDO_F, &regval);
+	if (err) {
+		dev_warn(&reg->dev, "failed to get LDO_F regulator "
+		       "value\n");
+		return err;
+	}
+
+	if (min_uV < 1050000)
+		v = 7;
+	else if (min_uV < 1100000)
+		v = 5;
+	else if (min_uV < 1200000)
+		v = 4;
+	else if (min_uV < 1300000)
+		v = 3;
+	else if (min_uV < 1400000)
+		v = 2;
+	else if (min_uV < 1800000)
+		v = 1;
+	else if (min_uV < 2500000)
+		v = 0;
+	else
+		v = 6;
+
+	regval &= ~0xE0;
+	regval |= (v << 5);
+	err = ab3100_set_register_interruptible(ab3100,
+						AB3100_LDO_F, regval);
+	if (err)
+		dev_warn(&reg->dev, "failed to set LDO_F regulator "
+			 "value\n");
+	return err;
+}
+
+static int ab3100_get_voltage_regulator_LDO_G(struct regulator_dev *reg)
+{
+	struct ab3100 *ab3100 = reg->reg_data;
+	u8 regval;
+	int err;
+
+	err = ab3100_get_register_interruptible(ab3100,
+						AB3100_LDO_G, &regval);
+	if (err) {
+		dev_warn(&reg->dev, "failed to get LDO_G regulator value\n");
+		return err;
+	}
+
+	regval &= 0xE0;
+	switch (regval >> 5) {
+	case 0:
+		return 2850000;
+	case 1:
+		return 2750000;
+	case 2:
+		return 1800000;
+	case 3:
+		return 1500000;
+	case 7:
+		return 0;
+	default:
+		dev_warn(&reg->dev, "errnoeous voltage register in LDO_G %u\n",
+		       regval);
+		break;
+	}
+
+	return 0;
+}
+
+static int ab3100_set_voltage_regulator_LDO_G(struct regulator_dev *reg,
+					      int min_uV, int max_uV)
+{
+	struct ab3100 *ab3100 = reg->reg_data;
+	int err, v = 0;
+	u8 regval;
+
+	err = ab3100_get_register_interruptible(ab3100,
+						AB3100_LDO_G, &regval);
+	if (err) {
+		dev_warn(&reg->dev, "failed to get LDO_G regulator value\n");
+		return err;
+	}
+
+	if (min_uV < 1500000)
+		v = 7;
+	else if (min_uV < 1800000)
+		v = 3;
+	else if (min_uV < 2750000)
+		v = 2;
+	else if (min_uV < 2850000)
+		v = 1;
+	else
+		v = 0;
+
+	regval &= ~0xE0;
+	regval |= (v << 5);
+	err = ab3100_set_register_interruptible(ab3100,
+						AB3100_LDO_G, regval);
+	if (err)
+		dev_warn(&reg->dev, "failed to set LDO_G regulator value\n");
+	return err;
+}
+
+static int ab3100_get_voltage_regulator_LDO_H(struct regulator_dev *reg)
+{
+	struct ab3100 *ab3100 = reg->reg_data;
+	u8 regval;
+	int err;
+
+	err = ab3100_get_register_interruptible(ab3100,
+						AB3100_LDO_H, &regval);
+	if (err) {
+		dev_warn(&reg->dev, "failed to get LDO_H regulator value\n");
+		return err;
+	}
+
+	regval &= 0xE0;
+	switch (regval >> 5) {
+	case 0:
+		return 2750000;
+	case 1:
+		return 1800000;
+	case 2:
+		return 1500000;
+	case 3:
+		return 1200000;
+	case 7:
+		return 0;
+	default:
+		dev_warn(&reg->dev, "erroneous voltage register LDO_H %u\n",
+		       regval);
+		break;
+	}
+
+	return 0;
+}
+
+static int ab3100_set_voltage_regulator_LDO_H(struct regulator_dev *reg,
+					      int min_uV, int max_uV)
+{
+	struct ab3100 *ab3100 = reg->reg_data;
+	int err, v = 0;
+	u8 regval;
+
+	err = ab3100_get_register_interruptible(ab3100,
+						AB3100_LDO_H, &regval);
+	if (err) {
+		dev_warn(&reg->dev, "failed to get LDO_H regulator value\n");
+		return err;
+	}
+
+	if (min_uV < 1200000)
+		v = 7;
+	else if (min_uV < 1500000)
+		v = 3;
+	else if (min_uV < 1800000)
+		v = 2;
+	else if (min_uV < 2750000)
+		v = 1;
+	else
+		v = 0;
+
+	regval &= ~0xE0;
+	regval |= (v << 5);
+	err = ab3100_set_register_interruptible(ab3100,
+						AB3100_LDO_H, regval);
+	if (err)
+		dev_warn(&reg->dev, "failed to set LDO H regulator value\n");
+	return err;
+}
+
+static int ab3100_get_voltage_regulator_LDO_K(struct regulator_dev *reg)
+{
+	struct ab3100 *ab3100 = reg->reg_data;
+	u8 regval;
+	int err;
+
+	err = ab3100_get_register_interruptible(ab3100,
+						AB3100_LDO_K, &regval);
+	if (err) {
+		dev_warn(&reg->dev, "failed to get LDO_K regulator value\n");
+		return err;
+	}
+
+	regval &= 0xE0;
+	switch (regval >> 5) {
+	case 0:
+		return 2750000;
+	case 1:
+		return 1800000;
+	case 3:
+		return 0;
+	default:
+		dev_warn(&reg->dev,
+			 "erroneous voltage in register LDO_K %u\n",
+			 regval);
+		break;
+	}
+
+	return 0;
+}
+
+static int ab3100_set_voltage_regulator_LDO_K(struct regulator_dev *reg,
+					      int min_uV, int max_uV)
+{
+	struct ab3100 *ab3100 = reg->reg_data;
+	int err, v = 0;
+	u8 regval;
+
+	err = ab3100_get_register_interruptible(ab3100,
+						AB3100_LDO_K, &regval);
+	if (err) {
+		dev_warn(&reg->dev, "failed to get LDO_K regulator value\n");
+		return err;
+	}
+
+	if (min_uV < 1800000)
+		v = 3;
+	else if (min_uV < 2750000)
+		v = 1;
+	else
+		v = 0;
+
+	regval &= ~0xE0;
+	regval |= (v << 5);
+	err = ab3100_set_register_interruptible(ab3100,
+						AB3100_LDO_K, regval);
+	if (err)
+		dev_warn(&reg->dev, "failed to set MMC regulator value\n");
+	return err;
+}
+
+static int ab3100_get_voltage_regulator_LDO_EXT(struct regulator_dev *reg)
+{
+	/* Default return zero because it's unknown */
+	return 0;
+}
+
+static struct regulator_ops regulator_ops_LDO_AC = {
+	.enable      = ab3100_enable_regulator_LDO,
+	.disable     = ab3100_disable_regulator_LDO,
+	.is_enabled  = ab3100_is_enabled_regulator_LDO,
+	.get_voltage = ab3100_get_voltage_regulator_LDO_ACD,
+};
+
+static struct regulator_ops regulator_ops_LDO_D = {
+	/* LDO D cannot be enabled, this is done during set-up */
+	.disable     = ab3100_disable_regulator_LDO_D,
+	.is_enabled  = ab3100_is_enabled_regulator_LDO,
+	.get_voltage = ab3100_get_voltage_regulator_LDO_ACD,
+};
+
+static struct regulator_ops regulator_ops_LDO_E = {
+	.enable      = ab3100_enable_regulator_LDO,
+	.disable     = ab3100_disable_regulator_LDO,
+	.is_enabled  = ab3100_is_enabled_regulator_LDO,
+	.get_voltage = ab3100_get_voltage_regulator_LDO_E_BUCK,
+	.set_voltage = ab3100_set_voltage_regulator_LDO_E_BUCK,
+};
+
+static struct regulator_ops regulator_ops_LDO_F = {
+	.enable      = ab3100_enable_regulator_LDO,
+	.disable     = ab3100_disable_regulator_LDO,
+	.is_enabled  = ab3100_is_enabled_regulator_LDO,
+	.get_voltage = ab3100_get_voltage_regulator_LDO_F,
+	.set_voltage = ab3100_set_voltage_regulator_LDO_F,
+};
+
+static struct regulator_ops regulator_ops_LDO_G = {
+	.enable      = ab3100_enable_regulator_LDO,
+	.disable     = ab3100_disable_regulator_LDO,
+	.is_enabled  = ab3100_is_enabled_regulator_LDO,
+	.get_voltage = ab3100_get_voltage_regulator_LDO_G,
+	.set_voltage = ab3100_set_voltage_regulator_LDO_G,
+};
+
+static struct regulator_ops regulator_ops_LDO_H = {
+	.enable      = ab3100_enable_regulator_LDO,
+	.disable     = ab3100_disable_regulator_LDO,
+	.is_enabled  = ab3100_is_enabled_regulator_LDO,
+	.get_voltage = ab3100_get_voltage_regulator_LDO_H,
+	.set_voltage = ab3100_set_voltage_regulator_LDO_H,
+};
+
+static struct regulator_ops regulator_ops_LDO_K = {
+	.enable      = ab3100_enable_regulator_LDO,
+	.disable     = ab3100_disable_regulator_LDO,
+	.is_enabled  = ab3100_is_enabled_regulator_LDO,
+	.get_voltage = ab3100_get_voltage_regulator_LDO_K,
+	.set_voltage = ab3100_set_voltage_regulator_LDO_K,
+};
+
+/*
+ * LDO EXT is an external regulator so it is really
+ * not possible to get or set any voltage here, AB3100
+ * acts as a mere on/off switch for this regulator.
+ */
+static struct regulator_ops regulator_ops_LDO_EXT = {
+	.enable      = ab3100_enable_regulator_LDO,
+	.disable     = ab3100_disable_regulator_LDO,
+	.is_enabled  = ab3100_is_enabled_regulator_LDO,
+	.get_voltage = ab3100_get_voltage_regulator_LDO_EXT,
+};
+
+static struct regulator_ops regulator_ops_LDO_BUCK = {
+	.enable      = ab3100_enable_regulator_LDO,
+	.disable     = ab3100_disable_regulator_LDO,
+	.is_enabled  = ab3100_is_enabled_regulator_LDO,
+	.get_voltage = ab3100_get_voltage_regulator_LDO_E_BUCK,
+	.set_voltage = ab3100_set_voltage_regulator_LDO_E_BUCK,
+};
+
+static struct regulator_desc regulator_desc_ldo[AB3100_NUM_REGULATORS] = {
+	{
+		.name = "LDO_A",
+		.id   = AB3100_LDO_A,
+		.ops  = &regulator_ops_LDO_AC,
+		.type = REGULATOR_VOLTAGE,
+	},
+	{
+		.name = "LDO_C",
+		.id   = AB3100_LDO_C,
+		.ops  = &regulator_ops_LDO_AC,
+		.type = REGULATOR_VOLTAGE,
+	},
+	{
+		.name = "LDO_D",
+		.id   = AB3100_LDO_D,
+		.ops  = &regulator_ops_LDO_D,
+		.type = REGULATOR_VOLTAGE,
+	},
+	{
+		.name = "LDO_E",
+		.id   = AB3100_LDO_E,
+		.ops  = &regulator_ops_LDO_E,
+		.type = REGULATOR_VOLTAGE,
+	},
+	{
+		.name = "LDO_F",
+		.id   = AB3100_LDO_F,
+		.ops  = &regulator_ops_LDO_F,
+		.type = REGULATOR_VOLTAGE,
+	},
+	{
+		.name = "LDO_G",
+		.id   = AB3100_LDO_G,
+		.ops  = &regulator_ops_LDO_G,
+		.type = REGULATOR_VOLTAGE,
+	},
+	{
+		.name = "LDO_H",
+		.id   = AB3100_LDO_H,
+		.ops  = &regulator_ops_LDO_H,
+		.type = REGULATOR_VOLTAGE,
+	},
+	{
+		.name = "LDO_K",
+		.id   = AB3100_LDO_K,
+		.ops  = &regulator_ops_LDO_K,
+		.type = REGULATOR_VOLTAGE,
+	},
+	{
+		.name = "LDO_EXT",
+		.id   = AB3100_LDO_EXT,
+		.ops  = &regulator_ops_LDO_EXT,
+		.type = REGULATOR_VOLTAGE,
+	},
+	{
+		.name = "LDO_BUCK",
+		.id   = AB3100_LDO_BUCK,
+		.ops  = &regulator_ops_LDO_BUCK,
+		.type = REGULATOR_VOLTAGE,
+	},
+};
+
+static int __init ab3100_regulator_probe(struct platform_device *pdev)
+{
+	struct ab3100_regulator *reg = platform_get_drvdata(pdev);
+	struct ab3100 *ab3100 = reg->ab3100;
+	struct regulator_dev *rdev;
+	int err = 0;
+
+	rdev = regulator_register(&regulator_desc_ldo[pdev->id],
+				  &pdev->dev,
+				  &reg->plfdata->reg_constraints[pdev->id],
+				  /*
+				   * The regulators only really need
+				   * a pointer back to our MFD AB3100
+				   * driver so use this as reg_data
+				   */
+				  ab3100);
+	if (IS_ERR(rdev)) {
+		err = PTR_ERR(rdev);
+		dev_err(&pdev->dev, "%s: failed to register regulator"
+		       " %s err %d\n",
+		       __func__, regulator_desc_ldo[pdev->id].name,
+		       err);
+	}
+
+	/*
+	 * At last set up the board routings, constraints etc
+	 * for the regulator.
+	 */
+	if (reg->plfdata->regulator_init)
+		err = reg->plfdata->regulator_init(rdev);
+
+	return err;
+}
+
+static int __exit ab3100_regulator_remove(struct platform_device *pdev)
+{
+	struct regulator_dev *rdev = platform_get_drvdata(pdev);
+
+	regulator_unregister(rdev);
+	return 0;
+}
+
+static struct platform_driver ab3100_regulator_driver = {
+	.driver = {
+		.name  = "ab3100-regulator",
+		.owner = THIS_MODULE,
+	},
+	.remove = __exit_p(ab3100_regulator_remove),
+};
+
+
+/*
+ * This creates a platform device for a regulator and
+ * registers it to the platform devices.
+ */
+static int __init ab3100_add_regulator_pdev(struct device *parent,
+				    struct ab3100_regulator *rdev,
+				    struct ab3100 *ab3100,
+				    struct ab3100_platform_data *plfdata,
+				    int regid)
+{
+	struct platform_device *pdev;
+	int ret;
+
+	pdev = platform_device_alloc("ab3100-regulator", regid);
+	if (!pdev)
+		return -ENOMEM;
+
+	/*
+	 * Initialize per-regulator struct.
+	 * Inherit platform data, this comes down hierarchally
+	 * from regulators (pluralis), from ab3100 core, from
+	 * i2c boarddata, from the machine. So if you want to
+	 * see what it looks like for a certain machine, go
+	 * into the machine I2C setup.
+	 */
+	rdev->ab3100 = ab3100;
+	rdev->plfdata = plfdata;
+	platform_set_drvdata(pdev, rdev);
+
+	ret = platform_device_add(pdev);
+	if (ret != 0) {
+		dev_err(parent,
+			"Failed to register regulator: %d err %d\n",
+			regid, ret);
+		platform_device_del(pdev);
+	}
+	rdev->pdev = pdev;
+	/* This would seem logical but makes everything break... */
+	/* pdev->dev.parent = parent; */
+
+	return ret;
+}
+
+/* Struct containing the regulator sub-platform devices */
+static struct ab3100_regulator ab3100_regulators[AB3100_NUM_REGULATORS];
+
+/*
+ * NOTE: the following functions are regulators pluralis - it is the
+ * binding to the AB3100 core driver and the parent platform device
+ * for all the different regulators.
+ */
+
+static int __init ab3100_regulators_probe(struct platform_device *pdev)
+{
+	struct ab3100_platform_data *plfdata = pdev->dev.platform_data;
+	struct ab3100 *ab3100 = platform_get_drvdata(pdev);
+	int err = 0;
+	u8 data;
+	int i;
+
+	/* Check chip state */
+	err = ab3100_get_register_interruptible(ab3100,
+						AB3100_LDO_D, &data);
+	if (err) {
+		dev_err(&pdev->dev, "AB3100 regulator: "
+			"Could not read initial status "
+			"of LDO_D\n");
+		return err;
+	} else {
+		if (data & 0x10) {
+			dev_notice(&pdev->dev, "AB3100 regulator LDO D: "
+				   "chip is already in active "
+				   "mode (Warm start)\n");
+		} else {
+			dev_notice(&pdev->dev, "AB3100 regulator LDO D: "
+				   "chip is in inactive mode "
+				   "(Cold start)\n");
+		}
+	}
+
+	/* Set up regulators */
+	for (i = 0; i < ARRAY_SIZE(ldo_init_settings); i++) {
+		err = ab3100_set_register_interruptible(ab3100,
+					  ldo_init_settings[i].abreg,
+					  ldo_init_settings[i].setting);
+		if (err == -ERESTARTSYS) {
+			dev_err(&pdev->dev, "regulator initialization "
+			       "interrupted by system restart");
+			return err;
+		} else if (err != 0) {
+			dev_err(&pdev->dev, "regulator initialization "
+			       "failed with error %d\n",
+			       err);
+			return err;
+		}
+	}
+
+	/* Add platform devices for the regulators if not already done */
+	for (i = 0; i < AB3100_NUM_REGULATORS; i++) {
+		struct ab3100_regulator *rdev = &ab3100_regulators[i];
+
+		if (rdev->pdev)
+			/* Already registered */
+			continue;
+
+		err = ab3100_add_regulator_pdev(&pdev->dev,
+						rdev,
+						ab3100,
+						plfdata,
+						i);
+		if (err) {
+			dev_err(&pdev->dev, "register regulator %d failed "
+				"err %d\n", i, err);
+			return err;
+		}
+	}
+
+	/*
+	 * This makes the core shut down all unused regulators
+	 * after all the initcalls have completed.
+	 */
+	regulator_has_full_constraints();
+
+	/* Add the common platform driver for all regulators */
+	return platform_driver_probe(&ab3100_regulator_driver,
+				     ab3100_regulator_probe);
+}
+
+static int __exit ab3100_regulators_remove(struct platform_device *pdev)
+{
+	/* Remove driver for the regulators */
+	platform_driver_unregister(&ab3100_regulator_driver);
+	/* Keep the platform devices */
+	return 0;
+}
+
+static struct platform_driver ab3100_regulators_driver = {
+	.driver = {
+		.name  = "ab3100-regulators",
+		.owner = THIS_MODULE,
+	},
+	.remove = __exit_p(ab3100_regulators_remove),
+};
+
+static __init int ab3100_regulators_init(void)
+{
+	return platform_driver_probe(&ab3100_regulators_driver,
+				     ab3100_regulators_probe);
+}
+
+static __exit void ab3100_regulators_exit(void)
+{
+	platform_driver_unregister(&ab3100_regulators_driver);
+}
+
+/*
+ * This needs to be an fs_initcall() because the
+ * AB3100 core driver that registers the regulators
+ * device comes in at subsys_initcall() later than
+ * this driver due to link order.
+ */
+fs_initcall(ab3100_regulators_init);
+module_exit(ab3100_regulators_exit);
+
+MODULE_AUTHOR("Mattias Wallin <mattias.wallin@...ricsson.com>");
+MODULE_DESCRIPTION("AB3100 Regulator driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/ab3100.h b/include/linux/mfd/ab3100.h
index 56343b8..7d04435 100644
--- a/include/linux/mfd/ab3100.h
+++ b/include/linux/mfd/ab3100.h
@@ -6,6 +6,7 @@
  */
 
 #include <linux/device.h>
+#include <linux/regulator/machine.h>
 
 #ifndef MFD_AB3100_H
 #define MFD_AB3100_H
@@ -56,6 +57,12 @@
 #define AB3100_STR_BATT_REMOVAL				(0x40)
 #define AB3100_STR_VBUS					(0x80)
 
+/*
+ * AB3100 contains 8 regulators, one external regulator controller
+ * and a buck converter
+ */
+#define AB3100_NUM_REGULATORS				10
+
 /**
  * struct ab3100
  * @access_mutex: lock out concurrent accesses to the AB3100 registers
@@ -86,6 +93,18 @@ struct ab3100 {
 	bool startup_events_read;
 };
 
+/**
+ * struct ab3100_platform_data
+ * Data supplied to initialize board connections to the AB3100
+ * @reg_constraints: regulator constraints for target board
+ * @regulator_init: init function to set up the regulators,
+ *     when the regulators are initialized, this will be called
+ */
+struct ab3100_platform_data {
+	struct regulator_init_data reg_constraints[AB3100_NUM_REGULATORS];
+	int (*regulator_init)(void *data);
+};
+
 int ab3100_set_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 regval);
 int ab3100_get_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 *regval);
 int ab3100_get_register_page_interruptible(struct ab3100 *ab3100,
-- 
1.6.2.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ