[prev in list] [next in list] [prev in thread] [next in thread] 

List:       linux-i2c
Subject:    [i2c] [PATCH 5/12] neo1973: pcf50606 power management unit driver
From:       Harald Welte <laforge () openmoko ! org>
Date:       2007-12-18 11:07:24
Message-ID: 20071218110724.GL29882 () prithivi ! gnumonks ! org
[Download RAW message or body]

This is a Linux kernel driver for the NXP PCF50606 power management
unit, which is used in the FIC/OpenMoko Neo1973 GTA01 GSM phone.

Signed-off-by: Harald Welte <laforge@openmoko.org>

---

Index: linux-2.6/drivers/i2c/chips/pcf50606.c
===================================================================
--- /dev/null
+++ linux-2.6/drivers/i2c/chips/pcf50606.c
@@ -0,0 +1,1946 @@
+/* Philips/NXP PCF50606 Power Management Unit (PMU) driver
+ *
+ * (C) 2006-2007 by OpenMoko, Inc.
+ * Authors: Harald Welte <laforge@openmoko.org>,
+ *	    Matt Hsu <matt@openmoko.org>
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ * This driver is a monster ;) It provides the following features
+ * - voltage control for a dozen different voltage domains
+ * - charging control for main and backup battery
+ * - rtc / alarm
+ * - watchdog
+ * - adc driver (hw_sensors like)
+ * - pwm driver
+ * - backlight
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/workqueue.h>
+#include <linux/rtc.h>
+#include <linux/bcd.h>
+#include <linux/watchdog.h>
+#include <linux/miscdevice.h>
+#include <linux/input.h>
+#include <linux/fb.h>
+#include <linux/backlight.h>
+#include <linux/sched.h>
+#include <linux/platform_device.h>
+#include <linux/pcf50606.h>
+#include <linux/apm-emulation.h>
+
+#include <asm/mach-types.h>
+#include <asm/arch/gta01.h>
+
+#include "pcf50606.h"
+
+/* we use dev_dbg() throughout the code, but sometimes don't want to
+ * write an entire line of debug related information.  This DEBUGPC
+ * macro is a continuation for dev_dbg() */
+#ifdef DEBUG
+#define DEBUGPC(x, args ...) printk(x, ## args)
+#else
+#define DEBUGPC(x, args ...)
+#endif
+
+/***********************************************************************
+ * Static data / structures
+ ***********************************************************************/
+
+static unsigned short normal_i2c[] = { 0x08, I2C_CLIENT_END };
+
+I2C_CLIENT_INSMOD_1(pcf50606);
+
+#define PCF50606_F_CHG_FAST	0x00000001	/* Charger Fast allowed */
+#define PCF50606_F_CHG_PRESENT	0x00000002	/* Charger present */
+#define PCF50606_F_CHG_FOK	0x00000004	/* Fast OK for battery */
+#define PCF50606_F_CHG_ERR	0x00000008	/* Charger Error */
+#define PCF50606_F_CHG_PROT	0x00000010	/* Charger Protection */
+#define PCF50606_F_CHG_READY	0x00000020	/* Charging completed */
+#define PCF50606_F_CHG_MASK	0x000000fc
+
+#define PCF50606_F_PWR_PRESSED	0x00000100
+#define PCF50606_F_RTC_SECOND	0x00000200
+
+enum close_state {
+	CLOSE_STATE_NOT,
+	CLOSE_STATE_ALLOW = 0x2342,
+};
+
+struct pcf50606_data {
+	struct i2c_client client;
+	struct pcf50606_platform_data *pdata;
+	struct backlight_device *backlight;
+	struct mutex lock;
+	unsigned int flags;
+	unsigned int working;
+	struct work_struct work;
+	struct rtc_device *rtc;
+	struct input_dev *input_dev;
+	int allow_close;
+	int onkey_seconds;
+	int irq;
+#ifdef CONFIG_PM
+	struct {
+		u_int8_t dcdc1, dcdc2;
+		u_int8_t dcdec1;
+		u_int8_t dcudc1;
+		u_int8_t ioregc;
+		u_int8_t d1regc1;
+		u_int8_t d2regc1;
+		u_int8_t d3regc1;
+		u_int8_t lpregc1;
+		u_int8_t adcc1, adcc2;
+		u_int8_t pwmc1;
+		u_int8_t int1m, int2m, int3m;
+	} standby_regs;
+#endif
+};
+
+static struct i2c_driver pcf50606_driver;
+
+/* This is an ugly construct on how to access the (currently single/global)
+ * pcf50606 handle from other code in the kernel.  I didn't really come up with
+ * a more decent method of dynamically resolving this */
+struct pcf50606_data *pcf50606_global;
+EXPORT_SYMBOL_GPL(pcf50606_global);
+
+static struct platform_device *pcf50606_pdev;
+
+/* This is a mitsubishi TN11-3H103J T,B NTC Thermistor -10..79 centigrade */
+static const u_int16_t ntc_table_tn11_3h103j[] = {
+	/* -10 */
+	40260, 38560, 36950, 35410, 33950, 32550, 31220, 29960, 28750, 27590,
+	26490, 25440, 24440, 23480, 22560, 21680, 20830, 20020, 19240, 18500,
+	17780, 17710, 16440, 15810, 15210, 14630, 14070, 13540, 13030, 12540,
+	12070, 11620, 11190, 10780, 10380, 10000, 9635, 9286, 8950, 8629,
+	8320, 8024, 7740, 7467, 7205, 6954, 6713, 6481, 6258, 6044,
+	5839, 5641, 5451, 5269, 5093, 4924, 4762, 4605, 4455, 4310,
+	4171, 4037, 3908, 3784, 3664, 3549, 3438, 3313, 3227, 3128,
+	3032, 2939, 2850, 2763, 2680, 2600, 2522, 2448, 2375, 2306,
+	2239, 2174, 2111, 2050, 1922, 1935, 1881, 1828, 1776, 1727,
+};
+
+
+/***********************************************************************
+ * Low-Level routines
+ ***********************************************************************/
+
+static inline int __reg_write(struct pcf50606_data *pcf, u_int8_t reg,
+			      u_int8_t val)
+{
+	return i2c_smbus_write_byte_data(&pcf->client, reg, val);
+}
+
+static int reg_write(struct pcf50606_data *pcf, u_int8_t reg, u_int8_t val)
+{
+	int ret;
+
+	mutex_lock(&pcf->lock);
+	ret = __reg_write(pcf, reg, val);
+	mutex_unlock(&pcf->lock);
+
+	return ret;
+}
+
+static inline int32_t __reg_read(struct pcf50606_data *pcf, u_int8_t reg)
+{
+	int32_t ret;
+
+	ret = i2c_smbus_read_byte_data(&pcf->client, reg);
+
+	return ret;
+}
+
+static u_int8_t reg_read(struct pcf50606_data *pcf, u_int8_t reg)
+{
+	int32_t ret;
+
+	mutex_lock(&pcf->lock);
+	ret = __reg_read(pcf, reg);
+	mutex_unlock(&pcf->lock);
+
+	return ret & 0xff;
+}
+
+static int reg_set_bit_mask(struct pcf50606_data *pcf,
+			    u_int8_t reg, u_int8_t mask, u_int8_t val)
+{
+	int ret;
+	u_int8_t tmp;
+
+	val &= mask;
+
+	mutex_lock(&pcf->lock);
+
+	tmp = __reg_read(pcf, reg);
+	tmp &= ~mask;
+	tmp |= val;
+	ret = __reg_write(pcf, reg, tmp);
+
+	mutex_unlock(&pcf->lock);
+
+	return ret;
+}
+
+static int reg_clear_bits(struct pcf50606_data *pcf, u_int8_t reg, u_int8_t val)
+{
+	int ret;
+	u_int8_t tmp;
+
+	mutex_lock(&pcf->lock);
+
+	tmp = __reg_read(pcf, reg);
+	tmp &= ~val;
+	ret = __reg_write(pcf, reg, tmp);
+
+	mutex_unlock(&pcf->lock);
+
+	return ret;
+}
+
+/* synchronously read one ADC channel (busy-wait for result to be complete) */
+static u_int16_t adc_read(struct pcf50606_data *pcf,  int channel,
+			  u_int16_t *data2)
+{
+	u_int8_t adcs2, adcs1;
+	u_int16_t ret;
+
+	dev_dbg(&pcf->client.dev, "entering (pcf=%p, channel=%u, data2=%p)\n",
+		pcf, channel, data2);
+
+	channel &= PCF50606_ADCC2_ADCMUX_MASK;
+
+	mutex_lock(&pcf->lock);
+
+	/* start ADC conversion of selected channel */
+	__reg_write(pcf, PCF50606_REG_ADCC2, channel |
+		    PCF50606_ADCC2_ADCSTART | PCF50606_ADCC2_RES_10BIT);
+
+	do {
+		adcs2 = __reg_read(pcf, PCF50606_REG_ADCS2);
+	} while (!(adcs2 & PCF50606_ADCS2_ADCRDY));
+
+	adcs1 = __reg_read(pcf, PCF50606_REG_ADCS1);
+	ret = (adcs1 << 2) | (adcs2 & 0x03);
+
+	if (data2) {
+		adcs1 = __reg_read(pcf, PCF50606_REG_ADCS3);
+		*data2 = (adcs1 << 2) | (adcs2 & 0x0c);
+	}
+
+	mutex_unlock(&pcf->lock);
+
+	dev_dbg(&pcf->client.dev, "returning %u %u\n", ret,
+		data2 ? *data2 : 0);
+
+	return ret;
+}
+
+/***********************************************************************
+ * Voltage / ADC
+ ***********************************************************************/
+
+static u_int8_t dcudc_voltage(unsigned int millivolts)
+{
+	if (millivolts < 900)
+		return 0;
+	if (millivolts > 5500)
+		return 0x1f;
+	if (millivolts <= 3300) {
+		millivolts -= 900;
+		return millivolts/300;
+	}
+	if (millivolts < 4000)
+		return 0x0f;
+	else {
+		millivolts -= 4000;
+		return millivolts/100;
+	}
+}
+
+static unsigned int dcudc_2voltage(u_int8_t bits)
+{
+	bits &= 0x1f;
+	if (bits < 0x08)
+		return 900 + bits * 300;
+	else if (bits < 0x10)
+		return 3300;
+	else
+		return 4000 + bits * 100;
+}
+
+static u_int8_t dcdec_voltage(unsigned int millivolts)
+{
+	if (millivolts < 900)
+		return 0;
+	else if (millivolts > 3300)
+		return 0x0f;
+
+	millivolts -= 900;
+	return millivolts/300;
+}
+
+static unsigned int dcdec_2voltage(u_int8_t bits)
+{
+	bits &= 0x0f;
+	return 900 + bits*300;
+}
+
+static u_int8_t dcdc_voltage(unsigned int millivolts)
+{
+	if (millivolts < 900)
+		return 0;
+	else if (millivolts > 3600)
+		return 0x1f;
+
+	if (millivolts < 1500) {
+		millivolts -= 900;
+		return millivolts/25;
+	} else {
+		millivolts -= 1500;
+		return 0x18 + millivolts/300;
+	}
+}
+
+static unsigned int dcdc_2voltage(u_int8_t bits)
+{
+	bits &= 0x1f;
+	if ((bits & 0x18) == 0x18)
+		return 1500 + ((bits & 0x7) * 300);
+	else
+		return 900 + (bits * 25);
+}
+
+static u_int8_t dx_voltage(unsigned int millivolts)
+{
+	if (millivolts < 900)
+		return 0;
+	else if (millivolts > 3300)
+		return 0x18;
+
+	millivolts -= 900;
+	return millivolts/100;
+}
+
+static unsigned int dx_2voltage(u_int8_t bits)
+{
+	bits &= 0x1f;
+	return 900 + (bits * 100);
+}
+
+static const u_int8_t regulator_registers[__NUM_PCF50606_REGULATORS] = {
+	[PCF50606_REGULATOR_DCD]	= PCF50606_REG_DCDC1,
+	[PCF50606_REGULATOR_DCDE]	= PCF50606_REG_DCDEC1,
+	[PCF50606_REGULATOR_DCUD]	= PCF50606_REG_DCUDC1,
+	[PCF50606_REGULATOR_D1REG]	= PCF50606_REG_D1REGC1,
+	[PCF50606_REGULATOR_D2REG]	= PCF50606_REG_D2REGC1,
+	[PCF50606_REGULATOR_D3REG]	= PCF50606_REG_D3REGC1,
+	[PCF50606_REGULATOR_LPREG]	= PCF50606_REG_LPREGC1,
+	[PCF50606_REGULATOR_IOREG]	= PCF50606_REG_IOREGC,
+};
+
+int pcf50606_onoff_set(struct pcf50606_data *pcf,
+		       enum pcf50606_regulator_id reg, int on)
+{
+	u_int8_t addr;
+
+	if (reg >= __NUM_PCF50606_REGULATORS)
+		return -EINVAL;
+
+	/* IOREG cannot be powered off since it powers the PMU I2C */
+	if (reg == PCF50606_REGULATOR_IOREG)
+		return -EIO;
+
+	addr = regulator_registers[reg];
+
+	if (on == 0)
+		reg_set_bit_mask(pcf, addr, 0xe0, 0x00);
+	else
+		reg_set_bit_mask(pcf, addr, 0xe0, 0xe0);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(pcf50606_onoff_set);
+
+int pcf50606_onoff_get(struct pcf50606_data *pcf,
+		       enum pcf50606_regulator_id reg)
+{
+	u_int8_t val, addr;
+
+	if (reg >= __NUM_PCF50606_REGULATORS)
+		return -EINVAL;
+
+	addr = regulator_registers[reg];
+	val = (reg_read(pcf, addr) & 0xe0) >> 5;
+
+	/* PWREN1 = 1, PWREN2 = 1, see table 16 of datasheet */
+	switch (val) {
+	case 0:
+	case 5:
+		return 0;
+	default:
+		return 1;
+	}
+}
+EXPORT_SYMBOL_GPL(pcf50606_onoff_get);
+
+int pcf50606_voltage_set(struct pcf50606_data *pcf,
+			 enum pcf50606_regulator_id reg,
+			 unsigned int millivolts)
+{
+	u_int8_t volt_bits;
+	u_int8_t regnr;
+	int rc;
+
+	dev_dbg(&pcf->client.dev, "pcf=%p, reg=%d, mvolts=%d\n", pcf, reg,
+		millivolts);
+
+	if (reg >= __NUM_PCF50606_REGULATORS)
+		return -EINVAL;
+
+	if (millivolts > pcf->pdata->rails[reg].voltage.max)
+		return -EINVAL;
+
+	switch (reg) {
+	case PCF50606_REGULATOR_DCD:
+		volt_bits = dcdc_voltage(millivolts);
+		rc = reg_set_bit_mask(pcf, PCF50606_REG_DCDC1, 0x1f,
+				      volt_bits);
+		break;
+	case PCF50606_REGULATOR_DCDE:
+		volt_bits = dcdec_voltage(millivolts);
+		rc = reg_set_bit_mask(pcf, PCF50606_REG_DCDEC1, 0x0f,
+				      volt_bits);
+		break;
+	case PCF50606_REGULATOR_DCUD:
+		volt_bits = dcudc_voltage(millivolts);
+		rc = reg_set_bit_mask(pcf, PCF50606_REG_DCUDC1, 0x1f,
+				      volt_bits);
+		break;
+	case PCF50606_REGULATOR_D1REG:
+	case PCF50606_REGULATOR_D2REG:
+	case PCF50606_REGULATOR_D3REG:
+		regnr = PCF50606_REG_D1REGC1 + (reg - PCF50606_REGULATOR_D1REG);
+		volt_bits = dx_voltage(millivolts);
+		rc = reg_set_bit_mask(pcf, regnr, 0x1f, volt_bits);
+		break;
+	case PCF50606_REGULATOR_LPREG:
+		volt_bits = dx_voltage(millivolts);
+		rc = reg_set_bit_mask(pcf, PCF50606_REG_LPREGC1, 0x1f,
+				      volt_bits);
+		break;
+	case PCF50606_REGULATOR_IOREG:
+		if (millivolts < 1800)
+			return -EINVAL;
+		volt_bits = dx_voltage(millivolts);
+		rc = reg_set_bit_mask(pcf, PCF50606_REG_IOREGC, 0x1f,
+				      volt_bits);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return rc;
+}
+EXPORT_SYMBOL_GPL(pcf50606_voltage_set);
+
+unsigned int pcf50606_voltage_get(struct pcf50606_data *pcf,
+			 enum pcf50606_regulator_id reg)
+{
+	u_int8_t volt_bits;
+	u_int8_t regnr;
+	unsigned int rc = 0;
+
+	if (reg >= __NUM_PCF50606_REGULATORS)
+		return -EINVAL;
+
+	switch (reg) {
+	case PCF50606_REGULATOR_DCD:
+		volt_bits = reg_read(pcf, PCF50606_REG_DCDC1) & 0x1f;
+		rc = dcdc_2voltage(volt_bits);
+		break;
+	case PCF50606_REGULATOR_DCDE:
+		volt_bits = reg_read(pcf, PCF50606_REG_DCDEC1) & 0x0f;
+		rc = dcdec_2voltage(volt_bits);
+		break;
+	case PCF50606_REGULATOR_DCUD:
+		volt_bits = reg_read(pcf, PCF50606_REG_DCUDC1) & 0x1f;
+		rc = dcudc_2voltage(volt_bits);
+		break;
+	case PCF50606_REGULATOR_D1REG:
+	case PCF50606_REGULATOR_D2REG:
+	case PCF50606_REGULATOR_D3REG:
+		regnr = PCF50606_REG_D1REGC1 + (reg - PCF50606_REGULATOR_D1REG);
+		volt_bits = reg_read(pcf, regnr) & 0x1f;
+		if (volt_bits > 0x18)
+			volt_bits = 0x18;
+		rc = dx_2voltage(volt_bits);
+		break;
+	case PCF50606_REGULATOR_LPREG:
+		volt_bits = reg_read(pcf, PCF50606_REG_LPREGC1) & 0x1f;
+		if (volt_bits > 0x18)
+			volt_bits = 0x18;
+		rc = dx_2voltage(volt_bits);
+		break;
+	case PCF50606_REGULATOR_IOREG:
+		volt_bits = reg_read(pcf, PCF50606_REG_IOREGC) & 0x1f;
+		if (volt_bits > 0x18)
+			volt_bits = 0x18;
+		rc = dx_2voltage(volt_bits);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return rc;
+}
+EXPORT_SYMBOL_GPL(pcf50606_voltage_get);
+
+/* go into 'STANDBY' mode, i.e. power off the main CPU and peripherals */
+void pcf50606_go_standby(void)
+{
+	reg_write(pcf50606_global, PCF50606_REG_OOCC1,
+		  PCF50606_OOCC1_GOSTDBY);
+}
+EXPORT_SYMBOL_GPL(pcf50606_go_standby);
+
+void pcf50606_gpo0_set(struct pcf50606_data *pcf, int on)
+{
+	u_int8_t val;
+
+	if (on)
+		val = 0x07;
+	else
+		val = 0x0f;
+
+	reg_set_bit_mask(pcf, PCF50606_REG_GPOC1, 0x0f, val);
+}
+EXPORT_SYMBOL_GPL(pcf50606_gpo0_set);
+
+int pcf50606_gpo0_get(struct pcf50606_data *pcf)
+{
+	u_int8_t reg = reg_read(pcf, PCF50606_REG_GPOC1) & 0x0f;
+
+	if (reg == 0x07 || reg == 0x08)
+		return 1;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(pcf50606_gpo0_get);
+
+static void pcf50606_work(struct work_struct *work)
+{
+	struct pcf50606_data *pcf =
+			container_of(work, struct pcf50606_data, work);
+	u_int8_t int1, int2, int3;
+
+	pcf->working = 1;
+
+	int1 = __reg_read(pcf, PCF50606_REG_INT1);
+	int2 = __reg_read(pcf, PCF50606_REG_INT2);
+	int3 = __reg_read(pcf, PCF50606_REG_INT3);
+
+	dev_dbg(&pcf->client.dev, "INT1=0x%02x INT2=0x%02x INT3=0x%02x:",
+		int1, int2, int3);
+
+	if (int1 & PCF50606_INT1_ONKEYF) {
+		/* ONKEY falling edge (start of button press) */
+		DEBUGPC("ONKEYF ");
+		pcf->flags |= PCF50606_F_PWR_PRESSED;
+		input_report_key(pcf->input_dev, KEY_POWER, 1);
+	}
+	if (int1 & PCF50606_INT1_ONKEY1S) {
+		/* ONKEY pressed for more than 1 second */
+		pcf->onkey_seconds = 0;
+		DEBUGPC("ONKEY1S ");
+		/* Tell PMU we are taking care of this */
+		reg_set_bit_mask(pcf, PCF50606_REG_OOCC1,
+				 PCF50606_OOCC1_TOTRST,
+				 PCF50606_OOCC1_TOTRST);
+		/* enable SECOND interrupt (hz tick) */
+		reg_clear_bits(pcf, PCF50606_REG_INT1M, PCF50606_INT1_SECOND);
+	}
+	if (int1 & PCF50606_INT1_ONKEYR) {
+		/* ONKEY rising edge (end of button press) */
+		DEBUGPC("ONKEYR ");
+		pcf->flags &= ~PCF50606_F_PWR_PRESSED;
+		pcf->onkey_seconds = -1;
+		input_report_key(pcf->input_dev, KEY_POWER, 0);
+		/* disable SECOND interrupt in case RTC didn't
+		 * request it */
+		if (!(pcf->flags & PCF50606_F_RTC_SECOND))
+			reg_set_bit_mask(pcf, PCF50606_REG_INT1M,
+					 PCF50606_INT1_SECOND,
+					 PCF50606_INT1_SECOND);
+	}
+	if (int1 & PCF50606_INT1_EXTONR) {
+		DEBUGPC("EXTONR ");
+		input_report_key(pcf->input_dev, KEY_POWER2, 1);
+	}
+	if (int1 & PCF50606_INT1_EXTONF) {
+		DEBUGPC("EXTONF ");
+		input_report_key(pcf->input_dev, KEY_POWER2, 0);
+	}
+	if (int1 & PCF50606_INT1_SECOND) {
+		DEBUGPC("SECOND ");
+		if (pcf->flags & PCF50606_F_RTC_SECOND)
+			rtc_update_irq(pcf->rtc, 1,
+				       RTC_PF | RTC_IRQF);
+
+		if (pcf->onkey_seconds >= 0 &&
+		    pcf->flags & PCF50606_F_PWR_PRESSED) {
+			DEBUGPC("ONKEY_SECONDS(%u, OOCC1=0x%02x) ",
+				pcf->onkey_seconds,
+				reg_read(pcf, PCF50606_REG_OOCC1));
+			pcf->onkey_seconds++;
+			if (pcf->onkey_seconds >=
+			    pcf->pdata->onkey_seconds_required) {
+				/* Ask init to do 'ctrlaltdel' */
+				DEBUGPC("SIGINT(init) ");
+				kill_proc(1, SIGINT, 1);
+				/* FIXME: what to do if userspace doesn't
+				 * shut down? Do we want to force it? */
+			}
+		}
+	}
+	if (int1 & PCF50606_INT1_ALARM) {
+		DEBUGPC("ALARM ");
+		if (pcf->pdata->used_features & PCF50606_FEAT_RTC)
+			rtc_update_irq(pcf->rtc, 1,
+				       RTC_AF | RTC_IRQF);
+	}
+
+	if (int2 & PCF50606_INT2_CHGINS) {
+		/* Charger inserted */
+		DEBUGPC("CHGINS ");
+		input_report_key(pcf->input_dev, KEY_BATTERY, 1);
+		apm_queue_event(APM_POWER_STATUS_CHANGE);
+		pcf->flags |= PCF50606_F_CHG_PRESENT;
+		if (pcf->pdata->cb)
+			pcf->pdata->cb(&pcf->client.dev,
+				       PCF50606_FEAT_MBC, PMU_EVT_INSERT);
+		/* FIXME: how to signal this to userspace */
+	}
+	if (int2 & PCF50606_INT2_CHGRM) {
+		/* Charger removed */
+		DEBUGPC("CHGRM ");
+		input_report_key(pcf->input_dev, KEY_BATTERY, 0);
+		apm_queue_event(APM_POWER_STATUS_CHANGE);
+		pcf->flags &= ~(PCF50606_F_CHG_MASK|PCF50606_F_CHG_PRESENT);
+		if (pcf->pdata->cb)
+			pcf->pdata->cb(&pcf->client.dev,
+				       PCF50606_FEAT_MBC, PMU_EVT_INSERT);
+		/* FIXME: how signal this to userspace */
+	}
+	if (int2 & PCF50606_INT2_CHGFOK) {
+		/* Battery ready for fast charging */
+		DEBUGPC("CHGFOK ");
+		pcf->flags |= PCF50606_F_CHG_FOK;
+		/* FIXME: how to signal this to userspace */
+	}
+	if (int2 & PCF50606_INT2_CHGERR) {
+		/* Error in charge mode */
+		DEBUGPC("CHGERR ");
+		pcf->flags |= PCF50606_F_CHG_ERR;
+		pcf->flags &= ~(PCF50606_F_CHG_FOK|PCF50606_F_CHG_READY);
+		/* FIXME: how to signal this to userspace */
+	}
+	if (int2 & PCF50606_INT2_CHGFRDY) {
+		/* Fast charge completed */
+		DEBUGPC("CHGFRDY ");
+		pcf->flags |= PCF50606_F_CHG_READY;
+		pcf->flags &= ~PCF50606_F_CHG_FOK;
+		/* FIXME: how to signal this to userspace */
+	}
+	if (int2 & PCF50606_INT2_CHGPROT) {
+		/* Charging protection interrupt */
+		DEBUGPC("CHGPROT ");
+		pcf->flags &= ~(PCF50606_F_CHG_FOK|PCF50606_F_CHG_READY);
+		/* FIXME: signal this to userspace */
+	}
+	if (int2 & PCF50606_INT2_CHGWD10S) {
+		/* Charger watchdog will expire in 10 seconds */
+		DEBUGPC("CHGWD10S ");
+		reg_set_bit_mask(pcf, PCF50606_REG_OOCC1,
+				 PCF50606_OOCC1_WDTRST,
+				 PCF50606_OOCC1_WDTRST);
+	}
+	if (int2 & PCF50606_INT2_CHGWDEXP) {
+		/* Charger watchdog expires */
+		DEBUGPC("CHGWDEXP ");
+		/* FIXME: how to signal this to userspace */
+	}
+
+	if (int3 & PCF50606_INT3_ADCRDY) {
+		/* ADC result ready */
+		DEBUGPC("ADCRDY ");
+	}
+	if (int3 & PCF50606_INT3_ACDINS) {
+		/* Accessory insertion detected */
+		DEBUGPC("ACDINS ");
+		if (pcf->pdata->cb)
+			pcf->pdata->cb(&pcf->client.dev,
+				       PCF50606_FEAT_ACD, PMU_EVT_INSERT);
+	}
+	if (int3 & PCF50606_INT3_ACDREM) {
+		/* Accessory removal detected */
+		DEBUGPC("ACDREM ");
+		if (pcf->pdata->cb)
+			pcf->pdata->cb(&pcf->client.dev,
+				       PCF50606_FEAT_ACD, PMU_EVT_REMOVE);
+	}
+	/* FIXME: TSCPRES */
+	if (int3 & PCF50606_INT3_LOWBAT) {
+		/* Really low battery voltage, we have 8 seconds left */
+		DEBUGPC("LOWBAT ");
+		apm_queue_event(APM_LOW_BATTERY);
+		DEBUGPC("SIGPWR(init) ");
+		kill_proc(1, SIGPWR, 1);
+		/* Tell PMU we are taking care of this */
+		reg_set_bit_mask(pcf, PCF50606_REG_OOCC1,
+				 PCF50606_OOCC1_TOTRST,
+				 PCF50606_OOCC1_TOTRST);
+	}
+	if (int3 & PCF50606_INT3_HIGHTMP) {
+		/* High temperature */
+		DEBUGPC("HIGHTMP ");
+		apm_queue_event(APM_CRITICAL_SUSPEND);
+	}
+
+	DEBUGPC("\n");
+
+	pcf->working = 0;
+	input_sync(pcf->input_dev);
+	put_device(&pcf->client.dev);
+
+	enable_irq(pcf->irq);
+}
+
+static void pcf50606_schedule_work(struct pcf50606_data *pcf)
+{
+	int status;
+
+	get_device(&pcf->client.dev);
+	status = schedule_work(&pcf->work);
+	if (!status && !pcf->working)
+		dev_dbg(&pcf->client.dev, "work item may be lost\n");
+}
+
+
+static irqreturn_t pcf50606_irq(int irq, void *_pcf)
+{
+	struct pcf50606_data *pcf = _pcf;
+
+	dev_dbg(&pcf->client.dev, "entering(irq=%u, pcf=%p): scheduling work\n",
+		irq, _pcf);
+	pcf50606_schedule_work(pcf);
+
+	/* Disable any further interrupts until we have processed
+	 * the current one */
+	disable_irq(irq);
+
+	return IRQ_HANDLED;
+}
+
+static u_int16_t adc_to_batt_millivolts(u_int16_t adc)
+{
+	u_int16_t mvolts;
+
+	mvolts = (adc * 6000) / 1024;
+
+	return mvolts;
+}
+
+#define BATTVOLT_SCALE_START 2800
+#define BATTVOLT_SCALE_END 4200
+#define BATTVOLT_SCALE_DIVIDER ((BATTVOLT_SCALE_END - BATTVOLT_SCALE_START)/100)
+
+static u_int8_t battvolt_scale(u_int16_t battvolt)
+{
+	/* FIXME: this linear scale is completely bogus */
+	u_int16_t battvolt_relative = battvolt - BATTVOLT_SCALE_START;
+	unsigned int percent = battvolt_relative / BATTVOLT_SCALE_DIVIDER;
+
+	return percent;
+}
+
+u_int16_t pcf50606_battvolt(struct pcf50606_data *pcf)
+{
+	u_int16_t adc;
+	adc = adc_read(pcf, PCF50606_ADCMUX_BATVOLT_RES, NULL);
+
+	return adc_to_batt_millivolts(adc);
+}
+EXPORT_SYMBOL_GPL(pcf50606_battvolt);
+
+static ssize_t show_battvolt(struct device *dev, struct device_attribute *attr,
+			     char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50606_data *pcf = i2c_get_clientdata(client);
+
+	return sprintf(buf, "%u\n", pcf50606_battvolt(pcf));
+}
+static DEVICE_ATTR(battvolt, S_IRUGO | S_IWUSR, show_battvolt, NULL);
+
+static int reg_id_by_name(const char *name)
+{
+	int reg_id;
+
+	if (!strcmp(name, "voltage_dcd"))
+		reg_id = PCF50606_REGULATOR_DCD;
+	else if (!strcmp(name, "voltage_dcde"))
+		reg_id = PCF50606_REGULATOR_DCDE;
+	else if (!strcmp(name, "voltage_dcud"))
+		reg_id = PCF50606_REGULATOR_DCUD;
+	else if (!strcmp(name, "voltage_d1reg"))
+		reg_id = PCF50606_REGULATOR_D1REG;
+	else if (!strcmp(name, "voltage_d2reg"))
+		reg_id = PCF50606_REGULATOR_D2REG;
+	else if (!strcmp(name, "voltage_d3reg"))
+		reg_id = PCF50606_REGULATOR_D3REG;
+	else if (!strcmp(name, "voltage_lpreg"))
+		reg_id = PCF50606_REGULATOR_LPREG;
+	else if (!strcmp(name, "voltage_ioreg"))
+		reg_id = PCF50606_REGULATOR_IOREG;
+	else
+		reg_id = -1;
+
+	return reg_id;
+}
+
+static ssize_t show_vreg(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50606_data *pcf = i2c_get_clientdata(client);
+	unsigned int reg_id;
+
+	reg_id = reg_id_by_name(attr->attr.name);
+	if (reg_id < 0)
+		return 0;
+
+	if (pcf50606_onoff_get(pcf, reg_id) > 0)
+		return sprintf(buf, "%u\n", pcf50606_voltage_get(pcf, reg_id));
+	else
+		return strlcpy(buf, "0\n", PAGE_SIZE);
+}
+
+static ssize_t set_vreg(struct device *dev, struct device_attribute *attr,
+			const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50606_data *pcf = i2c_get_clientdata(client);
+	unsigned long mvolts = simple_strtoul(buf, NULL, 10);
+	unsigned int reg_id;
+
+	reg_id = reg_id_by_name(attr->attr.name);
+	if (reg_id < 0)
+		return -EIO;
+
+	dev_dbg(dev, "attempting to set %s(%d) to %lu mvolts\n",
+		attr->attr.name, reg_id, mvolts);
+
+	if (mvolts == 0) {
+		pcf50606_onoff_set(pcf, reg_id, 0);
+	} else {
+		if (pcf50606_voltage_set(pcf, reg_id, mvolts) < 0) {
+			dev_warn(dev, "refusing to set %s(%d) to %lu mvolts "
+				 "(max=%u)\n", attr->attr.name, reg_id, mvolts,
+				 pcf->pdata->rails[reg_id].voltage.max);
+			return -EINVAL;
+		}
+		pcf50606_onoff_set(pcf, reg_id, 1);
+	}
+
+	return count;
+}
+
+static DEVICE_ATTR(voltage_dcd, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
+static DEVICE_ATTR(voltage_dcde, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
+static DEVICE_ATTR(voltage_dcud, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
+static DEVICE_ATTR(voltage_d1reg, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
+static DEVICE_ATTR(voltage_d2reg, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
+static DEVICE_ATTR(voltage_d3reg, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
+static DEVICE_ATTR(voltage_lpreg, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
+static DEVICE_ATTR(voltage_ioreg, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
+
+/***********************************************************************
+ * Charger Control
+ ***********************************************************************/
+
+/* Enable/disable fast charging (500mA in the GTA01) */
+void pcf50606_charge_fast(struct pcf50606_data *pcf, int on)
+{
+	if (!(pcf->pdata->used_features & PCF50606_FEAT_MBC))
+		return;
+
+	if (on) {
+		/* We can allow PCF to automatically charge
+		 * using Ifast */
+		pcf->flags |= PCF50606_F_CHG_FAST;
+		reg_set_bit_mask(pcf, PCF50606_REG_MBCC1,
+				 PCF50606_MBCC1_AUTOFST,
+				 PCF50606_MBCC1_AUTOFST);
+	} else {
+		pcf->flags &= ~PCF50606_F_CHG_FAST;
+		/* disable automatic fast-charge */
+		reg_clear_bits(pcf, PCF50606_REG_MBCC1,
+				PCF50606_MBCC1_AUTOFST);
+		/* switch to idle mode to abort existing charge
+		 * process */
+		reg_set_bit_mask(pcf, PCF50606_REG_MBCC1,
+				 PCF50606_MBCC1_CHGMOD_MASK,
+				 PCF50606_MBCC1_CHGMOD_IDLE);
+	}
+}
+EXPORT_SYMBOL_GPL(pcf50606_charge_fast);
+
+#define ONE			1000000
+static inline u_int16_t adc_to_rntc(struct pcf50606_data *pcf, u_int16_t adc)
+{
+	u_int32_t r_batt = (adc * pcf->pdata->r_fix_batt) / (1023 - adc);
+	u_int16_t r_ntc;
+
+	/* The battery NTC has a parallell 10kOhms resistor */
+	r_ntc = ONE / ((ONE/r_batt) - (ONE/pcf->pdata->r_fix_batt_par));
+
+	return r_ntc;
+}
+
+static inline int16_t rntc_to_temp(u_int16_t rntc)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(ntc_table_tn11_3h103j); i++) {
+		if (rntc > ntc_table_tn11_3h103j[i])
+			return i - 10;
+	}
+	return 2342;
+}
+
+static ssize_t show_battemp(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50606_data *pcf = i2c_get_clientdata(client);
+	u_int16_t adc;
+
+	adc = adc_read(pcf, PCF50606_ADCMUX_BATTEMP, NULL);
+
+	return sprintf(buf, "%d\n", rntc_to_temp(adc_to_rntc(pcf, adc)));
+}
+static DEVICE_ATTR(battemp, S_IRUGO | S_IWUSR, show_battemp, NULL);
+
+static inline u_int16_t adc_to_chg_milliamps(struct pcf50606_data *pcf,
+					     u_int16_t adc_adcin1,
+					     u_int16_t adc_batvolt)
+{
+	u_int32_t res = ((adc_adcin1 - adc_batvolt) * 6000);
+	return res / (pcf->pdata->r_sense_milli * 1024 / 1000);
+}
+
+static ssize_t show_chgcur(struct device *dev, struct device_attribute *attr,
+			   char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50606_data *pcf = i2c_get_clientdata(client);
+	u_int16_t adc_batvolt, adc_adcin1;
+	u_int16_t ma;
+
+	adc_batvolt = adc_read(pcf, PCF50606_ADCMUX_BATVOLT_ADCIN1,
+			       &adc_adcin1);
+	ma = adc_to_chg_milliamps(pcf, adc_adcin1, adc_batvolt);
+
+	return sprintf(buf, "%u\n", ma);
+}
+static DEVICE_ATTR(chgcur, S_IRUGO | S_IWUSR, show_chgcur, NULL);
+
+static const char *chgmode_names[] = {
+	[PCF50606_MBCC1_CHGMOD_QUAL]		= "qualification",
+	[PCF50606_MBCC1_CHGMOD_PRE]		= "pre",
+	[PCF50606_MBCC1_CHGMOD_TRICKLE]		= "trickle",
+	[PCF50606_MBCC1_CHGMOD_FAST_CCCV]	= "fast_cccv",
+	[PCF50606_MBCC1_CHGMOD_FAST_NOCC]	= "fast_nocc",
+	[PCF50606_MBCC1_CHGMOD_FAST_NOCV]	= "fast_nocv",
+	[PCF50606_MBCC1_CHGMOD_FAST_SW]		= "fast_switch",
+	[PCF50606_MBCC1_CHGMOD_IDLE]		= "idle",
+};
+
+static ssize_t show_chgmode(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50606_data *pcf = i2c_get_clientdata(client);
+	u_int8_t mbcc1 = reg_read(pcf, PCF50606_REG_MBCC1);
+	u_int8_t chgmod = (mbcc1 & PCF50606_MBCC1_CHGMOD_MASK);
+
+	return sprintf(buf, "%s\n", chgmode_names[chgmod]);
+}
+
+static ssize_t set_chgmode(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50606_data *pcf = i2c_get_clientdata(client);
+	u_int8_t mbcc1 = reg_read(pcf, PCF50606_REG_MBCC1);
+
+	mbcc1 &= ~PCF50606_MBCC1_CHGMOD_MASK;
+
+	if (!strcmp(buf, "qualification"))
+		mbcc1 |= PCF50606_MBCC1_CHGMOD_QUAL;
+	else if (!strcmp(buf, "pre"))
+		mbcc1 |= PCF50606_MBCC1_CHGMOD_PRE;
+	else if (!strcmp(buf, "trickle"))
+		mbcc1 |= PCF50606_MBCC1_CHGMOD_TRICKLE;
+	else if (!strcmp(buf, "fast_cccv"))
+		mbcc1 |= PCF50606_MBCC1_CHGMOD_FAST_CCCV;
+	/* We don't allow the other fast modes for security reasons */
+	else if (!strcmp(buf, "idle"))
+		mbcc1 |= PCF50606_MBCC1_CHGMOD_IDLE;
+	else
+		return -EINVAL;
+
+	reg_write(pcf, PCF50606_REG_MBCC1, mbcc1);
+
+	return count;
+}
+
+static DEVICE_ATTR(chgmode, S_IRUGO | S_IWUSR, show_chgmode, set_chgmode);
+
+static const char *chgstate_names[] = {
+	[PCF50606_F_CHG_FAST]			= "fast_enabled",
+	[PCF50606_F_CHG_PRESENT] 		= "present",
+	[PCF50606_F_CHG_FOK]			= "fast_ok",
+	[PCF50606_F_CHG_ERR]			= "error",
+	[PCF50606_F_CHG_PROT]			= "protection",
+	[PCF50606_F_CHG_READY]			= "ready",
+};
+
+static ssize_t show_chgstate(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50606_data *pcf = i2c_get_clientdata(client);
+	char *b = buf;
+	int i;
+
+	for (i = 0; i < 32; i++)
+		if (pcf->flags & (1 << i) && i < ARRAY_SIZE(chgstate_names))
+			b += sprintf(b, "%s ", chgstate_names[i]);
+
+	if (b > buf)
+		b += sprintf(b, "\n");
+
+	return b - buf;
+}
+static DEVICE_ATTR(chgstate, S_IRUGO | S_IWUSR, show_chgstate, NULL);
+
+/***********************************************************************
+ * APM emulation
+ ***********************************************************************/
+
+static void pcf50606_get_power_status(struct apm_power_info *info)
+{
+	struct pcf50606_data *pcf = pcf50606_global;
+	u_int8_t mbcc1 = reg_read(pcf, PCF50606_REG_MBCC1);
+	u_int8_t chgmod = mbcc1 & PCF50606_MBCC1_CHGMOD_MASK;
+	u_int16_t battvolt = pcf50606_battvolt(pcf);
+
+	if (reg_read(pcf, PCF50606_REG_OOCS) & PCF50606_OOCS_EXTON)
+		info->ac_line_status = APM_AC_ONLINE;
+	else
+		info->ac_line_status = APM_AC_OFFLINE;
+
+	switch (chgmod) {
+	case PCF50606_MBCC1_CHGMOD_QUAL:
+	case PCF50606_MBCC1_CHGMOD_PRE:
+	case PCF50606_MBCC1_CHGMOD_IDLE:
+		info->battery_life = battvolt_scale(battvolt);
+		break;
+	default:
+		info->battery_status = APM_BATTERY_STATUS_CHARGING;
+		info->battery_flag = APM_BATTERY_FLAG_CHARGING;
+		break;
+	}
+}
+
+/***********************************************************************
+ * RTC
+ ***********************************************************************/
+
+struct pcf50606_time {
+	u_int8_t sec;
+	u_int8_t min;
+	u_int8_t hour;
+	u_int8_t wkday;
+	u_int8_t day;
+	u_int8_t month;
+	u_int8_t year;
+};
+
+static void pcf2rtc_time(struct rtc_time *rtc, struct pcf50606_time *pcf)
+{
+	rtc->tm_sec = BCD2BIN(pcf->sec);
+	rtc->tm_min = BCD2BIN(pcf->min);
+	rtc->tm_hour = BCD2BIN(pcf->hour);
+	rtc->tm_wday = BCD2BIN(pcf->wkday);
+	rtc->tm_mday = BCD2BIN(pcf->day);
+	rtc->tm_mon = BCD2BIN(pcf->month);
+	rtc->tm_year = BCD2BIN(pcf->year) + 100;
+}
+
+static void rtc2pcf_time(struct pcf50606_time *pcf, struct rtc_time *rtc)
+{
+	pcf->sec = BIN2BCD(rtc->tm_sec);
+	pcf->min = BIN2BCD(rtc->tm_min);
+	pcf->hour = BIN2BCD(rtc->tm_hour);
+	pcf->wkday = BIN2BCD(rtc->tm_wday);
+	pcf->day = BIN2BCD(rtc->tm_mday);
+	pcf->month = BIN2BCD(rtc->tm_mon);
+	pcf->year = BIN2BCD(rtc->tm_year - 100);
+}
+
+static int pcf50606_rtc_ioctl(struct device *dev, unsigned int cmd,
+			      unsigned long arg)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50606_data *pcf = i2c_get_clientdata(client);
+	switch (cmd) {
+	case RTC_PIE_OFF:
+		/* disable periodic interrupt (hz tick) */
+		pcf->flags &= ~PCF50606_F_RTC_SECOND;
+		reg_set_bit_mask(pcf, PCF50606_REG_INT1M,
+				 PCF50606_INT1_SECOND, PCF50606_INT1_SECOND);
+		return 0;
+	case RTC_PIE_ON:
+		/* ensable periodic interrupt (hz tick) */
+		pcf->flags |= PCF50606_F_RTC_SECOND;
+		reg_clear_bits(pcf, PCF50606_REG_INT1M, PCF50606_INT1_SECOND);
+		return 0;
+	}
+	return -ENOIOCTLCMD;
+}
+
+static int pcf50606_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50606_data *pcf = i2c_get_clientdata(client);
+	struct pcf50606_time pcf_tm;
+
+	mutex_lock(&pcf->lock);
+	pcf_tm.sec = __reg_read(pcf, PCF50606_REG_RTCSC);
+	pcf_tm.min = __reg_read(pcf, PCF50606_REG_RTCMN);
+	pcf_tm.hour = __reg_read(pcf, PCF50606_REG_RTCHR);
+	pcf_tm.wkday = __reg_read(pcf, PCF50606_REG_RTCWD);
+	pcf_tm.day = __reg_read(pcf, PCF50606_REG_RTCDT);
+	pcf_tm.month = __reg_read(pcf, PCF50606_REG_RTCMT);
+	pcf_tm.year = __reg_read(pcf, PCF50606_REG_RTCYR);
+	mutex_unlock(&pcf->lock);
+
+	dev_dbg(dev, "PCF_TIME: %02x.%02x.%02x %02x:%02x:%02x\n",
+		pcf_tm.day, pcf_tm.month, pcf_tm.year,
+		pcf_tm.hour, pcf_tm.min, pcf_tm.sec);
+
+	pcf2rtc_time(tm, &pcf_tm);
+
+	dev_dbg(dev, "RTC_TIME: %u.%u.%u %u:%u:%u\n",
+		tm->tm_mday, tm->tm_mon, tm->tm_year,
+		tm->tm_hour, tm->tm_min, tm->tm_sec);
+
+	return 0;
+}
+
+static int pcf50606_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50606_data *pcf = i2c_get_clientdata(client);
+	struct pcf50606_time pcf_tm;
+	u_int8_t int1m;
+
+	dev_dbg(dev, "RTC_TIME: %u.%u.%u %u:%u:%u\n",
+		tm->tm_mday, tm->tm_mon, tm->tm_year,
+		tm->tm_hour, tm->tm_min, tm->tm_sec);
+	rtc2pcf_time(&pcf_tm, tm);
+	dev_dbg(dev, "PCF_TIME: %02x.%02x.%02x %02x:%02x:%02x\n",
+		pcf_tm.day, pcf_tm.month, pcf_tm.year,
+		pcf_tm.hour, pcf_tm.min, pcf_tm.sec);
+
+	mutex_lock(&pcf->lock);
+
+	/* disable SECOND interrupt */
+	int1m = __reg_read(pcf, PCF50606_REG_INT1M);
+	__reg_write(pcf, PCF50606_REG_INT1M, int1m | PCF50606_INT1_SECOND);
+
+	__reg_write(pcf, PCF50606_REG_RTCSC, pcf_tm.sec);
+	__reg_write(pcf, PCF50606_REG_RTCMN, pcf_tm.min);
+	__reg_write(pcf, PCF50606_REG_RTCHR, pcf_tm.hour);
+	__reg_write(pcf, PCF50606_REG_RTCWD, pcf_tm.wkday);
+	__reg_write(pcf, PCF50606_REG_RTCDT, pcf_tm.day);
+	__reg_write(pcf, PCF50606_REG_RTCMT, pcf_tm.month);
+	__reg_write(pcf, PCF50606_REG_RTCYR, pcf_tm.year);
+
+	/* restore INT1M, potentially re-enable SECOND interrupt */
+	__reg_write(pcf, PCF50606_REG_INT1M, int1m);
+
+	mutex_unlock(&pcf->lock);
+
+	return 0;
+}
+
+static int pcf50606_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50606_data *pcf = i2c_get_clientdata(client);
+	struct pcf50606_time pcf_tm;
+
+	mutex_lock(&pcf->lock);
+	pcf_tm.sec = __reg_read(pcf, PCF50606_REG_RTCSCA);
+	pcf_tm.min = __reg_read(pcf, PCF50606_REG_RTCMNA);
+	pcf_tm.hour = __reg_read(pcf, PCF50606_REG_RTCHRA);
+	pcf_tm.wkday = __reg_read(pcf, PCF50606_REG_RTCWDA);
+	pcf_tm.day = __reg_read(pcf, PCF50606_REG_RTCDTA);
+	pcf_tm.month = __reg_read(pcf, PCF50606_REG_RTCMTA);
+	pcf_tm.year = __reg_read(pcf, PCF50606_REG_RTCYRA);
+	mutex_unlock(&pcf->lock);
+
+	pcf2rtc_time(&alrm->time, &pcf_tm);
+
+	return 0;
+}
+
+static int pcf50606_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50606_data *pcf = i2c_get_clientdata(client);
+	struct pcf50606_time pcf_tm;
+	u_int8_t irqmask;
+
+	rtc2pcf_time(&pcf_tm, &alrm->time);
+
+	mutex_lock(&pcf->lock);
+
+	/* disable alarm interrupt */
+	irqmask = __reg_read(pcf, PCF50606_REG_INT1M);
+	irqmask |= PCF50606_INT1_ALARM;
+	__reg_write(pcf, PCF50606_REG_INT1M, irqmask);
+
+	__reg_write(pcf, PCF50606_REG_RTCSCA, pcf_tm.sec);
+	__reg_write(pcf, PCF50606_REG_RTCMNA, pcf_tm.min);
+	__reg_write(pcf, PCF50606_REG_RTCHRA, pcf_tm.hour);
+	__reg_write(pcf, PCF50606_REG_RTCWDA, pcf_tm.wkday);
+	__reg_write(pcf, PCF50606_REG_RTCDTA, pcf_tm.day);
+	__reg_write(pcf, PCF50606_REG_RTCMTA, pcf_tm.month);
+	__reg_write(pcf, PCF50606_REG_RTCYRA, pcf_tm.year);
+
+	if (alrm->enabled) {
+		/* (re-)enaable alarm interrupt */
+		irqmask = __reg_read(pcf, PCF50606_REG_INT1M);
+		irqmask &= ~PCF50606_INT1_ALARM;
+		__reg_write(pcf, PCF50606_REG_INT1M, irqmask);
+	}
+
+	mutex_unlock(&pcf->lock);
+
+	/* FIXME */
+	return 0;
+}
+
+static struct rtc_class_ops pcf50606_rtc_ops = {
+	.ioctl		= pcf50606_rtc_ioctl,
+	.read_time	= pcf50606_rtc_read_time,
+	.set_time	= pcf50606_rtc_set_time,
+	.read_alarm	= pcf50606_rtc_read_alarm,
+	.set_alarm	= pcf50606_rtc_set_alarm,
+};
+
+/***********************************************************************
+ * Watchdog
+ ***********************************************************************/
+
+static void pcf50606_wdt_start(struct pcf50606_data *pcf)
+{
+	reg_set_bit_mask(pcf, PCF50606_REG_OOCC1, PCF50606_OOCC1_WDTRST,
+			 PCF50606_OOCC1_WDTRST);
+}
+
+static void pcf50606_wdt_stop(struct pcf50606_data *pcf)
+{
+	reg_clear_bits(pcf, PCF50606_REG_OOCS, PCF50606_OOCS_WDTEXP);
+}
+
+static void pcf50606_wdt_keepalive(struct pcf50606_data *pcf)
+{
+	pcf50606_wdt_start(pcf);
+}
+
+static int pcf50606_wdt_open(struct inode *inode, struct file *file)
+{
+	struct pcf50606_data *pcf = pcf50606_global;
+
+	file->private_data = pcf;
+
+	/* start the timer */
+	pcf50606_wdt_start(pcf);
+
+	return nonseekable_open(inode, file);
+}
+
+static int pcf50606_wdt_release(struct inode *inode, struct file *file)
+{
+	struct pcf50606_data *pcf = file->private_data;
+
+	if (pcf->allow_close == CLOSE_STATE_ALLOW)
+		pcf50606_wdt_stop(pcf);
+	else {
+		printk(KERN_CRIT "Unexpected close, not stopping watchdog!\n");
+		pcf50606_wdt_keepalive(pcf);
+	}
+
+	pcf->allow_close = CLOSE_STATE_NOT;
+
+	return 0;
+}
+
+static ssize_t pcf50606_wdt_write(struct file *file, const char __user *data,
+				  size_t len, loff_t *ppos)
+{
+	struct pcf50606_data *pcf = file->private_data;
+	if (len) {
+		size_t i;
+
+		for (i = 0; i != len; i++) {
+			char c;
+			if (get_user(c, data + i))
+				return -EFAULT;
+			if (c == 'V')
+				pcf->allow_close = CLOSE_STATE_ALLOW;
+		}
+		pcf50606_wdt_keepalive(pcf);
+	}
+
+	return len;
+}
+
+static struct watchdog_info pcf50606_wdt_ident = {
+	.options	= WDIOF_MAGICCLOSE,
+	.firmware_version = 0,
+	.identity	= "PCF50606 Watchdog",
+};
+
+static int pcf50606_wdt_ioctl(struct inode *inode, struct file *file,
+			      unsigned int cmd, unsigned long arg)
+{
+	struct pcf50606_data *pcf = file->private_data;
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+
+	switch (cmd) {
+	case WDIOC_GETSUPPORT:
+		return copy_to_user(argp, &pcf50606_wdt_ident,
+				    sizeof(pcf50606_wdt_ident)) ? -EFAULT : 0;
+		break;
+	case WDIOC_GETSTATUS:
+	case WDIOC_GETBOOTSTATUS:
+		return put_user(0, p);
+	case WDIOC_KEEPALIVE:
+		pcf50606_wdt_keepalive(pcf);
+		return 0;
+	case WDIOC_GETTIMEOUT:
+		return put_user(8, p);
+	default:
+		return -ENOIOCTLCMD;
+	}
+}
+
+static struct file_operations pcf50606_wdt_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= &pcf50606_wdt_write,
+	.ioctl		= &pcf50606_wdt_ioctl,
+	.open		= &pcf50606_wdt_open,
+	.release	= &pcf50606_wdt_release,
+};
+
+static struct miscdevice pcf50606_wdt_miscdev = {
+	.minor		= WATCHDOG_MINOR,
+	.name		= "watchdog",
+	.fops		= &pcf50606_wdt_fops,
+};
+
+/***********************************************************************
+ * PWM
+ ***********************************************************************/
+
+static const char *pwm_dc_table[] = {
+	"0/16", "1/16", "2/16", "3/16",
+	"4/16", "5/16", "6/16", "7/16",
+	"8/16", "9/16", "10/16", "11/16",
+	"12/16", "13/16", "14/16", "15/16",
+};
+
+static ssize_t show_pwm_dc(struct device *dev, struct device_attribute *attr,
+			   char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50606_data *pcf = i2c_get_clientdata(client);
+	u_int8_t val;
+
+	val = reg_read(pcf, PCF50606_REG_PWMC1) >> PCF50606_PWMC1_DC_SHIFT;
+	val &= 0xf;
+
+	return sprintf(buf, "%s\n", pwm_dc_table[val]);
+}
+
+static ssize_t set_pwm_dc(struct device *dev, struct device_attribute *attr,
+			  const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50606_data *pcf = i2c_get_clientdata(client);
+	u_int8_t i;
+
+	for (i = 0; i < ARRAY_SIZE(pwm_dc_table); i++) {
+		if (!strncmp(buf, pwm_dc_table[i], strlen(pwm_dc_table[i]))) {
+			dev_dbg(dev, "setting pwm dc %s\n\r", pwm_dc_table[i]);
+			reg_set_bit_mask(pcf, PCF50606_REG_PWMC1, 0x1e,
+					 (i << PCF50606_PWMC1_DC_SHIFT));
+		}
+	}
+	return count;
+}
+
+static DEVICE_ATTR(pwm_dc, S_IRUGO | S_IWUSR, show_pwm_dc, set_pwm_dc);
+
+static const char *pwm_clk_table[] = {
+	"512", "256", "128", "64",
+	"56300", "28100", "14100", "7000",
+};
+
+static ssize_t show_pwm_clk(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50606_data *pcf = i2c_get_clientdata(client);
+	u_int8_t val;
+
+	val = reg_read(pcf, PCF50606_REG_PWMC1) >> PCF50606_PWMC1_CLK_SHIFT;
+	val &= 0x7;
+
+	return sprintf(buf, "%s\n", pwm_clk_table[val]);
+}
+
+static ssize_t set_pwm_clk(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50606_data *pcf = i2c_get_clientdata(client);
+	u_int8_t i;
+
+	for (i = 0; i < ARRAY_SIZE(pwm_clk_table); i++) {
+		if (!strncmp(buf, pwm_clk_table[i], strlen(pwm_clk_table[i]))) {
+			dev_dbg(dev, "setting pwm clk %s\n\r",
+				pwm_clk_table[i]);
+			reg_set_bit_mask(pcf, PCF50606_REG_PWMC1, 0xe0,
+					 (i << PCF50606_PWMC1_CLK_SHIFT));
+		}
+	}
+	return count;
+}
+
+static DEVICE_ATTR(pwm_clk, S_IRUGO | S_IWUSR, show_pwm_clk, set_pwm_clk);
+
+static int pcf50606bl_get_intensity(struct backlight_device *bd)
+{
+	struct pcf50606_data *pcf = bl_get_data(bd);
+	int intensity = reg_read(pcf, PCF50606_REG_PWMC1);
+	intensity = (intensity >> PCF50606_PWMC1_DC_SHIFT);
+
+	return intensity & 0xf;
+}
+
+static int pcf50606bl_set_intensity(struct backlight_device *bd)
+{
+	struct pcf50606_data *pcf = bl_get_data(bd);
+	int intensity = bd->props.brightness;
+
+	if (bd->props.power != FB_BLANK_UNBLANK)
+		intensity = 0;
+	if (bd->props.fb_blank != FB_BLANK_UNBLANK)
+		intensity = 0;
+
+	return reg_set_bit_mask(pcf, PCF50606_REG_PWMC1, 0x1e,
+				(intensity << PCF50606_PWMC1_DC_SHIFT));
+}
+
+static struct backlight_ops pcf50606bl_ops = {
+	.get_brightness	= pcf50606bl_get_intensity,
+	.update_status	= pcf50606bl_set_intensity,
+};
+
+/***********************************************************************
+ * Driver initialization
+ ***********************************************************************/
+
+#ifdef CONFIG_MACH_NEO1973_GTA01
+/* We currently place those platform devices here to make sure the device
+ * suspend/resume order is correct */
+static struct platform_device gta01_pm_gps_dev = {
+	.name		= "neo1973-pm-gps",
+};
+
+static struct platform_device gta01_pm_bt_dev = {
+	.name		= "neo1973-pm-bt",
+};
+#endif
+
+static struct attribute *pcf_sysfs_entries[16] = {
+	&dev_attr_voltage_dcd.attr,
+	&dev_attr_voltage_dcde.attr,
+	&dev_attr_voltage_dcud.attr,
+	&dev_attr_voltage_d1reg.attr,
+	&dev_attr_voltage_d2reg.attr,
+	&dev_attr_voltage_d3reg.attr,
+	&dev_attr_voltage_lpreg.attr,
+	&dev_attr_voltage_ioreg.attr,
+	NULL
+};
+
+static struct attribute_group pcf_attr_group = {
+	.name	= NULL,			/* put in device directory */
+	.attrs	= pcf_sysfs_entries,
+};
+
+static void populate_sysfs_group(struct pcf50606_data *pcf)
+{
+	int i = 0;
+	struct attribute **attr;
+
+	for (attr = pcf_sysfs_entries; *attr; attr++)
+		i++;
+
+	if (pcf->pdata->used_features & PCF50606_FEAT_MBC) {
+		pcf_sysfs_entries[i++] = &dev_attr_chgstate.attr;
+		pcf_sysfs_entries[i++] = &dev_attr_chgmode.attr;
+	}
+
+	if (pcf->pdata->used_features & PCF50606_FEAT_CHGCUR)
+		pcf_sysfs_entries[i++] = &dev_attr_chgcur.attr;
+
+	if (pcf->pdata->used_features & PCF50606_FEAT_BATVOLT)
+		pcf_sysfs_entries[i++] = &dev_attr_battvolt.attr;
+
+	if (pcf->pdata->used_features & PCF50606_FEAT_BATTEMP)
+		pcf_sysfs_entries[i++] = &dev_attr_battemp.attr;
+
+	if (pcf->pdata->used_features & PCF50606_FEAT_PWM) {
+		pcf_sysfs_entries[i++] = &dev_attr_pwm_dc.attr;
+		pcf_sysfs_entries[i++] = &dev_attr_pwm_clk.attr;
+	}
+}
+
+static int pcf50606_detect(struct i2c_adapter *adapter, int address, int kind)
+{
+	struct i2c_client *new_client;
+	struct pcf50606_data *data;
+	int err = 0;
+	int irq;
+
+	if (!pcf50606_pdev) {
+		printk(KERN_ERR "pcf50606: driver needs a platform_device!\n");
+		return -EIO;
+	}
+
+	irq = platform_get_irq(pcf50606_pdev, 0);
+	if (irq < 0) {
+		dev_err(&pcf50606_pdev->dev, "no irq in platform resources!\n");
+		return -EIO;
+	}
+
+	/* At the moment, we only support one PCF50606 in a system */
+	if (pcf50606_global) {
+		dev_err(&pcf50606_pdev->dev,
+			"currently only one chip supported\n");
+		return -EBUSY;
+	}
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	mutex_init(&data->lock);
+	INIT_WORK(&data->work, pcf50606_work);
+	data->irq = irq;
+	data->working = 0;
+	data->onkey_seconds = -1;
+	data->pdata = pcf50606_pdev->dev.platform_data;
+
+	new_client = &data->client;
+	i2c_set_clientdata(new_client, data);
+	new_client->addr = address;
+	new_client->adapter = adapter;
+	new_client->driver = &pcf50606_driver;
+	new_client->flags = 0;
+	strlcpy(new_client->name, "pcf50606", I2C_NAME_SIZE);
+
+	/* now we try to detect the chip */
+
+	/* register with i2c core */
+	err = i2c_attach_client(new_client);
+	if (err) {
+		dev_err(&new_client->dev,
+			"error during i2c_attach_client()\n");
+		goto exit_free;
+	}
+
+	populate_sysfs_group(data);
+
+	err = sysfs_create_group(&new_client->dev.kobj, &pcf_attr_group);
+	if (err) {
+		dev_err(&new_client->dev, "error creating sysfs group\n");
+		goto exit_detach;
+	}
+
+	/* create virtual charger 'device' */
+
+	/* input device registration */
+	data->input_dev = input_allocate_device();
+	if (!data->input_dev)
+		goto exit_sysfs;
+
+	data->input_dev->name = "FIC Neo1973 PMU events";
+	data->input_dev->phys = "I2C";
+	data->input_dev->id.bustype = BUS_I2C;
+	data->input_dev->cdev.dev = &new_client->dev;
+
+	data->input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_PWR);
+	set_bit(KEY_POWER, data->input_dev->keybit);
+	set_bit(KEY_POWER2, data->input_dev->keybit);
+	set_bit(KEY_BATTERY, data->input_dev->keybit);
+
+	err = input_register_device(data->input_dev);
+	if (err)
+		goto exit_sysfs;
+
+	/* register power off handler with core power management */
+	pm_power_off = &pcf50606_go_standby;
+
+	/* configure interrupt mask */
+	reg_write(data, PCF50606_REG_INT1M, PCF50606_INT1_SECOND);
+	reg_write(data, PCF50606_REG_INT2M, 0x00);
+	reg_write(data, PCF50606_REG_INT3M, PCF50606_INT3_TSCPRES);
+
+	err = request_irq(irq, pcf50606_irq, IRQF_DISABLED,
+			  "pcf50606", data);
+	if (err < 0)
+		goto exit_input;
+
+	set_irq_type(irq, IRQT_FALLING);
+
+	if (enable_irq_wake(irq) < 0)
+		dev_err(&new_client->dev, "IRQ %u cannot be enabled as wake-up"
+			"source in this hardware revision!", irq);
+
+	pcf50606_global = data;
+
+	if (data->pdata->used_features & PCF50606_FEAT_RTC) {
+		data->rtc = rtc_device_register("pcf50606", &new_client->dev,
+						&pcf50606_rtc_ops, THIS_MODULE);
+		if (IS_ERR(data->rtc)) {
+			err = PTR_ERR(data->rtc);
+			goto exit_irq;
+		}
+	}
+
+	if (data->pdata->used_features & PCF50606_FEAT_WDT) {
+		err = misc_register(&pcf50606_wdt_miscdev);
+		if (err) {
+			dev_err(&new_client->dev, "cannot register miscdev on "
+			       "minor=%d (%d)\n", WATCHDOG_MINOR, err);
+			goto exit_rtc;
+		}
+	}
+
+	if (data->pdata->used_features & PCF50606_FEAT_PWM) {
+		/* enable PWM controller */
+		reg_set_bit_mask(data, PCF50606_REG_PWMC1,
+				 PCF50606_PWMC1_ACTSET,
+				 PCF50606_PWMC1_ACTSET);
+	}
+
+	if (data->pdata->used_features & PCF50606_FEAT_PWM_BL) {
+		data->backlight = backlight_device_register("pcf50606-bl",
+							    &new_client->dev,
+							    data,
+							    &pcf50606bl_ops);
+		if (!data->backlight)
+			goto exit_misc;
+		data->backlight->props.max_brightness = 16;
+		data->backlight->props.power = FB_BLANK_UNBLANK;
+		data->backlight->props.brightness =
+					data->pdata->init_brightness;
+		backlight_update_status(data->backlight);
+	}
+
+	apm_get_power_status = pcf50606_get_power_status;
+
+#ifdef CONFIG_MACH_NEO1973_GTA01
+	if (machine_is_neo1973_gta01()) {
+		gta01_pm_gps_dev.dev.parent = &new_client->dev;
+		switch (system_rev) {
+		case GTA01Bv2_SYSTEM_REV:
+		case GTA01Bv3_SYSTEM_REV:
+		case GTA01Bv4_SYSTEM_REV:
+			gta01_pm_bt_dev.dev.parent = &new_client->dev;
+			platform_device_register(&gta01_pm_bt_dev);
+			break;
+		}
+		platform_device_register(&gta01_pm_gps_dev);
+	}
+#endif
+
+	if (data->pdata->used_features & PCF50606_FEAT_ACD)
+		reg_set_bit_mask(data, PCF50606_REG_ACDC1,
+				 PCF50606_ACDC1_ACDAPE, PCF50606_ACDC1_ACDAPE);
+	else
+		reg_clear_bits(data, PCF50606_REG_ACDC1,
+			       PCF50606_ACDC1_ACDAPE);
+
+	return 0;
+
+exit_misc:
+	if (data->pdata->used_features & PCF50606_FEAT_WDT)
+		misc_deregister(&pcf50606_wdt_miscdev);
+exit_rtc:
+	if (data->pdata->used_features & PCF50606_FEAT_RTC)
+		rtc_device_unregister(pcf50606_global->rtc);
+exit_irq:
+	free_irq(pcf50606_global->irq, pcf50606_global);
+	pcf50606_global = NULL;
+exit_input:
+	pm_power_off = NULL;
+	input_unregister_device(data->input_dev);
+exit_sysfs:
+	sysfs_remove_group(&new_client->dev.kobj, &pcf_attr_group);
+exit_detach:
+	i2c_detach_client(new_client);
+exit_free:
+	kfree(data);
+	return err;
+}
+
+static int pcf50606_attach_adapter(struct i2c_adapter *adapter)
+{
+	return i2c_probe(adapter, &addr_data, &pcf50606_detect);
+}
+
+static int pcf50606_detach_client(struct i2c_client *client)
+{
+	struct pcf50606_data *pcf = i2c_get_clientdata(client);
+
+	apm_get_power_status = NULL;
+	input_unregister_device(pcf->input_dev);
+
+	if (pcf->pdata->used_features & PCF50606_FEAT_PWM_BL)
+		backlight_device_unregister(pcf->backlight);
+
+	if (pcf->pdata->used_features & PCF50606_FEAT_WDT)
+		misc_deregister(&pcf50606_wdt_miscdev);
+
+	if (pcf->pdata->used_features & PCF50606_FEAT_RTC)
+		rtc_device_unregister(pcf->rtc);
+
+	free_irq(pcf->irq, pcf);
+
+	sysfs_remove_group(&client->dev.kobj, &pcf_attr_group);
+
+	pm_power_off = NULL;
+
+	kfree(pcf);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+#define INT1M_RESUMERS	(PCF50606_INT1_ALARM | \
+			 PCF50606_INT1_ONKEYF | \
+			 PCF50606_INT1_EXTONR)
+#define INT2M_RESUMERS	(PCF50606_INT2_CHGWD10S | \
+			 PCF50606_INT2_CHGPROT | \
+			 PCF50606_INT2_CHGERR)
+#define INT3M_RESUMERS	(PCF50606_INT3_LOWBAT | \
+			 PCF50606_INT3_HIGHTMP | \
+			 PCF50606_INT3_ACDINS)
+static int pcf50606_suspend(struct device *dev, pm_message_t state)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50606_data *pcf = i2c_get_clientdata(client);
+	int i;
+
+	/* The general idea is to power down all unused power supplies,
+	 * and then mask all PCF50606 interrup sources but EXTONR, ONKEYF
+	 * and ALARM */
+
+	mutex_lock(&pcf->lock);
+
+	/* Save all registers that don't "survive" standby state */
+	pcf->standby_regs.dcdc1 = __reg_read(pcf, PCF50606_REG_DCDC1);
+	pcf->standby_regs.dcdc2 = __reg_read(pcf, PCF50606_REG_DCDC2);
+	pcf->standby_regs.dcdec1 = __reg_read(pcf, PCF50606_REG_DCDEC1);
+	pcf->standby_regs.dcudc1 = __reg_read(pcf, PCF50606_REG_DCUDC1);
+	pcf->standby_regs.ioregc = __reg_read(pcf, PCF50606_REG_IOREGC);
+	pcf->standby_regs.d1regc1 = __reg_read(pcf, PCF50606_REG_D1REGC1);
+	pcf->standby_regs.d2regc1 = __reg_read(pcf, PCF50606_REG_D2REGC1);
+	pcf->standby_regs.d3regc1 = __reg_read(pcf, PCF50606_REG_D3REGC1);
+	pcf->standby_regs.lpregc1 = __reg_read(pcf, PCF50606_REG_LPREGC1);
+	pcf->standby_regs.adcc1 = __reg_read(pcf, PCF50606_REG_ADCC1);
+	pcf->standby_regs.adcc2 = __reg_read(pcf, PCF50606_REG_ADCC2);
+	pcf->standby_regs.pwmc1 = __reg_read(pcf, PCF50606_REG_PWMC1);
+
+	/* switch off power supplies that are not needed during suspend */
+	for (i = 0; i < __NUM_PCF50606_REGULATORS; i++) {
+		if (!(pcf->pdata->rails[i].flags & PMU_VRAIL_F_SUSPEND_ON)) {
+			u_int8_t tmp;
+
+			/* IOREG powers the I@C interface so we cannot switch
+			 * it off */
+			if (i == PCF50606_REGULATOR_IOREG)
+				continue;
+
+			dev_dbg(dev, "disabling pcf50606 regulator %u\n", i);
+			/* we cannot use pcf50606_onoff_set() because we're
+			 * already under the mutex */
+			tmp = __reg_read(pcf, regulator_registers[i]);
+			tmp &= 0x1f;
+			__reg_write(pcf, regulator_registers[i], tmp);
+		}
+	}
+
+	pcf->standby_regs.int1m = __reg_read(pcf, PCF50606_REG_INT1M);
+	pcf->standby_regs.int2m = __reg_read(pcf, PCF50606_REG_INT2M);
+	pcf->standby_regs.int3m = __reg_read(pcf, PCF50606_REG_INT3M);
+	__reg_write(pcf, PCF50606_REG_INT1M, ~INT1M_RESUMERS & 0xff);
+	__reg_write(pcf, PCF50606_REG_INT2M, ~INT2M_RESUMERS & 0xff);
+	__reg_write(pcf, PCF50606_REG_INT3M, ~INT3M_RESUMERS & 0xff);
+
+	mutex_unlock(&pcf->lock);
+
+	return 0;
+}
+
+static int pcf50606_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50606_data *pcf = i2c_get_clientdata(client);
+
+	mutex_lock(&pcf->lock);
+
+	/* Resume all saved registers that don't "survive" standby state */
+	__reg_write(pcf, PCF50606_REG_INT1M, pcf->standby_regs.int1m);
+	__reg_write(pcf, PCF50606_REG_INT2M, pcf->standby_regs.int2m);
+	__reg_write(pcf, PCF50606_REG_INT3M, pcf->standby_regs.int3m);
+
+	__reg_write(pcf, PCF50606_REG_DCDC1, pcf->standby_regs.dcdc1);
+	__reg_write(pcf, PCF50606_REG_DCDC2, pcf->standby_regs.dcdc2);
+	__reg_write(pcf, PCF50606_REG_DCDEC1, pcf->standby_regs.dcdec1);
+	__reg_write(pcf, PCF50606_REG_DCUDC1, pcf->standby_regs.dcudc1);
+	__reg_write(pcf, PCF50606_REG_IOREGC, pcf->standby_regs.ioregc);
+	__reg_write(pcf, PCF50606_REG_D1REGC1, pcf->standby_regs.d1regc1);
+	__reg_write(pcf, PCF50606_REG_D2REGC1, pcf->standby_regs.d2regc1);
+	__reg_write(pcf, PCF50606_REG_D3REGC1, pcf->standby_regs.d3regc1);
+	__reg_write(pcf, PCF50606_REG_LPREGC1, pcf->standby_regs.lpregc1);
+	__reg_write(pcf, PCF50606_REG_ADCC1, pcf->standby_regs.adcc1);
+	__reg_write(pcf, PCF50606_REG_ADCC2, pcf->standby_regs.adcc2);
+	__reg_write(pcf, PCF50606_REG_PWMC1, pcf->standby_regs.pwmc1);
+
+	mutex_unlock(&pcf->lock);
+
+	return 0;
+}
+#else
+#define pcf50606_suspend NULL
+#define pcf50606_resume NULL
+#endif
+
+static struct i2c_driver pcf50606_driver = {
+	.driver = {
+		.name	 = "pcf50606",
+		.suspend = pcf50606_suspend,
+		.resume	 = pcf50606_resume,
+	},
+	.id		= I2C_DRIVERID_PCF50606,
+	.attach_adapter	= pcf50606_attach_adapter,
+	.detach_client	= pcf50606_detach_client,
+};
+
+/* platform driver, since i2c devices don't have platform_data */
+static int __init pcf50606_plat_probe(struct platform_device *pdev)
+{
+	struct pcf50606_platform_data *pdata = pdev->dev.platform_data;
+
+	if (!pdata)
+		return -ENODEV;
+
+	pcf50606_pdev = pdev;
+
+	return 0;
+}
+
+static int pcf50606_plat_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static struct platform_driver pcf50606_plat_driver = {
+	.probe	= pcf50606_plat_probe,
+	.remove	= pcf50606_plat_remove,
+	.driver = {
+		.owner	= THIS_MODULE,
+		.name 	= "pcf50606",
+	},
+};
+
+static int __init pcf50606_init(void)
+{
+	int rc;
+
+	rc = platform_driver_register(&pcf50606_plat_driver);
+	if (!rc)
+		rc = i2c_add_driver(&pcf50606_driver);
+
+	return rc;
+}
+
+static void pcf50606_exit(void)
+{
+	i2c_del_driver(&pcf50606_driver);
+	platform_driver_unregister(&pcf50606_plat_driver);
+}
+
+MODULE_DESCRIPTION("I2C chip driver for NXP PCF50606 power management unit");
+MODULE_AUTHOR("Harald Welte <laforge@openmoko.org>");
+MODULE_LICENSE("GPL");
+
+module_init(pcf50606_init);
+module_exit(pcf50606_exit);
Index: linux-2.6/drivers/i2c/chips/pcf50606.h
===================================================================
--- /dev/null
+++ linux-2.6/drivers/i2c/chips/pcf50606.h
@@ -0,0 +1,302 @@
+#ifndef _PCF50606_H
+#define _PCF50606_H
+
+/* Philips PCF50606 Power Managemnt Unit (PMU) driver
+ * (C) 2006-2007 by OpenMoko, Inc.
+ * Author: Harald Welte <laforge@openmoko.org>
+ *
+ */
+
+enum pfc50606_regs {
+	PCF50606_REG_ID		= 0x00,
+	PCF50606_REG_OOCS	= 0x01,
+	PCF50606_REG_INT1	= 0x02,	/* Interrupt Status */
+	PCF50606_REG_INT2	= 0x03,	/* Interrupt Status */
+	PCF50606_REG_INT3	= 0x04,	/* Interrupt Status */
+	PCF50606_REG_INT1M	= 0x05,	/* Interrupt Mask */
+	PCF50606_REG_INT2M	= 0x06,	/* Interrupt Mask */
+	PCF50606_REG_INT3M	= 0x07,	/* Interrupt Mask */
+	PCF50606_REG_OOCC1	= 0x08,
+	PCF50606_REG_OOCC2	= 0x09,
+	PCF50606_REG_RTCSC	= 0x0a,	/* Second */
+	PCF50606_REG_RTCMN	= 0x0b,	/* Minute */
+	PCF50606_REG_RTCHR	= 0x0c,	/* Hour */
+	PCF50606_REG_RTCWD	= 0x0d,	/* Weekday */
+	PCF50606_REG_RTCDT	= 0x0e,	/* Day */
+	PCF50606_REG_RTCMT	= 0x0f,	/* Month */
+	PCF50606_REG_RTCYR	= 0x10,	/* Year */
+	PCF50606_REG_RTCSCA	= 0x11, /* Alarm Second */
+	PCF50606_REG_RTCMNA	= 0x12, /* Alarm Minute */
+	PCF50606_REG_RTCHRA	= 0x13, /* Alarm Hour */
+	PCF50606_REG_RTCWDA	= 0x14, /* Alarm Weekday */
+	PCF50606_REG_RTCDTA	= 0x15, /* Alarm Day */
+	PCF50606_REG_RTCMTA	= 0x16, /* Alarm Month */
+	PCF50606_REG_RTCYRA	= 0x17, /* Alarm Year */
+	PCF50606_REG_PSSC	= 0x18,	/* Power sequencing */
+	PCF50606_REG_PWROKM	= 0x19,	/* PWROK mask */
+	PCF50606_REG_PWROKS	= 0x1a,	/* PWROK status */
+	PCF50606_REG_DCDC1	= 0x1b,
+	PCF50606_REG_DCDC2	= 0x1c,
+	PCF50606_REG_DCDC3	= 0x1d,
+	PCF50606_REG_DCDC4	= 0x1e,
+	PCF50606_REG_DCDEC1	= 0x1f,
+	PCF50606_REG_DCDEC2	= 0x20,
+	PCF50606_REG_DCUDC1	= 0x21,
+	PCF50606_REG_DCUDC2	= 0x22,
+	PCF50606_REG_IOREGC	= 0x23,
+	PCF50606_REG_D1REGC1	= 0x24,
+	PCF50606_REG_D2REGC1	= 0x25,
+	PCF50606_REG_D3REGC1	= 0x26,
+	PCF50606_REG_LPREGC1	= 0x27,
+	PCF50606_REG_LPREGC2	= 0x28,
+	PCF50606_REG_MBCC1	= 0x29,
+	PCF50606_REG_MBCC2	= 0x2a,
+	PCF50606_REG_MBCC3	= 0x2b,
+	PCF50606_REG_MBCS1	= 0x2c,
+	PCF50606_REG_BBCC	= 0x2d,
+	PCF50606_REG_ADCC1	= 0x2e,
+	PCF50606_REG_ADCC2	= 0x2f,
+	PCF50606_REG_ADCS1	= 0x30,
+	PCF50606_REG_ADCS2	= 0x31,
+	PCF50606_REG_ADCS3	= 0x32,
+	PCF50606_REG_ACDC1	= 0x33,
+	PCF50606_REG_BVMC	= 0x34,
+	PCF50606_REG_PWMC1	= 0x35,
+	PCF50606_REG_LEDC1	= 0x36,
+	PCF50606_REG_LEDC2	= 0x37,
+	PCF50606_REG_GPOC1	= 0x38,
+	PCF50606_REG_GPOC2	= 0x39,
+	PCF50606_REG_GPOC3	= 0x3a,
+	PCF50606_REG_GPOC4	= 0x3b,
+	PCF50606_REG_GPOC5	= 0x3c,
+	__NUM_PCF50606_REGS
+};
+
+enum pcf50606_reg_oocs {
+	PFC50606_OOCS_ONKEY	= 0x01,
+	PCF50606_OOCS_EXTON	= 0x02,
+	PCF50606_OOCS_PWROKRST	= 0x04,
+	PCF50606_OOCS_BATOK	= 0x08,
+	PCF50606_OOCS_BACKOK	= 0x10,
+	PCF50606_OOCS_CHGOK	= 0x20,
+	PCF50606_OOCS_TEMPOK	= 0x40,
+	PCF50606_OOCS_WDTEXP	= 0x80,
+};
+
+enum pcf50606_reg_oocc1 {
+	PCF50606_OOCC1_GOSTDBY	= 0x01,
+	PCF50606_OOCC1_TOTRST	= 0x02,
+	PCF50606_OOCC1_CLK32ON	= 0x04,
+	PCF50606_OOCC1_WDTRST	= 0x08,
+	PCF50606_OOCC1_RTCWAK	= 0x10,
+	PCF50606_OOCC1_CHGWAK	= 0x20,
+	PCF50606_OOCC1_EXTONWAK_HIGH	= 0x40,
+	PCF50606_OOCC1_EXTONWAK_LOW	= 0x80,
+};
+
+enum pcf50606_reg_oocc2 {
+	PCF50606_OOCC2_ONKEYDB_NONE	= 0x00,
+	PCF50606_OOCC2_ONKEYDB_14ms	= 0x01,
+	PCF50606_OOCC2_ONKEYDB_62ms	= 0x02,
+	PCF50606_OOCC2_ONKEYDB_500ms	= 0x03,
+	PCF50606_OOCC2_EXTONDB_NONE	= 0x00,
+	PCF50606_OOCC2_EXTONDB_14ms	= 0x04,
+	PCF50606_OOCC2_EXTONDB_62ms	= 0x08,
+	PCF50606_OOCC2_EXTONDB_500ms	= 0x0c,
+};
+
+enum pcf50606_reg_int1 {
+	PCF50606_INT1_ONKEYR	= 0x01,	/* ONKEY rising edge */
+	PCF50606_INT1_ONKEYF	= 0x02,	/* ONKEY falling edge */
+	PCF50606_INT1_ONKEY1S	= 0x04,	/* OMKEY at least 1sec low */
+	PCF50606_INT1_EXTONR	= 0x08,	/* EXTON rising edge */
+	PCF50606_INT1_EXTONF	= 0x10,	/* EXTON falling edge */
+	PCF50606_INT1_SECOND	= 0x40,	/* RTC periodic second interrupt */
+	PCF50606_INT1_ALARM	= 0x80, /* RTC alarm time is reached */
+};
+
+enum pcf50606_reg_int2 {
+	PCF50606_INT2_CHGINS	= 0x01, /* Charger inserted */
+	PCF50606_INT2_CHGRM	= 0x02, /* Charger removed */
+	PCF50606_INT2_CHGFOK	= 0x04,	/* Fast charging OK */
+	PCF50606_INT2_CHGERR	= 0x08,	/* Error in charging mode */
+	PCF50606_INT2_CHGFRDY	= 0x10,	/* Fast charge completed */
+	PCF50606_INT2_CHGPROT	= 0x20,	/* Charging protection interrupt */
+	PCF50606_INT2_CHGWD10S	= 0x40,	/* Charger watchdig expires in 10s */
+	PCF50606_INT2_CHGWDEXP	= 0x80,	/* Charger watchdog expires */
+};
+
+enum pcf50606_reg_int3 {
+	PCF50606_INT3_ADCRDY	= 0x01,	/* ADC conversion finished */
+	PCF50606_INT3_ACDINS	= 0x02,	/* Accessory inserted */
+	PCF50606_INT3_ACDREM	= 0x04, /* Accessory removed */
+	PCF50606_INT3_TSCPRES	= 0x08,	/* Touch screen pressed */
+	PCF50606_INT3_LOWBAT	= 0x40,	/* Low battery voltage */
+	PCF50606_INT3_HIGHTMP	= 0x80, /* High temperature */
+};
+
+/* used by PSSC, PWROKM, PWROKS, */
+enum pcf50606_regu {
+	PCF50606_REGU_DCD	= 0x01,	/* DCD in phase 2 */
+	PCF50606_REGU_DCDE	= 0x02,	/* DCDE in phase 2 */
+	PCF50606_REGU_DCUD	= 0x04,	/* DCDU in phase 2 */
+	PCF50606_REGU_IO	= 0x08,	/* IO in phase 2 */
+	PCF50606_REGU_D1	= 0x10, /* D1 in phase 2 */
+	PCF50606_REGU_D2	= 0x20,	/* D2 in phase 2 */
+	PCF50606_REGU_D3	= 0x40,	/* D3 in phase 2 */
+	PCF50606_REGU_LP	= 0x80,	/* LP in phase 2 */
+};
+
+enum pcf50606_reg_dcdc4 {
+	PCF50606_DCDC4_MODE_AUTO	= 0x00,
+	PCF50606_DCDC4_MODE_PWM		= 0x01,
+	PCF50606_DCDC4_MODE_PCF		= 0x02,
+	PCF50606_DCDC4_OFF_FLOAT	= 0x00,
+	PCF50606_DCDC4_OFF_BYPASS	= 0x04,
+	PCF50606_DCDC4_OFF_PULLDOWN	= 0x08,
+	PCF50606_DCDC4_CURLIM_500mA	= 0x00,
+	PCF50606_DCDC4_CURLIM_750mA	= 0x10,
+	PCF50606_DCDC4_CURLIM_1000mA	= 0x20,
+	PCF50606_DCDC4_CURLIM_1250mA	= 0x30,
+	PCF50606_DCDC4_TOGGLE		= 0x40,
+	PCF50606_DCDC4_REGSEL_DCDC2	= 0x80,
+};
+
+enum pcf50606_reg_dcdec2 {
+	PCF50606_DCDEC2_MODE_AUTO	= 0x00,
+	PCF50606_DCDEC2_MODE_PWM	= 0x01,
+	PCF50606_DCDEC2_MODE_PCF	= 0x02,
+	PCF50606_DCDEC2_OFF_FLOAT	= 0x00,
+	PCF50606_DCDEC2_OFF_BYPASS	= 0x04,
+};
+
+enum pcf50606_reg_dcudc2 {
+	PCF50606_DCUDC2_MODE_AUTO	= 0x00,
+	PCF50606_DCUDC2_MODE_PWM	= 0x01,
+	PCF50606_DCUDC2_MODE_PCF	= 0x02,
+	PCF50606_DCUDC2_OFF_FLOAT	= 0x00,
+	PCF50606_DCUDC2_OFF_BYPASS	= 0x04,
+};
+
+enum pcf50606_reg_adcc1 {
+	PCF50606_ADCC1_TSCMODACT	= 0x01,
+	PCF50606_ADCC1_TSCMODSTB	= 0x02,
+	PCF50606_ADCC1_TRATSET		= 0x04,
+	PCF50606_ADCC1_NTCSWAPE		= 0x08,
+	PCF50606_ADCC1_NTCSWAOFF	= 0x10,
+	PCF50606_ADCC1_EXTSYNCBREAK	= 0x20,
+	/* reserved */
+	PCF50606_ADCC1_TSCINT		= 0x80,
+};
+
+enum pcf50606_reg_adcc2 {
+	PCF50606_ADCC2_ADCSTART		= 0x01,
+	/* see enum pcf50606_adcc2_adcmux */
+	PCF50606_ADCC2_SYNC_NONE	= 0x00,
+	PCF50606_ADCC2_SYNC_TXON	= 0x20,
+	PCF50606_ADCC2_SYNC_PWREN1	= 0x40,
+	PCF50606_ADCC2_SYNC_PWREN2	= 0x60,
+	PCF50606_ADCC2_RES_10BIT	= 0x00,
+	PCF50606_ADCC2_RES_8BIT		= 0x80,
+};
+
+#define PCF50606_ADCC2_ADCMUX_MASK	(0xf << 1)
+
+#define ADCMUX_SHIFT	1
+enum pcf50606_adcc2_adcmux {
+	PCF50606_ADCMUX_BATVOLT_RES	= 0x0 << ADCMUX_SHIFT,
+	PCF50606_ADCMUX_BATVOLT_SUBTR	= 0x1 << ADCMUX_SHIFT,
+	PCF50606_ADCMUX_ADCIN1_RES	= 0x2 << ADCMUX_SHIFT,
+	PCF50606_ADCMUX_ADCIN1_SUBTR	= 0x3 << ADCMUX_SHIFT,
+	PCF50606_ADCMUX_BATTEMP		= 0x4 << ADCMUX_SHIFT,
+	PCF50606_ADCMUX_ADCIN2		= 0x5 << ADCMUX_SHIFT,
+	PCF50606_ADCMUX_ADCIN3		= 0x6 << ADCMUX_SHIFT,
+	PCF50606_ADCMUX_ADCIN3_RATIO	= 0x7 << ADCMUX_SHIFT,
+	PCF50606_ADCMUX_XPOS		= 0x8 << ADCMUX_SHIFT,
+	PCF50606_ADCMUX_YPOS		= 0x9 << ADCMUX_SHIFT,
+	PCF50606_ADCMUX_P1		= 0xa << ADCMUX_SHIFT,
+	PCF50606_ADCMUX_P2		= 0xb << ADCMUX_SHIFT,
+	PCF50606_ADCMUX_BATVOLT_ADCIN1	= 0xc << ADCMUX_SHIFT,
+	PCF50606_ADCMUX_XY_SEQUENCE	= 0xe << ADCMUX_SHIFT,
+	PCF50606_P1_P2_RESISTANCE	= 0xf << ADCMUX_SHIFT,
+};
+
+enum pcf50606_adcs2 {
+	PCF50606_ADCS2_ADCRDY		= 0x80,
+};
+
+enum pcf50606_reg_mbcc1 {
+	PCF50606_MBCC1_CHGAPE		= 0x01,
+	PCF50606_MBCC1_AUTOFST		= 0x02,
+#define	PCF50606_MBCC1_CHGMOD_MASK	  0x1c
+#define	PCF50606_MBCC1_CHGMOD_SHIFT	  2
+	PCF50606_MBCC1_CHGMOD_QUAL	= 0x00,
+	PCF50606_MBCC1_CHGMOD_PRE	= 0x04,
+	PCF50606_MBCC1_CHGMOD_TRICKLE	= 0x08,
+	PCF50606_MBCC1_CHGMOD_FAST_CCCV	= 0x0c,
+	PCF50606_MBCC1_CHGMOD_FAST_NOCC	= 0x10,
+	PCF50606_MBCC1_CHGMOD_FAST_NOCV	= 0x14,
+	PCF50606_MBCC1_CHGMOD_FAST_SW	= 0x18,
+	PCF50606_MBCC1_CHGMOD_IDLE	= 0x1c,
+	PCF50606_MBCC1_DETMOD_LOWCHG	= 0x20,
+	PCF50606_MBCC1_DETMOD_WDRST	= 0x40,
+};
+
+enum pcf50606_reg_acdc1 {
+	PCF50606_ACDC1_ACDDET		= 0x01,
+	PCF50606_ACDC1_THRSHLD_1V0	= 0x00,
+	PCF50606_ACDC1_THRSHLD_1V2	= 0x02,
+	PCF50606_ACDC1_THRSHLD_1V4	= 0x04,
+	PCF50606_ACDC1_THRSHLD_1V6	= 0x06,
+	PCF50606_ACDC1_THRSHLD_1V8	= 0x08,
+	PCF50606_ACDC1_THRSHLD_2V0	= 0x0a,
+	PCF50606_ACDC1_THRSHLD_2V2	= 0x0c,
+	PCF50606_ACDC1_THRSHLD_2V4	= 0x0e,
+	PCF50606_ACDC1_DISDB		= 0x10,
+	PCF50606_ACDC1_ACDAPE		= 0x80,
+};
+
+enum pcf50606_reg_bvmc {
+	PCF50606_BVMC_LOWBAT		= 0x01,
+	PCF50606_BVMC_THRSHLD_NULL	= 0x00,
+	PCF50606_BVMC_THRSHLD_2V8	= 0x02,
+	PCF50606_BVMC_THRSHLD_2V9	= 0x04,
+	PCF50606_BVMC_THRSHLD_3V	= 0x08,
+	PCF50606_BVMC_THRSHLD_3V1	= 0x08,
+	PCF50606_BVMC_THRSHLD_3V2	= 0x0a,
+	PCF50606_BVMC_THRSHLD_3V3	= 0x0c,
+	PCF50606_BVMC_THRSHLD_3V4	= 0x0e,
+	PCF50606_BVMC_DISDB		= 0x10,
+};
+
+enum pcf50606_reg_pwmc1 {
+	PCF50606_PWMC1_ACTSET		= 0x01,
+	PCF50606_PWMC1_PWMDC_0_16	= 0x00,
+	PCF50606_PWMC1_PWMDC_1_16	= 0x02,
+	PCF50606_PWMC1_PWMDC_2_16	= 0x04,
+	PCF50606_PWMC1_PWMDC_3_16	= 0x06,
+	PCF50606_PWMC1_PWMDC_4_16	= 0x08,
+	PCF50606_PWMC1_PWMDC_5_16	= 0x0a,
+	PCF50606_PWMC1_PWMDC_6_16	= 0x0c,
+	PCF50606_PWMC1_PWMDC_7_16	= 0x0e,
+	PCF50606_PWMC1_PWMDC_8_16	= 0x10,
+	PCF50606_PWMC1_PWMDC_9_16	= 0x12,
+	PCF50606_PWMC1_PWMDC_10_16	= 0x14,
+	PCF50606_PWMC1_PWMDC_11_16	= 0x16,
+	PCF50606_PWMC1_PWMDC_12_16	= 0x18,
+	PCF50606_PWMC1_PWMDC_13_16	= 0x1a,
+	PCF50606_PWMC1_PWMDC_14_16	= 0x1c,
+	PCF50606_PWMC1_PWMDC_15_16	= 0x1e,
+	PCF50606_PWMC1_PRESC_512Hz	= 0x20,
+	PCF50606_PWMC1_PRESC_256Hz	= 0x40,
+	PCF50606_PWMC1_PRESC_64Hz	= 0x60,
+	PCF50606_PWMC1_PRESC_56kHz	= 0x80,
+	PCF50606_PWMC1_PRESC_28kHz	= 0xa0,
+	PCF50606_PWMC1_PRESC_14kHz	= 0xc0,
+	PCF50606_PWMC1_PRESC_7kHz	= 0xe0,
+};
+#define PCF50606_PWMC1_CLK_SHIFT	5
+#define PCF50606_PWMC1_DC_SHIFT		1
+
+#endif /* _PCF50606_H */
+
Index: linux-2.6/drivers/i2c/chips/Kconfig
===================================================================
--- linux-2.6.orig/drivers/i2c/chips/Kconfig
+++ linux-2.6/drivers/i2c/chips/Kconfig
@@ -51,6 +51,17 @@
 	  This driver can also be built as a module.  If so, the module
 	  will be called eeprom.
 
