diff --git a/doc/chips/smartbatt b/doc/chips/smartbatt new file mode 100644 index 00000000..38c2a3fd --- /dev/null +++ b/doc/chips/smartbatt @@ -0,0 +1,46 @@ +Smart Battery Driver +------------- +Supports smart batteries at address 0x0b. + +Reference: Smart Battery Data Specification Revision 1.1 + http://www.sbs-forum.org/ + + +Stability +--------- +Alpha + + +DETECTION +------------- +Very poor. Just looks for the presence of some registers. + + +USES +------------- +Provides read-only access to battery voltage, current, temperature, +charge status, time remaining, and alarms. + + +/PROC ENTRIES +------------- +average readings are one minute rolling averages. + + alarms bitmask + charge relative charge, absolute charge (%) + i average current, instantaneous current (amps) + temp temperature (deg. C) + time avg. time to empty, avg. time to full, + instantaneous time to full (minutes) + v max voltage, current voltage (volts) + + +TO DO +----- +more features + + +Author/Maintainer +----------------- +Mark D. Studebaker (mdsxyz123@yahoo.com) + diff --git a/kernel/chips/Module.mk b/kernel/chips/Module.mk index 436251c8..086d688c 100644 --- a/kernel/chips/Module.mk +++ b/kernel/chips/Module.mk @@ -25,6 +25,7 @@ KERNELCHIPSDIR := $(MODULE_DIR) # defined value verbatim into the command-list of rules... # These targets are NOT included in 'mkpatch' ... KERNELCHIPSTARGETS := +KERNELCHIPSTARGETS += $(MODULE_DIR)/smartbatt.o KERNELCHIPSTARGETS += $(MODULE_DIR)/smbus-arp.o KERNELCHIPSTARGETS += $(MODULE_DIR)/vt8231.o diff --git a/kernel/chips/smartbatt.c b/kernel/chips/smartbatt.c new file mode 100644 index 00000000..74cdac97 --- /dev/null +++ b/kernel/chips/smartbatt.c @@ -0,0 +1,542 @@ +/* + smartbatt.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 2002 M. D. Studebaker + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + With GPL code from: + battery.c + Copyright (C) 2000 Linuxcare, Inc. + battery.h + Copyright (C) 2000 Hypercore Software Design, Ltd. +*/ + +#include +#include +#include +#include +#include "sensors.h" +#include "version.h" +#include + + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,2,18)) || \ + (LINUX_VERSION_CODE == KERNEL_VERSION(2,3,0)) +#define init_MUTEX(s) do { *(s) = MUTEX; } while(0) +#endif + +#ifndef THIS_MODULE +#define THIS_MODULE NULL +#endif + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { 0x0b, SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod parameters */ +SENSORS_INSMOD_1(smartbatt); + +/* Conversions */ +#define TEMP_FROM_REG(r) (r - 2732) /* tenths of degree kelvin to celsius */ + +/* The SMARTBATT registers */ +#define SMARTBATT_REG_MODE 0x03 +#define SMARTBATT_REG_TEMP 0x08 +#define SMARTBATT_REG_V 0x09 +#define SMARTBATT_REG_I 0x0a +#define SMARTBATT_REG_AVGI 0x0b +#define SMARTBATT_REG_RELCHG 0x0d +#define SMARTBATT_REG_ABSCHG 0x0e +#define SMARTBATT_REG_RUNTIME_E 0x11 +#define SMARTBATT_REG_AVGTIME_E 0x12 +#define SMARTBATT_REG_AVGTIME_F 0x13 +#define SMARTBATT_REG_STATUS 0x16 +#define SMARTBATT_REG_DESV 0x19 +#define SMARTBATT_REG_DATE 0x1b +#define SMARTBATT_REG_SERIAL 0x1c +#define SMARTBATT_REG_MANUF 0x20 +#define SMARTBATT_REG_NAME 0x21 +#define SMARTBATT_REG_CHEM 0x22 + +#define COMM_TIMEOUT 16 +#define BATTERY_STRING_MAX 33 + +/* Each client has this additional data */ +struct smartbatt_data { + int sysctl_id; + + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ +#if 0 + char manufacturer[BATTERY_STRING_MAX]; + char device[BATTERY_STRING_MAX]; + char chemistry[BATTERY_STRING_MAX]; + int serial; + struct { + unsigned int day:5; /* Day (1-31) */ + unsigned int month:4; /* Month (1-12) */ + unsigned int year:7; /* Year (1980 + 0-127) */ + } manufacture_date; +#endif + u16 temp, v, desv, i, avgi; /* Register values */ + u16 rte, ate, atf, alarms; /* Register values */ + u16 relchg, abschg; /* Register values */ +}; + +#ifdef MODULE +extern int init_module(void); +extern int cleanup_module(void); +#endif /* MODULE */ + +#ifdef MODULE +static +#else +extern +#endif +int __init sensors_smartbatt_init(void); +static int __init smartbatt_cleanup(void); +static int smartbatt_attach_adapter(struct i2c_adapter *adapter); +static int smartbatt_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static void smartbatt_init_client(struct i2c_client *client); +static int smartbatt_detach_client(struct i2c_client *client); +static int smartbatt_command(struct i2c_client *client, unsigned int cmd, + void *arg); +static void smartbatt_inc_use(struct i2c_client *client); +static void smartbatt_dec_use(struct i2c_client *client); +static u16 swap_bytes(u16 val); +static int sb_read(struct i2c_client *client, u8 reg); +#if 0 +static int smartbatt_write_value(struct i2c_client *client, u8 reg, u16 value); +#endif +static void smartbatt_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void smartbatt_i(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void smartbatt_v(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void smartbatt_time(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void smartbatt_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void smartbatt_charge(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void smartbatt_update_client(struct i2c_client *client); + + +/* This is the driver that will be inserted */ +static struct i2c_driver smartbatt_driver = { + /* name */ "Smart Battery chip driver", + /* id */ I2C_DRIVERID_SMARTBATT, + /* flags */ I2C_DF_NOTIFY, + /* attach_adapter */ &smartbatt_attach_adapter, + /* detach_client */ &smartbatt_detach_client, + /* command */ &smartbatt_command, + /* inc_use */ &smartbatt_inc_use, + /* dec_use */ &smartbatt_dec_use +}; + +/* These files are created for each detected SMARTBATT. This is just a template; + though at first sight, you might think we could use a statically + allocated list, we need some way to get back to the parent - which + is done through one of the 'extra' fields which are initialized + when a new copy is allocated. */ +static ctl_table smartbatt_dir_table_template[] = { + {SMARTBATT_SYSCTL_I, "i", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &smartbatt_i}, + {SMARTBATT_SYSCTL_V, "v", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &smartbatt_v}, + {SMARTBATT_SYSCTL_TEMP, "temp", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &smartbatt_temp}, + {SMARTBATT_SYSCTL_TIME, "time", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &smartbatt_time}, + {SMARTBATT_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &smartbatt_alarms}, + {SMARTBATT_SYSCTL_CHARGE, "charge", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &smartbatt_charge}, + {0} +}; + +/* Used by init/cleanup */ +static int __initdata smartbatt_initialized = 0; + +static int smartbatt_id = 0; + +int smartbatt_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, smartbatt_detect); +} + +/* This function is called by i2c_detect */ +int smartbatt_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i, cur, conf, hyst, os; + struct i2c_client *new_client; + struct smartbatt_data *data; + int err = 0; + const char *type_name, *client_name; + + /* Make sure we aren't probing the ISA bus!! This is just a safety check + at this moment; i2c_detect really won't call us. */ +#ifdef DEBUG + if (i2c_is_isa_adapter(adapter)) { + printk + ("smartbatt.o: smartbatt_detect called for an ISA bus adapter?!?\n"); + return 0; + } +#endif + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) + goto ERROR0; + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. + But it allows us to access smartbatt_{read,write}_value. */ + if (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct smartbatt_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = (struct smartbatt_data *) (new_client + 1); + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &smartbatt_driver; + new_client->flags = 0; + + /* Now, we do the remaining detection. It is lousy. */ + if (kind < 0) { + for (i = 0x08; i <= 0x0a; i++) + if (i2c_smbus_read_word_data(new_client, i) != 0xff) + goto ERROR1; + } + + kind = smartbatt; + type_name = "smartbatt"; + client_name = "Smart Battery"; + + /* Fill in the remaining client fields and put it into the global list */ + strcpy(new_client->name, client_name); + + new_client->id = smartbatt_id++; + data->valid = 0; + init_MUTEX(&data->update_lock); + + /* Tell the I2C layer a new client has arrived */ + if ((err = i2c_attach_client(new_client))) + goto ERROR3; + + /* Register a new directory entry with module sensors */ + if ((i = i2c_register_entry(new_client, type_name, + smartbatt_dir_table_template, + THIS_MODULE)) < 0) { + err = i; + goto ERROR4; + } + data->sysctl_id = i; + + smartbatt_init_client(new_client); + return 0; + +/* OK, this is not exactly good programming practice, usually. But it is + very code-efficient in this case. */ + + ERROR4: + i2c_detach_client(new_client); + ERROR3: + ERROR1: + kfree(new_client); + ERROR0: + return err; +} + +int smartbatt_detach_client(struct i2c_client *client) +{ + int err; + +#ifdef MODULE + if (MOD_IN_USE) + return -EBUSY; +#endif + + + i2c_deregister_entry(((struct smartbatt_data *) (client->data))-> + sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk + ("smartbatt.o: Client deregistration failed, client not detached.\n"); + return err; + } + + kfree(client); + + return 0; +} + + +/* No commands defined yet */ +int smartbatt_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + return 0; +} + +/* Nothing here yet */ +void smartbatt_inc_use(struct i2c_client *client) +{ +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif +} + +/* Nothing here yet */ +void smartbatt_dec_use(struct i2c_client *client) +{ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif +} + +u16 swap_bytes(u16 val) +{ + return (val >> 8) | (val << 8); +} + +int sb_read(struct i2c_client *client, u8 reg) +{ + return swap_bytes(i2c_smbus_read_word_data(client, reg)); +} + +#if 0 +int smartbatt_write_value(struct i2c_client *client, u8 reg, u16 value) +{ + return i2c_smbus_write_word_data(client, reg, swap_bytes(value)); +} +#endif + +#if 0 +/* this is code from battery.c. No strings support yet in i2c-proc.c so + all we could do is print this out at startup if we wanted. +*/ +int +battery_info(int fd, struct battery_info *info) +{ + int n; + int val; + + /* ManufactureDate */ + val = sb_read(SMARTBATT_REG_DATE); + info->manufacture_date.day=val & 0x1F; + info->manufacture_date.month=(val >> 5) & 0x0F; + info->manufacture_date.year=(val >> 9) & 0x7F; + + /* SerialNumber */ + info->serial = sb_read(SMARTBATT_REG_SERIAL + + /* ManufacturerName */ + n = COMM_TIMEOUT; + do { + val = i2c_smbus_read_block_data(fd, 0x20, info->manufacturer); + } while ((val == -1) && (n-- > 0)); + info->manufacturer[val]=0; + + /* DeviceName */ + n = COMM_TIMEOUT; + do { + val = i2c_smbus_read_block_data(fd, 0x21, info->device); + } while ((val == -1) && (n-- > 0)); + info->device[val]=0; + + /* DeviceChemistry */ + n = COMM_TIMEOUT; + do { + val = i2c_smbus_read_block_data(fd, 0x22, info->chemistry); + } while ((val == -1) && (n-- > 0)); + info->chemistry[val]=0; + + return 0; +} +#endif + +void smartbatt_init_client(struct i2c_client *client) +{ + +} + +void smartbatt_update_client(struct i2c_client *client) +{ + struct smartbatt_data *data = client->data; + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ + HZ / 2) || + (jiffies < data->last_updated) || !data->valid) { + data->temp = sb_read(client, SMARTBATT_REG_TEMP); + data->i = sb_read(client, SMARTBATT_REG_I); + data->avgi = sb_read(client, SMARTBATT_REG_AVGI); + data->v = sb_read(client, SMARTBATT_REG_V); + data->desv = sb_read(client, SMARTBATT_REG_DESV); + data->ate = sb_read(client, SMARTBATT_REG_AVGTIME_E); + data->atf = sb_read(client, SMARTBATT_REG_AVGTIME_F); + data->rte = sb_read(client, SMARTBATT_REG_RUNTIME_E); + data->alarms = sb_read(client, SMARTBATT_REG_STATUS); + data->relchg = sb_read(client, SMARTBATT_REG_RELCHG); + data->abschg = sb_read(client, SMARTBATT_REG_ABSCHG); + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + + +void smartbatt_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct smartbatt_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + smartbatt_update_client(client); + results[0] = TEMP_FROM_REG(data->temp); + *nrels_mag = 1; + } +} + +void smartbatt_i(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct smartbatt_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 3; + else if (operation == SENSORS_PROC_REAL_READ) { + smartbatt_update_client(client); + results[0] = data->avgi; + results[1] = data->i; + *nrels_mag = 2; + } +} + +void smartbatt_v(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct smartbatt_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 3; + else if (operation == SENSORS_PROC_REAL_READ) { + smartbatt_update_client(client); + results[0] = data->desv; + results[1] = data->v; + *nrels_mag = 2; + } +} + +void smartbatt_time(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct smartbatt_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + smartbatt_update_client(client); + results[0] = data->ate; + results[1] = data->atf; + results[2] = data->rte; + *nrels_mag = 3; + } +} + +void smartbatt_alarms(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct smartbatt_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + smartbatt_update_client(client); + results[0] = data->alarms; + *nrels_mag = 1; + } +} + +void smartbatt_charge(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct smartbatt_data *data = client->data; + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + smartbatt_update_client(client); + results[0] = data->relchg; + results[1] = data->abschg; + *nrels_mag = 2; + } +} + +int __init sensors_smartbatt_init(void) +{ + int res; + + printk("smartbatt.o version %s (%s)\n", LM_VERSION, LM_DATE); + smartbatt_initialized = 0; + if ((res = i2c_add_driver(&smartbatt_driver))) { + printk + ("smartbatt.o: Driver registration failed, module not inserted.\n"); + smartbatt_cleanup(); + return res; + } + smartbatt_initialized++; + return 0; +} + +int __init smartbatt_cleanup(void) +{ + int res; + + if (smartbatt_initialized >= 1) { + if ((res = i2c_del_driver(&smartbatt_driver))) + return res; + smartbatt_initialized--; + } + + return 0; +} + +EXPORT_NO_SYMBOLS; + +#ifdef MODULE +MODULE_AUTHOR("M. Studebaker "); +MODULE_DESCRIPTION("Smart Battery driver"); +#ifdef MODULE_LICENSE +MODULE_LICENSE("GPL"); +#endif + +int init_module(void) +{ + return sensors_smartbatt_init(); +} + +int cleanup_module(void) +{ + return smartbatt_cleanup(); +} +#endif /* MODULE */ diff --git a/kernel/include/sensors.h b/kernel/include/sensors.h index ba2a0058..4f1d7559 100644 --- a/kernel/include/sensors.h +++ b/kernel/include/sensors.h @@ -676,4 +676,12 @@ #define VT8231_ALARM_TEMP6 VT8231_ALARM_IN3 #define VT8231_ALARM_TEMP7 VT8231_ALARM_IN4 +#define SMARTBATT_SYSCTL_I 1001 +#define SMARTBATT_SYSCTL_V 1002 +#define SMARTBATT_SYSCTL_TEMP 1003 +#define SMARTBATT_SYSCTL_TIME 1004 +#define SMARTBATT_SYSCTL_ALARMS 1005 +#define SMARTBATT_SYSCTL_CHARGE 1006 + + #endif /* def SENSORS_SENSORS_H */ diff --git a/prog/detect/sensors-detect b/prog/detect/sensors-detect index fe298248..cdaa9199 100755 --- a/prog/detect/sensors-detect +++ b/prog/detect/sensors-detect @@ -687,7 +687,7 @@ use subs qw(mtp008_detect lm78_detect lm78_isa_detect lm78_alias_detect lm87_detect ite_detect ite_isa_detect ite_alias_detect ddcmonitor_detect ds1621_detect adm1024_detect fscpos_detect fscscy_detect pcf8591_detect arp_detect ipmi_kcs_detect - ipmi_smic_detect via8231_isa_detect lm85_detect); + ipmi_smic_detect via8231_isa_detect lm85_detect smartbatt_detect); # This is a list of all recognized chips. # Each entry must have the following fields: @@ -1015,6 +1015,12 @@ use subs qw(mtp008_detect lm78_detect lm78_isa_detect lm78_alias_detect isa_addrs => [ 0x0ca8 ], isa_detect => sub { ipmi_smic_detect @_ }, }, + { + name => "SMart Battery", + driver => "smartbatt", + i2c_addrs => [0x0b], + i2c_detect => sub { smartbatt_detect @_}, + }, ); @@ -2619,6 +2625,26 @@ sub arp_detect return (1); } +# $_[0]: A reference to the file descriptor to access this chip. +# We may assume an i2c_set_slave_addr was already done. +# $_[1]: Address +# Returns: 1 +sub smartbatt_detect +{ + my ($file,$addr) = @_; + # check some registers + if (i2c_smbus_read_byte_data($file,0x08) == 0xff) { + return; + } + if (i2c_smbus_read_byte_data($file,0x09) != 0xff) { + return; + } + if (i2c_smbus_read_byte_data($file,0x0a) != 0xff) { + return; + } + return (3); +} + # Returns: 4 # These are simple detectors that only look for a register at the # standard location. No writes are performed.