ACPI: add support for Smart Battery

Most batteries today are ACPI "Control Method" batteries,
but some models ship with the older "Smart Battery"
that requires this code.

Rich Townsend and Bruno Ducrot were the original authors.
Vladimir Lebedev updated to run on latest kernel.

http://bugzilla.kernel.org/show_bug.cgi?id=3734

Signed-off-by: Len Brown <len.brown@intel.com>
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index bc2652d..fef7bab 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -352,6 +352,18 @@
 	  If one selects "m," this driver can be loaded using the following
 	  command: 
 		$>modprobe acpi_memhotplug 
+
+config ACPI_SBS
+	tristate "Smart Battery System (EXPERIMENTAL)"
+	depends on X86 && I2C
+	depends on EXPERIMENTAL
+	default y
+	help
+	  This driver adds support for the Smart Battery System.
+	  Depends on I2C (Device Drivers ---> I2C support)
+	  A "Smart Battery" is quite old and quite rare compared
+	  to today's ACPI "Control Method" battery.
+
 endif	# ACPI
 
 endmenu
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index f0a68ec..bce7ca2 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -58,3 +58,5 @@
 obj-$(CONFIG_ACPI_TOSHIBA)	+= toshiba_acpi.o
 obj-y				+= scan.o motherboard.o
 obj-$(CONFIG_ACPI_HOTPLUG_MEMORY)	+= acpi_memhotplug.o
+obj-y				+= cm_sbs.o
+obj-$(CONFIG_ACPI_SBS)		+= i2c_ec.o sbs.o
diff --git a/drivers/acpi/ac.c b/drivers/acpi/ac.c
index 4537ae4..e0a1b15 100644
--- a/drivers/acpi/ac.c
+++ b/drivers/acpi/ac.c
@@ -50,6 +50,9 @@
 MODULE_DESCRIPTION(ACPI_AC_DRIVER_NAME);
 MODULE_LICENSE("GPL");
 
+extern struct proc_dir_entry *acpi_lock_ac_dir(void);
+extern void *acpi_unlock_ac_dir(struct proc_dir_entry *acpi_ac_dir);
+
 static int acpi_ac_add(struct acpi_device *device);
 static int acpi_ac_remove(struct acpi_device *device, int type);
 static int acpi_ac_open_fs(struct inode *inode, struct file *file);
@@ -278,17 +281,16 @@
 
 static int __init acpi_ac_init(void)
 {
-	int result = 0;
+	int result;
 
 
-	acpi_ac_dir = proc_mkdir(ACPI_AC_CLASS, acpi_root_dir);
+	acpi_ac_dir = acpi_lock_ac_dir();
 	if (!acpi_ac_dir)
 		return -ENODEV;
-	acpi_ac_dir->owner = THIS_MODULE;
 
 	result = acpi_bus_register_driver(&acpi_ac_driver);
 	if (result < 0) {
-		remove_proc_entry(ACPI_AC_CLASS, acpi_root_dir);
+		acpi_unlock_ac_dir(acpi_ac_dir);
 		return -ENODEV;
 	}
 
@@ -300,7 +302,7 @@
 
 	acpi_bus_unregister_driver(&acpi_ac_driver);
 
-	remove_proc_entry(ACPI_AC_CLASS, acpi_root_dir);
+	acpi_unlock_ac_dir(acpi_ac_dir);
 
 	return;
 }
diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c
index 2b8aab5..3ea79de 100644
--- a/drivers/acpi/battery.c
+++ b/drivers/acpi/battery.c
@@ -59,6 +59,9 @@
 MODULE_DESCRIPTION(ACPI_BATTERY_DRIVER_NAME);
 MODULE_LICENSE("GPL");
 
+extern struct proc_dir_entry *acpi_lock_battery_dir(void);
+extern void *acpi_unlock_battery_dir(struct proc_dir_entry *acpi_battery_dir);
+
 static int acpi_battery_add(struct acpi_device *device);
 static int acpi_battery_remove(struct acpi_device *device, int type);
 
@@ -750,17 +753,15 @@
 
 static int __init acpi_battery_init(void)
 {
-	int result = 0;
+	int result;
 
-
-	acpi_battery_dir = proc_mkdir(ACPI_BATTERY_CLASS, acpi_root_dir);
+	acpi_battery_dir = acpi_lock_battery_dir();
 	if (!acpi_battery_dir)
 		return -ENODEV;
-	acpi_battery_dir->owner = THIS_MODULE;
 
 	result = acpi_bus_register_driver(&acpi_battery_driver);
 	if (result < 0) {
-		remove_proc_entry(ACPI_BATTERY_CLASS, acpi_root_dir);
+		acpi_unlock_battery_dir(acpi_battery_dir);
 		return -ENODEV;
 	}
 
@@ -772,7 +773,7 @@
 
 	acpi_bus_unregister_driver(&acpi_battery_driver);
 
-	remove_proc_entry(ACPI_BATTERY_CLASS, acpi_root_dir);
+	acpi_unlock_battery_dir(acpi_battery_dir);
 
 	return;
 }