+config SENSORS_PCF50606
+	tristate "Philips/NXP PCF50606"
+	depends on I2C
+	help
+	  If you say yes here you get support for Philips/NXP PCF50606
+	  PMU (Power Management Unit) chips.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called pcf50606.
+
+
 config SENSORS_PCF8574
 	tristate "Philips PCF8574 and PCF8574A"
 	depends on EXPERIMENTAL
Index: linux-2.6/drivers/i2c/chips/Makefile
===================================================================
--- linux-2.6.orig/drivers/i2c/chips/Makefile
+++ linux-2.6/drivers/i2c/chips/Makefile
@@ -9,6 +9,7 @@
 obj-$(CONFIG_SENSORS_MAX6875)	+= max6875.o
 obj-$(CONFIG_SENSORS_M41T00)	+= m41t00.o
 obj-$(CONFIG_SENSORS_PCA9539)	+= pca9539.o
+obj-$(CONFIG_SENSORS_PCF50606)	+= pcf50606.o
 obj-$(CONFIG_SENSORS_PCF8574)	+= pcf8574.o
 obj-$(CONFIG_SENSORS_PCF8591)	+= pcf8591.o
 obj-$(CONFIG_ISP1301_OMAP)	+= isp1301_omap.o
Index: linux-2.6/include/linux/i2c-id.h
===================================================================
--- linux-2.6.orig/include/linux/i2c-id.h
+++ linux-2.6/include/linux/i2c-id.h
@@ -163,6 +163,7 @@
 #define I2C_DRIVERID_FSCHER 1046
 #define I2C_DRIVERID_W83L785TS 1047
 #define I2C_DRIVERID_OV7670 1048	/* Omnivision 7670 camera */
