From linux-kernel-owner+greg=40kroah.com-S964915AbWJWSUm@vger.kernel.org Mon Oct 23 11: 22:06 2006 From: David Woodhouse Date: Mon, 23 Oct 2006 19:20:33 +0100 Cc: davidz@redhat.com, greg@kroah.com, mjg59@srcf.ucam.org, len.brown@intel.com, sfr@canb.auug.org.au, benh@kernel.crashing.org Subject: Battery class driver. Content-Type: text/plain; charset=UTF-8 To: linux-kernel@vger.kernel.org, olpc-dev@laptop.org Mime-Version: 1.0 Message-Id: <1161627633.19446.387.camel@pmac.infradead.org> Status: RO Content-Length: 21135 Lines: 691 [BATTERY] Add initial implementation of battery class I really don't like the sysfs interaction, and I don't much like the internal interaction with the battery drivers either. In fact, there isn't much I _do_ like, but it's good enough as a straw man. Signed-off-by: David Woodhouse --- drivers/Kconfig | 2 drivers/Makefile | 1 drivers/battery/Kconfig | 22 +++ drivers/battery/Makefile | 4 drivers/battery/battery-class.c | 292 ++++++++++++++++++++++++++++++++++++++++ drivers/battery/olpc-battery.c | 198 +++++++++++++++++++++++++++ include/linux/battery.h | 84 +++++++++++ 7 files changed, 603 insertions(+) --- gregkh-2.6.orig/drivers/Kconfig +++ gregkh-2.6/drivers/Kconfig @@ -34,6 +34,8 @@ source "drivers/ieee1394/Kconfig" source "drivers/message/i2o/Kconfig" +source "drivers/battery/Kconfig" + source "drivers/macintosh/Kconfig" source "drivers/net/Kconfig" --- gregkh-2.6.orig/drivers/Makefile +++ gregkh-2.6/drivers/Makefile @@ -30,6 +30,7 @@ obj-$(CONFIG_PARPORT) += parport/ obj-y += base/ block/ misc/ mfd/ net/ media/ obj-$(CONFIG_NUBUS) += nubus/ obj-$(CONFIG_ATM) += atm/ +obj-$(CONFIG_BATTERY_CLASS) += battery/ obj-$(CONFIG_PPC_PMAC) += macintosh/ obj-$(CONFIG_IDE) += ide/ obj-$(CONFIG_FC4) += fc4/ --- /dev/null +++ gregkh-2.6/drivers/battery/Kconfig @@ -0,0 +1,22 @@ + +menu "Battery support" + +config BATTERY_CLASS + tristate "Battery support" + help + Say Y to enable battery class support. This allows a battery + information to be presented in a uniform manner for all types + of batteries. + + Battery information from APM and ACPI is not yet available by + this method, but should soon be. If you use APM or ACPI, say + 'N', although saying 'Y' would be harmless. + +config OLPC_BATTERY + tristate "One Laptop Per Child battery" + depends on BATTERY_CLASS && X86_32 + help + Say Y to enable support for the battery on the $100 laptop. + + +endmenu --- /dev/null +++ gregkh-2.6/drivers/battery/Makefile @@ -0,0 +1,4 @@ +# Battery code +obj-$(CONFIG_BATTERY_CLASS) += battery-class.o + +obj-$(CONFIG_OLPC_BATTERY) += olpc-battery.o --- /dev/null +++ gregkh-2.6/drivers/battery/battery-class.c @@ -0,0 +1,292 @@ +/* + * Battery class core + * + * © 2006 David Woodhouse + * + * Based on LED Class support, by John Lenz and Richard Purdie: + * + * © 2005 John Lenz + * © 2005-2006 Richard Purdie + * + * 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 +#include +#include +#include +#include +#include + +static struct class *battery_class; + +/* OMFG we can't just have a single 'show' routine which is given the + 'class_attribute' as an argument -- we have to have 20-odd copies + of almost identical routines */ + +static ssize_t battery_attribute_show_int(struct device *dev, + struct device_attribute *dev_attr, + char *buf, int attr) +{ + struct battery_dev *battery_dev = dev_get_drvdata(dev); + ssize_t ret = 0; + long value; + + ret = battery_dev->query_long(battery_dev, attr, &value); + if (ret) + return ret; + + sprintf(buf, "%ld\n", value); + ret = strlen(buf) + 1; + + return ret; +} + +static ssize_t battery_attribute_show_milli(struct device *dev, + struct device_attribute *dev_attr, + char *buf, int attr) +{ + struct battery_dev *battery_dev = dev_get_drvdata(dev); + ssize_t ret = 0; + long value; + + ret = battery_dev->query_long(battery_dev, attr, &value); + if (ret) + return ret; + + sprintf(buf, "%ld.%03ld\n", value/1000, value % 1000); + ret = strlen(buf) + 1; + return ret; +} + +static ssize_t battery_attribute_show_string(struct device *dev, + struct device_attribute *dev_attr, + char *buf, int attr) +{ + struct battery_dev *battery_dev = dev_get_drvdata(dev); + ssize_t ret = 0; + + ret = battery_dev->query_str(battery_dev, attr, buf, PAGE_SIZE-1); + if (ret) + return ret; + + strcat(buf, "\n"); + ret = strlen(buf) + 1; + return ret; +} + +static ssize_t battery_attribute_show_status(struct device *dev, + struct device_attribute *dev_attr, + char *buf) +{ + struct battery_dev *battery_dev = dev_get_drvdata(dev); + ssize_t ret = 0; + unsigned long status; + + status = battery_dev->status(battery_dev, ~BAT_STAT_AC); + if (status & BAT_STAT_ERROR) + return -EIO; + + if (status & BAT_STAT_PRESENT) + sprintf(buf, "present"); + else + sprintf(buf, "absent"); + + if (status & BAT_STAT_LOW) + strcat(buf, ",low"); + + if (status & BAT_STAT_FULL) + strcat(buf, ",full"); + + if (status & BAT_STAT_CHARGING) + strcat(buf, ",charging"); + + if (status & BAT_STAT_DISCHARGING) + strcat(buf, ",discharging"); + + if (status & BAT_STAT_OVERTEMP) + strcat(buf, ",overtemp"); + + if (status & BAT_STAT_FIRE) + strcat(buf, ",on-fire"); + + if (status & BAT_STAT_CHARGE_DONE) + strcat(buf, ",charge-done"); + + strcat(buf, "\n"); + ret = strlen(buf) + 1; + return ret; +} + +static ssize_t battery_attribute_show_ac_status(struct device *dev, + struct device_attribute *dev_attr, + char *buf) +{ + struct battery_dev *battery_dev = dev_get_drvdata(dev); + ssize_t ret = 0; + unsigned long status; + + status = battery_dev->status(battery_dev, BAT_STAT_AC); + if (status & BAT_STAT_ERROR) + return -EIO; + + if (status & BAT_STAT_AC) + sprintf(buf, "on-line"); + else + sprintf(buf, "off-line"); + + strcat(buf, "\n"); + ret = strlen(buf) + 1; + return ret; +} + +/* Ew. We can't even use DEVICE_ATTR() if we want one named 'current' */ +#define BATTERY_DEVICE_ATTR(_name, _attr, _type) \ +static ssize_t battery_attr_show_##_attr(struct device *dev, struct device_attribute *attr, char *buf) \ +{ \ + return battery_attribute_show_##_type(dev, attr, buf, BAT_INFO_##_attr); \ +} \ +static struct device_attribute dev_attr_##_attr = { \ + .attr = { .name = _name, .mode = 0444, .owner = THIS_MODULE }, \ + .show = battery_attr_show_##_attr }; + +static DEVICE_ATTR(status,0444,battery_attribute_show_status, NULL); +static DEVICE_ATTR(ac,0444,battery_attribute_show_ac_status, NULL); +BATTERY_DEVICE_ATTR("temp1",TEMP1,milli); +BATTERY_DEVICE_ATTR("temp1_name",TEMP1_NAME,string); +BATTERY_DEVICE_ATTR("temp2",TEMP2,milli); +BATTERY_DEVICE_ATTR("temp2_name",TEMP2_NAME,string); +BATTERY_DEVICE_ATTR("voltage",VOLTAGE,milli); +BATTERY_DEVICE_ATTR("voltage_design",VOLTAGE_DESIGN,milli); +BATTERY_DEVICE_ATTR("current",CURRENT,milli); +BATTERY_DEVICE_ATTR("charge_rate",CHARGE_RATE,milli); +BATTERY_DEVICE_ATTR("charge_max",CHARGE_MAX,milli); +BATTERY_DEVICE_ATTR("charge_last",CHARGE_LAST,milli); +BATTERY_DEVICE_ATTR("charge_low",CHARGE_LOW,milli); +BATTERY_DEVICE_ATTR("charge_warn",CHARGE_WARN,milli); +BATTERY_DEVICE_ATTR("charge_unit",CHARGE_UNITS,string); +BATTERY_DEVICE_ATTR("charge_percent",CHARGE_PCT,int); +BATTERY_DEVICE_ATTR("time_remaining",TIME_REMAINING,int); +BATTERY_DEVICE_ATTR("manufacturer",MANUFACTURER,string); +BATTERY_DEVICE_ATTR("technology",TECHNOLOGY,string); +BATTERY_DEVICE_ATTR("model",MODEL,string); +BATTERY_DEVICE_ATTR("serial",SERIAL,string); +BATTERY_DEVICE_ATTR("type",TYPE,string); +BATTERY_DEVICE_ATTR("oem_info",OEM_INFO,string); + +#define REGISTER_ATTR(_attr) \ + if (battery_dev->capabilities & (1<dev, &dev_attr_##_attr); +#define UNREGISTER_ATTR(_attr) \ + if (battery_dev->capabilities & (1<dev, &dev_attr_##_attr); +/** + * battery_dev_register - register a new object of battery_dev class. + * @dev: The parent of the device to register. + * @battery_dev: the battery_dev structure for this device. + */ +int battery_dev_register(struct device *parent, + struct battery_dev *battery_dev) +{ + battery_dev->dev = device_create(battery_class, parent, 0, + "%s", battery_dev->name); + + if (unlikely(IS_ERR(battery_dev->dev))) + return PTR_ERR(battery_dev->dev); + + dev_set_drvdata(battery_dev->dev, battery_dev); + + /* register the attributes */ + device_create_file(battery_dev->dev, &dev_attr_status); + + if (battery_dev->status_cap & (1<dev, &dev_attr_ac); + + REGISTER_ATTR(TEMP1); + REGISTER_ATTR(TEMP1_NAME); + REGISTER_ATTR(TEMP2); + REGISTER_ATTR(TEMP2_NAME); + REGISTER_ATTR(VOLTAGE); + REGISTER_ATTR(VOLTAGE_DESIGN); + REGISTER_ATTR(CHARGE_PCT); + REGISTER_ATTR(CURRENT); + REGISTER_ATTR(CHARGE_RATE); + REGISTER_ATTR(CHARGE_MAX); + REGISTER_ATTR(CHARGE_LAST); + REGISTER_ATTR(CHARGE_LOW); + REGISTER_ATTR(CHARGE_WARN); + REGISTER_ATTR(CHARGE_UNITS); + REGISTER_ATTR(TIME_REMAINING); + REGISTER_ATTR(MANUFACTURER); + REGISTER_ATTR(TECHNOLOGY); + REGISTER_ATTR(MODEL); + REGISTER_ATTR(SERIAL); + REGISTER_ATTR(TYPE); + REGISTER_ATTR(OEM_INFO); + + dev_info(battery_dev->dev, "Registered battery device\n"); + + return 0; +} +EXPORT_SYMBOL_GPL(battery_dev_register); + +/** + * battery_dev_unregister - unregisters a object of battery_properties class. + * @battery_dev: the battery device to unreigister + * + * Unregisters a previously registered via battery_dev_register object. + */ +void battery_dev_unregister(struct battery_dev *battery_dev) +{ + device_remove_file(battery_dev->dev, &dev_attr_status); + + if (battery_dev->status_cap & (1<dev, &dev_attr_ac); + + UNREGISTER_ATTR(TEMP1); + UNREGISTER_ATTR(TEMP1_NAME); + UNREGISTER_ATTR(TEMP2); + UNREGISTER_ATTR(TEMP2_NAME); + UNREGISTER_ATTR(VOLTAGE); + UNREGISTER_ATTR(VOLTAGE_DESIGN); + UNREGISTER_ATTR(CHARGE_PCT); + UNREGISTER_ATTR(CURRENT); + UNREGISTER_ATTR(CHARGE_RATE); + UNREGISTER_ATTR(CHARGE_MAX); + UNREGISTER_ATTR(CHARGE_LAST); + UNREGISTER_ATTR(CHARGE_LOW); + UNREGISTER_ATTR(CHARGE_WARN); + UNREGISTER_ATTR(CHARGE_UNITS); + UNREGISTER_ATTR(TIME_REMAINING); + UNREGISTER_ATTR(MANUFACTURER); + UNREGISTER_ATTR(TECHNOLOGY); + UNREGISTER_ATTR(MODEL); + UNREGISTER_ATTR(SERIAL); + UNREGISTER_ATTR(TYPE); + UNREGISTER_ATTR(OEM_INFO); + + device_unregister(battery_dev->dev); +} +EXPORT_SYMBOL_GPL(battery_dev_unregister); + +static int __init battery_init(void) +{ + battery_class = class_create(THIS_MODULE, "battery"); + if (IS_ERR(battery_class)) + return PTR_ERR(battery_class); + return 0; +} + +static void __exit battery_exit(void) +{ + class_destroy(battery_class); +} + +subsys_initcall(battery_init); +module_exit(battery_exit); + +MODULE_AUTHOR("David Woodhouse "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Battery class interface"); --- /dev/null +++ gregkh-2.6/drivers/battery/olpc-battery.c @@ -0,0 +1,198 @@ +/* + * Battery driver for One Laptop Per Child ($100 laptop) board. + * + * © 2006 David Woodhouse + * + * 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 +#include +#include +#include +#include +#include +#include + +#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_dev *dev, 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_dev *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_dev *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_dev olpc_bat = { + .name = "OLPC", + .capabilities = (1<"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Battery class interface"); --- /dev/null +++ gregkh-2.6/include/linux/battery.h @@ -0,0 +1,84 @@ +/* + * Driver model for batteries + * + * © 2006 David Woodhouse + * + * Based on LED Class support, by John Lenz and Richard Purdie: + * + * © 2005 John Lenz + * © 2005-2006 Richard Purdie + * + * 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. + * + */ +#ifndef __LINUX_BATTERY_H__ +#define __LINUX_BATTERY_H__ + +struct device; +struct class_device; + +/* + * Battery Core + */ +#define BAT_STAT_AC (1<<0) +#define BAT_STAT_PRESENT (1<<1) +#define BAT_STAT_LOW (1<<2) +#define BAT_STAT_FULL (1<<3) +#define BAT_STAT_CHARGING (1<<4) +#define BAT_STAT_DISCHARGING (1<<5) +#define BAT_STAT_OVERTEMP (1<<6) +#define BAT_STAT_FIRE (1<<7) +#define BAT_STAT_CHARGE_DONE (1<<8) + +#define BAT_STAT_ERROR (1<<31) + +#define BAT_INFO_TEMP1 (0) /* °C/1000 */ +#define BAT_INFO_TEMP1_NAME (1) /* string */ + +#define BAT_INFO_TEMP2 (2) /* °C/1000 */ +#define BAT_INFO_TEMP2_NAME (3) /* string */ + +#define BAT_INFO_VOLTAGE (4) /* mV */ +#define BAT_INFO_VOLTAGE_DESIGN (5) /* mV */ + +#define BAT_INFO_CURRENT (6) /* mA */ + +#define BAT_INFO_CHARGE_RATE (7) /* BAT_INFO_CHARGE_UNITS */ +#define BAT_INFO_CHARGE (8) /* BAT_INFO_CHARGE_UNITS */ +#define BAT_INFO_CHARGE_MAX (9) /* BAT_INFO_CHARGE_UNITS */ +#define BAT_INFO_CHARGE_LAST (10) /* BAT_INFO_CHARGE_UNITS */ +#define BAT_INFO_CHARGE_LOW (11) /* BAT_INFO_CHARGE_UNITS */ +#define BAT_INFO_CHARGE_WARN (12) /* BAT_INFO_CHARGE_UNITS */ +#define BAT_INFO_CHARGE_UNITS (13) /* string */ +#define BAT_INFO_CHARGE_PCT (14) /* % */ + +#define BAT_INFO_TIME_REMAINING (15) /* seconds */ + +#define BAT_INFO_MANUFACTURER (16) /* string */ +#define BAT_INFO_TECHNOLOGY (17) /* string */ +#define BAT_INFO_MODEL (18) /* string */ +#define BAT_INFO_SERIAL (19) /* string */ +#define BAT_INFO_TYPE (20) /* string */ +#define BAT_INFO_OEM_INFO (21) /* string */ + +struct battery_dev { + const char *name; + /* Capabilities of this battery driver */ + unsigned long capabilities, status_cap; + + /* Query functions */ + unsigned long (*status)(struct battery_dev *, unsigned long mask); + int (*query_long)(struct battery_dev *, int attr, long *result); + int (*query_str)(struct battery_dev *, int attr, char *str, ssize_t len); + + struct device *dev; + struct list_head node; /* Battery Device list */ +}; + +extern int battery_dev_register(struct device *parent, + struct battery_dev *battery_dev); +extern void battery_dev_unregister(struct battery_dev *battery_dev); + +#endif /* __LINUX_BATTERY_H__ */