diff --git a/drivers/acpi/cm_sbs.c b/drivers/acpi/cm_sbs.c
new file mode 100644
index 0000000..d11507c
--- /dev/null
+++ b/drivers/acpi/cm_sbs.c
@@ -0,0 +1,135 @@
+/*
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ *  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.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/acpi.h>
+#include <linux/types.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+#include <acpi/acmacros.h>
+#include <acpi/actypes.h>
+#include <acpi/acutils.h>
+
+ACPI_MODULE_NAME("cm_sbs")
+#define ACPI_AC_CLASS		"ac_adapter"
+#define ACPI_BATTERY_CLASS	"battery"
+#define ACPI_SBS_COMPONENT	0x00080000
+#define _COMPONENT		ACPI_SBS_COMPONENT
+static struct proc_dir_entry *acpi_ac_dir;
+static struct proc_dir_entry *acpi_battery_dir;
+
+static struct semaphore cm_sbs_sem;
+
+static int lock_ac_dir_cnt = 0;
+static int lock_battery_dir_cnt = 0;
+
+struct proc_dir_entry *acpi_lock_ac_dir(void)
+{
+	ACPI_FUNCTION_TRACE("acpi_lock_ac_dir");
+
+	down(&cm_sbs_sem);
+	if (!acpi_ac_dir) {
+		acpi_ac_dir = proc_mkdir(ACPI_AC_CLASS, acpi_root_dir);
+	}
+	if (acpi_ac_dir) {
+		lock_ac_dir_cnt++;
+	} else {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "Cannot create %s\n", ACPI_AC_CLASS));
+	}
+	up(&cm_sbs_sem);
+	return (acpi_ac_dir);
+}
+
+EXPORT_SYMBOL(acpi_lock_ac_dir);
+
+void acpi_unlock_ac_dir(struct proc_dir_entry *acpi_ac_dir_param)
+{
+	ACPI_FUNCTION_TRACE("acpi_unlock_ac_dir");
+
+	down(&cm_sbs_sem);
+	if (acpi_ac_dir_param) {
+		lock_ac_dir_cnt--;
+	}
+	if (lock_ac_dir_cnt == 0 && acpi_ac_dir_param && acpi_ac_dir) {
+		remove_proc_entry(ACPI_AC_CLASS, acpi_root_dir);
+		acpi_ac_dir = 0;
+	}
+	up(&cm_sbs_sem);
+}
+
+EXPORT_SYMBOL(acpi_unlock_ac_dir);
+
+struct proc_dir_entry *acpi_lock_battery_dir(void)
+{
+	ACPI_FUNCTION_TRACE("acpi_lock_battery_dir");
+
+	down(&cm_sbs_sem);
+	if (!acpi_battery_dir) {
+		acpi_battery_dir =
+		    proc_mkdir(ACPI_BATTERY_CLASS, acpi_root_dir);
+	}
+	if (acpi_battery_dir) {
+		lock_battery_dir_cnt++;
+	} else {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "Cannot create %s\n", ACPI_BATTERY_CLASS));
+	}
+	up(&cm_sbs_sem);
+	return (acpi_battery_dir);
+}
+
+EXPORT_SYMBOL(acpi_lock_battery_dir);
+
+void acpi_unlock_battery_dir(struct proc_dir_entry *acpi_battery_dir_param)
+{
+	ACPI_FUNCTION_TRACE("acpi_unlock_battery_dir");
+
+	down(&cm_sbs_sem);
+	if (acpi_battery_dir_param) {
+		lock_battery_dir_cnt--;
+	}
+	if (lock_battery_dir_cnt == 0 && acpi_battery_dir_param
+	    && acpi_battery_dir) {
+		remove_proc_entry(ACPI_BATTERY_CLASS, acpi_root_dir);
+		acpi_battery_dir = 0;
+	}
+	up(&cm_sbs_sem);
+}
+
+EXPORT_SYMBOL(acpi_unlock_battery_dir);
+
+static int __init acpi_cm_sbs_init(void)
+{
+	ACPI_FUNCTION_TRACE("acpi_cm_sbs_init");
+
+	if (acpi_disabled)
+		return_VALUE(0);
+
+	init_MUTEX(&cm_sbs_sem);
+
+	return_VALUE(0);
+}
+
+subsys_initcall(acpi_cm_sbs_init);
diff --git a/drivers/acpi/i2c_ec.c b/drivers/acpi/i2c_ec.c
new file mode 100644
index 0000000..72478a6
--- /dev/null
+++ b/drivers/acpi/i2c_ec.c
@@ -0,0 +1,420 @@
+/*
+ * SMBus driver for ACPI Embedded Controller ($Revision: 1.3 $)
+ *
+ * Copyright (c) 2002, 2005 Ducrot Bruno
+ * Copyright (c) 2005 Rich Townsend (tiny hacks & tweaks)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 2.
+ */
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/stddef.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/acpi.h>
+#include <linux/delay.h>
+
+#include "i2c_ec.h"
+
+#define	xudelay(t)	udelay(t)
+#define	xmsleep(t)	msleep(t)
+
+#define ACPI_EC_HC_COMPONENT	0x00080000
+#define ACPI_EC_HC_CLASS	"ec_hc_smbus"
+#define ACPI_EC_HC_HID		"ACPI0001"
+#define ACPI_EC_HC_DRIVER_NAME	"ACPI EC HC smbus driver"
+#define ACPI_EC_HC_DEVICE_NAME	"EC HC smbus"
+
+#define _COMPONENT		ACPI_EC_HC_COMPONENT
+
+ACPI_MODULE_NAME("acpi_smbus")
+
+static int acpi_ec_hc_add(struct acpi_device *device);
+static int acpi_ec_hc_remove(struct acpi_device *device, int type);
+
+static struct acpi_driver acpi_ec_hc_driver = {
+	.name = ACPI_EC_HC_DRIVER_NAME,
+	.class = ACPI_EC_HC_CLASS,
+	.ids = ACPI_EC_HC_HID,
+	.ops = {
+		.add = acpi_ec_hc_add,
+		.remove = acpi_ec_hc_remove,
+		},
+};
+
+/* Various bit mask for EC_SC (R) */
+#define OBF		0x01
+#define IBF		0x02
+#define CMD		0x08
+#define BURST		0x10
+#define SCI_EVT		0x20
+#define SMI_EVT		0x40
+
+/* Commands for EC_SC (W) */
+#define RD_EC		0x80
+#define WR_EC		0x81
+#define BE_EC		0x82
+#define BD_EC		0x83
+#define QR_EC		0x84
+
+/*
+ * ACPI 2.0 chapter 13 SMBus 2.0 EC register model
+ */
+
+#define ACPI_EC_SMB_PRTCL	0x00	/* protocol, PEC */
+#define ACPI_EC_SMB_STS		0x01	/* status */
+#define ACPI_EC_SMB_ADDR	0x02	/* address */
+#define ACPI_EC_SMB_CMD		0x03	/* command */
+#define ACPI_EC_SMB_DATA	0x04	/* 32 data registers */
+#define ACPI_EC_SMB_BCNT	0x24	/* number of data bytes */
+#define ACPI_EC_SMB_ALRM_A	0x25	/* alarm address */
+#define ACPI_EC_SMB_ALRM_D	0x26	/* 2 bytes alarm data */
+
+#define ACPI_EC_SMB_STS_DONE	0x80
+#define ACPI_EC_SMB_STS_ALRM	0x40
+#define ACPI_EC_SMB_STS_RES	0x20
+#define ACPI_EC_SMB_STS_STATUS	0x1f
+
+#define ACPI_EC_SMB_STATUS_OK		0x00
+#define ACPI_EC_SMB_STATUS_FAIL		0x07
+#define ACPI_EC_SMB_STATUS_DNAK		0x10
+#define ACPI_EC_SMB_STATUS_DERR		0x11
+#define ACPI_EC_SMB_STATUS_CMD_DENY	0x12
+#define ACPI_EC_SMB_STATUS_UNKNOWN	0x13
+#define ACPI_EC_SMB_STATUS_ACC_DENY	0x17
+#define ACPI_EC_SMB_STATUS_TIMEOUT	0x18
+#define ACPI_EC_SMB_STATUS_NOTSUP	0x19
+#define ACPI_EC_SMB_STATUS_BUSY		0x1A
+#define ACPI_EC_SMB_STATUS_PEC		0x1F
+
+#define ACPI_EC_SMB_PRTCL_WRITE			0x00
+#define ACPI_EC_SMB_PRTCL_READ			0x01
+#define ACPI_EC_SMB_PRTCL_QUICK			0x02
+#define ACPI_EC_SMB_PRTCL_BYTE			0x04
+#define ACPI_EC_SMB_PRTCL_BYTE_DATA		0x06
+#define ACPI_EC_SMB_PRTCL_WORD_DATA		0x08
+#define ACPI_EC_SMB_PRTCL_BLOCK_DATA		0x0a
+#define ACPI_EC_SMB_PRTCL_PROC_CALL		0x0c
+#define ACPI_EC_SMB_PRTCL_BLOCK_PROC_CALL	0x0d
+#define ACPI_EC_SMB_PRTCL_I2C_BLOCK_DATA	0x4a
+#define ACPI_EC_SMB_PRTCL_PEC			0x80
+
+/* Length of pre/post transaction sleep (msec) */
+#define ACPI_EC_SMB_TRANSACTION_SLEEP		1
+#define ACPI_EC_SMB_ACCESS_SLEEP1		1
+#define ACPI_EC_SMB_ACCESS_SLEEP2		10
+
+static int acpi_ec_smb_read(struct acpi_ec_smbus *smbus, u8 address, u8 * data)
+{
+	u8 val;
+	int err;
+
+	ACPI_FUNCTION_TRACE("acpi_ec_smb_read");
+
+	err = ec_read(smbus->base + address, &val);
+	if (!err) {
+		*data = val;
+	}
+	xmsleep(ACPI_EC_SMB_TRANSACTION_SLEEP);
+	return (err);
+}
+
+static int acpi_ec_smb_write(struct acpi_ec_smbus *smbus, u8 address, u8 data)
+{
+	int err;
+
+	ACPI_FUNCTION_TRACE("acpi_ec_smb_write");
+
+	err = ec_write(smbus->base + address, data);
+	return (err);
+}
+
+static int
+acpi_ec_smb_access(struct i2c_adapter *adap, u16 addr, unsigned short flags,
+		   char read_write, u8 command, int size,
+		   union i2c_smbus_data *data)
+{
+	struct acpi_ec_smbus *smbus = adap->algo_data;
+	unsigned char protocol, len = 0, pec, temp[2] = { 0, 0 };
+	int i;
+
+	ACPI_FUNCTION_TRACE("acpi_ec_smb_access");
+
+	if (read_write == I2C_SMBUS_READ) {
+		protocol = ACPI_EC_SMB_PRTCL_READ;
+	} else {
+		protocol = ACPI_EC_SMB_PRTCL_WRITE;
+	}
+	pec = (flags & I2C_CLIENT_PEC) ? ACPI_EC_SMB_PRTCL_PEC : 0;
+
+	switch (size) {
+
+	case I2C_SMBUS_QUICK:
+		protocol |= ACPI_EC_SMB_PRTCL_QUICK;
+		read_write = I2C_SMBUS_WRITE;
+		break;
+
+	case I2C_SMBUS_BYTE:
+		if (read_write == I2C_SMBUS_WRITE) {
+			acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA, data->byte);
+		}
+		protocol |= ACPI_EC_SMB_PRTCL_BYTE;
+		break;
+
+	case I2C_SMBUS_BYTE_DATA:
+		acpi_ec_smb_write(smbus, ACPI_EC_SMB_CMD, command);
+		if (read_write == I2C_SMBUS_WRITE) {
+			acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA, data->byte);
+		}
+		protocol |= ACPI_EC_SMB_PRTCL_BYTE_DATA;
+		break;
+
+	case I2C_SMBUS_WORD_DATA:
+		acpi_ec_smb_write(smbus, ACPI_EC_SMB_CMD, command);
+		if (read_write == I2C_SMBUS_WRITE) {
+			acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA, data->word);
+			acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA + 1,
+					  data->word >> 8);
+		}
+		protocol |= ACPI_EC_SMB_PRTCL_WORD_DATA | pec;
+		break;
+
+	case I2C_SMBUS_BLOCK_DATA:
+		acpi_ec_smb_write(smbus, ACPI_EC_SMB_CMD, command);
+		if (read_write == I2C_SMBUS_WRITE) {
+			len = min_t(u8, data->block[0], 32);
+			acpi_ec_smb_write(smbus, ACPI_EC_SMB_BCNT, len);
+			for (i = 0; i < len; i++)
+				acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA + i,
+						  data->block[i + 1]);
+		}
+		protocol |= ACPI_EC_SMB_PRTCL_BLOCK_DATA | pec;
+		break;
+
+	case I2C_SMBUS_I2C_BLOCK_DATA:
+		len = min_t(u8, data->block[0], 32);
+		acpi_ec_smb_write(smbus, ACPI_EC_SMB_CMD, command);
+		acpi_ec_smb_write(smbus, ACPI_EC_SMB_BCNT, len);
+		if (read_write == I2C_SMBUS_WRITE) {
+			for (i = 0; i < len; i++) {
+				acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA + i,
+						  data->block[i + 1]);
+			}
+		}
+		protocol |= ACPI_EC_SMB_PRTCL_I2C_BLOCK_DATA;
+		break;
+
+	case I2C_SMBUS_PROC_CALL:
+		acpi_ec_smb_write(smbus, ACPI_EC_SMB_CMD, command);
+		acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA, data->word);
+		acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA + 1, data->word >> 8);
+		protocol = ACPI_EC_SMB_PRTCL_PROC_CALL | pec;
+		read_write = I2C_SMBUS_READ;
+		break;
+
+	case I2C_SMBUS_BLOCK_PROC_CALL:
+		protocol |= pec;
+		len = min_t(u8, data->block[0], 31);
+		acpi_ec_smb_write(smbus, ACPI_EC_SMB_CMD, command);
+		acpi_ec_smb_write(smbus, ACPI_EC_SMB_BCNT, len);
+		for (i = 0; i < len; i++)
+			acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA + i,
+					  data->block[i + 1]);
+		protocol = ACPI_EC_SMB_PRTCL_BLOCK_PROC_CALL | pec;
+		read_write = I2C_SMBUS_READ;
+		break;
+
+	default:
+		ACPI_DEBUG_PRINT((ACPI_DB_WARN, "EC SMBus adapter: "
+				  "Unsupported transaction %d\n", size));
+		return (-1);
+	}
+
+	acpi_ec_smb_write(smbus, ACPI_EC_SMB_ADDR, addr << 1);
+	acpi_ec_smb_write(smbus, ACPI_EC_SMB_PRTCL, protocol);
+
+	acpi_ec_smb_read(smbus, ACPI_EC_SMB_STS, temp + 0);
+
+	if (~temp[0] & ACPI_EC_SMB_STS_DONE) {
+		xudelay(500);
+		acpi_ec_smb_read(smbus, ACPI_EC_SMB_STS, temp + 0);
+	}
+	if (~temp[0] & ACPI_EC_SMB_STS_DONE) {
+		xmsleep(ACPI_EC_SMB_ACCESS_SLEEP2);
+		acpi_ec_smb_read(smbus, ACPI_EC_SMB_STS, temp + 0);
+	}
+	if ((~temp[0] & ACPI_EC_SMB_STS_DONE)
+	    || (temp[0] & ACPI_EC_SMB_STS_STATUS)) {
+		return (-1);
+	}
+
+	if (read_write == I2C_SMBUS_WRITE) {
+		return (0);
+	}
+
+	switch (size) {
+
+	case I2C_SMBUS_BYTE:
+	case I2C_SMBUS_BYTE_DATA:
+		acpi_ec_smb_read(smbus, ACPI_EC_SMB_DATA, &data->byte);
+		break;
+
+	case I2C_SMBUS_WORD_DATA:
+	case I2C_SMBUS_PROC_CALL:
+		acpi_ec_smb_read(smbus, ACPI_EC_SMB_DATA, temp + 0);
+		acpi_ec_smb_read(smbus, ACPI_EC_SMB_DATA + 1, temp + 1);
+		data->word = (temp[1] << 8) | temp[0];
+		break;
+
+	case I2C_SMBUS_BLOCK_DATA:
+	case I2C_SMBUS_BLOCK_PROC_CALL:
+		len = 0;
+		acpi_ec_smb_read(smbus, ACPI_EC_SMB_BCNT, &len);
+		len = min_t(u8, len, 32);
+	case I2C_SMBUS_I2C_BLOCK_DATA:
+		for (i = 0; i < len; i++)
+			acpi_ec_smb_read(smbus, ACPI_EC_SMB_DATA + i,
+					 data->block + i + 1);
+		data->block[0] = len;
+		break;
+	}
+
+	return (0);
+}
+
+static u32 acpi_ec_smb_func(struct i2c_adapter *adapter)
+{
+	ACPI_FUNCTION_TRACE("acpi_ec_smb_func");
+
+	return (I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
+		I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
+		I2C_FUNC_SMBUS_BLOCK_DATA |
+		I2C_FUNC_SMBUS_PROC_CALL |
+		I2C_FUNC_SMBUS_BLOCK_PROC_CALL |
+		I2C_FUNC_SMBUS_I2C_BLOCK | I2C_FUNC_SMBUS_HWPEC_CALC);
+}
+
+static struct i2c_algorithm acpi_ec_smbus_algorithm = {
+	.smbus_xfer = acpi_ec_smb_access,
+	.functionality = acpi_ec_smb_func,
+};
+
+static int acpi_ec_hc_add(struct acpi_device *device)
+{
+	int status;
+	unsigned long val;
+	struct acpi_ec_hc *ec_hc;
+	struct acpi_ec_smbus *smbus;
+
+	ACPI_FUNCTION_TRACE("acpi_ec_hc_add");
+
+	if (!device) {
+		return_VALUE(-EINVAL);
+	}
+
+	ec_hc = kmalloc(sizeof(struct acpi_ec_hc), GFP_KERNEL);
+	if (!ec_hc) {
+		return_VALUE(-ENOMEM);
+	}
+	memset(ec_hc, 0, sizeof(struct acpi_ec_hc));
+
+	smbus = kmalloc(sizeof(struct acpi_ec_smbus), GFP_KERNEL);
+	if (!smbus) {
+		kfree(ec_hc);
+		return_VALUE(-ENOMEM);
+	}
+	memset(smbus, 0, sizeof(struct acpi_ec_smbus));
+
+	ec_hc->handle = device->handle;
+	strcpy(acpi_device_name(device), ACPI_EC_HC_DEVICE_NAME);
+	strcpy(acpi_device_class(device), ACPI_EC_HC_CLASS);
+	acpi_driver_data(device) = ec_hc;
+
+	status = acpi_evaluate_integer(ec_hc->handle, "_EC", NULL, &val);
+	if (ACPI_FAILURE(status)) {
+		ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Error obtaining _EC\n"));
+		kfree(ec_hc->smbus);
+		kfree(smbus);
+		return_VALUE(-EIO);
+	}
+
+	smbus->ec = acpi_driver_data(device->parent);
+	smbus->base = (val & 0xff00ull) >> 8;
+	smbus->alert = val & 0xffull;
+
+	smbus->adapter.owner = THIS_MODULE;
+	smbus->adapter.algo = &acpi_ec_smbus_algorithm;
+	smbus->adapter.algo_data = smbus;
+
+	if (i2c_add_adapter(&smbus->adapter)) {
+		ACPI_DEBUG_PRINT((ACPI_DB_WARN,
+				  "EC SMBus adapter: Failed to register adapter\n"));
+		kfree(smbus);
+		kfree(ec_hc);
+		return_VALUE(-EIO);
+	}
+
+	ec_hc->smbus = smbus;
+
+	printk(KERN_INFO PREFIX "%s [%s]\n",
+	       acpi_device_name(device), acpi_device_bid(device));
+
+	return_VALUE(AE_OK);
+}
+
+static int acpi_ec_hc_remove(struct acpi_device *device, int type)
+{
+	struct acpi_ec_hc *ec_hc;
+
+	ACPI_FUNCTION_TRACE("acpi_ec_hc_remove");
+
+	if (!device) {
+		return_VALUE(-EINVAL);
+	}
+	ec_hc = acpi_driver_data(device);
+
+	i2c_del_adapter(&ec_hc->smbus->adapter);
+	kfree(ec_hc->smbus);
+	kfree(ec_hc);
+
+	return_VALUE(AE_OK);
+}
+
+static int __init acpi_ec_hc_init(void)
+{
+	int result;
+
+	ACPI_FUNCTION_TRACE("acpi_ec_hc_init");
+	result = acpi_bus_register_driver(&acpi_ec_hc_driver);
+	if (result < 0) {
+		return_VALUE(-ENODEV);
+	}
+	return_VALUE(0);
+}
+
+static void __exit acpi_ec_hc_exit(void)
+{
+	ACPI_FUNCTION_TRACE("acpi_ec_hc_exit");
+	acpi_bus_unregister_driver(&acpi_ec_hc_driver);
+}
+
+struct acpi_ec_hc *acpi_get_ec_hc(struct acpi_device *device)
+{
+	ACPI_FUNCTION_TRACE("acpi_get_ec_hc");
+	return ((struct acpi_ec_hc *)acpi_driver_data(device->parent));
+}
+
+EXPORT_SYMBOL(acpi_get_ec_hc);
+
+module_init(acpi_ec_hc_init);
+module_exit(acpi_ec_hc_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Ducrot Bruno");
+MODULE_DESCRIPTION("ACPI EC SMBus driver");
diff --git a/drivers/acpi/i2c_ec.h b/drivers/acpi/i2c_ec.h
new file mode 100644
index 0000000..7c53fb7
--- /dev/null
+++ b/drivers/acpi/i2c_ec.h
@@ -0,0 +1,23 @@
+/*
+ * SMBus driver for ACPI Embedded Controller ($Revision: 1.2 $)
+ *
+ * Copyright (c) 2002, 2005 Ducrot Bruno
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 2.
+ */
+
+struct acpi_ec_smbus {
+	struct i2c_adapter adapter;
+	union acpi_ec *ec;
+	int base;
+	int alert;
+};
+
+struct acpi_ec_hc {
+	acpi_handle handle;
+	struct acpi_ec_smbus *smbus;
+};
+
+struct acpi_ec_hc *acpi_get_ec_hc(struct acpi_device *device);
diff --git a/drivers/acpi/sbs.c b/drivers/acpi/sbs.c
new file mode 100644
index 0000000..8bebceb
--- /dev/null
+++ b/drivers/acpi/sbs.c
@@ -0,0 +1,1828 @@
+/*
+ *  acpi_sbs.c - ACPI Smart Battery System Driver ($Revision: 1.16 $)
+ *
+ *  Copyright (c) 2005 Rich Townsend <rhdt@bartol.udel.edu>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ *  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.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <asm/uaccess.h>
+#include <linux/acpi.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+
+#include "i2c_ec.h"
+
+#define	DEF_CAPACITY_UNIT	3
+#define	MAH_CAPACITY_UNIT	1
+#define	MWH_CAPACITY_UNIT	2
+#define	CAPACITY_UNIT		DEF_CAPACITY_UNIT
+
+#define	REQUEST_UPDATE_MODE	1
+#define	QUEUE_UPDATE_MODE	2
+
+#define	DATA_TYPE_COMMON	0
+#define	DATA_TYPE_INFO		1
+#define	DATA_TYPE_STATE		2
+#define	DATA_TYPE_ALARM		3
+#define	DATA_TYPE_AC_STATE	4
+
+extern struct proc_dir_entry *acpi_lock_ac_dir(void);
+extern struct proc_dir_entry *acpi_lock_battery_dir(void);
+extern void acpi_unlock_ac_dir(struct proc_dir_entry *acpi_ac_dir);
+extern void acpi_unlock_battery_dir(struct proc_dir_entry *acpi_battery_dir);
+
+#define ACPI_SBS_COMPONENT		0x00080000
+#define ACPI_SBS_CLASS			"sbs"
+#define ACPI_AC_CLASS			"ac_adapter"
+#define ACPI_BATTERY_CLASS		"battery"
+#define ACPI_SBS_HID			"ACPI0002"
+#define ACPI_SBS_DRIVER_NAME		"ACPI Smart Battery System Driver"
+#define ACPI_SBS_DEVICE_NAME		"Smart Battery System"
+#define ACPI_SBS_FILE_INFO		"info"
+#define ACPI_SBS_FILE_STATE		"state"
+#define ACPI_SBS_FILE_ALARM		"alarm"
+#define ACPI_BATTERY_DIR_NAME		"BAT%i"
+#define ACPI_AC_DIR_NAME		"AC0"
+#define ACPI_SBC_SMBUS_ADDR		0x9
+#define ACPI_SBSM_SMBUS_ADDR		0xa
+#define ACPI_SB_SMBUS_ADDR		0xb
+#define ACPI_SBS_AC_NOTIFY_STATUS	0x80
+#define ACPI_SBS_BATTERY_NOTIFY_STATUS	0x80
+#define ACPI_SBS_BATTERY_NOTIFY_INFO	0x81
+
+#define _COMPONENT			ACPI_SBS_COMPONENT
+
+#define	MAX_SBS_BAT			4
+#define	MAX_SMBUS_ERR			1
+
+ACPI_MODULE_NAME("acpi_sbs");
+
+MODULE_AUTHOR("Rich Townsend");
+MODULE_DESCRIPTION("Smart Battery System ACPI interface driver");
+MODULE_LICENSE("GPL");
+
+static struct semaphore sbs_sem;
+
+#define	UPDATE_MODE		QUEUE_UPDATE_MODE
+/* REQUEST_UPDATE_MODE  QUEUE_UPDATE_MODE */
+#define	UPDATE_INFO_MODE	0
+#define	UPDATE_TIME		60
+#define	UPDATE_TIME2		0
+
+static int capacity_mode = CAPACITY_UNIT;
+static int update_mode = UPDATE_MODE;
+static int update_info_mode = UPDATE_INFO_MODE;
+static int update_time = UPDATE_TIME;
+static int update_time2 = UPDATE_TIME2;
+
+module_param(capacity_mode, int, CAPACITY_UNIT);
+module_param(update_mode, int, UPDATE_MODE);
+module_param(update_info_mode, int, UPDATE_INFO_MODE);
+module_param(update_time, int, UPDATE_TIME);
+module_param(update_time2, int, UPDATE_TIME2);
+
+static int acpi_sbs_add(struct acpi_device *device);
+static int acpi_sbs_remove(struct acpi_device *device, int type);
+static void acpi_battery_smbus_err_handler(struct acpi_ec_smbus *smbus);
+static void acpi_sbs_update_queue(void *data);
+
+static struct acpi_driver acpi_sbs_driver = {
+	.name = ACPI_SBS_DRIVER_NAME,
+	.class = ACPI_SBS_CLASS,
+	.ids = ACPI_SBS_HID,
+	.ops = {
+		.add = acpi_sbs_add,
+		.remove = acpi_sbs_remove,
+		},
+};
+
+struct acpi_battery_info {
+	int capacity_mode;
+	s16 full_charge_capacity;
+	s16 design_capacity;
+	s16 design_voltage;
+	int vscale;
+	int ipscale;
+	s16 serial_number;
+	char manufacturer_name[I2C_SMBUS_BLOCK_MAX + 3];
+	char device_name[I2C_SMBUS_BLOCK_MAX + 3];
+	char device_chemistry[I2C_SMBUS_BLOCK_MAX + 3];
+};
+
+struct acpi_battery_state {
+	s16 voltage;
+	s16 amperage;
+	s16 remaining_capacity;
+	s16 average_time_to_empty;
+	s16 average_time_to_full;
+	s16 battery_status;
+};
+
+struct acpi_battery_alarm {
+	s16 remaining_capacity;
+};
+
+struct acpi_battery {
+	int alive;
+	int battery_present;
+	int id;
+	int init_state;
+	struct acpi_sbs *sbs;
+	struct acpi_battery_info info;
+	struct acpi_battery_state state;
+	struct acpi_battery_alarm alarm;
+	struct proc_dir_entry *battery_entry;
+};
+
+struct acpi_sbs {
+	acpi_handle handle;
+	struct acpi_device *device;
+	struct acpi_ec_smbus *smbus;
+	int sbsm_present;
+	int sbsm_batteries_supported;
+	int ac_present;
+	struct proc_dir_entry *ac_entry;
+	struct acpi_battery battery[MAX_SBS_BAT];
+	int update_info_mode;
+	int zombie;
+	int update_time;
+	int update_time2;
+	struct timer_list update_timer;
+};
+
+static void acpi_update_delay(struct acpi_sbs *sbs);
+static int acpi_sbs_update_run(struct acpi_sbs *sbs, int data_type);
+
+/* --------------------------------------------------------------------------
+                               SMBus Communication
+   -------------------------------------------------------------------------- */
+
+static void acpi_battery_smbus_err_handler(struct acpi_ec_smbus *smbus)
+{
+	union i2c_smbus_data data;
+	int result = 0;
+	char *err_str;
+	int err_number;
+
+	ACPI_FUNCTION_TRACE("acpi_battery_smbus_err_handler");
+
+	data.word = 0;
+
+	result = smbus->adapter.algo->
+	    smbus_xfer(&smbus->adapter,
+		       ACPI_SB_SMBUS_ADDR,
+		       0, I2C_SMBUS_READ, 0x16, I2C_SMBUS_BLOCK_DATA, &data);
+
+	err_number = (data.word & 0x000f);
+
+	switch (data.word & 0x000f) {
+	case 0x0000:
+		err_str = "unexpected bus error";
+		break;
+	case 0x0001:
+		err_str = "busy";
+		break;
+	case 0x0002:
+		err_str = "reserved command";
+		break;
+	case 0x0003:
+		err_str = "unsupported command";
+		break;
+	case 0x0004:
+		err_str = "access denied";
+		break;
+	case 0x0005:
+		err_str = "overflow/underflow";
+		break;
+	case 0x0006:
+		err_str = "bad size";
+		break;
+	case 0x0007:
+		err_str = "unknown error";
+		break;
+	default:
+		err_str = "unrecognized error";
+	}
+	ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+			  "%s: ret %i, err %i\n", err_str, result, err_number));
+}
+
+static int
+acpi_sbs_smbus_read_word(struct acpi_ec_smbus *smbus, int addr, int func,
+			 u16 * word,
+			 void (*err_handler) (struct acpi_ec_smbus * smbus))
+{
+	union i2c_smbus_data data;
+	int result = 0;
+	int i;
+
+	ACPI_FUNCTION_TRACE("acpi_sbs_smbus_read_word");
+
+	if (err_handler == NULL) {
+		err_handler = acpi_battery_smbus_err_handler;
+	}
+
+	for (i = 0; i < MAX_SMBUS_ERR; i++) {
+		result =
+		    smbus->adapter.algo->smbus_xfer(&smbus->adapter, addr, 0,
+						    I2C_SMBUS_READ, func,
+						    I2C_SMBUS_WORD_DATA, &data);
+		if (result) {
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					  "try %i: smbus->adapter.algo->smbus_xfer() failed\n",
+					  i));
+			if (err_handler) {
+				err_handler(smbus);
+			}
+		} else {
+			*word = data.word;
+			break;
+		}
+	}
+
+	return_VALUE(result);
+}
+
+static int
+acpi_sbs_smbus_read_str(struct acpi_ec_smbus *smbus, int addr, int func,
+			char *str,
+			void (*err_handler) (struct acpi_ec_smbus * smbus))
+{
+	union i2c_smbus_data data;
+	int result = 0;
+	int i;
+
+	ACPI_FUNCTION_TRACE("acpi_sbs_smbus_read_str");
+
+	if (err_handler == NULL) {
+		err_handler = acpi_battery_smbus_err_handler;
+	}
+
+	for (i = 0; i < MAX_SMBUS_ERR; i++) {
+		result =
+		    smbus->adapter.algo->smbus_xfer(&smbus->adapter, addr, 0,
+						    I2C_SMBUS_READ, func,
+						    I2C_SMBUS_BLOCK_DATA,
+						    &data);
+		if (result) {
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					  "try %i: smbus->adapter.algo->smbus_xfer() failed\n",
+					  i));
+			if (err_handler) {
+				err_handler(smbus);
+			}
+		} else {
+			strncpy(str, (const char *)data.block + 1,
+				data.block[0]);
+			str[data.block[0]] = 0;
+			break;
+		}
+	}
+
+	return_VALUE(result);
+}
+
+static int
+acpi_sbs_smbus_write_word(struct acpi_ec_smbus *smbus, int addr, int func,
+			  int word,
+			  void (*err_handler) (struct acpi_ec_smbus * smbus))
+{
+	union i2c_smbus_data data;
+	int result = 0;
+	int i;
+
+	ACPI_FUNCTION_TRACE("acpi_sbs_smbus_write_word");
+
+	if (err_handler == NULL) {
+		err_handler = acpi_battery_smbus_err_handler;
+	}
+
+	data.word = word;
+
+	for (i = 0; i < MAX_SMBUS_ERR; i++) {
+		result =
+		    smbus->adapter.algo->smbus_xfer(&smbus->adapter, addr, 0,
+						    I2C_SMBUS_WRITE, func,
+						    I2C_SMBUS_WORD_DATA, &data);
+		if (result) {
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					  "try %i: smbus->adapter.algo"
+					  "->smbus_xfer() failed\n", i));
+			if (err_handler) {
+				err_handler(smbus);
+			}
+		} else {
+			break;
+		}
+	}
+
+	return_VALUE(result);
+}
+
+/* --------------------------------------------------------------------------
+                            Smart Battery System Management
+   -------------------------------------------------------------------------- */
+
+/* Smart Battery */
+
+static int acpi_sbs_generate_event(struct acpi_device *device,
+				   int event, int state, char *bid, char *class)
+{
+	char bid_saved[5];
+	char class_saved[20];
+	int result = 0;
+
+	ACPI_FUNCTION_TRACE("acpi_sbs_generate_event");
+
+	strcpy(bid_saved, acpi_device_bid(device));
+	strcpy(class_saved, acpi_device_class(device));
+
+	strcpy(acpi_device_bid(device), bid);
+	strcpy(acpi_device_class(device), class);
+
+	result = acpi_bus_generate_event(device, event, state);
+
+	strcpy(acpi_device_bid(device), bid_saved);
+	strcpy(acpi_device_class(device), class_saved);
+
+	return_VALUE(result);
+}
+
+static int acpi_battery_get_present(struct acpi_battery *battery)
+{
+	s16 state;
+	int result = 0;
+	int is_present = 0;
+
+	ACPI_FUNCTION_TRACE("acpi_battery_get_present");
+
+	result = acpi_sbs_smbus_read_word(battery->sbs->smbus,
+					  ACPI_SBSM_SMBUS_ADDR, 0x01,
+					  &state, NULL);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_smbus_read_word() failed"));
+	}
+	if (!result) {
+		is_present = (state & 0x000f) & (1 << battery->id);
+	}
+	battery->battery_present = is_present;
+
+	return_VALUE(result);
+}
+
+static int acpi_battery_is_present(struct acpi_battery *battery)
+{
+	return (battery->battery_present);
+}
+
+static int acpi_ac_is_present(struct acpi_sbs *sbs)
+{
+	return (sbs->ac_present);
+}
+
+static int acpi_battery_select(struct acpi_battery *battery)
+{
+	struct acpi_ec_smbus *smbus = battery->sbs->smbus;
+	int result = 0;
+	s16 state;
+	int foo;
+
+	ACPI_FUNCTION_TRACE("acpi_battery_select");
+
+	if (battery->sbs->sbsm_present) {
+
+		/* Take special care not to knobble other nibbles of
+		 * state (aka selector_state), since
+		 * it causes charging to halt on SBSELs */
+
+		result =
+		    acpi_sbs_smbus_read_word(smbus, ACPI_SBSM_SMBUS_ADDR, 0x01,
+					     &state, NULL);
+		if (result) {
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					  "acpi_sbs_smbus_read_word() failed\n"));
+			goto end;
+		}
+
+		foo = (state & 0x0fff) | (1 << (battery->id + 12));
+		result =
+		    acpi_sbs_smbus_write_word(smbus, ACPI_SBSM_SMBUS_ADDR, 0x01,
+					      foo, NULL);
+		if (result) {
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					  "acpi_sbs_smbus_write_word() failed\n"));
+			goto end;
+		}
+	}
+
+      end:
+	return_VALUE(result);
+}
+
+static int acpi_sbsm_get_info(struct acpi_sbs *sbs)
+{
+	struct acpi_ec_smbus *smbus = sbs->smbus;
+	int result = 0;
+	s16 battery_system_info;
+
+	ACPI_FUNCTION_TRACE("acpi_sbsm_get_info");
+
+	result = acpi_sbs_smbus_read_word(smbus, ACPI_SBSM_SMBUS_ADDR, 0x04,
+					  &battery_system_info, NULL);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_smbus_read_word() failed\n"));
+		goto end;
+	}
+
+	sbs->sbsm_batteries_supported = battery_system_info & 0x000f;
+
+      end:
+
+	return_VALUE(result);
+}
+
+static int acpi_battery_get_info(struct acpi_battery *battery)
+{
+	struct acpi_ec_smbus *smbus = battery->sbs->smbus;
+	int result = 0;
+	s16 battery_mode;
+	s16 specification_info;
+
+	ACPI_FUNCTION_TRACE("acpi_battery_get_info");
+
+	result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x03,
+					  &battery_mode,
+					  &acpi_battery_smbus_err_handler);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_smbus_read_word() failed\n"));
+		goto end;
+	}
+	battery->info.capacity_mode = (battery_mode & 0x8000) >> 15;
+
+	result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x10,
+					  &battery->info.full_charge_capacity,
+					  &acpi_battery_smbus_err_handler);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_smbus_read_word() failed\n"));
+		goto end;
+	}
+
+	result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x18,
+					  &battery->info.design_capacity,
+					  &acpi_battery_smbus_err_handler);
+
+	if (result) {
+		goto end;
+	}
+
+	result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x19,
+					  &battery->info.design_voltage,
+					  &acpi_battery_smbus_err_handler);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_smbus_read_word() failed\n"));
+		goto end;
+	}
+
+	result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x1a,
+					  &specification_info,
+					  &acpi_battery_smbus_err_handler);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_smbus_read_word() failed\n"));
+		goto end;
+	}
+
+	switch ((specification_info & 0x0f00) >> 8) {
+	case 1:
+		battery->info.vscale = 10;
+		break;
+	case 2:
+		battery->info.vscale = 100;
+		break;
+	case 3:
+		battery->info.vscale = 1000;
+		break;
+	default:
+		battery->info.vscale = 1;
+	}
+
+	switch ((specification_info & 0xf000) >> 12) {
+	case 1:
+		battery->info.ipscale = 10;
+		break;
+	case 2:
+		battery->info.ipscale = 100;
+		break;
+	case 3:
+		battery->info.ipscale = 1000;
+		break;
+	default:
+		battery->info.ipscale = 1;
+	}
+
+	result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x1c,
+					  &battery->info.serial_number,
+					  &acpi_battery_smbus_err_handler);
+	if (result) {
+		goto end;
+	}
+
+	result = acpi_sbs_smbus_read_str(smbus, ACPI_SB_SMBUS_ADDR, 0x20,
+					 battery->info.manufacturer_name,
+					 &acpi_battery_smbus_err_handler);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_smbus_read_str() failed\n"));
+		goto end;
+	}
+
+	result = acpi_sbs_smbus_read_str(smbus, ACPI_SB_SMBUS_ADDR, 0x21,
+					 battery->info.device_name,
+					 &acpi_battery_smbus_err_handler);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_smbus_read_str() failed\n"));
+		goto end;
+	}
+
+	result = acpi_sbs_smbus_read_str(smbus, ACPI_SB_SMBUS_ADDR, 0x22,
+					 battery->info.device_chemistry,
+					 &acpi_battery_smbus_err_handler);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_smbus_read_str() failed\n"));
+		goto end;
+	}
+
+      end:
+	return_VALUE(result);
+}
+
+static void acpi_update_delay(struct acpi_sbs *sbs)
+{
+	ACPI_FUNCTION_TRACE("acpi_update_delay");
+	if (sbs->zombie) {
+		return;
+	}
+	if (sbs->update_time2 > 0) {
+		msleep(sbs->update_time2 * 1000);
+	}
+}
+
+static int acpi_battery_get_state(struct acpi_battery *battery)
+{
+	struct acpi_ec_smbus *smbus = battery->sbs->smbus;
+	int result = 0;
+
+	ACPI_FUNCTION_TRACE("acpi_battery_get_state");
+
+	acpi_update_delay(battery->sbs);
+	result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x09,
+					  &battery->state.voltage,
+					  &acpi_battery_smbus_err_handler);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_smbus_read_word() failed\n"));
+		goto end;
+	}
+
+	acpi_update_delay(battery->sbs);
+	result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x0a,
+					  &battery->state.amperage,
+					  &acpi_battery_smbus_err_handler);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_smbus_read_word() failed\n"));
+		goto end;
+	}
+
+	acpi_update_delay(battery->sbs);
+	result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x0f,
+					  &battery->state.remaining_capacity,
+					  &acpi_battery_smbus_err_handler);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_smbus_read_word() failed\n"));
+		goto end;
+	}
+
+	acpi_update_delay(battery->sbs);
+	result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x12,
+					  &battery->state.average_time_to_empty,
+					  &acpi_battery_smbus_err_handler);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_smbus_read_word() failed\n"));
+		goto end;
+	}
+
+	acpi_update_delay(battery->sbs);
+	result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x13,
+					  &battery->state.average_time_to_full,
+					  &acpi_battery_smbus_err_handler);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_smbus_read_word() failed\n"));
+		goto end;
+	}
+
+	acpi_update_delay(battery->sbs);
+	result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x16,
+					  &battery->state.battery_status,
+					  &acpi_battery_smbus_err_handler);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_smbus_read_word() failed\n"));
+		goto end;
+	}
+
+	acpi_update_delay(battery->sbs);
+
+      end:
+	return_VALUE(result);
+}
+
+static int acpi_battery_get_alarm(struct acpi_battery *battery)
+{
+	struct acpi_ec_smbus *smbus = battery->sbs->smbus;
+	int result = 0;
+
+	ACPI_FUNCTION_TRACE("acpi_battery_get_alarm");
+
+	result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x01,
+					  &battery->alarm.remaining_capacity,
+					  &acpi_battery_smbus_err_handler);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_smbus_read_word() failed\n"));
+		goto end;
+	}
+
+	acpi_update_delay(battery->sbs);
+
+      end:
+
+	return_VALUE(result);
+}
+
+static int acpi_battery_set_alarm(struct acpi_battery *battery,
+				  unsigned long alarm)
+{
+	struct acpi_ec_smbus *smbus = battery->sbs->smbus;
+	int result = 0;
+	s16 battery_mode;
+	int foo;
+
+	ACPI_FUNCTION_TRACE("acpi_battery_set_alarm");
+
+	result = acpi_battery_select(battery);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_battery_select() failed\n"));
+		goto end;
+	}
+
+	/* If necessary, enable the alarm */
+
+	if (alarm > 0) {
+		result =
+		    acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x03,
+					     &battery_mode,
+					     &acpi_battery_smbus_err_handler);
+		if (result) {
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					  "acpi_sbs_smbus_read_word() failed\n"));
+			goto end;
+		}
+
+		result =
+		    acpi_sbs_smbus_write_word(smbus, ACPI_SB_SMBUS_ADDR, 0x01,
+					      battery_mode & 0xbfff,
+					      &acpi_battery_smbus_err_handler);
+		if (result) {
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					  "acpi_sbs_smbus_write_word() failed\n"));
+			goto end;
+		}
+	}
+
+	foo = alarm / (battery->info.capacity_mode ? 10 : 1);
+	result = acpi_sbs_smbus_write_word(smbus, ACPI_SB_SMBUS_ADDR, 0x01,
+					   foo,
+					   &acpi_battery_smbus_err_handler);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_smbus_write_word() failed\n"));
+		goto end;
+	}
+
+      end:
+
+	return_VALUE(result);
+}
+
+static int acpi_battery_set_mode(struct acpi_battery *battery)
+{
+	int result = 0;
+	s16 battery_mode;
+
+	ACPI_FUNCTION_TRACE("acpi_battery_set_mode");
+
+	if (capacity_mode == DEF_CAPACITY_UNIT) {
+		goto end;
+	}
+
+	result = acpi_sbs_smbus_read_word(battery->sbs->smbus,
+					  ACPI_SB_SMBUS_ADDR, 0x03,
+					  &battery_mode, NULL);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_smbus_read_word() failed\n"));
+		goto end;
+	}
+
+	if (capacity_mode == MAH_CAPACITY_UNIT) {
+		battery_mode &= 0x7fff;
+	} else {
+		battery_mode |= 0x8000;
+	}
+	result = acpi_sbs_smbus_write_word(battery->sbs->smbus,
+					   ACPI_SB_SMBUS_ADDR, 0x03,
+					   battery_mode, NULL);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_smbus_write_word() failed\n"));
+		goto end;
+	}
+
+	result = acpi_sbs_smbus_read_word(battery->sbs->smbus,
+					  ACPI_SB_SMBUS_ADDR, 0x03,
+					  &battery_mode, NULL);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_smbus_read_word() failed\n"));
+		goto end;
+	}
+
+      end:
+	return_VALUE(result);
+}
+
+static int acpi_battery_init(struct acpi_battery *battery)
+{
+	int result = 0;
+
+	ACPI_FUNCTION_TRACE("acpi_battery_init");
+
+	result = acpi_battery_select(battery);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_battery_init() failed\n"));
+		goto end;
+	}
+
+	result = acpi_battery_set_mode(battery);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_battery_set_mode() failed\n"));
+		goto end;
+	}
+
+	result = acpi_battery_get_info(battery);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_battery_get_info() failed\n"));
+		goto end;
+	}
+
+	result = acpi_battery_get_state(battery);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_battery_get_state() failed\n"));
+		goto end;
+	}
+
+	result = acpi_battery_get_alarm(battery);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_battery_get_alarm() failed\n"));
+		goto end;
+	}
+
+      end:
+	return_VALUE(result);
+}
+
+static int acpi_ac_get_present(struct acpi_sbs *sbs)
+{
+	struct acpi_ec_smbus *smbus = sbs->smbus;
+	int result = 0;
+	s16 charger_status;
+
+	ACPI_FUNCTION_TRACE("acpi_ac_get_present");
+
+	result = acpi_sbs_smbus_read_word(smbus, ACPI_SBC_SMBUS_ADDR, 0x13,
+					  &charger_status, NULL);
+
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_smbus_read_word() failed\n"));
+		goto end;
+	}
+
+	sbs->ac_present = (charger_status & 0x8000) >> 15;
+
+      end:
+
+	return_VALUE(result);
+}
+
+/* --------------------------------------------------------------------------
+                              FS Interface (/proc/acpi)
+   -------------------------------------------------------------------------- */
+
+/* Generic Routines */
+
+static int
+acpi_sbs_generic_add_fs(struct proc_dir_entry **dir,
+			struct proc_dir_entry *parent_dir,
+			char *dir_name,
+			struct file_operations *info_fops,
+			struct file_operations *state_fops,
+			struct file_operations *alarm_fops, void *data)
+{
+	struct proc_dir_entry *entry = NULL;
+
+	ACPI_FUNCTION_TRACE("acpi_sbs_generic_add_fs");
+
+	if (!*dir) {
+		*dir = proc_mkdir(dir_name, parent_dir);
+		if (!*dir) {
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					  "proc_mkdir() failed\n"));
+			return_VALUE(-ENODEV);
+		}
+		(*dir)->owner = THIS_MODULE;
+	}
+
+	/* 'info' [R] */
+	if (info_fops) {
+		entry = create_proc_entry(ACPI_SBS_FILE_INFO, S_IRUGO, *dir);
+		if (!entry) {
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					  "create_proc_entry() failed\n"));
+		} else {
+			entry->proc_fops = info_fops;
+			entry->data = data;
+			entry->owner = THIS_MODULE;
+		}
+	}
+
+	/* 'state' [R] */
+	if (state_fops) {
+		entry = create_proc_entry(ACPI_SBS_FILE_STATE, S_IRUGO, *dir);
+		if (!entry) {
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					  "create_proc_entry() failed\n"));
+		} else {
+			entry->proc_fops = state_fops;
+			entry->data = data;
+			entry->owner = THIS_MODULE;
+		}
+	}
+
+	/* 'alarm' [R/W] */
+	if (alarm_fops) {
+		entry = create_proc_entry(ACPI_SBS_FILE_ALARM, S_IRUGO, *dir);
+		if (!entry) {
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					  "create_proc_entry() failed\n"));
+		} else {
+			entry->proc_fops = alarm_fops;
+			entry->data = data;
+			entry->owner = THIS_MODULE;
+		}
+	}
+
+	return_VALUE(0);
+}
+
+static void
+acpi_sbs_generic_remove_fs(struct proc_dir_entry **dir,
+			   struct proc_dir_entry *parent_dir)
+{
+	ACPI_FUNCTION_TRACE("acpi_sbs_generic_remove_fs");
+
+	if (*dir) {
+		remove_proc_entry(ACPI_SBS_FILE_INFO, *dir);
+		remove_proc_entry(ACPI_SBS_FILE_STATE, *dir);
+		remove_proc_entry(ACPI_SBS_FILE_ALARM, *dir);
+		remove_proc_entry((*dir)->name, parent_dir);
+		*dir = NULL;
+	}
+
+}
+
+/* Smart Battery Interface */
+
+static struct proc_dir_entry *acpi_battery_dir = NULL;
+
+static int acpi_battery_read_info(struct seq_file *seq, void *offset)
+{
+	struct acpi_battery *battery = (struct acpi_battery *)seq->private;
+	int cscale;
+	int result = 0;
+
+	ACPI_FUNCTION_TRACE("acpi_battery_read_info");
+
+	if (battery->sbs->zombie) {
+		return_VALUE(-ENODEV);
+	}
+
+	down(&sbs_sem);
+
+	if (update_mode == REQUEST_UPDATE_MODE) {
+		result = acpi_sbs_update_run(battery->sbs, DATA_TYPE_INFO);
+		if (result) {
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					  "acpi_sbs_update_run() failed\n"));
+		}
+	}
+
+	if (acpi_battery_is_present(battery)) {
+		seq_printf(seq, "present:                 yes\n");
+	} else {
+		seq_printf(seq, "present:                 no\n");
+		goto end;
+	}
+
+	if (battery->info.capacity_mode) {
+		cscale = battery->info.vscale * battery->info.ipscale;
+	} else {
+		cscale = battery->info.ipscale;
+	}
+	seq_printf(seq, "design capacity:         %i%s",
+		   battery->info.design_capacity * cscale,
+		   battery->info.capacity_mode ? "0 mWh\n" : " mAh\n");
+
+	seq_printf(seq, "last full capacity:      %i%s",
+		   battery->info.full_charge_capacity * cscale,
+		   battery->info.capacity_mode ? "0 mWh\n" : " mAh\n");
+
+	seq_printf(seq, "battery technology:      rechargeable\n");
+
+	seq_printf(seq, "design voltage:          %i mV\n",
+		   battery->info.design_voltage * battery->info.vscale);
+
+	seq_printf(seq, "design capacity warning: unknown\n");
+	seq_printf(seq, "design capacity low:     unknown\n");
+	seq_printf(seq, "capacity granularity 1:  unknown\n");
+	seq_printf(seq, "capacity granularity 2:  unknown\n");
+
+	seq_printf(seq, "model number:            %s\n",
+		   battery->info.device_name);
+
+	seq_printf(seq, "serial number:           %i\n",
+		   battery->info.serial_number);
+
+	seq_printf(seq, "battery type:            %s\n",
+		   battery->info.device_chemistry);
+
+	seq_printf(seq, "OEM info:                %s\n",
+		   battery->info.manufacturer_name);
+
+      end:
+
+	up(&sbs_sem);
+
+	return_VALUE(result);
+}
+
+static int acpi_battery_info_open_fs(struct inode *inode, struct file *file)
+{
+	return single_open(file, acpi_battery_read_info, PDE(inode)->data);
+}
+
+static int acpi_battery_read_state(struct seq_file *seq, void *offset)
+{
+	struct acpi_battery *battery = (struct acpi_battery *)seq->private;
+	int result = 0;
+	int cscale;
+	int foo;
+
+	ACPI_FUNCTION_TRACE("acpi_battery_read_state");
+
+	if (battery->sbs->zombie) {
+		return_VALUE(-ENODEV);
+	}
+
+	down(&sbs_sem);
+
+	if (update_mode == REQUEST_UPDATE_MODE) {
+		result = acpi_sbs_update_run(battery->sbs, DATA_TYPE_STATE);
+		if (result) {
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					  "acpi_sbs_update_run() failed\n"));
+		}
+	}
+
+	if (acpi_battery_is_present(battery)) {
+		seq_printf(seq, "present:                 yes\n");
+	} else {
+		seq_printf(seq, "present:                 no\n");
+		goto end;
+	}
+
+	if (battery->info.capacity_mode) {
+		cscale = battery->info.vscale * battery->info.ipscale;
+	} else {
+		cscale = battery->info.ipscale;
+	}
+
+	if (battery->state.battery_status & 0x0010) {
+		seq_printf(seq, "capacity state:          critical\n");
+	} else {
+		seq_printf(seq, "capacity state:          ok\n");
+	}
+	if (battery->state.amperage < 0) {
+		seq_printf(seq, "charging state:          discharging\n");
+		foo = battery->state.remaining_capacity * cscale * 60 /
+		    (battery->state.average_time_to_empty == 0 ? 1 :
+		     battery->state.average_time_to_empty);
+		seq_printf(seq, "present rate:            %i%s\n",
+			   foo, battery->info.capacity_mode ? "0 mW" : " mA");
+	} else if (battery->state.amperage > 0) {
+		seq_printf(seq, "charging state:          charging\n");
+		foo = (battery->info.full_charge_capacity -
+		       battery->state.remaining_capacity) * cscale * 60 /
+		    (battery->state.average_time_to_full == 0 ? 1 :
+		     battery->state.average_time_to_full);
+		seq_printf(seq, "present rate:            %i%s\n",
+			   foo, battery->info.capacity_mode ? "0 mW" : " mA");
+	} else {
+		seq_printf(seq, "charging state:          charged\n");
+		seq_printf(seq, "present rate:            0 %s\n",
+			   battery->info.capacity_mode ? "mW" : "mA");
+	}
+
+	seq_printf(seq, "remaining capacity:      %i%s",
+		   battery->state.remaining_capacity * cscale,
+		   battery->info.capacity_mode ? "0 mWh\n" : " mAh\n");
+
+	seq_printf(seq, "present voltage:         %i mV\n",
+		   battery->state.voltage * battery->info.vscale);
+
+      end:
+
+	up(&sbs_sem);
+
+	return_VALUE(result);
+}
+
+static int acpi_battery_state_open_fs(struct inode *inode, struct file *file)
+{
+	return single_open(file, acpi_battery_read_state, PDE(inode)->data);
+}
+
+static int acpi_battery_read_alarm(struct seq_file *seq, void *offset)
+{
+	struct acpi_battery *battery = (struct acpi_battery *)seq->private;
+	int result = 0;
+	int cscale;
+
+	ACPI_FUNCTION_TRACE("acpi_battery_read_alarm");
+
+	if (battery->sbs->zombie) {
+		return_VALUE(-ENODEV);
+	}
+
+	down(&sbs_sem);
+
+	if (update_mode == REQUEST_UPDATE_MODE) {
+		result = acpi_sbs_update_run(battery->sbs, DATA_TYPE_ALARM);
+		if (result) {
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					  "acpi_sbs_update_run() failed\n"));
+		}
+	}
+
+	if (!acpi_battery_is_present(battery)) {
+		seq_printf(seq, "present:                 no\n");
+		goto end;
+	}
+
+	if (battery->info.capacity_mode) {
+		cscale = battery->info.vscale * battery->info.ipscale;
+	} else {
+		cscale = battery->info.ipscale;
+	}
+
+	seq_printf(seq, "alarm:                   ");
+	if (battery->alarm.remaining_capacity) {
+		seq_printf(seq, "%i%s",
+			   battery->alarm.remaining_capacity * cscale,
+			   battery->info.capacity_mode ? "0 mWh\n" : " mAh\n");
+	} else {
+		seq_printf(seq, "disabled\n");
+	}
+
+      end:
+
+	up(&sbs_sem);
+
+	return_VALUE(result);
+}
+
+static ssize_t
+acpi_battery_write_alarm(struct file *file, const char __user * buffer,
+			 size_t count, loff_t * ppos)
+{
+	struct seq_file *seq = (struct seq_file *)file->private_data;
+	struct acpi_battery *battery = (struct acpi_battery *)seq->private;
+	char alarm_string[12] = { '\0' };
+	int result, old_alarm, new_alarm;
+
+	ACPI_FUNCTION_TRACE("acpi_battery_write_alarm");
+
+	if (battery->sbs->zombie) {
+		return_VALUE(-ENODEV);
+	}
+
+	down(&sbs_sem);
+
+	if (!acpi_battery_is_present(battery)) {
+		result = -ENODEV;
+		goto end;
+	}
+
+	if (count > sizeof(alarm_string) - 1) {
+		result = -EINVAL;
+		goto end;
+	}
+
+	if (copy_from_user(alarm_string, buffer, count)) {
+		result = -EFAULT;
+		goto end;
+	}
+
+	alarm_string[count] = 0;
+
+	old_alarm = battery->alarm.remaining_capacity;
+	new_alarm = simple_strtoul(alarm_string, NULL, 0);
+
+	result = acpi_battery_set_alarm(battery, new_alarm);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_battery_set_alarm() failed\n"));
+		(void)acpi_battery_set_alarm(battery, old_alarm);
+		goto end;
+	}
+	result = acpi_battery_get_alarm(battery);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_battery_get_alarm() failed\n"));
+		(void)acpi_battery_set_alarm(battery, old_alarm);
+		goto end;
+	}
+
+      end:
+	up(&sbs_sem);
+
+	if (result) {
+		return_VALUE(result);
+	} else {
+		return_VALUE(count);
+	}
+}
+
+static int acpi_battery_alarm_open_fs(struct inode *inode, struct file *file)
+{
+	return single_open(file, acpi_battery_read_alarm, PDE(inode)->data);
+}
+
+static struct file_operations acpi_battery_info_fops = {
+	.open = acpi_battery_info_open_fs,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+	.owner = THIS_MODULE,
+};
+
+static struct file_operations acpi_battery_state_fops = {
+	.open = acpi_battery_state_open_fs,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+	.owner = THIS_MODULE,
+};
+
+static struct file_operations acpi_battery_alarm_fops = {
+	.open = acpi_battery_alarm_open_fs,
+	.read = seq_read,
+	.write = acpi_battery_write_alarm,
+	.llseek = seq_lseek,
+	.release = single_release,
+	.owner = THIS_MODULE,
+};
+
+/* Legacy AC Adapter Interface */
+
+static struct proc_dir_entry *acpi_ac_dir = NULL;
+
+static int acpi_ac_read_state(struct seq_file *seq, void *offset)
+{
+	struct acpi_sbs *sbs = (struct acpi_sbs *)seq->private;
+	int result;
+
+	ACPI_FUNCTION_TRACE("acpi_ac_read_state");
+
+	if (sbs->zombie) {
+		return_VALUE(-ENODEV);
+	}
+
+	down(&sbs_sem);
+
+	if (update_mode == REQUEST_UPDATE_MODE) {
+		result = acpi_sbs_update_run(sbs, DATA_TYPE_AC_STATE);
+		if (result) {
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					  "acpi_sbs_update_run() failed\n"));
+		}
+	}
+
+	seq_printf(seq, "state:                   %s\n",
+		   sbs->ac_present ? "on-line" : "off-line");
+
+	up(&sbs_sem);
+
+	return_VALUE(0);
+}
+
+static int acpi_ac_state_open_fs(struct inode *inode, struct file *file)
+{
+	return single_open(file, acpi_ac_read_state, PDE(inode)->data);
+}
+
+static struct file_operations acpi_ac_state_fops = {
+	.open = acpi_ac_state_open_fs,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+	.owner = THIS_MODULE,
+};
+
+/* --------------------------------------------------------------------------
+                                 Driver Interface
+   -------------------------------------------------------------------------- */
+
+/* Smart Battery */
+
+static int acpi_battery_add(struct acpi_sbs *sbs, int id)
+{
+	int is_present;
+	int result;
+	char dir_name[32];
+	struct acpi_battery *battery;
+
+	ACPI_FUNCTION_TRACE("acpi_battery_add");
+
+	battery = &sbs->battery[id];
+
+	battery->alive = 0;
+
+	battery->init_state = 0;
+	battery->id = id;
+	battery->sbs = sbs;
+
+	result = acpi_battery_select(battery);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_battery_select() failed\n"));
+		goto end;
+	}
+
+	result = acpi_battery_get_present(battery);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_battery_get_present() failed\n"));
+		goto end;
+	}
+
+	is_present = acpi_battery_is_present(battery);
+
+	if (is_present) {
+		result = acpi_battery_init(battery);
+		if (result) {
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					  "acpi_battery_init() failed\n"));
+			goto end;
+		}
+		battery->init_state = 1;
+	}
+
+	(void)sprintf(dir_name, ACPI_BATTERY_DIR_NAME, id);
+
+	result = acpi_sbs_generic_add_fs(&battery->battery_entry,
+					 acpi_battery_dir,
+					 dir_name,
+					 &acpi_battery_info_fops,
+					 &acpi_battery_state_fops,
+					 &acpi_battery_alarm_fops, battery);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_generic_add_fs() failed\n"));
+		goto end;
+	}
+	battery->alive = 1;
+
+      end:
+	return_VALUE(result);
+}
+
+static void acpi_battery_remove(struct acpi_sbs *sbs, int id)
+{
+	ACPI_FUNCTION_TRACE("acpi_battery_remove");
+
+	if (sbs->battery[id].battery_entry) {
+		acpi_sbs_generic_remove_fs(&(sbs->battery[id].battery_entry),
+					   acpi_battery_dir);
+	}
+}
+
+static int acpi_ac_add(struct acpi_sbs *sbs)
+{
+	int result;
+
+	ACPI_FUNCTION_TRACE("acpi_ac_add");
+
+	result = acpi_ac_get_present(sbs);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_ac_get_present() failed\n"));
+		goto end;
+	}
+
+	result = acpi_sbs_generic_add_fs(&sbs->ac_entry,
+					 acpi_ac_dir,
+					 ACPI_AC_DIR_NAME,
+					 NULL, &acpi_ac_state_fops, NULL, sbs);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_generic_add_fs() failed\n"));
+		goto end;
+	}
+
+      end:
+
+	return_VALUE(result);
+}
+
+static void acpi_ac_remove(struct acpi_sbs *sbs)
+{
+	ACPI_FUNCTION_TRACE("acpi_ac_remove");
+
+	if (sbs->ac_entry) {
+		acpi_sbs_generic_remove_fs(&sbs->ac_entry, acpi_ac_dir);
+	}
+}
+
+static void acpi_sbs_update_queue_run(unsigned long data)
+{
+	ACPI_FUNCTION_TRACE("acpi_sbs_update_queue_run");
+	acpi_os_execute(OSL_GPE_HANDLER, acpi_sbs_update_queue, (void *)data);
+}
+
+static int acpi_sbs_update_run(struct acpi_sbs *sbs, int data_type)
+{
+	struct acpi_battery *battery;
+	int result = 0;
+	int old_ac_present;
+	int old_battery_present;
+	int new_ac_present;
+	int new_battery_present;
+	int id;
+	char dir_name[32];
+	int do_battery_init, do_ac_init;
+	s16 old_remaining_capacity;
+
+	ACPI_FUNCTION_TRACE("acpi_sbs_update_run");
+
+	if (sbs->zombie) {
+		goto end;
+	}
+
+	old_ac_present = acpi_ac_is_present(sbs);
+
+	result = acpi_ac_get_present(sbs);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_ac_get_present() failed\n"));
+	}
+
+	new_ac_present = acpi_ac_is_present(sbs);
+
+	do_ac_init = (old_ac_present != new_ac_present);
+
+	if (data_type == DATA_TYPE_AC_STATE) {
+		goto end;
+	}
+
+	for (id = 0; id < MAX_SBS_BAT; id++) {
+		battery = &sbs->battery[id];
+		if (battery->alive == 0) {
+			continue;
+		}
+
+		old_remaining_capacity = battery->state.remaining_capacity;
+
+		old_battery_present = acpi_battery_is_present(battery);
+
+		result = acpi_battery_select(battery);
+		if (result) {
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					  "acpi_battery_select() failed\n"));
+		}
+		if (sbs->zombie) {
+			goto end;
+		}
+
+		result = acpi_battery_get_present(battery);
+		if (result) {
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					  "acpi_battery_get_present() failed\n"));
+		}
+		if (sbs->zombie) {
+			goto end;
+		}
+
+		new_battery_present = acpi_battery_is_present(battery);
+
+		do_battery_init = ((old_battery_present != new_battery_present)
+				   && new_battery_present);
+
+		if (sbs->zombie) {
+			goto end;
+		}
+		if (do_ac_init || do_battery_init ||
+		    update_info_mode || sbs->update_info_mode) {
+			if (sbs->update_info_mode) {
+				sbs->update_info_mode = 0;
+			} else {
+				sbs->update_info_mode = 1;
+			}
+			result = acpi_battery_init(battery);
+			if (result) {
+				ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+						  "acpi_battery_init() "
+						  "failed\n"));
+			}
+		}
+		if (data_type == DATA_TYPE_INFO) {
+			continue;
+		}
+
+		if (sbs->zombie) {
+			goto end;
+		}
+		if (new_battery_present) {
+			result = acpi_battery_get_alarm(battery);
+			if (result) {
+				ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+						  "acpi_battery_get_alarm() "
+						  "failed\n"));
+			}
+			if (data_type == DATA_TYPE_ALARM) {
+				continue;
+			}
+
+			result = acpi_battery_get_state(battery);
+			if (result) {
+				ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+						  "acpi_battery_get_state() "
+						  "failed\n"));
+			}
+		}
+		if (sbs->zombie) {
+			goto end;
+		}
+		if (data_type != DATA_TYPE_COMMON) {
+			continue;
+		}
+
+		if (old_battery_present != new_battery_present) {
+			(void)sprintf(dir_name, ACPI_BATTERY_DIR_NAME, id);
+			result = acpi_sbs_generate_event(sbs->device,
+							 ACPI_SBS_BATTERY_NOTIFY_STATUS,
+							 new_battery_present,
+							 dir_name,
+							 ACPI_BATTERY_CLASS);
+			if (result) {
+				ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+						  "acpi_sbs_generate_event() "
+						  "failed\n"));
+			}
+		}
+		if (old_remaining_capacity != battery->state.remaining_capacity) {
+			(void)sprintf(dir_name, ACPI_BATTERY_DIR_NAME, id);
+			result = acpi_sbs_generate_event(sbs->device,
+							 ACPI_SBS_BATTERY_NOTIFY_STATUS,
+							 new_battery_present,
+							 dir_name,
+							 ACPI_BATTERY_CLASS);
+			if (result) {
+				ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+						  "acpi_sbs_generate_event() failed\n"));
+			}
+		}
+
+	}
+	if (sbs->zombie) {
+		goto end;
+	}
+	if (data_type != DATA_TYPE_COMMON) {
+		goto end;
+	}
+
+	if (old_ac_present != new_ac_present) {
+		result = acpi_sbs_generate_event(sbs->device,
+						 ACPI_SBS_AC_NOTIFY_STATUS,
+						 new_ac_present,
+						 ACPI_AC_DIR_NAME,
+						 ACPI_AC_CLASS);
+		if (result) {
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					  "acpi_sbs_generate_event() failed\n"));
+		}
+	}
+
+      end:
+	return_VALUE(result);
+}
+
+static void acpi_sbs_update_queue(void *data)
+{
+	struct acpi_sbs *sbs = data;
+	unsigned long delay = -1;
+	int result;
+
+	ACPI_FUNCTION_TRACE("acpi_sbs_update_queue");
+
+	if (sbs->zombie) {
+		goto end;
+	}
+
+	result = acpi_sbs_update_run(sbs, DATA_TYPE_COMMON);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_sbs_update_run() failed\n"));
+	}
+
+	if (sbs->zombie) {
+		goto end;
+	}
+
+	if (update_mode == REQUEST_UPDATE_MODE) {
+		goto end;
+	}
+
+	delay = jiffies + HZ * update_time;
+	sbs->update_timer.data = (unsigned long)data;
+	sbs->update_timer.function = acpi_sbs_update_queue_run;
+	sbs->update_timer.expires = delay;
+	add_timer(&sbs->update_timer);
+      end:
+	;
+}
+
+static int acpi_sbs_add(struct acpi_device *device)
+{
+	struct acpi_sbs *sbs = NULL;
+	struct acpi_ec_hc *ec_hc = NULL;
+	int result, remove_result = 0;
+	unsigned long sbs_obj;
+	int id, cnt;
+	acpi_status status = AE_OK;
+
+	ACPI_FUNCTION_TRACE("acpi_sbs_add");
+
+	sbs = kmalloc(sizeof(struct acpi_sbs), GFP_KERNEL);
+	if (!sbs) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "kmalloc() failed\n"));
+		return_VALUE(-ENOMEM);
+	}
+	memset(sbs, 0, sizeof(struct acpi_sbs));
+
+	cnt = 0;
+	while (cnt < 10) {
+		cnt++;
+		ec_hc = acpi_get_ec_hc(device);
+		if (ec_hc) {
+			break;
+		}
+		msleep(1000);
+	}
+
+	if (!ec_hc) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_get_ec_hc() failed: "
+				  "NO driver found for EC HC SMBus\n"));
+		result = -ENODEV;
+		goto end;
+	}
+
+	sbs->device = device;
+	sbs->smbus = ec_hc->smbus;
+
+	strcpy(acpi_device_name(device), ACPI_SBS_DEVICE_NAME);
+	strcpy(acpi_device_class(device), ACPI_SBS_CLASS);
+	acpi_driver_data(device) = sbs;
+
+	sbs->update_time = 0;
+	sbs->update_time2 = 0;
+
+	result = acpi_ac_add(sbs);
+	if (result) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "acpi_ac_add() failed\n"));
+		goto end;
+	}
+	result = acpi_evaluate_integer(device->handle, "_SBS", NULL, &sbs_obj);
+	if (ACPI_FAILURE(result)) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_evaluate_integer() failed\n"));
+		result = -EIO;
+		goto end;
+	}
+
+	if (sbs_obj > 0) {
+		result = acpi_sbsm_get_info(sbs);
+		if (result) {
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					  "acpi_sbsm_get_info() failed\n"));
+			goto end;
+		}
+		sbs->sbsm_present = 1;
+	}
+	if (sbs->sbsm_present == 0) {
+		result = acpi_battery_add(sbs, 0);
+		if (result) {
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					  "acpi_battery_add() failed\n"));
+			goto end;
+		}
+	} else {
+		for (id = 0; id < MAX_SBS_BAT; id++) {
+			if ((sbs->sbsm_batteries_supported & (1 << id))) {
+				result = acpi_battery_add(sbs, id);
+				if (result) {
+					ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+							  "acpi_battery_add() "
+							  "failed\n"));
+					goto end;
+				}
+			}
+		}
+	}
+
+	sbs->handle = device->handle;
+
+	init_timer(&sbs->update_timer);
+	if (update_mode == QUEUE_UPDATE_MODE) {
+		status = acpi_os_execute(OSL_GPE_HANDLER,
+					 acpi_sbs_update_queue, (void *)sbs);
+		if (status != AE_OK) {
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					  "acpi_os_execute() failed\n"));
+		}
+	}
+	sbs->update_time = update_time;
+	sbs->update_time2 = update_time2;
+
+	printk(KERN_INFO PREFIX "%s [%s]\n",
+	       acpi_device_name(device), acpi_device_bid(device));
+
+      end:
+	if (result) {
+		remove_result = acpi_sbs_remove(device, 0);
+		if (remove_result) {
+			ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+					  "acpi_sbs_remove() failed\n"));
+		}
+	}
+
+	return_VALUE(result);
+}
+
+int acpi_sbs_remove(struct acpi_device *device, int type)
+{
+	struct acpi_sbs *sbs = (struct acpi_sbs *)acpi_driver_data(device);
+	int id;
+
+	ACPI_FUNCTION_TRACE("acpi_sbs_remove");
+
+	if (!device || !sbs) {
+		return_VALUE(-EINVAL);
+	}
+
+	sbs->zombie = 1;
+	sbs->update_time = 0;
+	sbs->update_time2 = 0;
+	del_timer_sync(&sbs->update_timer);
+	acpi_os_wait_events_complete(NULL);
+	del_timer_sync(&sbs->update_timer);
+
+	for (id = 0; id < MAX_SBS_BAT; id++) {
+		acpi_battery_remove(sbs, id);
+	}
+
+	acpi_ac_remove(sbs);
+
+	kfree(sbs);
+
+	return_VALUE(0);
+}
+
+static int __init acpi_sbs_init(void)
+{
+	int result = 0;
+
+	ACPI_FUNCTION_TRACE("acpi_sbs_init");
+
+	init_MUTEX(&sbs_sem);
+
+	if (capacity_mode != DEF_CAPACITY_UNIT
+	    && capacity_mode != MAH_CAPACITY_UNIT
+	    && capacity_mode != MWH_CAPACITY_UNIT) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "acpi_sbs_init: "
+				  "invalid capacity_mode = %d\n",
+				  capacity_mode));
+		return_VALUE(-EINVAL);
+	}
+
+	acpi_ac_dir = acpi_lock_ac_dir();
+	if (!acpi_ac_dir) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_lock_ac_dir() failed\n"));
+		return_VALUE(-ENODEV);
+	}
+
+	acpi_battery_dir = acpi_lock_battery_dir();
+	if (!acpi_battery_dir) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_lock_battery_dir() failed\n"));
+		return_VALUE(-ENODEV);
+	}
+
+	result = acpi_bus_register_driver(&acpi_sbs_driver);
+	if (result < 0) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "acpi_bus_register_driver() failed\n"));
+		return_VALUE(-ENODEV);
+	}
+
+	return_VALUE(0);
+}
+
+static void __exit acpi_sbs_exit(void)
+{
+	ACPI_FUNCTION_TRACE("acpi_sbs_exit");
+
+	acpi_bus_unregister_driver(&acpi_sbs_driver);
+
+	acpi_unlock_ac_dir(acpi_ac_dir);
+	acpi_ac_dir = NULL;
+	acpi_unlock_battery_dir(acpi_battery_dir);
+	acpi_battery_dir = NULL;
+
+	return_VOID;
+}
+
+module_init(acpi_sbs_init);
+module_exit(acpi_sbs_exit);