From b04e2c56a057f288342a8ce8f88b7a1dfc88afa1 Mon Sep 17 00:00:00 2001
From: mokopatches <mokopatches@openmoko.org>
Date: Wed, 16 Jul 2008 14:44:50 +0100
Subject: [PATCH] pcf50633.patch

---
 drivers/i2c/chips/Kconfig    |    9 +
 drivers/i2c/chips/Makefile   |    1 +
 drivers/i2c/chips/pcf50633.c | 1964 ++++++++++++++++++++++++++++++++++++++++++
 drivers/i2c/chips/pcf50633.h |  402 +++++++++
 include/linux/i2c-id.h       |    1 +
 include/linux/pcf50633.h     |  114 +++
 6 files changed, 2491 insertions(+), 0 deletions(-)
 create mode 100644 drivers/i2c/chips/pcf50633.c
 create mode 100644 drivers/i2c/chips/pcf50633.h
 create mode 100644 include/linux/pcf50633.h

diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig
index e8e64aa..e38c006 100644
--- a/drivers/i2c/chips/Kconfig
+++ b/drivers/i2c/chips/Kconfig
@@ -35,6 +35,15 @@ config SENSORS_PCF50606
 	  This driver can also be built as a module.  If so, the module
 	  will be called pcf50606.
 
+config SENSORS_PCF50633
+	tristate "Philips PCF50633"
+	depends on I2C
+	help
+	  If you say yes here you get support for Philips PCF50633
+	  PMU (Power Management Unit) chips.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called pcf50633.
 
 config SENSORS_PCF8574
 	tristate "Philips PCF8574 and PCF8574A"
diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile
index 60d3d5a..9fa353d 100644
--- a/drivers/i2c/chips/Makefile
+++ b/drivers/i2c/chips/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_SENSORS_EEPROM)	+= eeprom.o
 obj-$(CONFIG_SENSORS_MAX6875)	+= max6875.o
 obj-$(CONFIG_SENSORS_PCA9539)	+= pca9539.o
 obj-$(CONFIG_SENSORS_PCF50606)	+= pcf50606.o
+obj-$(CONFIG_SENSORS_PCF50633)	+= pcf50633.o
 obj-$(CONFIG_SENSORS_PCF8574)	+= pcf8574.o
 obj-$(CONFIG_PCF8575)		+= pcf8575.o
 obj-$(CONFIG_SENSORS_PCF8591)	+= pcf8591.o
diff --git a/drivers/i2c/chips/pcf50633.c b/drivers/i2c/chips/pcf50633.c
new file mode 100644
index 0000000..5488084
--- /dev/null
+++ b/drivers/i2c/chips/pcf50633.c
@@ -0,0 +1,1964 @@
+/* Philips PCF50633 Power Management Unit (PMU) driver
+ *
+ * (C) 2006-2007 by OpenMoko, Inc.
+ * Author: Harald Welte <laforge@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
+ * - adc driver (hw_sensors like)
+ * - 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/delay.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/pcf50633.h>
+#include <linux/apm-emulation.h>
+
+#include <asm/mach-types.h>
+#include <asm/arch/gta02.h>
+
+#include "pcf50633.h"
+
+#if 1
+#define DEBUGP(x, args ...) printk("%s: " x, __FUNCTION__, ## args)
+#define DEBUGPC(x, args ...) printk(x, ## args)
+#else
+#define DEBUGP(x, args ...)
+#define DEBUGPC(x, args ...)
+#endif
+
+/***********************************************************************
+ * Static data / structures
+ ***********************************************************************/
+
+static unsigned short normal_i2c[] = { 0x73, I2C_CLIENT_END };
+
+I2C_CLIENT_INSMOD_1(pcf50633);
+
+#define PCF50633_FIDX_CHG_ENABLED	0	/* Charger enabled */
+#define PCF50633_FIDX_CHG_PRESENT	1	/* Charger present */
+#define PCF50633_FIDX_CHG_ERR		3	/* Charger Error */
+#define PCF50633_FIDX_CHG_PROT		4	/* Charger Protection */
+#define PCF50633_FIDX_CHG_READY		5	/* Charging completed */
+#define PCF50633_FIDX_PWR_PRESSED	8
+#define PCF50633_FIDX_RTC_SECOND	9
+#define PCF50633_FIDX_USB_PRESENT	10
+
+#define PCF50633_F_CHG_ENABLED	(1 << PCF50633_FIDX_CHG_ENABLED)
+#define PCF50633_F_CHG_PRESENT	(1 << PCF50633_FIDX_CHG_PRESENT)
+#define PCF50633_F_CHG_ERR	(1 << PCF50633_FIDX_CHG_ERR)
+#define PCF50633_F_CHG_PROT	(1 << PCF50633_FIDX_CHG_PROT)
+#define PCF50633_F_CHG_READY	(1 << PCF50633_FIDX_CHG_READY)
+
+#define PCF50633_F_CHG_MASK	0x000000fc
+
+#define PCF50633_F_PWR_PRESSED	(1 << PCF50633_FIDX_PWR_PRESSED)
+#define PCF50633_F_RTC_SECOND	(1 << PCF50633_FIDX_RTC_SECOND)
+#define PCF50633_F_USB_PRESENT	(1 << PCF50633_FIDX_USB_PRESENT)
+
+enum close_state {
+	CLOSE_STATE_NOT,
+	CLOSE_STATE_ALLOW = 0x2342,
+};
+
+enum charger_type {
+	CHARGER_TYPE_NONE = 0,
+	CHARGER_TYPE_HOSTUSB,
+	CHARGER_TYPE_1A
+};
+
+#define ADC_NOM_CHG_DETECT_1A 6
+#define ADC_NOM_CHG_DETECT_NONE 43
+
+#define MAX_ADC_FIFO_DEPTH 8
+
+struct pcf50633_data {
+	struct i2c_client client;
+	struct pcf50633_platform_data *pdata;
+	struct backlight_device *backlight;
+	struct mutex lock;
+	unsigned int flags;
+	unsigned int working;
+	struct mutex working_lock;
+	struct work_struct work;
+	struct rtc_device *rtc;
+	struct input_dev *input_dev;
+	int allow_close;
+	int onkey_seconds;
+	int irq;
+
+	int coldplug_done; /* cleared by probe, set by first work service */
+	int flag_bat_voltage_read; /* ipc to /sys batt voltage read func */
+
+	int charger_adc_result_raw;
+	enum charger_type charger_type;
+
+	/* we have a FIFO of ADC measurement requests that are used only by
+	 * the workqueue service code after the ADC completion interrupt
+	 */
+	int adc_queue_mux[MAX_ADC_FIFO_DEPTH]; /* which ADC input to use */
+	int adc_queue_avg[MAX_ADC_FIFO_DEPTH]; /* amount of averaging */
+	int adc_queue_head; /* head owned by foreground code */
+	int adc_queue_tail; /* tail owned by service code */
+
+#ifdef CONFIG_PM
+	struct {
+		u_int8_t int1m, int2m, int3m, int4m, int5m;
+		u_int8_t ooctim2;
+		u_int8_t autoout, autoena, automxc;
+		u_int8_t down1out, down1mxc;
+		u_int8_t down2out, down2ena;
+		u_int8_t memldoout, memldoena;
+		u_int8_t ledout, ledena, leddim;
+		struct {
+			u_int8_t out;
+			u_int8_t ena;
+		} ldo[__NUM_PCF50633_REGS];
+	} standby_regs;
+#endif
+};
+
+static struct i2c_driver pcf50633_driver;
+
+struct pcf50633_data *pcf50633_global;
+EXPORT_SYMBOL_GPL(pcf50633_global);
+
+static struct platform_device *pcf50633_pdev;
+
+/***********************************************************************
+ * Low-Level routines
+ ***********************************************************************/
+
+static int __reg_write(struct pcf50633_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 pcf50633_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 int32_t __reg_read(struct pcf50633_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 pcf50633_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 pcf50633_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 pcf50633_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;
+}
+
+/* asynchronously setup reading one ADC channel */
+static void async_adc_read_setup(struct pcf50633_data *pcf,
+				 int channel, int avg)
+{
+	channel &= PCF50633_ADCC1_ADCMUX_MASK;
+
+	/* kill ratiometric, but enable ACCSW biasing */
+	__reg_write(pcf, PCF50633_REG_ADCC2, 0x00);
+	__reg_write(pcf, PCF50633_REG_ADCC3, 0x01);
+
+	/* start ADC conversion of selected channel */
+	__reg_write(pcf, PCF50633_REG_ADCC1, channel | avg |
+		    PCF50633_ADCC1_ADCSTART | PCF50633_ADCC1_RES_10BIT);
+
+}
+
+static u_int16_t async_adc_complete(struct pcf50633_data *pcf)
+{
+	u_int16_t ret = (__reg_read(pcf, PCF50633_REG_ADCS1) << 2) |
+			(__reg_read(pcf, PCF50633_REG_ADCS3) &
+						  PCF50633_ADCS3_ADCDAT1L_MASK);
+
+	return ret;
+}
+
+
+
+
+/***********************************************************************
+ * Voltage / ADC
+ ***********************************************************************/
+
+static u_int8_t auto_voltage(unsigned int millivolts)
+{
+	if (millivolts < 1800)
+		return 0;
+	if (millivolts > 3800)
+		return 0xff;
+
+	millivolts -= 625;
+	return millivolts/25;
+}
+
+static unsigned int auto_2voltage(u_int8_t bits)
+{
+	if (bits < 0x2f)
+		return 0;
+	return 625 + (bits * 25);
+}
+
+static u_int8_t down_voltage(unsigned int millivolts)
+{
+	if (millivolts < 625)
+		return 0;
+	else if (millivolts > 3000)
+		return 0xff;
+
+	millivolts -= 625;
+	return millivolts/25;
+}
+
+static unsigned int down_2voltage(u_int8_t bits)
+{
+	return 625 + (bits*25);
+}
+
+static u_int8_t ldo_voltage(unsigned int millivolts)
+{
+	if (millivolts < 900)
+		return 0;
+	else if (millivolts > 3600)
+		return 0x1f;
+
+	millivolts -= 900;
+	return millivolts/100;
+}
+
+static unsigned int ldo_2voltage(u_int8_t bits)
+{
+	bits &= 0x1f;
+	return 900 + (bits * 100);
+}
+
+static const u_int8_t regulator_registers[__NUM_PCF50633_REGULATORS] = {
+	[PCF50633_REGULATOR_AUTO]	= PCF50633_REG_AUTOOUT,
+	[PCF50633_REGULATOR_DOWN1]	= PCF50633_REG_DOWN1OUT,
+	[PCF50633_REGULATOR_DOWN2]	= PCF50633_REG_DOWN2OUT,
+	[PCF50633_REGULATOR_MEMLDO]	= PCF50633_REG_MEMLDOOUT,
+	[PCF50633_REGULATOR_LDO1]	= PCF50633_REG_LDO1OUT,
+	[PCF50633_REGULATOR_LDO2]	= PCF50633_REG_LDO2OUT,
+	[PCF50633_REGULATOR_LDO3]	= PCF50633_REG_LDO3OUT,
+	[PCF50633_REGULATOR_LDO4]	= PCF50633_REG_LDO4OUT,
+	[PCF50633_REGULATOR_LDO5]	= PCF50633_REG_LDO5OUT,
+	[PCF50633_REGULATOR_LDO6]	= PCF50633_REG_LDO6OUT,
+	[PCF50633_REGULATOR_HCLDO]	= PCF50633_REG_HCLDOOUT,
+};
+
+int pcf50633_onoff_set(struct pcf50633_data *pcf,
+		       enum pcf50633_regulator_id reg, int on)
+{
+	u_int8_t addr;
+
+	if (reg >= __NUM_PCF50633_REGULATORS)
+		return -EINVAL;
+
+	/* the *ENA register is always one after the *OUT register */
+	addr = regulator_registers[reg] + 1;
+
+	if (on == 0)
+		reg_set_bit_mask(pcf, addr, PCF50633_REGULATOR_ON, 0);
+	else
+		reg_set_bit_mask(pcf, addr, PCF50633_REGULATOR_ON,
+				 PCF50633_REGULATOR_ON);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(pcf50633_onoff_set);
+
+int pcf50633_onoff_get(struct pcf50633_data *pcf,
+		       enum pcf50633_regulator_id reg)
+{
+	u_int8_t val, addr;
+
+	if (reg >= __NUM_PCF50633_REGULATORS)
+		return -EINVAL;
+
+	/* the *ENA register is always one after the *OUT register */
+	addr = regulator_registers[reg] + 1;
+	val = reg_read(pcf, addr) & PCF50633_REGULATOR_ON;
+
+	return val;
+}
+EXPORT_SYMBOL_GPL(pcf50633_onoff_get);
+
+int pcf50633_voltage_set(struct pcf50633_data *pcf,
+			 enum pcf50633_regulator_id reg,
+			 unsigned int millivolts)
+{
+	u_int8_t volt_bits;
+	u_int8_t regnr;
+
+	DEBUGP("pcf=%p, reg=%d, mvolts=%d\n", pcf, reg, millivolts);
+
+	if (reg >= __NUM_PCF50633_REGULATORS)
+		return -EINVAL;
+
+	regnr = regulator_registers[reg];
+
+	if (millivolts > pcf->pdata->rails[reg].voltage.max)
+		return -EINVAL;
+
+	switch (reg) {
+	case PCF50633_REGULATOR_AUTO:
+		volt_bits = auto_voltage(millivolts);
+		break;
+	case PCF50633_REGULATOR_DOWN1:
+		volt_bits = down_voltage(millivolts);
+		break;
+	case PCF50633_REGULATOR_DOWN2:
+		volt_bits = down_voltage(millivolts);
+		break;
+	case PCF50633_REGULATOR_LDO1:
+	case PCF50633_REGULATOR_LDO2:
+	case PCF50633_REGULATOR_LDO3:
+	case PCF50633_REGULATOR_LDO4:
+	case PCF50633_REGULATOR_LDO5:
+	case PCF50633_REGULATOR_LDO6:
+	case PCF50633_REGULATOR_HCLDO:
+		volt_bits = ldo_voltage(millivolts);
+		DEBUGP("ldo_voltage(0x%x)=%u\n", millivolts, volt_bits);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return reg_write(pcf, regnr, volt_bits);
+}
+EXPORT_SYMBOL_GPL(pcf50633_voltage_set);
+
+unsigned int pcf50633_voltage_get(struct pcf50633_data *pcf,
+			 enum pcf50633_regulator_id reg)
+{
+	u_int8_t volt_bits;
+	u_int8_t regnr;
+	unsigned int rc = 0;
+
+	if (reg >= __NUM_PCF50633_REGULATORS)
+		return -EINVAL;
+
+	regnr = regulator_registers[reg];
+	volt_bits = reg_read(pcf, regnr);
+
+	switch (reg) {
+	case PCF50633_REGULATOR_AUTO:
+		rc = auto_2voltage(volt_bits);
+		break;
+	case PCF50633_REGULATOR_DOWN1:
+		rc = down_2voltage(volt_bits);
+		break;
+	case PCF50633_REGULATOR_DOWN2:
+		rc = down_2voltage(volt_bits);
+		break;
+	case PCF50633_REGULATOR_LDO1:
+	case PCF50633_REGULATOR_LDO2:
+	case PCF50633_REGULATOR_LDO3:
+	case PCF50633_REGULATOR_LDO4:
+	case PCF50633_REGULATOR_LDO5:
+	case PCF50633_REGULATOR_LDO6:
+	case PCF50633_REGULATOR_HCLDO:
+		rc = ldo_2voltage(volt_bits);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return rc;
+}
+EXPORT_SYMBOL_GPL(pcf50633_voltage_get);
+
+/* go into 'STANDBY' mode, i.e. power off the main CPU and peripherals */
+void pcf50633_go_standby(void)
+{
+	reg_set_bit_mask(pcf50633_global, PCF50633_REG_OOCSHDWN,
+		  PCF50633_OOCSHDWN_GOSTDBY, PCF50633_OOCSHDWN_GOSTDBY);
+}
+EXPORT_SYMBOL_GPL(pcf50633_go_standby);
+
+void pcf50633_gpio_set(struct pcf50633_data *pcf, enum pcf50633_gpio gpio,
+			int on)
+{
+	u_int8_t reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG;
+
+	if (on)
+		reg_set_bit_mask(pcf, reg, 0x0f, 0x07);
+	else
+		reg_set_bit_mask(pcf, reg, 0x0f, 0x00);
+}
+EXPORT_SYMBOL_GPL(pcf50633_gpio_set);
+
+int pcf50633_gpio_get(struct pcf50633_data *pcf, enum pcf50633_gpio gpio)
+{
+	u_int8_t reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG;
+	u_int8_t val = reg_read(pcf, reg) & 0x0f;
+
+	if (val == PCF50633_GPOCFG_GPOSEL_1 ||
+	    val == (PCF50633_GPOCFG_GPOSEL_0|PCF50633_GPOCFG_GPOSEL_INVERSE))
+		return 1;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(pcf50633_gpio_get);
+
+static int interpret_charger_type_from_adc(struct pcf50633_data *pcf,
+					   int sample)
+{
+	/* 1A capable charger? */
+
+	if (sample < ((ADC_NOM_CHG_DETECT_NONE + ADC_NOM_CHG_DETECT_1A) / 2))
+		return CHARGER_TYPE_1A;
+
+	/* well then, nothing in the USB hole, or USB host / unk adapter */
+
+	if (pcf->flags & PCF50633_F_USB_PRESENT) /* ooh power is in there */
+		return CHARGER_TYPE_HOSTUSB; /* HOSTUSB is the catchall */
+
+	return CHARGER_TYPE_NONE; /* no really -- nothing in there */
+}
+
+
+
+static void configure_pmu_for_charger(struct pcf50633_data *pcf,
+				      enum charger_type type)
+{
+	switch (type) {
+	case CHARGER_TYPE_NONE:
+		__reg_write(pcf, PCF50633_REG_MBCC7,
+						    PCF50633_MBCC7_USB_SUSPEND);
+		break;
+	/*
+	 * the PCF50633 has a feature that it will supply only excess current
+	 * from the charger that is not used to power the device.  So this
+	 * 500mA setting is "up to 500mA" according to that.
+	 */
+	case CHARGER_TYPE_HOSTUSB:
+		__reg_write(pcf, PCF50633_REG_MBCC7, PCF50633_MBCC7_USB_500mA);
+		break;
+	case CHARGER_TYPE_1A:
+		__reg_write(pcf, PCF50633_REG_MBCC7, PCF50633_MBCC7_USB_1000mA);
+		break;
+	}
+}
+
+static void trigger_next_adc_job_if_any(struct pcf50633_data *pcf)
+{
+	if (pcf->adc_queue_head == pcf->adc_queue_tail)
+		return;
+	async_adc_read_setup(pcf,
+			     pcf->adc_queue_mux[pcf->adc_queue_tail],
+			     pcf->adc_queue_avg[pcf->adc_queue_tail]);
+}
+
+static void add_request_to_adc_queue(struct pcf50633_data *pcf,
+				     int mux, int avg)
+{
+	int old_head = pcf->adc_queue_head;
+	pcf->adc_queue_mux[pcf->adc_queue_head] = mux;
+	pcf->adc_queue_avg[pcf->adc_queue_head] = avg;
+
+	pcf->adc_queue_head = (pcf->adc_queue_head + 1) &
+			      (MAX_ADC_FIFO_DEPTH - 1);
+
+	/* it was idle before we just added this?  we need to kick it then */
+	if (old_head == pcf->adc_queue_tail)
+		trigger_next_adc_job_if_any(pcf);
+}
+
+static void pcf50633_work(struct work_struct *work)
+{
+	struct pcf50633_data *pcf =
+			container_of(work, struct pcf50633_data, work);
+	u_int8_t pcfirq[5];
+	int ret;
+	int tail;
+
+	mutex_lock(&pcf->working_lock);
+	pcf->working = 1;
+	/*
+	 * datasheet says we have to read the five IRQ
+	 * status regs in one transaction
+	 */
+	ret = i2c_smbus_read_i2c_block_data(&pcf->client, PCF50633_REG_INT1, 5,
+					    pcfirq);
+	if (ret != 5)
+		DEBUGP("Oh crap PMU IRQ register read failed %d\n", ret);
+
+	if (!pcf->coldplug_done) {
+		DEBUGP("PMU Coldplug init\n");
+
+		/* we used SECOND to kick ourselves started -- turn it off */
+		pcfirq[0] &= ~PCF50633_INT1_SECOND;
+		reg_set_bit_mask(pcf, PCF50633_REG_INT1M,
+					PCF50633_INT1_SECOND,
+					PCF50633_INT1_SECOND);
+
+		/* coldplug the USB if present */
+		if ((__reg_read(pcf, PCF50633_REG_MBCS1) &
+		    (PCF50633_MBCS1_USBPRES | PCF50633_MBCS1_USBOK)) ==
+		    (PCF50633_MBCS1_USBPRES | PCF50633_MBCS1_USBOK)) {
+			DEBUGPC("COLD USBINS\n");
+			input_report_key(pcf->input_dev, KEY_POWER2, 1);
+			apm_queue_event(APM_POWER_STATUS_CHANGE);
+			pcf->flags |= PCF50633_F_USB_PRESENT;
+			if (pcf->pdata->cb)
+				pcf->pdata->cb(&pcf->client.dev,
+					PCF50633_FEAT_MBC, PMU_EVT_USB_INSERT);
+		}
+
+		/* figure out our initial charging stance */
+		add_request_to_adc_queue(pcf, PCF50633_ADCC1_MUX_ADCIN1,
+					      PCF50633_ADCC1_AVERAGE_16);
+
+		pcf->coldplug_done = 1;
+	}
+
+	DEBUGP("INT1=0x%02x INT2=0x%02x INT3=0x%02x INT4=0x%02x INT5=0x%02x\n",
+		pcfirq[0], pcfirq[1], pcfirq[2], pcfirq[3], pcfirq[4]);
+
+	if (pcfirq[0] & PCF50633_INT1_ADPINS) {
+		/* Charger inserted */
+		DEBUGPC("ADPINS ");
+		input_report_key(pcf->input_dev, KEY_BATTERY, 1);
+		apm_queue_event(APM_POWER_STATUS_CHANGE);
+		pcf->flags |= PCF50633_F_CHG_PRESENT;
+		if (pcf->pdata->cb)
+			pcf->pdata->cb(&pcf->client.dev,
+				       PCF50633_FEAT_MBC, PMU_EVT_INSERT);
+		/* FIXME: signal this to userspace */
+		//kobject_uevent( ,KOBJ_ADD);
+	}
+	if (pcfirq[0] & PCF50633_INT1_ADPREM) {
+		/* Charger removed */
+		DEBUGPC("ADPREM ");
+		input_report_key(pcf->input_dev, KEY_BATTERY, 0);
+		apm_queue_event(APM_POWER_STATUS_CHANGE);
+		pcf->flags &= ~PCF50633_F_CHG_PRESENT;
+		if (pcf->pdata->cb)
+			pcf->pdata->cb(&pcf->client.dev,
+				       PCF50633_FEAT_MBC, PMU_EVT_REMOVE);
+		/* FIXME: signal this to userspace */
+		//kobject_uevent( ,KOBJ_ADD);
+	}
+	if (pcfirq[0] & PCF50633_INT1_USBINS) {
+		DEBUGPC("USBINS ");
+		input_report_key(pcf->input_dev, KEY_POWER2, 1);
+		apm_queue_event(APM_POWER_STATUS_CHANGE);
+		pcf->flags |= PCF50633_F_USB_PRESENT;
+		if (pcf->pdata->cb)
+			pcf->pdata->cb(&pcf->client.dev,
+				       PCF50633_FEAT_MBC, PMU_EVT_USB_INSERT);
+		/* completion irq will figure out our charging stance */
+		add_request_to_adc_queue(pcf, PCF50633_ADCC1_MUX_ADCIN1,
+				     PCF50633_ADCC1_AVERAGE_16);
+	}
+	if (pcfirq[0] & PCF50633_INT1_USBREM) {
+		DEBUGPC("USBREM ");
+		/* only deal if we had understood it was in */
+		if (pcf->flags & PCF50633_F_USB_PRESENT) {
+			input_report_key(pcf->input_dev, KEY_POWER2, 0);
+			apm_queue_event(APM_POWER_STATUS_CHANGE);
+			pcf->flags &= ~PCF50633_F_USB_PRESENT;
+			if (pcf->pdata->cb)
+				pcf->pdata->cb(&pcf->client.dev,
+					PCF50633_FEAT_MBC, PMU_EVT_USB_REMOVE);
+			/* completion irq will figure out our charging stance */
+			add_request_to_adc_queue(pcf, PCF50633_ADCC1_MUX_ADCIN1,
+					PCF50633_ADCC1_AVERAGE_16);
+		}
+	}
+	if (pcfirq[0] & PCF50633_INT1_ALARM) {
+		DEBUGPC("ALARM ");
+		if (pcf->pdata->used_features & PCF50633_FEAT_RTC)
+			rtc_update_irq(pcf->rtc, 1, RTC_AF | RTC_IRQF);
+	}
+	if (pcfirq[0] & PCF50633_INT1_SECOND) {
+		DEBUGPC("SECOND ");
+		if (pcf->flags & PCF50633_F_RTC_SECOND)
+			rtc_update_irq(pcf->rtc, 1, RTC_PF | RTC_IRQF);
+
+		if (pcf->onkey_seconds >= 0 &&
+		    pcf->flags & PCF50633_F_PWR_PRESSED) {
+			DEBUGP("ONKEY_SECONDS(%u, OOCSTAT=0x%02x) ",
+				pcf->onkey_seconds,
+				reg_read(pcf, PCF50633_REG_OOCSTAT));
+			pcf->onkey_seconds++;
+			if (pcf->onkey_seconds >=
+			    pcf->pdata->onkey_seconds_sig_init) {
+				/* Ask init to do 'ctrlaltdel' */
+				DEBUGPC("SIGINT(init) ");
+				kill_proc(1, SIGINT, 1);
+				/* FIXME: what if userspace doesn't shut down? */
+			}
+			if (pcf->onkey_seconds >=
+				pcf->pdata->onkey_seconds_shutdown) {
+				DEBUGPC("Power Off ");
+				pcf50633_go_standby();
+			}
+		}
+	}
+
+	if (pcfirq[1] & PCF50633_INT2_ONKEYF) {
+		/* ONKEY falling edge (start of button press) */
+		DEBUGPC("ONKEYF ");
+		pcf->flags |= PCF50633_F_PWR_PRESSED;
+		input_report_key(pcf->input_dev, KEY_POWER, 1);
+	}
+	if (pcfirq[1] & PCF50633_INT2_ONKEYR) {
+		/* ONKEY rising edge (end of button press) */
+		DEBUGPC("ONKEYR ");
+		pcf->flags &= ~PCF50633_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 & PCF50633_F_RTC_SECOND))
+			reg_set_bit_mask(pcf, PCF50633_REG_INT1M,
+					 PCF50633_INT1_SECOND,
+					 PCF50633_INT1_SECOND);
+	}
+	/* FIXME: we don't use EXTON1/2/3. thats why we skip it */
+
+	if (pcfirq[2] & PCF50633_INT3_BATFULL) {
+		DEBUGPC("BATFULL ");
+		/* FIXME: signal this to userspace */
+	}
+	if (pcfirq[2] & PCF50633_INT3_CHGHALT) {
+		DEBUGPC("CHGHALT ");
+		/*
+		 * this is really "battery not pulling current" -- it can
+		 * appear with no battery attached
+		 */
+		/* FIXME: signal this to userspace */
+	}
+	if (pcfirq[2] & PCF50633_INT3_THLIMON) {
+		DEBUGPC("THLIMON ");
+		pcf->flags |= PCF50633_F_CHG_PROT;
+		/* FIXME: signal this to userspace */
+	}
+	if (pcfirq[2] & PCF50633_INT3_THLIMOFF) {
+		DEBUGPC("THLIMOFF ");
+		pcf->flags &= ~PCF50633_F_CHG_PROT;
+		/* FIXME: signal this to userspace */
+	}
+	if (pcfirq[2] & PCF50633_INT3_USBLIMON) {
+		DEBUGPC("USBLIMON ");
+		/* FIXME: signal this to userspace */
+	}
+	if (pcfirq[2] & PCF50633_INT3_USBLIMOFF) {
+		DEBUGPC("USBLIMOFF ");
+		/* FIXME: signal this to userspace */
+	}
+	if (pcfirq[2] & PCF50633_INT3_ADCRDY) {
+		/* ADC result ready */
+		DEBUGPC("ADCRDY ");
+		tail = pcf->adc_queue_tail;
+		pcf->adc_queue_tail = (pcf->adc_queue_tail + 1) &
+				      (MAX_ADC_FIFO_DEPTH - 1);
+
+		switch (pcf->adc_queue_mux[tail]) {
+		case PCF50633_ADCC1_MUX_BATSNS_RES: /* battery voltage */
+			pcf->flag_bat_voltage_read =
+						  async_adc_complete(pcf);
+			break;
+		case PCF50633_ADCC1_MUX_ADCIN1: /* charger type */
+			pcf->charger_adc_result_raw = async_adc_complete(pcf);
+			pcf->charger_type = interpret_charger_type_from_adc(
+					     pcf, pcf->charger_adc_result_raw);
+			configure_pmu_for_charger(pcf, pcf->charger_type);
+			break;
+		default:
+			async_adc_complete(pcf);
+			break;
+		}
+		trigger_next_adc_job_if_any(pcf);
+	}
+	if (pcfirq[2] & PCF50633_INT3_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, PCF50633_REG_OOCSHDWN,
+				 PCF50633_OOCSHDWN_TOTRST,
+				 PCF50633_OOCSHDWN_TOTRST);
+		/* enable SECOND interrupt (hz tick) */
+		reg_clear_bits(pcf, PCF50633_REG_INT1M, PCF50633_INT1_SECOND);
+	}
+
+	if (pcfirq[3] & (PCF50633_INT4_LOWBAT|PCF50633_INT4_LOWSYS)) {
+		/* 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, PCF50633_REG_OOCSHDWN,
+				 PCF50633_OOCSHDWN_TOTRST,
+				 PCF50633_OOCSHDWN_TOTRST);
+	}
+	if (pcfirq[3] & PCF50633_INT4_HIGHTMP) {
+		/* High temperature */
+		DEBUGPC("HIGHTMP ");
+		apm_queue_event(APM_CRITICAL_SUSPEND);
+	}
+	if (pcfirq[3] & PCF50633_INT4_AUTOPWRFAIL) {
+		DEBUGPC("PCF50633_INT4_AUTOPWRFAIL ");
+		/* FIXME: deal with this */
+	}
+	if (pcfirq[3] & PCF50633_INT4_DWN1PWRFAIL) {
+		DEBUGPC("PCF50633_INT4_DWN1PWRFAIL ");
+		/* FIXME: deal with this */
+	}
+	if (pcfirq[3] & PCF50633_INT4_DWN2PWRFAIL) {
+		DEBUGPC("PCF50633_INT4_DWN2PWRFAIL ");
+		/* FIXME: deal with this */
+	}
+	if (pcfirq[3] & PCF50633_INT4_LEDPWRFAIL) {
+		DEBUGPC("PCF50633_INT4_LEDPWRFAIL ");
+		/* FIXME: deal with this */
+	}
+	if (pcfirq[3] & PCF50633_INT4_LEDOVP) {
+		DEBUGPC("PCF50633_INT4_LEDOVP ");
+		/* FIXME: deal with this */
+	}
+
+	DEBUGPC("\n");
+
+	pcf->working = 0;
+	input_sync(pcf->input_dev);
+	put_device(&pcf->client.dev);
+	mutex_unlock(&pcf->working_lock);
+}
+
+static irqreturn_t pcf50633_irq(int irq, void *_pcf)
+{
+	struct pcf50633_data *pcf = _pcf;
+
+	DEBUGP("entering(irq=%u, pcf=%p): scheduling work\n", irq, _pcf);
+
+	get_device(&pcf->client.dev);
+	if (!schedule_work(&pcf->work) && !pcf->working)
+		dev_dbg(&pcf->client.dev, "work item may be lost\n");
+
+	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 pcf50633_battvolt(struct pcf50633_data *pcf)
+{
+	int count = 10;
+
+	pcf->flag_bat_voltage_read = -1;
+	add_request_to_adc_queue(pcf, PCF50633_ADCC1_MUX_BATSNS_RES,
+				      PCF50633_ADCC1_AVERAGE_16);
+
+	while ((count--) && (pcf->flag_bat_voltage_read < 0))
+		msleep(1);
+
+	if (count < 0) { /* timeout somehow */
+		DEBUGPC("pcf50633_battvolt timeout :-(\n");
+		return -1;
+	}
+
+	return adc_to_batt_millivolts(pcf->flag_bat_voltage_read);
+}
+EXPORT_SYMBOL_GPL(pcf50633_battvolt);
+
+static ssize_t show_battvolt(struct device *dev, struct device_attribute *attr,
+			     char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50633_data *pcf = i2c_get_clientdata(client);
+
+	return sprintf(buf, "%u\n", pcf50633_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_auto"))
+		reg_id = PCF50633_REGULATOR_AUTO;
+	else if (!strcmp(name, "voltage_down1"))
+		reg_id = PCF50633_REGULATOR_DOWN1;
+	else if (!strcmp(name, "voltage_down2"))
+		reg_id = PCF50633_REGULATOR_DOWN2;
+	else if (!strcmp(name, "voltage_memldo"))
+		reg_id = PCF50633_REGULATOR_MEMLDO;
+	else if (!strcmp(name, "voltage_ldo1"))
+		reg_id = PCF50633_REGULATOR_LDO1;
+	else if (!strcmp(name, "voltage_ldo2"))
+		reg_id = PCF50633_REGULATOR_LDO2;
+	else if (!strcmp(name, "voltage_ldo3"))
+		reg_id = PCF50633_REGULATOR_LDO3;
+	else if (!strcmp(name, "voltage_ldo4"))
+		reg_id = PCF50633_REGULATOR_LDO4;
+	else if (!strcmp(name, "voltage_ldo5"))
+		reg_id = PCF50633_REGULATOR_LDO5;
+	else if (!strcmp(name, "voltage_ldo6"))
+		reg_id = PCF50633_REGULATOR_LDO6;
+	else if (!strcmp(name, "voltage_hcldo"))
+		reg_id = PCF50633_REGULATOR_HCLDO;
+	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 pcf50633_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 (pcf50633_onoff_get(pcf, reg_id) > 0)
+		return sprintf(buf, "%u\n", pcf50633_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 pcf50633_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;
+
+	DEBUGP("attempting to set %s(%d) to %lu mvolts\n", attr->attr.name,
+		reg_id, mvolts);
+
+	if (mvolts == 0) {
+		pcf50633_onoff_set(pcf, reg_id, 0);
+	} else {
+		if (pcf50633_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;
+		}
+		pcf50633_onoff_set(pcf, reg_id, 1);
+	}
+
+	return count;
+}
+
+static DEVICE_ATTR(voltage_auto, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
+static DEVICE_ATTR(voltage_down1, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
+static DEVICE_ATTR(voltage_down2, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
+static DEVICE_ATTR(voltage_memldo, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
+static DEVICE_ATTR(voltage_ldo1, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
+static DEVICE_ATTR(voltage_ldo2, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
+static DEVICE_ATTR(voltage_ldo3, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
+static DEVICE_ATTR(voltage_ldo4, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
+static DEVICE_ATTR(voltage_ldo5, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
+static DEVICE_ATTR(voltage_ldo6, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
+static DEVICE_ATTR(voltage_hcldo, S_IRUGO | S_IWUSR, show_vreg, set_vreg);
+
+/***********************************************************************
+ * Charger Control
+ ***********************************************************************/
+
+/* Set maximum USB current limit */
+void pcf50633_usb_curlim_set(struct pcf50633_data *pcf, int ma)
+{
+	u_int8_t bits;
+
+	dev_dbg(&pcf->client.dev, "setting usb current limit to %d ma", ma);
+
+	if (ma >= 1000)
+		bits = PCF50633_MBCC7_USB_1000mA;
+	else if (ma >= 500)
+		bits = PCF50633_MBCC7_USB_500mA;
+	else if (ma >= 100)
+		bits = PCF50633_MBCC7_USB_100mA;
+	else
+		bits = PCF50633_MBCC7_USB_SUSPEND;
+
+	reg_set_bit_mask(pcf, PCF50633_REG_MBCC7, PCF56033_MBCC7_USB_MASK,
+			 bits);
+}
+EXPORT_SYMBOL_GPL(pcf50633_usb_curlim_set);
+
+static ssize_t show_usblim(struct device *dev, struct device_attribute *attr,
+			   char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50633_data *pcf = i2c_get_clientdata(client);
+	u_int8_t usblim = reg_read(pcf, PCF50633_REG_MBCC7) &
+						PCF56033_MBCC7_USB_MASK;
+	unsigned int ma;
+
+	if (usblim == PCF50633_MBCC7_USB_1000mA)
+		ma = 1000;
+	else if (usblim == PCF50633_MBCC7_USB_500mA)
+		ma = 500;
+	else if (usblim == PCF50633_MBCC7_USB_100mA)
+		ma = 100;
+	else
+		ma = 0;
+
+	return sprintf(buf, "%u\n", ma);
+}
+static DEVICE_ATTR(usb_curlim, S_IRUGO | S_IWUSR, show_usblim, NULL);
+
+/* Enable/disable charging */
+void pcf50633_charge_enable(struct pcf50633_data *pcf, int on)
+{
+	u_int8_t bits;
+
+	if (!(pcf->pdata->used_features & PCF50633_FEAT_MBC))
+		return;
+
+	if (on) {
+		pcf->flags |= PCF50633_F_CHG_ENABLED;
+		bits = PCF50633_MBCC1_CHGENA;
+	} else {
+		pcf->flags &= ~PCF50633_F_CHG_ENABLED;
+		bits = 0;
+	}
+	reg_set_bit_mask(pcf, PCF50633_REG_MBCC1, PCF50633_MBCC1_CHGENA,
+			 bits);
+}
+EXPORT_SYMBOL_GPL(pcf50633_charge_enable);
+
+#if 0
+#define ONE			1000000
+static u_int16_t adc_to_rntc(struct pcf50633_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;
+}
+#endif
+static ssize_t show_battemp(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	return sprintf(buf, "\n");
+}
+static DEVICE_ATTR(battemp, S_IRUGO | S_IWUSR, show_battemp, NULL);
+#if 0
+static u_int16_t adc_to_chg_milliamps(struct pcf50633_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);
+}
+#endif
+static ssize_t show_chgcur(struct device *dev, struct device_attribute *attr,
+			   char *buf)
+{
+	return sprintf(buf, "\n");
+}
+static DEVICE_ATTR(chgcur, S_IRUGO | S_IWUSR, show_chgcur, NULL);
+
+static const char *chgmode_names[] = {
+	[PCF50633_MBCS2_MBC_PLAY]		= "play-only",
+	[PCF50633_MBCS2_MBC_USB_PRE]		= "pre",
+	[PCF50633_MBCS2_MBC_ADP_PRE]		= "pre",
+	[PCF50633_MBCS2_MBC_USB_PRE_WAIT]	= "pre-wait",
+	[PCF50633_MBCS2_MBC_ADP_PRE_WAIT]	= "pre-wait",
+	[PCF50633_MBCS2_MBC_USB_FAST]		= "fast",
+	[PCF50633_MBCS2_MBC_ADP_FAST]		= "fast",
+	[PCF50633_MBCS2_MBC_USB_FAST_WAIT]	= "fast-wait",
+	[PCF50633_MBCS2_MBC_ADP_FAST_WAIT]	= "fast-wait",
+	[PCF50633_MBCS2_MBC_ADP_FAST_WAIT]	= "bat-full",
+};
+
+static ssize_t show_chgmode(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50633_data *pcf = i2c_get_clientdata(client);
+	u_int8_t mbcs2 = reg_read(pcf, PCF50633_REG_MBCS2);
+	u_int8_t chgmod = (mbcs2 & PCF50633_MBCS2_MBC_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 pcf50633_data *pcf = i2c_get_clientdata(client);
+
+	/* As opposed to the PCF50606, we can only enable or disable
+	 * charging and not directly jump into a certain mode! */
+
+	if (!strcmp(buf, "0\n"))
+		pcf50633_charge_enable(pcf, 0);
+	else
+		pcf50633_charge_enable(pcf, 1);
+
+	return count;
+}
+
+static DEVICE_ATTR(chgmode, S_IRUGO | S_IWUSR, show_chgmode, set_chgmode);
+
+static const char *chgstate_names[] = {
+	[PCF50633_FIDX_CHG_ENABLED]		= "enabled",
+	[PCF50633_FIDX_CHG_PRESENT] 		= "charger_present",
+	[PCF50633_FIDX_USB_PRESENT] 		= "usb_present",
+	[PCF50633_FIDX_CHG_ERR]			= "error",
+	[PCF50633_FIDX_CHG_PROT]		= "protection",
+	[PCF50633_FIDX_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 pcf50633_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
+ ***********************************************************************/
+
+extern void (*apm_get_power_status)(struct apm_power_info *);
+
+static void pcf50633_get_power_status(struct apm_power_info *info)
+{
+        struct pcf50633_data *pcf = pcf50633_global;
+	u_int8_t chgmod = reg_read(pcf, PCF50633_REG_MBCS2) &
+				   PCF50633_MBCS2_MBC_MASK;
+
+	u_int16_t battvolt = pcf50633_battvolt(pcf);
+
+	if (reg_read(pcf, PCF50633_REG_MBCS1) &
+			(PCF50633_MBCS1_USBPRES|PCF50633_MBCS1_ADAPTPRES))
+		info->ac_line_status = APM_AC_ONLINE;
+	else
+		info->ac_line_status = APM_AC_OFFLINE;
+
+	switch (chgmod) {
+	case PCF50633_MBCS2_MBC_PLAY:
+	case PCF50633_MBCS2_MBC_USB_PRE:
+	case PCF50633_MBCS2_MBC_USB_PRE_WAIT:
+	case PCF50633_MBCS2_MBC_USB_FAST_WAIT:
+	case PCF50633_MBCS2_MBC_ADP_PRE:
+	case PCF50633_MBCS2_MBC_ADP_PRE_WAIT:
+	case PCF50633_MBCS2_MBC_ADP_FAST_WAIT:
+	case PCF50633_MBCS2_MBC_BAT_FULL:
+	case PCF50633_MBCS2_MBC_HALT:
+		info->battery_life = battvolt_scale(battvolt);
+		break;
+	case PCF50633_MBCS2_MBC_USB_FAST:
+	case PCF50633_MBCS2_MBC_ADP_FAST:
+		info->battery_status = APM_BATTERY_STATUS_CHARGING;
+		info->battery_flag = APM_BATTERY_FLAG_CHARGING;
+	default:
+		break;
+	}
+}
+
+/***********************************************************************
+ * RTC
+ ***********************************************************************/
+
+struct pcf50633_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 pcf50633_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 pcf50633_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 pcf50633_rtc_ioctl(struct device *dev, unsigned int cmd,
+			      unsigned long arg)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50633_data *pcf = i2c_get_clientdata(client);
+	switch (cmd) {
+	case RTC_PIE_OFF:
+		/* disable periodic interrupt (hz tick) */
+		pcf->flags &= ~PCF50633_F_RTC_SECOND;
+		reg_set_bit_mask(pcf, PCF50633_REG_INT1M,
+				 PCF50633_INT1_SECOND, PCF50633_INT1_SECOND);
+		return 0;
+	case RTC_PIE_ON:
+		/* ensable periodic interrupt (hz tick) */
+		pcf->flags |= PCF50633_F_RTC_SECOND;
+		reg_clear_bits(pcf, PCF50633_REG_INT1M, PCF50633_INT1_SECOND);
+		return 0;
+	}
+	return -ENOIOCTLCMD;
+}
+
+static int pcf50633_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50633_data *pcf = i2c_get_clientdata(client);
+	struct pcf50633_time pcf_tm;
+
+	mutex_lock(&pcf->lock);
+	pcf_tm.sec = __reg_read(pcf, PCF50633_REG_RTCSC);
+	pcf_tm.min = __reg_read(pcf, PCF50633_REG_RTCMN);
+	pcf_tm.hour = __reg_read(pcf, PCF50633_REG_RTCHR);
+	pcf_tm.wkday = __reg_read(pcf, PCF50633_REG_RTCWD);
+	pcf_tm.day = __reg_read(pcf, PCF50633_REG_RTCDT);
+	pcf_tm.month = __reg_read(pcf, PCF50633_REG_RTCMT);
+	pcf_tm.year = __reg_read(pcf, PCF50633_REG_RTCYR);
+	mutex_unlock(&pcf->lock);
+
+	DEBUGP("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);
+
+	DEBUGP("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 pcf50633_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50633_data *pcf = i2c_get_clientdata(client);
+	struct pcf50633_time pcf_tm;
+
+	DEBUGP("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);
+	DEBUGP("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);
+	/* FIXME: disable second interrupt */
+	__reg_write(pcf, PCF50633_REG_RTCSC, pcf_tm.sec);
+	__reg_write(pcf, PCF50633_REG_RTCMN, pcf_tm.min);
+	__reg_write(pcf, PCF50633_REG_RTCHR, pcf_tm.hour);
+	__reg_write(pcf, PCF50633_REG_RTCWD, pcf_tm.wkday);
+	__reg_write(pcf, PCF50633_REG_RTCDT, pcf_tm.day);
+	__reg_write(pcf, PCF50633_REG_RTCMT, pcf_tm.month);
+	__reg_write(pcf, PCF50633_REG_RTCYR, pcf_tm.year);
+	/* FIXME: re-enable second interrupt */
+	mutex_unlock(&pcf->lock);
+
+	return 0;
+}
+
+static int pcf50633_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50633_data *pcf = i2c_get_clientdata(client);
+	struct pcf50633_time pcf_tm;
+
+	mutex_lock(&pcf->lock);
+	alrm->enabled =
+	     __reg_read(pcf, PCF50633_REG_INT1M) & PCF50633_INT1_ALARM ? 0 : 1;
+	pcf_tm.sec = __reg_read(pcf, PCF50633_REG_RTCSCA);
+	pcf_tm.min = __reg_read(pcf, PCF50633_REG_RTCMNA);
+	pcf_tm.hour = __reg_read(pcf, PCF50633_REG_RTCHRA);
+	pcf_tm.wkday = __reg_read(pcf, PCF50633_REG_RTCWDA);
+	pcf_tm.day = __reg_read(pcf, PCF50633_REG_RTCDTA);
+	pcf_tm.month = __reg_read(pcf, PCF50633_REG_RTCMTA);
+	pcf_tm.year = __reg_read(pcf, PCF50633_REG_RTCYRA);
+	mutex_unlock(&pcf->lock);
+
+	pcf2rtc_time(&alrm->time, &pcf_tm);
+
+	return 0;
+}
+
+static int pcf50633_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50633_data *pcf = i2c_get_clientdata(client);
+	struct pcf50633_time pcf_tm;
+	u_int8_t irqmask;
+
+	rtc2pcf_time(&pcf_tm, &alrm->time);
+
+	mutex_lock(&pcf->lock);
+
+	/* disable alarm interrupt */
+	irqmask = __reg_read(pcf, PCF50633_REG_INT1M);
+	irqmask |= PCF50633_INT1_ALARM;
+	__reg_write(pcf, PCF50633_REG_INT1M, irqmask);
+
+	__reg_write(pcf, PCF50633_REG_RTCSCA, pcf_tm.sec);
+	__reg_write(pcf, PCF50633_REG_RTCMNA, pcf_tm.min);
+	__reg_write(pcf, PCF50633_REG_RTCHRA, pcf_tm.hour);
+	__reg_write(pcf, PCF50633_REG_RTCWDA, pcf_tm.wkday);
+	__reg_write(pcf, PCF50633_REG_RTCDTA, pcf_tm.day);
+	__reg_write(pcf, PCF50633_REG_RTCMTA, pcf_tm.month);
+	__reg_write(pcf, PCF50633_REG_RTCYRA, pcf_tm.year);
+
+	if (alrm->enabled) {
+		/* (re-)enaable alarm interrupt */
+		irqmask = __reg_read(pcf, PCF50633_REG_INT1M);
+		irqmask &= ~PCF50633_INT1_ALARM;
+		__reg_write(pcf, PCF50633_REG_INT1M, irqmask);
+	}
+
+	mutex_unlock(&pcf->lock);
+
+	/* FIXME */
+	return 0;
+}
+
+static struct rtc_class_ops pcf50633_rtc_ops = {
+	.ioctl		= pcf50633_rtc_ioctl,
+	.read_time	= pcf50633_rtc_read_time,
+	.set_time	= pcf50633_rtc_set_time,
+	.read_alarm	= pcf50633_rtc_read_alarm,
+	.set_alarm	= pcf50633_rtc_set_alarm,
+};
+
+/***********************************************************************
+ * Backlight device
+ ***********************************************************************/
+
+static int pcf50633bl_get_intensity(struct backlight_device *bd)
+{
+	struct pcf50633_data *pcf = bl_get_data(bd);
+	int intensity = reg_read(pcf, PCF50633_REG_LEDOUT);
+
+	return intensity & 0x3f;
+}
+
+static int pcf50633bl_set_intensity(struct backlight_device *bd)
+{
+	struct pcf50633_data *pcf = bl_get_data(bd);
+	int intensity = bd->props.brightness;
+	int old_intensity = reg_read(pcf, PCF50633_REG_LEDOUT);
+	u_int8_t ledena;
+	int ret;
+
+	if (bd->props.power != FB_BLANK_UNBLANK)
+		intensity = 0;
+	if (bd->props.fb_blank != FB_BLANK_UNBLANK)
+		intensity = 0;
+
+	/* The PCF50633 seems to have some kind of oddity (bug?) when
+	 * the intensity was 0, you need to completely switch it off
+	 * and re-enable it, before it produces any output voltage again */
+
+	if (intensity != 0 && old_intensity == 0) {
+		ledena = reg_read(pcf, PCF50633_REG_LEDENA);
+		reg_write(pcf, PCF50633_REG_LEDENA, 0x00);
+	}
+
+	ret = reg_set_bit_mask(pcf, PCF50633_REG_LEDOUT, 0x3f,
+			       intensity);
+
+	if (intensity != 0 && old_intensity == 0)
+		reg_write(pcf, PCF50633_REG_LEDENA, ledena);
+
+	return ret;
+}
+
+static struct backlight_ops pcf50633bl_ops = {
+	.get_brightness	= pcf50633bl_get_intensity,
+	.update_status	= pcf50633bl_set_intensity,
+};
+
+/*
+ * Charger type
+ */
+
+static ssize_t show_charger_type(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50633_data *pcf = i2c_get_clientdata(client);
+	static const char *names_charger_type[] = {
+		[CHARGER_TYPE_NONE] 	= "none",
+		[CHARGER_TYPE_HOSTUSB] 	= "host/500mA usb",
+		[CHARGER_TYPE_1A] 	= "charger 1A",
+	};
+	static const char *names_charger_modes[] = {
+		[PCF50633_MBCC7_USB_1000mA] 	= "1A",
+		[PCF50633_MBCC7_USB_500mA] 	= "500mA",
+		[PCF50633_MBCC7_USB_100mA] 	= "100mA",
+		[PCF50633_MBCC7_USB_SUSPEND] 	= "suspend",
+	};
+	int mode = reg_read(pcf, PCF50633_REG_MBCC7) & PCF56033_MBCC7_USB_MASK;
+
+	return sprintf(buf, "%s mode %s\n",
+			    names_charger_type[pcf->charger_type],
+			    names_charger_modes[mode]);
+}
+
+static DEVICE_ATTR(charger_type, 0444, show_charger_type, NULL);
+
+/*
+ * Charger adc
+ */
+
+static ssize_t show_charger_adc(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50633_data *pcf = i2c_get_clientdata(client);
+
+	return sprintf(buf, "%d\n", pcf->charger_adc_result_raw);
+}
+
+static DEVICE_ATTR(charger_adc, 0444, show_charger_adc, NULL);
+
+/*
+ * Dump regs
+ */
+
+static ssize_t show_dump_regs(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50633_data *pcf = i2c_get_clientdata(client);
+	u8 dump[16];
+	int n, n1, idx = 0;
+	char *buf1 = buf;
+	static u8 address_no_read[] = { /* must be ascending */
+		PCF50633_REG_INT1,
+		PCF50633_REG_INT2,
+		PCF50633_REG_INT3,
+		PCF50633_REG_INT4,
+		PCF50633_REG_INT5,
+		0 /* terminator */
+	};
+
+	for (n = 0; n < 256; n += sizeof(dump)) {
+
+		for (n1 = 0; n1 < sizeof(dump); n1++)
+			if (n == address_no_read[idx]) {
+				idx++;
+				dump[n1] = 0x00;
+			} else
+				dump[n1] = reg_read(pcf, n + n1);
+
+		hex_dump_to_buffer(dump, sizeof(dump), 16, 1, buf1, 128, 0);
+		buf1 += strlen(buf1);
+		*buf1++ = '\n';
+		*buf1 = '\0';
+	}
+
+	return buf1 - buf;
+}
+
+static DEVICE_ATTR(dump_regs, 0400, show_dump_regs, NULL);
+
+
+/***********************************************************************
+ * Driver initialization
+ ***********************************************************************/
+
+#ifdef CONFIG_MACH_NEO1973_GTA02
+/* 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
+
+/*
+ * CARE!  This table is modified at runtime!
+ */
+static struct attribute *pcf_sysfs_entries[] = {
+	&dev_attr_voltage_auto.attr,
+	&dev_attr_voltage_down1.attr,
+	&dev_attr_voltage_down2.attr,
+	&dev_attr_voltage_memldo.attr,
+	&dev_attr_voltage_ldo1.attr,
+	&dev_attr_voltage_ldo2.attr,
+	&dev_attr_voltage_ldo3.attr,
+	&dev_attr_voltage_ldo4.attr,
+	&dev_attr_voltage_ldo5.attr,
+	&dev_attr_voltage_ldo6.attr,
+	&dev_attr_voltage_hcldo.attr,
+	&dev_attr_charger_type.attr,
+	&dev_attr_charger_adc.attr,
+	&dev_attr_dump_regs.attr,
+	NULL, /* going to add things at this point! */
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+};
+
+static struct attribute_group pcf_attr_group = {
+	.name	= NULL,			/* put in device directory */
+	.attrs	= pcf_sysfs_entries,
+};
+
+static void populate_sysfs_group(struct pcf50633_data *pcf)
+{
+	int i = 0;
+	struct attribute **attr;
+
+	for (attr = pcf_sysfs_entries; *attr; attr++)
+		i++;
+
+	if (pcf->pdata->used_features & PCF50633_FEAT_MBC) {
+		pcf_sysfs_entries[i++] = &dev_attr_chgstate.attr;
+		pcf_sysfs_entries[i++] = &dev_attr_chgmode.attr;
+		pcf_sysfs_entries[i++] = &dev_attr_usb_curlim.attr;
+	}
+
+	if (pcf->pdata->used_features & PCF50633_FEAT_CHGCUR)
+		pcf_sysfs_entries[i++] = &dev_attr_chgcur.attr;
+
+	if (pcf->pdata->used_features & PCF50633_FEAT_BATVOLT)
+		pcf_sysfs_entries[i++] = &dev_attr_battvolt.attr;
+
+	if (pcf->pdata->used_features & PCF50633_FEAT_BATTEMP)
+		pcf_sysfs_entries[i++] = &dev_attr_battemp.attr;
+
+}
+
+static int pcf50633_detect(struct i2c_adapter *adapter, int address, int kind)
+{
+	struct i2c_client *new_client;
+	struct pcf50633_data *data;
+	int err = 0;
+	int irq;
+
+	DEBUGP("entering\n");
+	if (!pcf50633_pdev) {
+		printk(KERN_ERR "pcf50633: driver needs a platform_device!\n");
+		return -EIO;
+	}
+
+	irq = platform_get_irq(pcf50633_pdev, 0);
+	if (irq < 0) {
+		dev_err(&pcf50633_pdev->dev, "no irq in platform resources!\n");
+		return -EIO;
+	}
+
+	/* At the moment, we only support one PCF50633 in a system */
+	if (pcf50633_global) {
+		dev_err(&pcf50633_pdev->dev,
+			"currently only one chip supported\n");
+		return -EBUSY;
+	}
+
+	if (!(data = kzalloc(sizeof(*data), GFP_KERNEL)))
+		return -ENOMEM;
+
+	mutex_init(&data->lock);
+	mutex_init(&data->working_lock);
+	INIT_WORK(&data->work, pcf50633_work);
+	data->irq = irq;
+	data->working = 0;
+	data->onkey_seconds = -1;
+	data->pdata = pcf50633_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 = &pcf50633_driver;
+	new_client->flags = 0;
+	strlcpy(new_client->name, "pcf50633", I2C_NAME_SIZE);
+
+	/* now we try to detect the chip */
+
+	/* register with i2c core */
+	if ((err = i2c_attach_client(new_client))) {
+		dev_err(&new_client->dev,
+			"error during i2c_attach_client()\n");
+		goto exit_free;
+	}
+
+	pcf50633_global = data;
+
+	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' */
+
+	/* register power off handler with core power management */
+	pm_power_off = &pcf50633_go_standby;
+
+	data->input_dev = input_allocate_device();
+	if (!data->input_dev)
+		goto exit_sysfs;
+
+	data->input_dev->name = "GTA02 PMU events";
+	data->input_dev->phys = "FIXME";
+	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;
+
+	/* configure interrupt mask */
+	reg_write(data, PCF50633_REG_INT1M, 0x00); /* we want SECOND to kick */
+	reg_write(data, PCF50633_REG_INT2M, 0x00);
+	reg_write(data, PCF50633_REG_INT3M, 0x00);
+	reg_write(data, PCF50633_REG_INT4M, 0x00);
+	reg_write(data, PCF50633_REG_INT5M, 0x00);
+
+	err = request_irq(irq, pcf50633_irq, IRQF_TRIGGER_FALLING,
+			  "pcf50633", data);
+	if (err < 0)
+		goto exit_input;
+
+	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);
+
+	if (data->pdata->used_features & PCF50633_FEAT_RTC) {
+		data->rtc = rtc_device_register("pcf50633", &new_client->dev,
+						&pcf50633_rtc_ops, THIS_MODULE);
+		if (IS_ERR(data->rtc)) {
+			err = PTR_ERR(data->rtc);
+			goto exit_irq;
+		}
+	}
+
+	if (data->pdata->used_features & PCF50633_FEAT_PWM_BL) {
+		data->backlight = backlight_device_register("pcf50633-bl",
+							    &new_client->dev,
+							    data,
+							    &pcf50633bl_ops);
+		if (!data->backlight)
+			goto exit_rtc;
+		/* FIXME: are we sure we want default == off? */
+		data->backlight->props.max_brightness = 0x3f;
+		data->backlight->props.power = FB_BLANK_UNBLANK;
+		data->backlight->props.fb_blank = FB_BLANK_UNBLANK;
+		data->backlight->props.brightness =
+					data->backlight->props.max_brightness;
+		backlight_update_status(data->backlight);
+	}
+
+	apm_get_power_status = pcf50633_get_power_status;
+
+#ifdef CONFIG_MACH_NEO1973_GTA02
+	if (machine_is_neo1973_gta02()) {
+		gta01_pm_gps_dev.dev.parent = &new_client->dev;
+		gta01_pm_bt_dev.dev.parent = &new_client->dev;
+		platform_device_register(&gta01_pm_bt_dev);
+		platform_device_register(&gta01_pm_gps_dev);
+	}
+#endif
+
+	return 0;
+exit_rtc:
+	if (data->pdata->used_features & PCF50633_FEAT_RTC)
+		rtc_device_unregister(pcf50633_global->rtc);
+exit_irq:
+	free_irq(pcf50633_global->irq, pcf50633_global);
+exit_input:
+	input_unregister_device(data->input_dev);
+exit_sysfs:
+	pm_power_off = NULL;
+	sysfs_remove_group(&new_client->dev.kobj, &pcf_attr_group);
+exit_detach:
+	i2c_detach_client(new_client);
+exit_free:
+	kfree(data);
+	pcf50633_global = NULL;
+	return err;
+}
+
+static int pcf50633_attach_adapter(struct i2c_adapter *adapter)
+{
+	DEBUGP("entering, calling i2c_probe\n");
+	return i2c_probe(adapter, &addr_data, &pcf50633_detect);
+}
+
+static int pcf50633_detach_client(struct i2c_client *client)
+{
+	struct pcf50633_data *pcf = i2c_get_clientdata(client);
+
+	DEBUGP("entering\n");
+
+	apm_get_power_status = NULL;
+
+	free_irq(pcf->irq, pcf);
+
+	input_unregister_device(pcf->input_dev);
+
+	if (pcf->pdata->used_features & PCF50633_FEAT_PWM_BL)
+		backlight_device_unregister(pcf->backlight);
+
+	if (pcf->pdata->used_features & PCF50633_FEAT_RTC)
+		rtc_device_unregister(pcf->rtc);
+
+#ifdef CONFIG_MACH_NEO1973_GTA02
+	if (machine_is_neo1973_gta02()) {
+		platform_device_unregister(&gta01_pm_bt_dev);
+		platform_device_unregister(&gta01_pm_gps_dev);
+	}
+#endif
+
+	sysfs_remove_group(&client->dev.kobj, &pcf_attr_group);
+
+	pm_power_off = NULL;
+
+	kfree(pcf);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+#define INT1M_RESUMERS	(PCF50633_INT1_ADPINS		| \
+			 PCF50633_INT1_ADPREM		| \
+			 PCF50633_INT1_USBINS		| \
+			 PCF50633_INT1_USBREM		| \
+			 PCF50633_INT1_ALARM)
+#define INT2M_RESUMERS	(PCF50633_INT2_ONKEYF)
+#define INT3M_RESUMERS	(PCF50633_INT3_BATFULL		| \
+			 PCF50633_INT3_CHGHALT		| \
+			 PCF50633_INT3_THLIMON		| \
+			 PCF50633_INT3_THLIMOFF		| \
+			 PCF50633_INT3_USBLIMON		| \
+			 PCF50633_INT3_USBLIMOFF	| \
+			 PCF50633_INT3_ONKEY1S)
+#define INT4M_RESUMERS	(PCF50633_INT4_LOWSYS		| \
+			 PCF50633_INT4_LOWBAT		| \
+			 PCF50633_INT4_HIGHTMP)
+#define INT5M_RESUMERS	(0)
+
+static int pcf50633_suspend(struct device *dev, pm_message_t state)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50633_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.ooctim2 = __reg_read(pcf, PCF50633_REG_OOCTIM2);
+	pcf->standby_regs.autoout = __reg_read(pcf, PCF50633_REG_AUTOOUT);
+	pcf->standby_regs.autoena = __reg_read(pcf, PCF50633_REG_AUTOENA);
+	pcf->standby_regs.automxc = __reg_read(pcf, PCF50633_REG_AUTOMXC);
+	pcf->standby_regs.down1out = __reg_read(pcf, PCF50633_REG_DOWN1OUT);
+	pcf->standby_regs.down1mxc = __reg_read(pcf, PCF50633_REG_DOWN1MXC);
+	pcf->standby_regs.down2out = __reg_read(pcf, PCF50633_REG_DOWN2OUT);
+	pcf->standby_regs.down2ena = __reg_read(pcf, PCF50633_REG_DOWN2ENA);
+	pcf->standby_regs.memldoout = __reg_read(pcf, PCF50633_REG_MEMLDOOUT);
+	pcf->standby_regs.memldoena = __reg_read(pcf, PCF50633_REG_MEMLDOENA);
+	pcf->standby_regs.ledout = __reg_read(pcf, PCF50633_REG_LEDOUT);
+	pcf->standby_regs.ledena = __reg_read(pcf, PCF50633_REG_LEDENA);
+	pcf->standby_regs.leddim = __reg_read(pcf, PCF50633_REG_LEDDIM);
+	/* FIXME: one big read? */
+	for (i = 0; i < 7; i++) {
+		u_int8_t reg_out = PCF50633_REG_LDO1OUT + 2*i;
+		pcf->standby_regs.ldo[i].out = __reg_read(pcf, reg_out);
+		pcf->standby_regs.ldo[i].ena = __reg_read(pcf, reg_out+1);
+	}
+
+	/* switch off power supplies that are not needed during suspend */
+	for (i = 0; i < __NUM_PCF50633_REGULATORS; i++) {
+		if (!(pcf->pdata->rails[i].flags & PMU_VRAIL_F_SUSPEND_ON)) {
+			u_int8_t tmp;
+
+			DEBUGP("disabling pcf50633 regulator %u\n", i);
+			/* we cannot use pcf50633_onoff_set() because we're
+			 * already under the mutex */
+			tmp = __reg_read(pcf, regulator_registers[i]+1);
+			tmp &= 0xfe;
+			__reg_write(pcf, regulator_registers[i]+1, tmp);
+		}
+	}
+
+	pcf->standby_regs.int1m = __reg_read(pcf, PCF50633_REG_INT1M);
+	pcf->standby_regs.int2m = __reg_read(pcf, PCF50633_REG_INT2M);
+	pcf->standby_regs.int3m = __reg_read(pcf, PCF50633_REG_INT3M);
+	pcf->standby_regs.int4m = __reg_read(pcf, PCF50633_REG_INT4M);
+	pcf->standby_regs.int5m = __reg_read(pcf, PCF50633_REG_INT5M);
+	__reg_write(pcf, PCF50633_REG_INT1M, ~INT1M_RESUMERS & 0xff);
+	__reg_write(pcf, PCF50633_REG_INT2M, ~INT2M_RESUMERS & 0xff);
+	__reg_write(pcf, PCF50633_REG_INT3M, ~INT3M_RESUMERS & 0xff);
+	__reg_write(pcf, PCF50633_REG_INT4M, ~INT4M_RESUMERS & 0xff);
+	__reg_write(pcf, PCF50633_REG_INT5M, ~INT5M_RESUMERS & 0xff);
+
+	mutex_unlock(&pcf->lock);
+
+	return 0;
+}
+
+static int pcf50633_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pcf50633_data *pcf = i2c_get_clientdata(client);
+	int i;
+
+	mutex_lock(&pcf->lock);
+
+	/* Resume all saved registers that don't "survive" standby state */
+	__reg_write(pcf, PCF50633_REG_INT1M, pcf->standby_regs.int1m);
+	__reg_write(pcf, PCF50633_REG_INT2M, pcf->standby_regs.int2m);
+	__reg_write(pcf, PCF50633_REG_INT3M, pcf->standby_regs.int3m);
+	__reg_write(pcf, PCF50633_REG_INT4M, pcf->standby_regs.int4m);
+	__reg_write(pcf, PCF50633_REG_INT5M, pcf->standby_regs.int5m);
+
+	__reg_write(pcf, PCF50633_REG_OOCTIM2, pcf->standby_regs.ooctim2);
+	__reg_write(pcf, PCF50633_REG_AUTOOUT, pcf->standby_regs.autoout);
+	__reg_write(pcf, PCF50633_REG_AUTOMXC, pcf->standby_regs.automxc);
+	__reg_write(pcf, PCF50633_REG_DOWN1OUT, pcf->standby_regs.down1out);
+	__reg_write(pcf, PCF50633_REG_DOWN1MXC, pcf->standby_regs.down1mxc);
+	__reg_write(pcf, PCF50633_REG_DOWN2OUT, pcf->standby_regs.down2out);
+	__reg_write(pcf, PCF50633_REG_DOWN2ENA, pcf->standby_regs.down2ena);
+	__reg_write(pcf, PCF50633_REG_MEMLDOOUT, pcf->standby_regs.memldoout);
+	__reg_write(pcf, PCF50633_REG_MEMLDOENA, pcf->standby_regs.memldoena);
+	__reg_write(pcf, PCF50633_REG_LEDOUT, pcf->standby_regs.ledout);
+	__reg_write(pcf, PCF50633_REG_LEDENA, pcf->standby_regs.ledena);
+	__reg_write(pcf, PCF50633_REG_LEDDIM, pcf->standby_regs.leddim);
+	/* FIXME: one big read? */
+	for (i = 0; i < 7; i++) {
+		u_int8_t reg_out = PCF50633_REG_LDO1OUT + 2*i;
+		__reg_write(pcf, reg_out, pcf->standby_regs.ldo[i].out);
+		__reg_write(pcf, reg_out+1, pcf->standby_regs.ldo[i].ena);
+	}
+
+	mutex_unlock(&pcf->lock);
+
+	pcf50633_irq(pcf->irq, pcf);
+
+	return 0;
+}
+#else
+#define pcf50633_suspend NULL
+#define pcf50633_resume NULL
+#endif
+
+static struct i2c_driver pcf50633_driver = {
+	.driver = {
+		.name	= "pcf50633",
+		.suspend= pcf50633_suspend,
+		.resume	= pcf50633_resume,
+	},
+	.id		= I2C_DRIVERID_PCF50633,
+	.attach_adapter	= pcf50633_attach_adapter,
+	.detach_client	= pcf50633_detach_client,
+};
+
+/* platform driver, since i2c devices don't have platform_data */
+static int __init pcf50633_plat_probe(struct platform_device *pdev)
+{
+	struct pcf50633_platform_data *pdata = pdev->dev.platform_data;
+
+	if (!pdata)
+		return -ENODEV;
+
+	pcf50633_pdev = pdev;
+
+	return 0;
+}
+
+static int pcf50633_plat_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static struct platform_driver pcf50633_plat_driver = {
+	.probe	= pcf50633_plat_probe,
+	.remove	= pcf50633_plat_remove,
+	.driver = {
+		.owner	= THIS_MODULE,
+		.name 	= "pcf50633",
+	},
+};
+
+static int __init pcf50633_init(void)
+{
+	int rc;
+
+	if (!(rc = platform_driver_register(&pcf50633_plat_driver)))
+		rc = i2c_add_driver(&pcf50633_driver);
+
+	return rc;
+}
+
+static void pcf50633_exit(void)
+{
+	i2c_del_driver(&pcf50633_driver);
+	platform_driver_unregister(&pcf50633_plat_driver);
+}
+
+MODULE_DESCRIPTION("I2C chip driver for NXP PCF50633 power management unit");
+MODULE_AUTHOR("Harald Welte <laforge@openmoko.org>");
+MODULE_LICENSE("GPL");
+
+module_init(pcf50633_init);
+module_exit(pcf50633_exit);
diff --git a/drivers/i2c/chips/pcf50633.h b/drivers/i2c/chips/pcf50633.h
new file mode 100644
index 0000000..9aad206
--- /dev/null
+++ b/drivers/i2c/chips/pcf50633.h
@@ -0,0 +1,402 @@
+#ifndef _PCF50633_H
+#define _PCF50633_H
+
+/* Philips PCF50633 Power Managemnt Unit (PMU) driver
+ * (C) 2006-2007 by OpenMoko, Inc.
+ * Author: Harald Welte <laforge@openmoko.org>
+ *
+ */
+
+enum pfc50633_regs {
+	PCF50633_REG_VERSION	= 0x00,
+	PCF50633_REG_VARIANT	= 0x01,
+	PCF50633_REG_INT1	= 0x02,	/* Interrupt Status */
+	PCF50633_REG_INT2	= 0x03,	/* Interrupt Status */
+	PCF50633_REG_INT3	= 0x04,	/* Interrupt Status */
+	PCF50633_REG_INT4	= 0x05,	/* Interrupt Status */
+	PCF50633_REG_INT5	= 0x06,	/* Interrupt Status */
+	PCF50633_REG_INT1M	= 0x07,	/* Interrupt Mask */
+	PCF50633_REG_INT2M	= 0x08,	/* Interrupt Mask */
+	PCF50633_REG_INT3M	= 0x09,	/* Interrupt Mask */
+	PCF50633_REG_INT4M	= 0x0a,	/* Interrupt Mask */
+	PCF50633_REG_INT5M	= 0x0b,	/* Interrupt Mask */
+	PCF50633_REG_OOCSHDWN	= 0x0c,
+	PCF50633_REG_OOCWAKE	= 0x0d,
+	PCF50633_REG_OOCTIM1	= 0x0e,
+	PCF50633_REG_OOCTIM2	= 0x0f,
+	PCF50633_REG_OOCMODE	= 0x10,
+	PCF50633_REG_OOCCTL	= 0x11,
+	PCF50633_REG_OOCSTAT	= 0x12,
+	PCF50633_REG_GPIOCTL	= 0x13,
+	PCF50633_REG_GPIO1CFG	= 0x14,
+	PCF50633_REG_GPIO2CFG	= 0x15,
+	PCF50633_REG_GPIO3CFG	= 0x16,
+	PCF50633_REG_GPOCFG	= 0x17,
+	PCF50633_REG_BVMCTL	= 0x18,
+	PCF50633_REG_SVMCTL	= 0x19,
+	PCF50633_REG_AUTOOUT	= 0x1a,
+	PCF50633_REG_AUTOENA	= 0x1b,
+	PCF50633_REG_AUTOCTL	= 0x1c,
+	PCF50633_REG_AUTOMXC	= 0x1d,
+	PCF50633_REG_DOWN1OUT	= 0x1e,
+	PCF50633_REG_DOWN1ENA	= 0x1f,
+	PCF50633_REG_DOWN1CTL	= 0x20,
+	PCF50633_REG_DOWN1MXC	= 0x21,
+	PCF50633_REG_DOWN2OUT	= 0x22,
+	PCF50633_REG_DOWN2ENA	= 0x23,
+	PCF50633_REG_DOWN2CTL	= 0x24,
+	PCF50633_REG_DOWN2MXC	= 0x25,
+	PCF50633_REG_MEMLDOOUT	= 0x26,
+	PCF50633_REG_MEMLDOENA	= 0x27,
+	PCF50633_REG_LEDOUT	= 0x28,
+	PCF50633_REG_LEDENA	= 0x29,
+	PCF50633_REG_LEDCTL	= 0x2a,
+	PCF50633_REG_LEDDIM	= 0x2b,
+	/* reserved */
+	PCF50633_REG_LDO1OUT	= 0x2d,
+	PCF50633_REG_LDO1ENA	= 0x2e,
+	PCF50633_REG_LDO2OUT	= 0x2f,
+	PCF50633_REG_LDO2ENA	= 0x30,
+	PCF50633_REG_LDO3OUT	= 0x31,
+	PCF50633_REG_LDO3ENA	= 0x32,
+	PCF50633_REG_LDO4OUT	= 0x33,
+	PCF50633_REG_LDO4ENA	= 0x34,
+	PCF50633_REG_LDO5OUT	= 0x35,
+	PCF50633_REG_LDO5ENA	= 0x36,
+	PCF50633_REG_LDO6OUT	= 0x37,
+	PCF50633_REG_LDO6ENA	= 0x38,
+	PCF50633_REG_HCLDOOUT	= 0x39,
+	PCF50633_REG_HCLDOENA	= 0x3a,
+	PCF50633_REG_STBYCTL1	= 0x3b,
+	PCF50633_REG_STBYCTL2	= 0x3c,
+	PCF50633_REG_DEBPF1	= 0x3d,
+	PCF50633_REG_DEBPF2	= 0x3e,
+	PCF50633_REG_DEBPF3	= 0x3f,
+	PCF50633_REG_HCLDOOVL	= 0x40,
+	PCF50633_REG_DCDCSTAT	= 0x41,
+	PCF50633_REG_LDOSTAT	= 0x42,
+	PCF50633_REG_MBCC1	= 0x43,
+	PCF50633_REG_MBCC2	= 0x44,
+	PCF50633_REG_MBCC3	= 0x45,
+	PCF50633_REG_MBCC4	= 0x46,
+	PCF50633_REG_MBCC5	= 0x47,
+	PCF50633_REG_MBCC6	= 0x48,
+	PCF50633_REG_MBCC7	= 0x49,
+	PCF50633_REG_MBCC8	= 0x4a,
+	PCF50633_REG_MBCS1	= 0x4b,
+	PCF50633_REG_MBCS2	= 0x4c,
+	PCF50633_REG_MBCS3	= 0x4d,
+	PCF50633_REG_BBCCTL	= 0x4e,
+	PCF50633_REG_ALMGAIN	= 0x4f,
+	PCF50633_REG_ALMDATA	= 0x50,
+	/* reserved */
+	PCF50633_REG_ADCC3	= 0x52,
+	PCF50633_REG_ADCC2	= 0x53,
+	PCF50633_REG_ADCC1	= 0x54,
+	PCF50633_REG_ADCS1	= 0x55,
+	PCF50633_REG_ADCS2	= 0x56,
+	PCF50633_REG_ADCS3	= 0x57,
+	/* reserved */
+	PCF50633_REG_RTCSC	= 0x59,	/* Second */
+	PCF50633_REG_RTCMN	= 0x5a,	/* Minute */
+	PCF50633_REG_RTCHR	= 0x5b,	/* Hour */
+	PCF50633_REG_RTCWD	= 0x5c,	/* Weekday */
+	PCF50633_REG_RTCDT	= 0x5d,	/* Day */
+	PCF50633_REG_RTCMT	= 0x5e,	/* Month */
+	PCF50633_REG_RTCYR	= 0x5f,	/* Year */
+	PCF50633_REG_RTCSCA	= 0x60, /* Alarm Second */
+	PCF50633_REG_RTCMNA	= 0x61, /* Alarm Minute */
+	PCF50633_REG_RTCHRA	= 0x62, /* Alarm Hour */
+	PCF50633_REG_RTCWDA	= 0x63, /* Alarm Weekday */
+	PCF50633_REG_RTCDTA	= 0x64, /* Alarm Day */
+	PCF50633_REG_RTCMTA	= 0x65, /* Alarm Month */
+	PCF50633_REG_RTCYRA	= 0x66, /* Alarm Year */
+
+	PCF50633_REG_MEMBYTE0	= 0x67,
+	PCF50633_REG_MEMBYTE1	= 0x68,
+	PCF50633_REG_MEMBYTE2	= 0x69,
+	PCF50633_REG_MEMBYTE3	= 0x6a,
+	PCF50633_REG_MEMBYTE4	= 0x6b,
+	PCF50633_REG_MEMBYTE5	= 0x6c,
+	PCF50633_REG_MEMBYTE6	= 0x6d,
+	PCF50633_REG_MEMBYTE7	= 0x6e,
+	/* reserved */
+	PCF50633_REG_DCDCPFM	= 0x84,
+	__NUM_PCF50633_REGS
+};
+
+enum pcf50633_reg_int1 {
+	PCF50633_INT1_ADPINS	= 0x01,	/* Adapter inserted */
+	PCF50633_INT1_ADPREM	= 0x02,	/* Adapter removed */
+	PCF50633_INT1_USBINS	= 0x04,	/* USB inserted */
+	PCF50633_INT1_USBREM	= 0x08,	/* USB removed */
+	/* reserved */
+	PCF50633_INT1_ALARM	= 0x40, /* RTC alarm time is reached */
+	PCF50633_INT1_SECOND	= 0x80,	/* RTC periodic second interrupt */
+};
+
+enum pcf50633_reg_int2 {
+	PCF50633_INT2_ONKEYR	= 0x01, /* ONKEY rising edge */
+	PCF50633_INT2_ONKEYF	= 0x02, /* ONKEY falling edge */
+	PCF50633_INT2_EXTON1R	= 0x04, /* EXTON1 rising edge */
+	PCF50633_INT2_EXTON1F	= 0x08, /* EXTON1 falling edge */
+	PCF50633_INT2_EXTON2R	= 0x10, /* EXTON2 rising edge */
+	PCF50633_INT2_EXTON2F	= 0x20, /* EXTON2 falling edge */
+	PCF50633_INT2_EXTON3R	= 0x40, /* EXTON3 rising edge */
+	PCF50633_INT2_EXTON3F	= 0x80, /* EXTON3 falling edge */
+};
+
+enum pcf50633_reg_int3 {
+	PCF50633_INT3_BATFULL	= 0x01, /* Battery full */
+	PCF50633_INT3_CHGHALT	= 0x02,	/* Charger halt */
+	PCF50633_INT3_THLIMON	= 0x04,
+	PCF50633_INT3_THLIMOFF	= 0x08,
+	PCF50633_INT3_USBLIMON	= 0x10,
+	PCF50633_INT3_USBLIMOFF	= 0x20,
+	PCF50633_INT3_ADCRDY	= 0x40,	/* ADC conversion finished */
+	PCF50633_INT3_ONKEY1S	= 0x80,	/* ONKEY pressed 1 second */
+};
+
+enum pcf50633_reg_int4 {
+	PCF50633_INT4_LOWSYS		= 0x01,
+	PCF50633_INT4_LOWBAT		= 0x02,
+	PCF50633_INT4_HIGHTMP		= 0x04,
+	PCF50633_INT4_AUTOPWRFAIL	= 0x08,
+	PCF50633_INT4_DWN1PWRFAIL	= 0x10,
+	PCF50633_INT4_DWN2PWRFAIL	= 0x20,
+	PCF50633_INT4_LEDPWRFAIL	= 0x40,
+	PCF50633_INT4_LEDOVP		= 0x80,
+};
+
+enum pcf50633_reg_int5 {
+	PCF50633_INT5_LDO1PWRFAIL	= 0x01,
+	PCF50633_INT5_LDO2PWRFAIL	= 0x02,
+	PCF50633_INT5_LDO3PWRFAIL	= 0x04,
+	PCF50633_INT5_LDO4PWRFAIL	= 0x08,
+	PCF50633_INT5_LDO5PWRFAIL	= 0x10,
+	PCF50633_INT5_LDO6PWRFAIL	= 0x20,
+	PCF50633_INT5_HCLDOPWRFAIL	= 0x40,
+	PCF50633_INT5_HCLDOOVL		= 0x80,
+};
+
+enum pcf50633_reg_oocshdwn {
+	PCF50633_OOCSHDWN_GOSTDBY	= 0x01,
+	PCF50633_OOCSHDWN_TOTRST	= 0x04,
+	PCF50633_OOCSHDWN_COLDBOOT	= 0x08,
+};
+
+enum pcf50633_reg_oocwake {
+	PCF50633_OOCWAKE_ONKEY		= 0x01,
+	PCF50633_OOCWAKE_EXTON1		= 0x02,
+	PCF50633_OOCWAKE_EXTON2		= 0x04,
+	PCF50633_OOCWAKE_EXTON3		= 0x08,
+	PCF50633_OOCWAKE_RTC		= 0x10,
+	/* reserved */
+	PCF50633_OOCWAKE_USB		= 0x40,
+	PCF50633_OOCWAKE_ADP		= 0x80,
+};
+
+enum pcf50633_reg_mbcc1 {
+	PCF50633_MBCC1_CHGENA		= 0x01,	/* Charger enable */
+	PCF50633_MBCC1_AUTOSTOP		= 0x02,
+	PCF50633_MBCC1_AUTORES		= 0x04, /* automatic resume */
+	PCF50633_MBCC1_RESUME		= 0x08, /* explicit resume cmd */
+	PCF50633_MBCC1_RESTART		= 0x10, /* restart charging */
+	PCF50633_MBCC1_PREWDTIME_60M	= 0x20,	/* max. precharging time */
+	PCF50633_MBCC1_WDTIME_1H	= 0x00,
+	PCF50633_MBCC1_WDTIME_2H	= 0x40,
+	PCF50633_MBCC1_WDTIME_4H	= 0x80,
+	PCF50633_MBCC1_WDTIME_6H	= 0xc0,
+};
+#define PCF50633_MBCC1_WDTIME_MASK	  0xc0
+
+enum pcf50633_reg_mbcc2 {
+	PCF50633_MBCC2_VBATCOND_2V7	= 0x00,
+	PCF50633_MBCC2_VBATCOND_2V85	= 0x01,
+	PCF50633_MBCC2_VBATCOND_3V0	= 0x02,
+	PCF50633_MBCC2_VBATCOND_3V15	= 0x03,
+	PCF50633_MBCC2_VMAX_4V		= 0x00,
+	PCF50633_MBCC2_VMAX_4V20	= 0x28,
+	PCF50633_MBCC2_VRESDEBTIME_64S	= 0x80,	/* debounce time (32/64sec) */
+};
+#define	PCF50633_MBCC2_VBATCOND_MASK	  0x03
+#define PCF50633_MBCC2_VMAX_MASK	  0x3c
+
+enum pcf50633_reg_adcc1 {
+	PCF50633_ADCC1_ADCSTART		= 0x01,
+	PCF50633_ADCC1_RES_10BIT	= 0x02,
+	PCF50633_ADCC1_AVERAGE_NO	= 0x00,
+	PCF50633_ADCC1_AVERAGE_4	= 0x04,
+	PCF50633_ADCC1_AVERAGE_8	= 0x08,
+	PCF50633_ADCC1_AVERAGE_16	= 0x0c,
+
+	PCF50633_ADCC1_MUX_BATSNS_RES	= 0x00,
+	PCF50633_ADCC1_MUX_BATSNS_SUBTR	= 0x10,
+	PCF50633_ADCC1_MUX_ADCIN2_RES	= 0x20,
+	PCF50633_ADCC1_MUX_ADCIN2_SUBTR	= 0x30,
+	PCF50633_ADCC1_MUX_BATTEMP	= 0x60,
+	PCF50633_ADCC1_MUX_ADCIN1	= 0x70,
+};
+#define PCF50633_ADCC1_AVERAGE_MASK	0x0c
+#define	PCF50633_ADCC1_ADCMUX_MASK	0xf0
+
+enum pcf50633_reg_adcc2 {
+	PCF50633_ADCC2_RATIO_NONE	= 0x00,
+	PCF50633_ADCC2_RATIO_BATTEMP	= 0x01,
+	PCF50633_ADCC2_RATIO_ADCIN1	= 0x02,
+	PCF50633_ADCC2_RATIO_BOTH	= 0x03,
+	PCF50633_ADCC2_RATIOSETTL_100US	= 0x04,
+};
+#define PCF50633_ADCC2_RATIO_MASK	0x03
+
+enum pcf50633_reg_adcc3 {
+	PCF50633_ADCC3_ACCSW_EN		= 0x01,
+	PCF50633_ADCC3_NTCSW_EN		= 0x04,
+	PCF50633_ADCC3_RES_DIV_TWO	= 0x10,
+	PCF50633_ADCC3_RES_DIV_THREE	= 0x00,
+};
+
+enum pcf50633_reg_adcs3 {
+	PCF50633_ADCS3_REF_NTCSW	= 0x00,
+	PCF50633_ADCS3_REF_ACCSW	= 0x10,
+	PCF50633_ADCS3_REF_2V0		= 0x20,
+	PCF50633_ADCS3_REF_VISA		= 0x30,
+	PCF50633_ADCS3_REF_2V0_2	= 0x70,
+	PCF50633_ADCS3_ADCRDY		= 0x80,
+};
+#define PCF50633_ADCS3_ADCDAT1L_MASK	0x03
+#define PCF50633_ADCS3_ADCDAT2L_MASK	0x0c
+#define PCF50633_ADCS3_ADCDAT2L_SHIFT	2
+#define PCF50633_ASCS3_REF_MASK		0x70
+
+enum pcf50633_regulator_enable {
+	PCF50633_REGULATOR_ON		= 0x01,
+	PCF50633_REGULATOR_ON_GPIO1	= 0x02,
+	PCF50633_REGULATOR_ON_GPIO2	= 0x04,
+	PCF50633_REGULATOR_ON_GPIO3	= 0x08,
+};
+#define PCF50633_REGULATOR_ON_MASK	0x0f
+
+enum pcf50633_regulator_phase {
+	PCF50633_REGULATOR_ACTPH1	= 0x00,
+	PCF50633_REGULATOR_ACTPH2	= 0x10,
+	PCF50633_REGULATOR_ACTPH3	= 0x20,
+	PCF50633_REGULATOR_ACTPH4	= 0x30,
+};
+#define PCF50633_REGULATOR_ACTPH_MASK	0x30
+
+enum pcf50633_reg_gpocfg {
+	PCF50633_GPOCFG_GPOSEL_0	= 0x00,
+	PCF50633_GPOCFG_GPOSEL_LED_NFET	= 0x01,
+	PCF50633_GPOCFG_GPOSEL_SYSxOK	= 0x02,
+	PCF50633_GPOCFG_GPOSEL_CLK32K	= 0x03,
+	PCF50633_GPOCFG_GPOSEL_ADAPUSB	= 0x04,
+	PCF50633_GPOCFG_GPOSEL_USBxOK	= 0x05,
+	PCF50633_GPOCFG_GPOSEL_ACTPH4	= 0x06,
+	PCF50633_GPOCFG_GPOSEL_1	= 0x07,
+	PCF50633_GPOCFG_GPOSEL_INVERSE	= 0x08,
+};
+#define PCF50633_GPOCFG_GPOSEL_MASK	0x07
+
+#if 0
+enum pcf50633_reg_mbcc1 {
+	PCF50633_MBCC1_CHGENA		= 0x01,
+	PCF50633_MBCC1_AUTOSTOP		= 0x02,
+	PCF50633_MBCC1_AUTORES		= 0x04,
+	PCF50633_MBCC1_RESUME		= 0x08,
+	PCF50633_MBCC1_RESTART		= 0x10,
+	PCF50633_MBCC1_PREWDTIME_30MIN	= 0x00,
+	PCF50633_MBCC1_PREWDTIME_60MIN	= 0x20,
+	PCF50633_MBCC1_WDTIME_2HRS	= 0x40,
+	PCF50633_MBCC1_WDTIME_4HRS	= 0x80,
+	PCF50633_MBCC1_WDTIME_6HRS	= 0xc0,
+};
+
+enum pcf50633_reg_mbcc2 {
+	PCF50633_MBCC2_VBATCOND_2V7	= 0x00,
+	PCF50633_MBCC2_VBATCOND_2V85	= 0x01,
+	PCF50633_MBCC2_VBATCOND_3V0	= 0x02,
+	PCF50633_MBCC2_VBATCOND_3V15	= 0x03,
+	PCF50633_MBCC2_VRESDEBTIME_64S	= 0x80,
+};
+#define PCF50633_MBCC2_VMAX_MASK	0x3c
+#endif
+
+enum pcf50633_reg_mbcc7 {
+	PCF50633_MBCC7_USB_100mA	= 0x00,
+	PCF50633_MBCC7_USB_500mA	= 0x01,
+	PCF50633_MBCC7_USB_1000mA	= 0x02,
+	PCF50633_MBCC7_USB_SUSPEND	= 0x03,
+	PCF50633_MBCC7_BATTEMP_EN	= 0x04,
+	PCF50633_MBCC7_BATSYSIMAX_1A6	= 0x00,
+	PCF50633_MBCC7_BATSYSIMAX_1A8	= 0x40,
+	PCF50633_MBCC7_BATSYSIMAX_2A0	= 0x80,
+	PCF50633_MBCC7_BATSYSIMAX_2A2	= 0xc0,
+};
+#define PCF56033_MBCC7_USB_MASK		0x03
+
+enum pcf50633_reg_mbcc8 {
+	PCF50633_MBCC8_USBENASUS	= 0x10,
+};
+
+enum pcf50633_reg_mbcs1 {
+	PCF50633_MBCS1_USBPRES		= 0x01,
+	PCF50633_MBCS1_USBOK		= 0x02,
+	PCF50633_MBCS1_ADAPTPRES	= 0x04,
+	PCF50633_MBCS1_ADAPTOK		= 0x08,
+	PCF50633_MBCS1_TBAT_OK		= 0x00,
+	PCF50633_MBCS1_TBAT_ABOVE	= 0x10,
+	PCF50633_MBCS1_TBAT_BELOW	= 0x20,
+	PCF50633_MBCS1_TBAT_UNDEF	= 0x30,
+	PCF50633_MBCS1_PREWDTEXP	= 0x40,
+	PCF50633_MBCS1_WDTEXP		= 0x80,
+};
+
+enum pcf50633_reg_mbcs2_mbcmod {
+	PCF50633_MBCS2_MBC_PLAY		= 0x00,
+	PCF50633_MBCS2_MBC_USB_PRE	= 0x01,
+	PCF50633_MBCS2_MBC_USB_PRE_WAIT	= 0x02,
+	PCF50633_MBCS2_MBC_USB_FAST	= 0x03,
+	PCF50633_MBCS2_MBC_USB_FAST_WAIT= 0x04,
+	PCF50633_MBCS2_MBC_USB_SUSPEND	= 0x05,
+	PCF50633_MBCS2_MBC_ADP_PRE	= 0x06,
+	PCF50633_MBCS2_MBC_ADP_PRE_WAIT	= 0x07,
+	PCF50633_MBCS2_MBC_ADP_FAST	= 0x08,
+	PCF50633_MBCS2_MBC_ADP_FAST_WAIT= 0x09,
+	PCF50633_MBCS2_MBC_BAT_FULL	= 0x0a,
+	PCF50633_MBCS2_MBC_HALT		= 0x0b,
+};
+#define PCF50633_MBCS2_MBC_MASK		0x0f
+enum pcf50633_reg_mbcs2_chgstat {
+	PCF50633_MBCS2_CHGS_NONE	= 0x00,
+	PCF50633_MBCS2_CHGS_ADAPTER	= 0x10,
+	PCF50633_MBCS2_CHGS_USB		= 0x20,
+	PCF50633_MBCS2_CHGS_BOTH	= 0x30,
+};
+#define PCF50633_MBCS2_RESSTAT_AUTO	0x40
+
+enum pcf50633_reg_mbcs3 {
+	PCF50633_MBCS3_USBLIM_PLAY	= 0x01,
+	PCF50633_MBCS3_USBLIM_CGH	= 0x02,
+	PCF50633_MBCS3_TLIM_PLAY	= 0x04,
+	PCF50633_MBCS3_TLIM_CHG		= 0x08,
+	PCF50633_MBCS3_ILIM		= 0x10,	/* 1: Ibat > Icutoff */
+	PCF50633_MBCS3_VLIM		= 0x20,	/* 1: Vbat == Vmax */
+	PCF50633_MBCS3_VBATSTAT		= 0x40,	/* 1: Vbat > Vbatcond */
+	PCF50633_MBCS3_VRES		= 0x80, /* 1: Vbat > Vth(RES) */
+};
+
+/* this is to be provided by the board implementation */
+extern const u_int8_t pcf50633_initial_regs[__NUM_PCF50633_REGS];
+
+void pcf50633_reg_write(u_int8_t reg, u_int8_t val);
+
+u_int8_t pcf50633_reg_read(u_int8_t reg);
+
+void pcf50633_reg_set_bit_mask(u_int8_t reg, u_int8_t mask, u_int8_t val);
+void pcf50633_reg_clear_bits(u_int8_t reg, u_int8_t bits);
+
+void pcf50633_charge_autofast(int on);
+
+#endif /* _PCF50606_H */
+
diff --git a/include/linux/i2c-id.h b/include/linux/i2c-id.h
index 2cda699..90f53e1 100644
--- a/include/linux/i2c-id.h
+++ b/include/linux/i2c-id.h
@@ -95,6 +95,7 @@
 
 #define I2C_DRIVERID_OV7670 1048	/* Omnivision 7670 camera */
 #define I2C_DRIVERID_PCF50606 1049
+#define I2C_DRIVERID_PCF50633 1051
 
 /*
  * ---- Adapter types ----------------------------------------------------
diff --git a/include/linux/pcf50633.h b/include/linux/pcf50633.h
new file mode 100644
index 0000000..5f32004
--- /dev/null
+++ b/include/linux/pcf50633.h
@@ -0,0 +1,114 @@
+#ifndef _LINUX_PCF50633_H
+#define _LINUX_PCF50633_H
+
+/* public in-kernel pcf50633 api */
+enum pcf50633_regulator_id {
+	PCF50633_REGULATOR_AUTO,
+	PCF50633_REGULATOR_DOWN1,
+	PCF50633_REGULATOR_DOWN2,
+	PCF50633_REGULATOR_MEMLDO,
+	PCF50633_REGULATOR_LDO1,
+	PCF50633_REGULATOR_LDO2,
+	PCF50633_REGULATOR_LDO3,
+	PCF50633_REGULATOR_LDO4,
+	PCF50633_REGULATOR_LDO5,
+	PCF50633_REGULATOR_LDO6,
+	PCF50633_REGULATOR_HCLDO,
+	__NUM_PCF50633_REGULATORS
+};
+
+struct pcf50633_data;
+extern struct pcf50633_data *pcf50633_global;
+
+extern void
+pcf50633_go_standby(void);
+
+enum pcf50633_gpio {
+	PCF50633_GPIO1 = 1,
+	PCF50633_GPIO2 = 2,
+	PCF50633_GPIO3 = 3,
+	PCF50633_GPO = 4,
+};
+
+extern void
+pcf50633_gpio_set(struct pcf50633_data *pcf, enum pcf50633_gpio gpio, int on);
+
+extern int
+pcf50633_gpio_get(struct pcf50633_data *pcf, enum pcf50633_gpio gpio);
+
+extern int
+pcf50633_voltage_set(struct pcf50633_data *pcf,
+		     enum pcf50633_regulator_id reg,
+		     unsigned int millivolts);
+extern unsigned int
+pcf50633_voltage_get(struct pcf50633_data *pcf,
+		     enum pcf50633_regulator_id reg);
+extern int
+pcf50633_onoff_get(struct pcf50633_data *pcf,
+		   enum pcf50633_regulator_id reg);
+
+extern int
+pcf50633_onoff_set(struct pcf50633_data *pcf,
+		   enum pcf50633_regulator_id reg, int on);
+
+extern void
+pcf50633_usb_curlim_set(struct pcf50633_data *pcf, int ma);
+
+extern void
+pcf50633_charge_enable(struct pcf50633_data *pcf, int on);
+
+/* FIXME: sharded with pcf50606 */
+#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,
+	PMU_EVT_USB_INSERT,
+	PMU_EVT_USB_REMOVE,
+	__NUM_PMU_EVTS
+};
+
+typedef int pmu_cb(struct device *dev, unsigned int feature,
+		   enum pmu_event event);
+
+#define PCF50633_FEAT_EXTON	0x00000001	/* not yet supported */
+#define PCF50633_FEAT_MBC	0x00000002
+#define PCF50633_FEAT_BBC	0x00000004	/* not yet supported */
+#define PCF50633_FEAT_RTC	0x00000040
+#define PCF50633_FEAT_CHGCUR	0x00000100
+#define PCF50633_FEAT_BATVOLT	0x00000200
+#define PCF50633_FEAT_BATTEMP	0x00000400
+#define PCF50633_FEAT_PWM_BL	0x00000800
+
+struct pcf50633_platform_data {
+	/* general */
+	unsigned int used_features;
+	unsigned int onkey_seconds_sig_init;
+	unsigned int onkey_seconds_shutdown;
+
+	/* voltage regulator related */
+	struct pmu_voltage_rail rails[__NUM_PCF50633_REGULATORS];
+	unsigned int used_regulators;
+
+	/* charger related */
+	unsigned int r_fix_batt;
+	unsigned int r_fix_batt_par;
+	unsigned int r_sense_milli;
+
+	struct {
+		u_int8_t mbcc3; /* charger voltage / current */
+	} charger;
+	pmu_cb *cb;
+};
+
+#endif /* _PCF50633_H */
-- 
1.5.6.3