+#define I2C_DRIVERID_PCF50606 1049
 
 /*
  * ---- Adapter types ----------------------------------------------------
Index: linux-2.6/include/linux/pcf50606.h
===================================================================
--- /dev/null
+++ linux-2.6/include/linux/pcf50606.h
@@ -0,0 +1,108 @@
+#ifndef _LINUX_PCF50606_H
+#define _LINUX_PCF50606_H
+
+/* public in-kernel pcf50606 api */
+enum pcf50606_regulator_id {
+	PCF50606_REGULATOR_DCD,
+	PCF50606_REGULATOR_DCDE,
+	PCF50606_REGULATOR_DCUD,
+	PCF50606_REGULATOR_D1REG,
+	PCF50606_REGULATOR_D2REG,
+	PCF50606_REGULATOR_D3REG,
+	PCF50606_REGULATOR_LPREG,
+	PCF50606_REGULATOR_IOREG,
+	__NUM_PCF50606_REGULATORS
+};
+
+struct pcf50606_data;
+
+/* This is an ugly construct on how to access the (currently single/global)
+ * pcf50606 handle from other code in the kernel.  I didn't really come up with
+ * a more decent method of dynamically resolving this */
+extern struct pcf50606_data *pcf50606_global;
+
+extern void
+pcf50606_go_standby(void);
+
+extern void
+pcf50606_gpo0_set(struct pcf50606_data *pcf, int on);
+
+extern int
+pcf50606_gpo0_get(struct pcf50606_data *pcf);
+
+extern int
+pcf50606_voltage_set(struct pcf50606_data *pcf,
+		     enum pcf50606_regulator_id reg,
+		     unsigned int millivolts);
+extern unsigned int
+pcf50606_voltage_get(struct pcf50606_data *pcf,
+		     enum pcf50606_regulator_id reg);
+extern int
+pcf50606_onoff_get(struct pcf50606_data *pcf,
+		   enum pcf50606_regulator_id reg);
+
+extern int
+pcf50606_onoff_set(struct pcf50606_data *pcf,
+		   enum pcf50606_regulator_id reg, int on);
+
+extern void
+pcf50606_charge_fast(struct pcf50606_data *pcf, int on);
+
+#define PMU_VRAIL_F_SUSPEND_ON	0x00000001	/* Remains on during suspend */
+#define PMU_VRAIL_F_UNUSED	0x00000002	/* This rail is not used */
+struct pmu_voltage_rail {
+	char *name;
+	unsigned int flags;
+	struct {
+		unsigned int init;
+		unsigned int max;
+	} voltage;
+};
+
+enum pmu_event {
+	PMU_EVT_NONE,
+	PMU_EVT_INSERT,
+	PMU_EVT_REMOVE,
+	__NUM_PMU_EVTS
+};
+
+typedef int pmu_cb(struct device *dev, unsigned int feature,
+		   enum pmu_event event);
+
+#define PCF50606_FEAT_EXTON	0x00000001	/* not yet supported */
+#define PCF50606_FEAT_MBC	0x00000002
+#define PCF50606_FEAT_BBC	0x00000004	/* not yet supported */
+#define PCF50606_FEAT_TSC	0x00000008	/* not yet supported */
+#define PCF50606_FEAT_WDT	0x00000010
+#define PCF50606_FEAT_ACD	0x00000020
+#define PCF50606_FEAT_RTC	0x00000040
+#define PCF50606_FEAT_PWM	0x00000080
+#define PCF50606_FEAT_CHGCUR	0x00000100
+#define PCF50606_FEAT_BATVOLT	0x00000200
+#define PCF50606_FEAT_BATTEMP	0x00000400
+#define PCF50606_FEAT_PWM_BL	0x00000800
+
+struct pcf50606_platform_data {
+	/* general */
+	unsigned int used_features;
+	unsigned int onkey_seconds_required;
+
+	/* voltage regulator related */
+	struct pmu_voltage_rail rails[__NUM_PCF50606_REGULATORS];
+	unsigned int used_regulators;
+
+	/* charger related */
+	unsigned int r_fix_batt;
+	unsigned int r_fix_batt_par;
+	unsigned int r_sense_milli;
+
+	/* backlight related */
+	unsigned int init_brightness;
+
+	struct {
+		u_int8_t mbcc3; /* charger voltage / current */
+	} charger;
+	pmu_cb *cb;
+};
+
+#endif
-- 
- Harald Welte <laforge@openmoko.org>          	        http://openmoko.org/
============================================================================
Software for the world's first truly open Free Software mobile phone

_______________________________________________
i2c mailing list
i2c@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/i2c
[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic