[BATTERY] Add support for OLPC battery

David Woodhouse dwmw2 at infradead.org
Tue Nov 7 22:38:26 EST 2006


Commit:     42fe507a262b2a2879ca62740c5312778ae78627
Parent:     6cbec3b84e3ce737b4217788841ea10a28a5e340
commit 42fe507a262b2a2879ca62740c5312778ae78627
Author:     David Woodhouse <dwmw2 at infradead.org>
AuthorDate: Mon Oct 23 18:14:54 2006 +0100
Commit:     David Woodhouse <dwmw2 at infradead.org>
CommitDate: Mon Oct 23 18:14:54 2006 +0100

    [BATTERY] Add support for OLPC battery
    
    Signed-off-by: David Woodhouse <dwmw2 at infradead.org>
---
 drivers/battery/olpc-battery.c |  198 ++++++++++++++++++++++++++++++++++++++++
 1 files changed, 198 insertions(+), 0 deletions(-)

diff --git a/drivers/battery/olpc-battery.c b/drivers/battery/olpc-battery.c
new file mode 100644
index 0000000..a95d511
--- /dev/null
+++ b/drivers/battery/olpc-battery.c
@@ -0,0 +1,198 @@
+/*
+ * Battery driver for One Laptop Per Child ($100 laptop) board.
+ *
+ *	© 2006 David Woodhouse <dwmw2 at infradead.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/battery.h>
+#include <linux/spinlock.h>
+#include <linux/err.h>
+#include <asm/io.h>
+
+#define wBAT_VOLTAGE		0xf900 /* *9.76/32,    mV */
+#define wBAT_CURRENT		0xf902 /* *15.625/120, mA */
+#define wBAT_TEMP		0xf906 /* *256/1000,   °C */
+#define wAMB_TEMP		0xf908 /* *256/1000,   °C */
+#define SOC			0xf910 /*      percentage */
+#define sMBAT_STATUS		0xfaa4
+#define		sBAT_PRESENT		1
+#define		sBAT_FULL		2
+#define		sBAT_DESTROY		4
+#define		sBAT_LOW			32
+#define		sBAT_DISCHG		64
+#define sMCHARGE_STATUS		0xfaa5
+#define		sBAT_CHARGE		1
+#define		sBAT_OVERTEMP		4
+#define		sBAT_NiMH		8
+#define sPOWER_FLAG		0xfa40
+#define		ADAPTER_IN		1
+
+static int lock_ec(void)
+{
+	unsigned long timeo = jiffies + HZ/20;
+
+	while (1) {
+                unsigned char lock = inb(0x6c) & 0x80;
+                if (!lock)
+                        return 0;
+		if (time_after(jiffies, timeo))
+			return 1;
+                yield();
+        }
+}
+
+static void unlock_ec(void)
+{
+        outb(0xff, 0x6c);
+}
+
+unsigned char read_ec_byte(unsigned short adr)
+{
+        outb(adr >> 8, 0x381);
+        outb(adr, 0x382);
+        return inb(0x383);
+}
+
+unsigned short read_ec_word(unsigned short adr)
+{
+        return (read_ec_byte(adr) << 8) | read_ec_byte(adr+1);
+}
+
+unsigned long olpc_bat_status(struct battery_classdev *cdev, unsigned long mask)
+{
+	unsigned long result = 0;
+	unsigned short tmp;
+
+	if (lock_ec()) {
+		printk(KERN_ERR "Failed to lock EC for battery access\n");
+		return BAT_STAT_ERROR;
+	}
+
+	if (mask & BAT_STAT_AC) {
+		if (read_ec_byte(sPOWER_FLAG) & ADAPTER_IN)
+			result |= BAT_STAT_AC;
+	}
+	if (mask & (BAT_STAT_PRESENT|BAT_STAT_FULL|BAT_STAT_FIRE|BAT_STAT_LOW|BAT_STAT_DISCHARGING)) {
+		tmp = read_ec_byte(sMBAT_STATUS);
+		
+		if (tmp & sBAT_PRESENT)
+			result |= BAT_STAT_PRESENT;
+		if (tmp & sBAT_FULL)
+			result |= BAT_STAT_FULL;
+		if (tmp & sBAT_DESTROY)
+			result |= BAT_STAT_FIRE;
+		if (tmp & sBAT_LOW)
+			result |= BAT_STAT_LOW;
+		if (tmp & sBAT_DISCHG)
+			result |= BAT_STAT_DISCHARGING;
+	}
+	if (mask & (BAT_STAT_CHARGING|BAT_STAT_OVERTEMP)) {
+		tmp = read_ec_byte(sMCHARGE_STATUS);
+		if (tmp & sBAT_CHARGE)
+			result |= BAT_STAT_CHARGING;
+		if (tmp & sBAT_OVERTEMP)
+			result |= BAT_STAT_OVERTEMP;
+	}
+	unlock_ec();
+	return result;
+}
+
+int olpc_bat_query_long(struct battery_classdev *dev, int attr, long *result)
+{
+	int ret = 0;
+
+	if (lock_ec())
+		return -EIO;
+
+	if (!(read_ec_byte(sMBAT_STATUS) & sBAT_PRESENT)) {
+		ret = -ENODEV;
+	} else if (attr == BAT_INFO_VOLTAGE) {
+		*result = read_ec_word(wBAT_VOLTAGE) * 9760 / 32000;
+	} else if (attr == BAT_INFO_CURRENT) {
+		*result = read_ec_word(wBAT_CURRENT) * 15625 / 120000;
+	} else if (attr == BAT_INFO_TEMP1) {
+		*result = read_ec_word(wBAT_TEMP) * 1000 / 256;
+	} else if (attr == BAT_INFO_TEMP2) {
+		*result = read_ec_word(wAMB_TEMP) * 1000 / 256;
+	} else if (attr == BAT_INFO_CHARGE_PCT) {
+		*result = read_ec_byte(SOC);
+	} else 
+		ret = -EINVAL;
+
+	unlock_ec();
+	return ret;
+}
+
+int olpc_bat_query_str(struct battery_classdev *dev, int attr, char *str, int len)
+{
+	int ret = 0;
+
+	if (attr == BAT_INFO_TYPE) {
+		snprintf(str, len, "OLPC");
+	} else if (attr == BAT_INFO_TEMP1_NAME) {
+		snprintf(str, len, "battery");
+	} else if (attr == BAT_INFO_TEMP2_NAME) {
+		snprintf(str, len, "ambient");
+	} else if (!(read_ec_byte(sMBAT_STATUS) & sBAT_PRESENT)) {
+		ret = -ENODEV;
+	} else if (attr == BAT_INFO_TECHNOLOGY) {
+		if (lock_ec())
+			ret = -EIO;
+		else {
+			unsigned short tmp = read_ec_byte(sMCHARGE_STATUS);
+			if (tmp & sBAT_NiMH)
+				snprintf(str, len, "NiMH");
+			else
+				snprintf(str, len, "unknown");
+		}
+		unlock_ec();
+	} else {
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static struct battery_classdev olpc_bat = {
+	.name = "OLPC",
+	.capabilities = (1<<BAT_INFO_VOLTAGE) |
+			(1<<BAT_INFO_CURRENT) |
+			(1<<BAT_INFO_TEMP1) |
+			(1<<BAT_INFO_TEMP2) |
+			(1<<BAT_INFO_CHARGE_PCT) |
+			(1<<BAT_INFO_TYPE) |
+			(1<<BAT_INFO_TECHNOLOGY) |
+			(1<<BAT_INFO_TEMP1_NAME) |
+			(1<<BAT_INFO_TEMP2_NAME),
+	.status_cap = BAT_STAT_AC | BAT_STAT_PRESENT | BAT_STAT_LOW | 
+		      BAT_STAT_FULL | BAT_STAT_CHARGING| BAT_STAT_DISCHARGING |
+		      BAT_STAT_OVERTEMP | BAT_STAT_FIRE,
+	.status = olpc_bat_status,
+	.query_long = olpc_bat_query_long,
+	.query_str = olpc_bat_query_str,
+};
+
+void __exit olpc_bat_exit(void)
+{
+	battery_classdev_unregister(&olpc_bat);
+}
+
+int __init olpc_bat_init(void)
+{
+	battery_classdev_register(NULL, &olpc_bat);
+	return 0;
+}
+
+module_init(olpc_bat_init);
+module_exit(olpc_bat_exit);
+
+MODULE_AUTHOR("David Woodhouse <dwmw2 at infradead.org>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Battery class interface");


More information about the Commits-kernel mailing list