[PATCH 8/8] One Laptop Per Child power/battery driver
From: Anton Vorontsov
Date: Thu May 03 2007 - 17:37:57 EST
This probably should be marked as BROKEN because, according
to David Woodhouse, EC-acccess method will change. Plus currently
it lacks mutexes. So this driver is just for reference. Converted
from the battery-2.6 repository, 1:1.
But nevertheless it should work.
Signed-off-by: Anton Vorontsov <cbou@xxxxxxx>
---
drivers/power/Kconfig | 6 +
drivers/power/Makefile | 1 +
drivers/power/olpc_battery.c | 300 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 307 insertions(+), 0 deletions(-)
create mode 100644 drivers/power/olpc_battery.c
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 051724f..3ac79c3 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -42,4 +42,10 @@ config BATTERY_PMU
Say Y here to expose battery information on Apple machines
through the generic battery class.
+config BATTERY_OLPC
+ tristate "One Laptop Per Child battery"
+ depends on X86_32
+ help
+ Say Y to enable support for the battery on the $100 laptop.
+
endif # POWER_SUPPLY
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index 0ebdc6d..62b58ca 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -19,3 +19,4 @@ obj-$(CONFIG_APM_POWER) += apm_power.o
obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o
obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o
+obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o
diff --git a/drivers/power/olpc_battery.c b/drivers/power/olpc_battery.c
new file mode 100644
index 0000000..40f76bb
--- /dev/null
+++ b/drivers/power/olpc_battery.c
@@ -0,0 +1,300 @@
+/*
+ * Battery driver for One Laptop Per Child ($100 laptop) board.
+ *
+ * Copyright б╘ 2006 David Woodhouse <dwmw2@xxxxxxxxxxxxx>
+ *
+ * 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/err.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.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 /* what is this exactly? */
+#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
+
+/*********************************************************************
+ * EC locking and access
+ *********************************************************************/
+
+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)) {
+ printk(KERN_ERR "olpc_battery: failed to lock EC for "
+ "battery access\n");
+ return 1;
+ }
+ yield();
+ }
+}
+
+static void unlock_ec(void)
+{
+ outb(0xff, 0x6c);
+ return;
+}
+
+static unsigned char read_ec_byte(unsigned short adr)
+{
+ outb(adr >> 8, 0x381);
+ outb(adr, 0x382);
+ return inb(0x383);
+}
+
+static unsigned short read_ec_word(unsigned short adr)
+{
+ return (read_ec_byte(adr) << 8) | read_ec_byte(adr + 1);
+}
+
+/*********************************************************************
+ * Power
+ *********************************************************************/
+
+static int olpc_ac_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+
+ if (lock_ec())
+ return -EIO;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ if (!(read_ec_byte(sMBAT_STATUS) & sBAT_PRESENT)) {
+ ret = -ENODEV;
+ goto out;
+ }
+ val->intval = !!(read_ec_byte(sPOWER_FLAG) & ADAPTER_IN);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+out:
+ unlock_ec();
+ return ret;
+}
+
+static enum power_supply_property olpc_ac_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static struct power_supply olpc_ac = {
+ .name = "olpc-ac",
+ .type = POWER_SUPPLY_TYPE_AC,
+ .properties = olpc_ac_props,
+ .num_properties = ARRAY_SIZE(olpc_ac_props),
+ .get_property = olpc_ac_get_prop,
+};
+
+/*********************************************************************
+ * Battery properties
+ *********************************************************************/
+
+static int olpc_bat_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+
+ if (lock_ec())
+ return -EIO;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ {
+ int status = POWER_SUPPLY_STATUS_UNKNOWN;
+
+ val->intval = read_ec_byte(sMBAT_STATUS);
+
+ if (!(val->intval & sBAT_PRESENT)) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ if (val->intval & sBAT_DISCHG)
+ status = POWER_SUPPLY_STATUS_DISCHARGING;
+ else if (val->intval & sBAT_FULL)
+ status = POWER_SUPPLY_STATUS_FULL;
+
+ val->intval = read_ec_byte(sMCHARGE_STATUS);
+ if (val->intval & sBAT_CHARGE)
+ status = POWER_SUPPLY_STATUS_CHARGING;
+
+ val->intval = status;
+ break;
+ }
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = !!(read_ec_byte(sMBAT_STATUS) & sBAT_PRESENT);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = read_ec_byte(sMCHARGE_STATUS);
+ if (val->intval & sBAT_OVERTEMP)
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = read_ec_byte(sMCHARGE_STATUS);
+ if (val->intval & sBAT_NiMH)
+ val->intval = POWER_SUPPLY_TECHNOLOGY_NIMH;
+ else
+ val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ val->intval = read_ec_byte(wBAT_VOLTAGE) * 9760L / 32;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ val->intval = read_ec_byte(wBAT_CURRENT) * 15625L / 120;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = read_ec_byte(SOC);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ val->intval = read_ec_byte(sMBAT_STATUS);
+ if (val->intval & sBAT_FULL)
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+ else if (val->intval & sBAT_LOW)
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ else
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = read_ec_byte(wBAT_TEMP) * 256 / 100;
+ break;
+ case POWER_SUPPLY_PROP_TEMP_AMBIENT:
+ val->intval = read_ec_byte(wAMB_TEMP) * 256 / 100;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+out:
+ unlock_ec();
+ return ret;
+}
+
+static enum power_supply_property olpc_bat_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TEMP_AMBIENT,
+};
+
+/*********************************************************************
+ * Initialisation
+ *********************************************************************/
+
+static struct platform_device *bat_pdev;
+
+static struct power_supply olpc_bat = {
+ .properties = olpc_bat_props,
+ .num_properties = ARRAY_SIZE(olpc_bat_props),
+ .get_property = olpc_bat_get_property,
+ .use_for_apm = 1,
+};
+
+static int __init olpc_bat_init(void)
+{
+ int ret = 0;
+ unsigned short tmp;
+
+ if (!request_region(0x380, 4, "olpc-battery")) {
+ ret = -EIO;
+ goto region_failed;
+ }
+
+ if (lock_ec()) {
+ ret = -EIO;
+ goto lock_failed;
+ }
+
+ tmp = read_ec_word(0xfe92);
+ unlock_ec();
+
+ if (tmp != 0x380) {
+ /* Doesn't look like OLPC EC */
+ ret = -ENODEV;
+ goto not_olpc_ec;
+ }
+
+ bat_pdev = platform_device_register_simple("olpc-battery", 0, NULL, 0);
+ if (IS_ERR(bat_pdev)) {
+ ret = PTR_ERR(bat_pdev);
+ goto pdev_failed;
+ }
+
+ ret = power_supply_register(&bat_pdev->dev, &olpc_ac);
+ if (ret)
+ goto ac_failed;
+
+ olpc_bat.name = bat_pdev->name;
+
+ ret = power_supply_register(&bat_pdev->dev, &olpc_bat);
+ if (ret)
+ goto battery_failed;
+
+ goto success;
+
+battery_failed:
+ power_supply_unregister(&olpc_ac);
+ac_failed:
+ platform_device_unregister(bat_pdev);
+pdev_failed:
+not_olpc_ec:
+lock_failed:
+ release_region(0x380, 4);
+region_failed:
+success:
+ return ret;
+}
+
+static void __exit olpc_bat_exit(void)
+{
+ power_supply_unregister(&olpc_bat);
+ power_supply_unregister(&olpc_ac);
+ platform_device_unregister(bat_pdev);
+ release_region(0x380, 4);
+ return;
+}
+
+module_init(olpc_bat_init);
+module_exit(olpc_bat_exit);
+
+MODULE_AUTHOR("David Woodhouse <dwmw2@xxxxxxxxxxxxx>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Battery driver for One Laptop Per Child "
+ "($100 laptop) board.");
--
1.5.1.1-dirty
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/