diff -urN oldtree/Documentation/tp_smapi.txt newtree/Documentation/tp_smapi.txt --- oldtree/Documentation/tp_smapi.txt 1969-12-31 19:00:00.000000000 -0500 +++ newtree/Documentation/tp_smapi.txt 2006-09-30 05:49:34.000000000 -0400 @@ -0,0 +1,206 @@ +tp_smapi version 0.30 +IBM ThinkPad hardware functions driver + +Author: Shem Multinymous +Project: http://sourceforge.net/projects/tpctl +Wiki: http://thinkwiki.org/wiki/tp_smapi +List: linux-thinkpad@linux-thinkpad.org + (http://mailman.linux-thinkpad.org/mailman/listinfo/linux-thinkpad) + +Description +----------- + +ThinkPad laptops include a proprietary interface called SMAPI BIOS +(System Management Application Program Interface) which provides some +hardware control functionality that is not accessible by other means. + +This driver exposes some features of the SMAPI BIOS through a sysfs +interface. It is suitable for newer models, on which SMAPI is invoked +through IO port writes. Older models use a different SMAPI interface; +for those, try the "thinkpad" module from the "tpctl" package. + +WARNING: +This driver uses undocumented features and direct hardware access. +It thus cannot be guaranteed to work, and may cause arbitrary damage +(especially on models it wasn't tested on). + + +Module parameters +----------------- + +tp_smapi module: + debug=1 enables verbose dmesg output. + + +Usage +----- + +Control of battery charging thresholds (in percents of current full charge +capacity): + +# echo 40 > /sys/devices/platform/smapi/BAT0/start_charge_thresh +# echo 70 > /sys/devices/platform/smapi/BAT0/stop_charge_thresh +# cat /sys/devices/platform/smapi/BAT0/*_charge_thresh + + (This is useful since Li-Ion batteries wear out much faster at very + high or low charge levels. The driver will also keeps the thresholds + across suspend-to-disk with AC disconnected; this isn't done + automatically by the hardware.) + +Inhibiting battery charging for 17 minutes (overrides thresholds): + +# echo 17 > /sys/devices/platform/smapi/BAT0/inhibit_charge_minutes +# echo 0 > /sys/devices/platform/smapi/BAT0/inhibit_charge_minutes # stop +# cat /sys/devices/platform/smapi/BAT0/inhibit_charge_minutes + + (This can be used to control which battery is charged when using an + Ultrabay battery.) + +Forcing battery discharging even if AC power available: + +# echo 1 > /sys/devices/platform/smapi/BAT0/force_discharge # start discharge +# echo 0 > /sys/devices/platform/smapi/BAT0/force_discharge # stop discharge +# cat /sys/devices/platform/smapi/BAT0/force_discharge + + (This can be used to control which battery is discharged when using an + Ultrabay battery.) + +Misc read-only battery status (see note about HDAPS below): + +# cat /sys/devices/platform/smapi/BAT0/installed +# cat /sys/devices/platform/smapi/BAT0/state # idle/charging/discharging +# cat /sys/devices/platform/smapi/BAT0/cycle_count +# cat /sys/devices/platform/smapi/BAT0/current_now # instantaneous current +# cat /sys/devices/platform/smapi/BAT0/current_avg # last minute average +# cat /sys/devices/platform/smapi/BAT0/power_now # instantaneous power +# cat /sys/devices/platform/smapi/BAT0/power_avg # last minute average +# cat /sys/devices/platform/smapi/BAT0/last_full_capacity +# cat /sys/devices/platform/smapi/BAT0/remaining_percent +# cat /sys/devices/platform/smapi/BAT0/remaining_running_time +# cat /sys/devices/platform/smapi/BAT0/remaining_charging_time +# cat /sys/devices/platform/smapi/BAT0/remaining_capacity +# cat /sys/devices/platform/smapi/BAT0/design_capacity +# cat /sys/devices/platform/smapi/BAT0/voltage +# cat /sys/devices/platform/smapi/BAT0/design_voltage +# cat /sys/devices/platform/smapi/BAT0/manufacturer +# cat /sys/devices/platform/smapi/BAT0/model +# cat /sys/devices/platform/smapi/BAT0/barcoding +# cat /sys/devices/platform/smapi/BAT0/chemistry +# cat /sys/devices/platform/smapi/BAT0/serial +# cat /sys/devices/platform/smapi/BAT0/manufacture_date +# cat /sys/devices/platform/smapi/BAT0/first_use_date +# cat /sys/devices/platform/smapi/BAT0/temperature # in milli-Celsius +# cat /sys/devices/platform/smapi/ac_connected + + +You can also get a hex dump of the raw status data, which contains additional data +now in the above (if you can figure it out). Some unused values are autodetected +and replaced by "--": + +# cat /sys/devices/platform/smapi/BAT0/dump + +In all of the above, replace BAT0 with BAT1 to address the 2nd battery. + + +Controlling PCI bus power saving: + +# cat /sys/devices/platform/smapi/enable_pci_power_saving_on_boot +# echo 1 > /sys/devices/platform/smapi/enable_pci_power_saving_on_boot # on +# echo 0 > /sys/devices/platform/smapi/enable_pci_power_saving_on_boot # off + +This controls the "PCI bus power saving" option in the BIOS, and takes +effect at the next boot. On ThinkPad T43, this setting is stored in bit 0x40 +of NVRAM byte 0x39, and turning it off increases idle power consumption +by about 350mW. Out-of-the-box default is 1. + + +Raw SMAPI calls: + +/sys/devices/platform/smapi/smapi_request +This performs raw SMAPI calls. It uses a bad interface that cannot handle +multiple simultaneous access. Don't touch it, it's for development only. +If you did touch it, you would so something like +# echo '211a 100 0 0' > /sys/devices/platform/smapi/smapi_request +# cat /sys/devices/platform/smapi/smapi_request +and notice that in the output "211a 34b b2 0 0 0 'OK'", the "4b" in the 2nd +parameter, converted to decimal is 75: the current charge stop threshold. + + +Model-specific status +--------------------- + +Works (at least partially) on the following ThinkPad model: +* A30 +* G41 +* R40, R50p, R51, R52 +* T23, T40, T40p, T41, T41p, T42, T42p, T43, T43p, T60 +* X24, X31, X32, X40, X41, X60 +* Z60t, Z61m + +Not all functions are available on all models; for detailed status, see: + http://thinkwiki.org/wiki/tp_smapi + +Please report success/failure by e-mail or on the Wiki. +If you get a "not implemented" or "not supported" message, your laptop +probably just can't do that (at least not via the SMAPI BIOS). +For negative reports, follow the bug reporting guidelines below. +If you send me the necessary technical data (i.e., SMAPI function +interfaces), I will support additional models. + + +Bug reporting +------------- + +Mail . Please include: +* Details about your model, +* Relevant "dmesg" output. Make sure thinkpad_ec and tp_smapi are loaded with + the "debug=1" parameter (e.g., use "make load HDAPS=1 DEBUG=1"). +* Output of "dmidecode | grep -C5 Product" +* Does the failed functionality works under Windows? + + +Ideas for improvement +--------------------- +(The best way to get these done is to send a patch.) + +Don't create /sys files for unsupported functions, and don't access those +functions on suspend+resume (requires probing on module load or a huge +white/blacklist). + +Make inhibit_charge_minutes return the time left, not the time originally +set (as returned by the SMAPI BIOS). Requires remembering when +inhibit_charge_minutes was set and comparing to current time. + +Save and and restore inhibit_charge_minutes across suspend-to-disk, as done +for charge thresholds (requires the above time calculations too). + + +More about SMAPI +---------------- + +For hints about what may be possible via the SMAPI BIOS and how, see: + +* IBM Technical Reference Manual for the ThinkPad 770 + (http://www-307.ibm.com/pc/support/site.wss/document.do?lndocid=PFAN-3TUQQD) +* Exported symbols in PWRMGRIF.DLL or TPPWRW32.DLL (e.g., use "objdump -x"). +* drivers/char/mwave/smapi.c in the Linux kernel tree.* +* The "thinkpad" SMAPI module (http://tpctl.sourceforge.net). +* The SMAPI_* constants in tp_smapi.c (some of these are presently unused). + +Note that in the above Technical Reference and in the "thinkpad" module, +SMAPI is invoked through a function call to some physical address. However, +the interface used by tp_smapi and the above mwave drive, and apparently +required by newer ThinkPad, is different: you set the parameters up in the +CPU's registers and write to ports 0xB2 (the APM control port) and 0x4F; this +triggers an SMI (System Management Interrupt), causing the CPU to enter +SMM (System Management Mode) and run the BIOS firmware; the results are +returned in the CPU's registers. It is not clear what is the relation between +the two variants of SMAPI, though the assignment of error codes seems to be +similar. + +In addition, the embedded controller on ThinkPad laptops has a non-standard +interface at IO ports 0x1600-0x161F (mapped to LCP channel 3 of the H8S chip). +The interface provides various system management services (currently known: +battery information and accelerometer readouts). For more information see the +thinkpad_ec modul and the H8S hardware documentation: +http://documentation.renesas.com/eng/products/mpumcu/rej09b0300_2140bhm.pdf diff -urN oldtree/drivers/firmware/Kconfig newtree/drivers/firmware/Kconfig --- oldtree/drivers/firmware/Kconfig 2006-09-29 13:50:42.000000000 -0400 +++ newtree/drivers/firmware/Kconfig 2006-09-30 05:49:34.000000000 -0400 @@ -84,4 +84,19 @@ Say Y or M here to enable the driver for use by Dell systems management software such as Dell OpenManage. +config THINKPAD_EC + tristate + +config TP_SMAPI + tristate "ThinkPad SMAPI Support" + depends on X86 + select THINKPAD_EC + default n + help + This adds SMAPI support on IBM ThinkPads, mostly used for battery + charge control. For more information about this driver see + . + + If you have an IBM ThinkPad laptop, say Y or M here. + endmenu diff -urN oldtree/drivers/firmware/Makefile newtree/drivers/firmware/Makefile --- oldtree/drivers/firmware/Makefile 2006-09-29 13:50:42.000000000 -0400 +++ newtree/drivers/firmware/Makefile 2006-09-30 05:49:34.000000000 -0400 @@ -7,3 +7,5 @@ obj-$(CONFIG_EFI_PCDP) += pcdp.o obj-$(CONFIG_DELL_RBU) += dell_rbu.o obj-$(CONFIG_DCDBAS) += dcdbas.o +obj-$(CONFIG_THINKPAD_EC) += thinkpad_ec.o +obj-$(CONFIG_TP_SMAPI) += tp_smapi.o diff -urN oldtree/drivers/firmware/dmi_scan.c newtree/drivers/firmware/dmi_scan.c --- oldtree/drivers/firmware/dmi_scan.c 2006-09-29 14:03:20.000000000 -0400 +++ newtree/drivers/firmware/dmi_scan.c 2006-09-30 05:55:52.000000000 -0400 @@ -123,26 +123,26 @@ dev->type = *d++ & 0x7f; dev->name = dmi_string(dm, *d); dev->device_data = NULL; - list_add(&dev->list, &dmi_devices); - } + list_add(&dev->list, &dmi_devices); + } } static void __init dmi_save_oem_strings_devices(struct dmi_header *dm) { - int i, count = *(u8 *)(dm + 1); - struct dmi_device *dev; - - for (i = 1; i <= count; i++) { - dev = dmi_alloc(sizeof(*dev)); - if (!dev) { - printk(KERN_ERR - "dmi_save_oem_strings_devices: out of memory.\n"); - break; - } - - dev->type = DMI_DEV_TYPE_OEM_STRING; - dev->name = dmi_string(dm, i); - dev->device_data = NULL; + int i, count = *(u8 *)(dm + 1); + struct dmi_device *dev; + + for (i = 1; i <= count; i++) { + dev = dmi_alloc(sizeof(*dev)); + if (!dev) { + printk(KERN_ERR + "dmi_save_oem_strings_devices: out of memory.\n"); + break; + } + + dev->type = DMI_DEV_TYPE_OEM_STRING; + dev->name = dmi_string(dm, i); + dev->device_data = NULL; list_add(&dev->list, &dmi_devices); } @@ -201,9 +201,9 @@ case 10: /* Onboard Devices Information */ dmi_save_devices(dm); break; - case 11: /* OEM Strings */ - dmi_save_oem_strings_devices(dm); - break; + case 11: /* OEM Strings */ + dmi_save_oem_strings_devices(dm); + break; case 38: /* IPMI Device Information */ dmi_save_ipmi_device(dm); } diff -urN oldtree/drivers/firmware/thinkpad_ec.c newtree/drivers/firmware/thinkpad_ec.c --- oldtree/drivers/firmware/thinkpad_ec.c 1969-12-31 19:00:00.000000000 -0500 +++ newtree/drivers/firmware/thinkpad_ec.c 2006-09-30 05:49:34.000000000 -0400 @@ -0,0 +1,470 @@ +/* + * thinkpad_ec.c - coordinate access to ThinkPad-specific hardware resources + * + * The embedded controller on ThinkPad laptops has a non-standard interface + * at IO ports 0x1600-0x161F (mapped to LCP channel 3 of the H8S chip). + * The interface provides various system management services (currently + * known: battery information and accelerometer readouts). This driver + * provides access and mutual exclusion for the EC interface. + * For information about the LPC protocol and terminology, see: + * "H8S/2104B Group Hardware Manual", + * http://documentation.renesas.com/eng/products/mpumcu/rej09b0300_2140bhm.pdf + * + * Copyright (C) 2006 Shem Multinymous + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#define TP_VERSION "0.30" + +MODULE_AUTHOR("Shem Multinymous"); +MODULE_DESCRIPTION("ThinkPad embedded controller hardware access"); +MODULE_VERSION(TP_VERSION); +MODULE_LICENSE("GPL"); + +/* IO ports used by embedded controller LPC channel 3: */ +#define TPC_BASE_PORT 0x1600 +#define TPC_NUM_PORTS 0x20 +#define TPC_STR3_PORT 0x1604 /* Reads H8S EC register STR3 */ +#define TPC_TWR0_PORT 0x1610 /* Mapped to H8S EC register TWR0MW/SW */ +#define TPC_TWR15_PORT 0x161F /* Mapped to H8S EC register TWR15. */ + /* (and port TPC_TWR0_PORT+i is mapped to H8S reg TWRi for 00x%02x", \ + msg, args->val[0x0], args->val[0xF], code) + +/* State of request prefetching: */ +static u8 prefetch_arg0, prefetch_argF; /* Args of last prefetch */ +static u64 prefetch_jiffies; /* time of prefetch, or: */ +#define TPC_PREFETCH_NONE INITIAL_JIFFIES /* - No prefetch */ +#define TPC_PREFETCH_JUNK (INITIAL_JIFFIES+1) /* - Ignore prefetch */ + +/* Locking: */ + +static DECLARE_MUTEX(thinkpad_ec_mutex); + +/** + * thinkpad_ec_lock - get lock on the ThinkPad EC + * + * Get exclusive lock for accesing the ThinkPad embedded controller LPC3 + * interface. Returns 0 iff lock acquired. + */ +int thinkpad_ec_lock(void) +{ + int ret; + ret = down_interruptible(&thinkpad_ec_mutex); + return ret; +} + +EXPORT_SYMBOL_GPL(thinkpad_ec_lock); + +/** + * thinkpad_ec_try_lock - try getting lock on the ThinkPad EC + * + * Try getting an exclusive lock for accesing the ThinkPad embedded + * controller LPC3. Returns immediately if lock is not available; neither + * blocks nor sleeps. Returns 0 iff lock acquired . + */ +int thinkpad_ec_try_lock(void) +{ + return down_trylock(&thinkpad_ec_mutex); +} + +EXPORT_SYMBOL_GPL(thinkpad_ec_try_lock); + +/** + * thinkpad_ec_unlock - release lock on ThinkPad EC + * + * Release a previously acquired exclusive lock on the ThinkPad ebmedded + * controller LPC3 interface. + */ +void thinkpad_ec_unlock(void) +{ + up(&thinkpad_ec_mutex); +} + +EXPORT_SYMBOL_GPL(thinkpad_ec_unlock); + +/* Tell embedded controller to prepare a row */ +static int thinkpad_ec_request_row(const struct thinkpad_ec_row *args) +{ + u8 str3; + int i; + + /* EC protocol requires write to TWR0 (function code): */ + if (!(args->mask & 0x0001)) { + printk(KERN_ERR MSG_FMT("bad args->mask=0x%02x", args->mask)); + return -EINVAL; + } + + /* Check initial STR3 status: */ + str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK; + if (str3 & H8S_STR3_OBF3B) { /* data already pending */ + inb(TPC_TWR15_PORT); /* marks end of previous transaction */ + if (prefetch_jiffies == TPC_PREFETCH_NONE) + printk(KERN_WARNING + REQ_FMT("readout already pending", str3)); + return -EBUSY; /* EC will be ready in a few usecs */ + } else if (str3 == H8S_STR3_SWMF) { /* busy with previous request */ + if (prefetch_jiffies == TPC_PREFETCH_NONE) + printk(KERN_WARNING + REQ_FMT("EC handles previous request", str3)); + return -EBUSY; /* data will be pending in a few usecs */ + } else if (str3 != 0x00) { /* unexpected status? */ + printk(KERN_WARNING REQ_FMT("bad initial STR3", str3)); + return -EIO; + } + + /* Send TWR0MW: */ + outb(args->val[0], TPC_TWR0_PORT); + str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK; + if (str3 != H8S_STR3_MWMF) { /* not accepted? */ + printk(KERN_WARNING REQ_FMT("arg0 rejected", str3)); + return -EIO; + } + + /* Send TWR1 through TWR14: */ + for (i=1; imask>>i)&1) + outb(args->val[i], TPC_TWR0_PORT+i); + + /* Send TWR15 (default to 0x01). This marks end of command. */ + outb((args->mask & 0x8000) ? args->val[0xF] : 0x01, TPC_TWR15_PORT); + + /* Wait until EC starts writing its reply (~60ns on average). + * Releasing locks before this happens may cause an EC hang + * due to firmware bug! + */ + for (i=0; ival[0] = inb(TPC_TWR0_PORT); + /* Optionally read 14 more bytes: */ + for (i=1; imask >> i)&1) + data->val[i] = inb(TPC_TWR0_PORT+i); + /* Read last byte from 0x161F (signals end of read transaction): */ + data->val[0xF] = inb(TPC_TWR15_PORT); + + /* Readout still pending? */ + str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK; + if (str3 & H8S_STR3_OBF3B) + printk(KERN_WARNING + REQ_FMT("OBF3B=1 after read", str3)); + /* If port 0x161F returns 0x80 too often, the EC may lock up: */ + if (data->val[0xF] == 0x80) + printk(KERN_WARNING + REQ_FMT("0x161F reports error", data->val[0xF])); + return 0; +} + +/* Is the given row currently prefetched? + * To keep things simple we compare only the first and last args; + * in practice this suffices .*/ +static int thinkpad_ec_is_row_fetched(const struct thinkpad_ec_row *args) +{ + return (prefetch_jiffies != TPC_PREFETCH_NONE) && + (prefetch_jiffies != TPC_PREFETCH_JUNK) && + (prefetch_arg0 == args->val[0]) && + (prefetch_argF == args->val[0xF]) && + (get_jiffies_64() < prefetch_jiffies + TPC_PREFETCH_TIMEOUT); +} + +/** + * thinkpad_ec_read_row - request and read data from ThinkPad EC + * @args Input register arguments + * @data Output register values + * + * Read a data row from the ThinkPad embedded controller LPC3 interface. + * Does fetching and retrying if needed. The row args are specified by + * 16 byte arguments, some of which may be missing (but the first is + * mandatory). These are given in @args->val[], where @args->val[i] is + * used iff (@args->mask>>i)&1). The rows's data is stored in @data->val[], + * but is only guaranteed to be valid for indices corresponding to set + * bit in @data->mask. That is, if (@data->mask>>i)&1==0 then @data->val[i] + * may not be filled (to save time). + * + * Returns -EBUSY on transient error and -EIO on abnormal condition. + * Caller must hold controller lock. + */ +int thinkpad_ec_read_row(const struct thinkpad_ec_row *args, + struct thinkpad_ec_row *data) +{ + int retries, ret; + + if (thinkpad_ec_is_row_fetched(args)) + goto read_row; /* already requested */ + + /* Request the row */ + for (retries=0; retriesval[0x0]; + prefetch_argF = args->val[0xF]; + } + return ret; +} + +EXPORT_SYMBOL_GPL(thinkpad_ec_prefetch_row); + +/** + * thinkpad_ec_invalidate - invalidate prefetched ThinkPad EC data + * + * Invalidate the data prefetched via thinkpad_ec_prefetch_row() from the + * ThinkPad embedded controller LPC3 interface. + * Must be called before unlocking by any code that accesses the controller + * ports directly. + */ +void thinkpad_ec_invalidate(void) +{ + prefetch_jiffies = TPC_PREFETCH_JUNK; +} + +EXPORT_SYMBOL_GPL(thinkpad_ec_invalidate); + + +/*** Checking for EC hardware ***/ + +/* thinkpad_ec_test: + * Ensure the EC LPC3 channel really works on this machine by making + * an arbitrary harmless EC request and seeing if the EC follows protocol. + * This test writes to IO ports, so execute only after checking DMI. + */ +static int thinkpad_ec_test(void) +{ + int ret; + const struct thinkpad_ec_row args = /* battery 0 basic status */ + { .mask=0x8001, .val={0x01,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x00} }; + struct thinkpad_ec_row data = { .mask = 0x0000 }; + ret = thinkpad_ec_lock(); + if (ret) + return ret; + ret = thinkpad_ec_read_row(&args, &data); + thinkpad_ec_unlock(); + return ret; +} + +/* Search all DMI device names of a given type for a substring */ +static int __init dmi_find_substring(int type, const char *substr) +{ + struct dmi_device *dev = NULL; + while ((dev = dmi_find_device(type, NULL, dev))) { + if (strstr(dev->name, substr)) + return 1; + } + return 0; +} + +#define TP_DMI_MATCH(vendor,model) { \ + .ident = vendor " " model, \ + .matches = { \ + DMI_MATCH(DMI_BOARD_VENDOR, vendor), \ + DMI_MATCH(DMI_PRODUCT_VERSION, model) \ + } \ +} + +/* Check DMI for existence of ThinkPad embedded controller */ +static int __init check_dmi_for_ec(void) +{ + /* A few old models that have a good EC but don't report it in DMI */ + struct dmi_system_id tp_whitelist[] = { + TP_DMI_MATCH("IBM","ThinkPad A30"), + TP_DMI_MATCH("IBM","ThinkPad T23"), + TP_DMI_MATCH("IBM","ThinkPad X24"), + { .ident = NULL } + }; + return dmi_find_substring(DMI_DEV_TYPE_OEM_STRING, + "IBM ThinkPad Embedded Controller") || + dmi_check_system(tp_whitelist); +} + +/*** Init and cleanup ***/ + +static int __init thinkpad_ec_init(void) +{ + if (!check_dmi_for_ec()) { + printk(KERN_WARNING "thinkpad_ec: no ThinkPad embedded controller!\n"); + return -ENODEV; + } + + if (!request_region(TPC_BASE_PORT, TPC_NUM_PORTS, + "thinkpad_ec")) { + printk(KERN_ERR "thinkpad_ec: cannot claim io ports %#x-%#x\n", + TPC_BASE_PORT, + TPC_BASE_PORT + TPC_NUM_PORTS -1); + return -ENXIO; + } + prefetch_jiffies = TPC_PREFETCH_JUNK; + if (thinkpad_ec_test()) { + printk(KERN_ERR "thinkpad_ec: initial ec test failed\n"); + release_region(TPC_BASE_PORT, TPC_NUM_PORTS); + return -ENXIO; + } + printk(KERN_INFO "thinkpad_ec: thinkpad_ec " TP_VERSION " loaded.\n"); + return 0; +} + +static void __exit thinkpad_ec_exit(void) +{ + release_region(TPC_BASE_PORT, TPC_NUM_PORTS); + printk(KERN_INFO "thinkpad_ec: unloaded.\n"); +} + +module_init(thinkpad_ec_init); +module_exit(thinkpad_ec_exit); diff -urN oldtree/drivers/firmware/tp_smapi.c newtree/drivers/firmware/tp_smapi.c --- oldtree/drivers/firmware/tp_smapi.c 1969-12-31 19:00:00.000000000 -0500 +++ newtree/drivers/firmware/tp_smapi.c 2006-09-30 05:49:34.000000000 -0400 @@ -0,0 +1,1459 @@ +/* + * tp_smapi.c - ThinkPad SMAPI support + * + * This driver exposes some features of the System Management Application + * Program Interface (SMAPI) BIOS found on ThinkPad laptops. It works on + * models in which the SMAPI BIOS runs in SMM and is invoked by writing + * to the APM control port 0xB2. Older models use a different interface; + * for those, try the out-of-tree "thinkpad" module from "tpctl". + * It also exposes battery status information, obtained from the ThinkPad + * embedded controller (via the thinkpad_ec module). + * + * + * Copyright (C) 2006 Shem Multinymous . + * SMAPI access code based on the mwave driver by Mike Sullivan. + * + * 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 +#include +#include +#include +#include +#include /* CMOS defines */ +#include +#include +#include +#include +#include +#include + +#define TP_VERSION "0.30" +#define TP_DESC "ThinkPad SMAPI Support" +#define TP_DIR "smapi" + +MODULE_AUTHOR("Shem Multinymous"); +MODULE_DESCRIPTION(TP_DESC); +MODULE_VERSION(TP_VERSION); +MODULE_LICENSE("GPL"); + +static struct platform_device *pdev; + +static int tp_debug = 0; +module_param_named(debug, tp_debug, int, 0600); +MODULE_PARM_DESC(debug, "Debug level (0=off, 1=on)"); + +/* A few macros for printk()ing: */ +#define TPRINTK(level, fmt, args...) \ + dev_printk(level, &(pdev->dev), "%s: " fmt "\n", __func__, ## args) +#define DPRINTK(fmt, args...) \ + do { if (tp_debug) TPRINTK(KERN_DEBUG, fmt, ## args); } while(0) + +/********************************************************************* + * SMAPI interface + */ + +/* SMAPI functions (register BX when making the SMM call). */ +#define SMAPI_GET_INHIBIT_CHARGE 0x2114 +#define SMAPI_SET_INHIBIT_CHARGE 0x2115 +#define SMAPI_GET_THRESH_START 0x2116 +#define SMAPI_SET_THRESH_START 0x2117 +#define SMAPI_GET_FORCE_DISCHARGE 0x2118 +#define SMAPI_SET_FORCE_DISCHARGE 0x2119 +#define SMAPI_GET_THRESH_STOP 0x211a +#define SMAPI_SET_THRESH_STOP 0x211b +#define SMAPI_GET_PCI_BUS_POWER_SAVING 0x4004 +#define SMAPI_SET_PCI_BUS_POWER_SAVING 0x4005 + +/* SMAPI error codes (see ThinkPad 770 Technical Reference Manual p.83 at + http://www-307.ibm.com/pc/support/site.wss/document.do?lndocid=PFAN-3TUQQD */ +#define SMAPI_RETCODE_EOF 0xff +static struct {u8 rc; char *msg; int ret;} smapi_retcode[]= +{ + {0x00,"OK",0}, + {0x53,"SMAPI fuction is not available",-ENXIO}, + {0x81,"Invalid parameter",-EINVAL}, + {0x86,"Function is not supported by SMAPI BIOS",-ENOSYS}, + {0x90,"System error",-EIO}, + {0x91,"System is invalid",-EIO}, + {0x92,"System is busy,-EBUSY"}, + {0xa0,"Device error (disk read error)",-EIO}, + {0xa1,"Device is busy",-EBUSY}, + {0xa2,"Device is not attached",-ENXIO}, + {0xa3,"Device is disbled",-EIO}, + {0xa4,"Request parameter is out of range",-EINVAL}, + {0xa5,"Request parameter is not accepted",-EINVAL}, + {0xa6,"Transient error",-EBUSY}, /* ? */ + {SMAPI_RETCODE_EOF,"Unknown error code",-EIO} +}; + + +#define SMAPI_MAX_RETRIES 10 +#define SMAPI_PORT2 0x4F /* fixed port, meaning unclear */ +static unsigned short smapi_port = 0; /* APM control port, normally 0xB2 */ + +static DECLARE_MUTEX(smapi_mutex); + +/** + * find_smapi_port - read SMAPI port from NVRAM + */ +static int find_smapi_port(void) +{ + u16 smapi_id = 0; + unsigned short port = 0; + unsigned long flags; + + spin_lock_irqsave(&rtc_lock, flags); + smapi_id = CMOS_READ(0x7C); + smapi_id |= (CMOS_READ(0x7D) << 8); + spin_unlock_irqrestore(&rtc_lock, flags); + + if (smapi_id != 0x5349) { + printk(KERN_ERR "SMAPI not supported (ID=0x%x)\n", smapi_id); + return -ENXIO; + } + spin_lock_irqsave(&rtc_lock, flags); + port = CMOS_READ(0x7E); + port |= (CMOS_READ(0x7F) << 8); + spin_unlock_irqrestore(&rtc_lock, flags); + if (port == 0) { + printk(KERN_ERR "unable to read SMAPI port number\n"); + return -ENXIO; + } + return port; +} + +/** + * smapi_request - make a SMAPI call + * @inEBX, @inECX, @inEDI, @inESI: input registers + * @outEBX, @outECX, @outEDX, @outEDI, @outESI: outputs registers + * @msg: textual error message + * Invokes the SMAPI SMBIOS with the given input and outpu args. + * All outputs are optional (can be %NULL). + * Returns 0 when successful, and a negative errno constant + * (see smapi_retcode above) upon failure. + */ +static int smapi_request(u32 inEBX, u32 inECX, + u32 inEDI, u32 inESI, + u32 *outEBX, u32 *outECX, u32 *outEDX, + u32 *outEDI, u32 *outESI, const char** msg) +{ + int ret = 0; + int i; + int retries; + u8 rc; + /* Must use local vars for output regs, due to reg pressure. */ + u32 tmpEAX, tmpEBX, tmpECX, tmpEDX, tmpEDI, tmpESI; + + for (retries=0; retries>8)&0xFF; + for (i=0; smapi_retcode[i].rc!=SMAPI_RETCODE_EOF && + smapi_retcode[i].rc!=rc; ++i) {} + ret = smapi_retcode[i].ret; + if (msg) + *msg = smapi_retcode[i].msg; + + DPRINTK("req_out: AX=%x BX=%x CX=%x DX=%x DI=%x SI=%x r=%d", + tmpEAX, tmpEBX, tmpECX, tmpEDX, tmpEDI, tmpESI, ret); + if (ret) + TPRINTK(KERN_NOTICE, "SMAPI error: %s (func=%x)", + smapi_retcode[i].msg, inEBX); + + if (ret!=-EBUSY) + return ret; + } + return ret; +} + +/* Convenience wrapper: discard output arguments */ +static int smapi_write(u32 inEBX, u32 inECX, + u32 inEDI, u32 inESI, const char **msg) +{ + return smapi_request(inEBX, inECX, inEDI, inESI, + NULL, NULL, NULL, NULL, NULL, msg); +} + + +/********************************************************************* + * Specific SMAPI services + * All of these functions return 0 upon success, and a negative errno + * constant (see smapi_retcode) on failure. + */ + +enum thresh_type { + THRESH_STOP = 0, /* the code assumes this is 0 for brevity */ + THRESH_START +}; +#define THRESH_NAME(which) ( (which==THRESH_START)?"start":"stop" ) + +/** + * __get_real_thresh - read battery charge start/stop threshold from SMAPI + * @bat: battery number (0 or 1) + * @which: THRESH_START or THRESH_STOP + * @thresh: 1..99, 0=default 1..99, 0=default (pass this as-is to SMAPI) + * @outEDI: some additional state that needs to be preserved, meaning unknown + * @outESI: some additional state that needs to be preserved, meaning unknown + */ +static int __get_real_thresh(int bat, enum thresh_type which, int *thresh, + u32 *outEDI, u32 *outESI) +{ + u32 ebx = (which==THRESH_START) ? SMAPI_GET_THRESH_START + : SMAPI_GET_THRESH_STOP; + u32 ecx = (bat+1)<<8; + const char* msg; + int ret = smapi_request(ebx, ecx, 0, 0, NULL, &ecx, NULL, outEDI, outESI, &msg); + if (ret) { + TPRINTK(KERN_NOTICE, "cannot get %s_thresh of bat=%d: %s", + THRESH_NAME(which), bat, msg); + return ret; + } + if (!(ecx&0x00000100)) { + TPRINTK(KERN_NOTICE, "cannot get %s_thresh of bat=%d: ecx=0%x", + THRESH_NAME(which), bat, ecx); + return -EIO; + } + if (thresh) + *thresh = ecx&0xFF; + return 0; +} + +/** + * get_real_thresh - read battery charge start/stop threshold from SMAPI + * @bat: battery number (0 or 1) + * @which: THRESH_START or THRESH_STOP + * @thresh: 1..99, 0=default (passes as-is to SMAPI) + */ +static int get_real_thresh(int bat, enum thresh_type which, int *thresh) +{ + return __get_real_thresh(bat, which, thresh, NULL, NULL); +} + +/** + * set_real_thresh - write battery start/top charge threshold to SMAPI + * @bat: battery number (0 or 1) + * @which: THRESH_START or THRESH_STOP + * @thresh: 1..99, 0=default (passes as-is to SMAPI) + */ +static int set_real_thresh(int bat, enum thresh_type which, int thresh) +{ + u32 ebx = (which==THRESH_START) ? SMAPI_SET_THRESH_START + : SMAPI_SET_THRESH_STOP; + u32 ecx = ((bat+1)<<8) + thresh; + u32 getDI, getSI; + const char* msg; + int ret; + + /* verify read before writing */ + ret = __get_real_thresh(bat, which, NULL, &getDI, &getSI); + if (ret) + return ret; + + ret = smapi_write(ebx, ecx, getDI, getSI, &msg); + if (ret) + TPRINTK(KERN_NOTICE, "set %s to %d for bat=%d failed: %s", + THRESH_NAME(which), thresh, bat, msg); + else + TPRINTK(KERN_INFO, "set %s to %d for bat=%d", + THRESH_NAME(which), thresh, bat); + return ret; +} + +/** + * __get_inhibit_charge_minutes - get inhibit charge period from SMAPI + * @bat: battery number (0 or 1) + * @minutes: period in minutes (1..65535 minutes, 0=disabled) + * @outECX: some additional state that needs to be preserved, meaning unknown + * Note that @minutes is the originally set value, it does not count down. + */ +static int __get_inhibit_charge_minutes(int bat, int *minutes, u32 *outECX) +{ + u32 ecx = (bat+1)<<8; + u32 esi; + const char* msg; + int ret = smapi_request(SMAPI_GET_INHIBIT_CHARGE, ecx, 0, 0, + NULL, &ecx, NULL, NULL, &esi, &msg); + if (ret) { + TPRINTK(KERN_NOTICE, "failed for bat=%d: %s", bat, msg); + return ret; + } + if (!(ecx&0x0100)) { + TPRINTK(KERN_NOTICE, "bad ecx=0x%x for bat=%d", ecx, bat); + return -EIO; + } + if (minutes) + *minutes = (ecx&0x0001)?esi:0; + if (outECX) + *outECX = ecx; + return 0; +} + +/** + * get_inhibit_charge_minutes - get inhibit charge period from SMAPI + * @bat: battery number (0 or 1) + * @minutes: period in minutes (1..65535 minutes, 0=disabled) + * Note that @minutes is the originally set value, it does not count down. + */ +static int get_inhibit_charge_minutes(int bat, int *minutes) +{ + return __get_inhibit_charge_minutes(bat, minutes, NULL); +} + +/** + * set_inhibit_charge_minutes - write inhibit charge period to SMAPI + * @bat: battery number (0 or 1) + * @minutes: period in minutes (1..65535 minutes, 0=disabled) + */ +static int set_inhibit_charge_minutes(int bat, int minutes) +{ + u32 ecx; + const char* msg; + int ret; + + /* verify read before writing */ + ret = __get_inhibit_charge_minutes(bat, NULL, &ecx); + if (ret) + return ret; + + ecx = ((bat+1)<<8) | (ecx&0x00FE) | (minutes>0 ? 0x0001 : 0x0000); + if (minutes>0xFFFF) + minutes=0xFFFF; + ret = smapi_write(SMAPI_SET_INHIBIT_CHARGE, ecx, 0, minutes, &msg); + if (ret) + TPRINTK(KERN_NOTICE, + "set to %d failed for bat=%d: %s", minutes, bat, msg); + else + TPRINTK(KERN_INFO, "set to %d for bat=%d\n", minutes, bat); + return ret; +} + + +/** + * get_force_discharge - get status of forced discharging from SMAPI + * @bat: battery number (0 or 1) + * @enabled: 1 if forced discharged is enabled, 0 if not + */ +static int get_force_discharge(int bat, int *enabled) +{ + u32 ecx = (bat+1)<<8; + const char* msg; + int ret = smapi_request(SMAPI_GET_FORCE_DISCHARGE, ecx, 0, 0, + NULL, &ecx, NULL, NULL, NULL, &msg); + if (ret) { + TPRINTK(KERN_NOTICE, "failed for bat=%d: %s", bat, msg); + return ret; + } + *enabled = (!(ecx&0x00000100) && (ecx&0x00000001))?1:0; + return 0; +} + +/** + * set_force_discharge - write status of forced discharging to SMAPI + * @bat: battery number (0 or 1) + * @enabled: 1 if forced discharged is enabled, 0 if not + */ +static int set_force_discharge(int bat, int enabled) +{ + u32 ecx = (bat+1)<<8; + const char* msg; + int ret = smapi_request(SMAPI_GET_FORCE_DISCHARGE, ecx, 0, 0, + NULL, &ecx, NULL, NULL, NULL, &msg); + if (ret) { + TPRINTK(KERN_NOTICE, "get failed for bat=%d: %s", bat, msg); + return ret; + } + if (ecx&0x00000100) { + TPRINTK(KERN_NOTICE, "cannot force discharge bat=%d", bat); + return -EIO; + } + + ecx = ((bat+1)<<8) | (ecx&0x000000FA) | (enabled?0x00000001:0); + ret = smapi_write(SMAPI_SET_FORCE_DISCHARGE, ecx, 0, 0, &msg); + if (ret) + TPRINTK(KERN_NOTICE, "set to %d failed for bat=%d: %s", + enabled, bat, msg); + else + TPRINTK(KERN_INFO, "set to %d for bat=%d", enabled, bat); + return ret; +} + +/** + * get_enable_pci_power_saving_on_boot - get PCI power enable status via SMAPI + * @on: 1 iff PCI bus power saving will be enabled in the next reboot + */ +static int get_enable_pci_power_saving_on_boot(int *on) +{ + u32 ebx, esi; + const char* msg; + int ret = smapi_request(SMAPI_GET_PCI_BUS_POWER_SAVING, 0,0,0, + &ebx, NULL, NULL, NULL, &esi, &msg); + if (ret) { + TPRINTK(KERN_NOTICE, "failed: %s",msg); + return ret; + } + if (!(ebx & 0x00000001)) { + TPRINTK(KERN_NOTICE, "unknown status ebx==0x%x esi==0x%x", + ebx, esi); + return -EIO; + } + *on = esi & 0x00000001; + return 0; +} + +/** + * set_enable_pci_power_saving_on_boot - set PCI power enable status via SMAPI + * @on: 1 iff PCI bus power saving should be enabled in the next reboot + */ +static int set_enable_pci_power_saving_on_boot(int on) +{ + u32 ecx, edi, esi; + const char* msg; + int ret = smapi_request(SMAPI_GET_PCI_BUS_POWER_SAVING, 0,0,0, + NULL, &ecx, NULL, &edi, &esi, &msg); + if (ret) { + TPRINTK(KERN_NOTICE, "get failed: %s", msg); + return ret; + } + + esi = (esi & 0xFFFE) | (on ? 0x0001 : 0x0000); + ret = smapi_write(SMAPI_SET_PCI_BUS_POWER_SAVING, + ecx, edi, esi, &msg); + if (ret) + TPRINTK(KERN_NOTICE, "set failed: %s", msg); + return ret; +} + +/********************************************************************* + * Wrappers to threshold-related SMAPI functions, which handle default + * thresholds and related quirks. + */ + +/* Minimum, default and minimum difference for battery charging thresholds: */ +#define MIN_THRESH_DELTA 4 /* Min delta between start and stop thresh */ +#define MIN_THRESH_START 2 +#define MAX_THRESH_START (100-MIN_THRESH_DELTA) +#define MIN_THRESH_STOP (MIN_THRESH_START + MIN_THRESH_DELTA) +#define MAX_THRESH_STOP 100 +#define DEFAULT_THRESH_START MAX_THRESH_START +#define DEFAULT_THRESH_STOP MAX_THRESH_STOP + +/* The GUI of IBM's Battery Maximizer seems to show a start threshold that + * is 1 more than the value we set/get via SMAPI. Since the threshold is + * maintained across reboot, this can be confusing. So we kludge our + * interface for interoperability: */ +#define BATMAX_FIX 1 + +/* Get charge start/stop threshold (1..100), + * substituting default values if needed and applying BATMAT_FIX. */ +static int get_thresh(int bat, enum thresh_type which, int *thresh) { + int ret = get_real_thresh(bat, which, thresh); + if (ret) + return ret; + if (*thresh==0) + *thresh = (which==THRESH_START) ? DEFAULT_THRESH_START + : DEFAULT_THRESH_STOP; + else if (which==THRESH_START) + *thresh += BATMAX_FIX; + return 0; +} + + +/* Set charge start/stop threshold (1..100), + * substituting default values if needed and applying BATMAT_FIX. */ +static int set_thresh(int bat, enum thresh_type which, int thresh) +{ + if (which==THRESH_STOP && thresh==DEFAULT_THRESH_STOP) + thresh = 0; /* 100 is out of range, but default means 100 */ + if (which==THRESH_START) + thresh -= BATMAX_FIX; + return set_real_thresh(bat, which, thresh); +} + +/********************************************************************* + * ThinkPad embedded controller readout and basic functions + */ + +/** + * read_tp_ec_row - read data row from the ThinkPad embedded controller + * @arg0: EC command code + * @bat: battery number, 0 or 1 + * @j: the byte value to be used for "junk" (unused) input/outputs + * @dataval: result vector + */ +static int read_tp_ec_row(u8 arg0, int bat, u8 j, u8* dataval) { + int ret; + const struct thinkpad_ec_row args = { .mask=0xFFFF, + .val={arg0, j,j,j,j,j,j,j,j,j,j,j,j,j,j, (u8)bat} }; + struct thinkpad_ec_row data = { .mask = 0xFFFF }; + + ret = thinkpad_ec_lock(); + if (ret) + return ret; + ret = thinkpad_ec_read_row(&args, &data); + thinkpad_ec_unlock(); + memcpy(dataval, &data.val, TP_CONTROLLER_ROW_LEN); + return ret; +} + +/** + * power_device_present - check for presence of battery or AC power + * @bat: 0 for battery 0, 1 for battery 1, otherwise AC power + * Returns 1 if present, 0 if not present, negative if error. + */ +static int power_device_present(int bat) +{ + u8 row[TP_CONTROLLER_ROW_LEN]; + u8 test; + int ret = read_tp_ec_row(1, bat, 0, row); + if (ret) + return ret; + if (bat==0) + test=0x40; + else if (bat==1) + test=0x20; + else + test=0x80; /* AC power */ + return (row[0] & test) ? 1 : 0; +} + +/** + * bat_has_status - check if battery can report detailed status + * @bat: 0 for battery 0, 1 for battery 1 + * Returns 1 if yes, 0 if no, negative if error. + */ +static int bat_has_status(int bat) +{ + u8 row[TP_CONTROLLER_ROW_LEN]; + int ret = read_tp_ec_row(1, bat, 0, row); + if (ret) + return ret; + if ((row[0] & (bat?0x20:0x40)) == 0) /* no battery */ + return 0; + if ((row[1] & (0x60)) == 0) /* no status */ + return 0; + return 1; +} + +/** + * get_tp_ec_bat_16 - read a 16-bit value from EC battery status data + * @arg0: first argument to EC + * @off: offset in row returned from EC + * @bat: battery (0 or 1) + * @val: the 16-bit value obtained + * Returns nonzero on error. + */ +static int get_tp_ec_bat_16(u8 arg0, int offset, int bat, u16 *val) +{ + u8 row[TP_CONTROLLER_ROW_LEN]; + int ret; + if (bat_has_status(bat)!=1) + return -ENXIO; + ret = read_tp_ec_row(arg0, bat, 0, row); + if (ret) + return ret; + *val = *(u16*)(row+offset); + return 0; +} + +/********************************************************************* + * sysfs attributes for batteries - + * definitions and helper functions + */ + +/* Define a custom device attribute struct which adds a battery number */ +struct bat_device_attribute { + struct device_attribute dev_attr; + int bat; +}; + +/* Get the battery to which the attribute belongs */ +static int attr_get_bat(struct device_attribute *attr) { + return container_of(attr, struct bat_device_attribute, dev_attr)->bat; +} + +/** + * show_tp_ec_bat_u16 - show an unsigned 16-bit battery attribute + * @arg0: specified 1st argument of EC raw to read + * @offset: byte offset in EC raw data + * @mul: correction factor to multiply by + * @na_msg: string to output is value not available (0xFFFFFFFF) + * @attr: battery attribute + * @buf: output buffer + * The 16-bit value is read from the EC, treated as unsigned, + * transformed as x->mul*x, and printed to the buffer. + * If the value is 0xFFFFFFFF and na_msg!=%NULL, na_msg is printed instead. + */ +static int show_tp_ec_bat_u16(u8 arg0, int offset, int mul, + const char* na_msg, + struct device_attribute *attr, char *buf) +{ + u16 val; + int ret = get_tp_ec_bat_16(arg0, offset, attr_get_bat(attr), &val); + if (ret) + return ret; + if (na_msg && val == 0xFFFF) + return sprintf(buf, "%s\n", na_msg); + else + return sprintf(buf, "%u\n", mul*(unsigned int)val); +} + +/** + * show_tp_ec_bat_s16 - show an signed 16-bit battery attribute + * @arg0: specified 1st argument of EC raw to read + * @offset: byte offset in EC raw data + * @mul: correction factor to multiply by + * @add: correction term to add after multiplication + * @attr: battery attribute + * @buf: output buffer + * The 16-bit value is read from the EC, treated as signed, + * transformed as x->mul*x+add, and printed to the buffer. + */ +static int show_tp_ec_bat_s16(u8 arg0, int offset, int mul, int add, + struct device_attribute *attr, char *buf) +{ + u16 val; + int ret = get_tp_ec_bat_16(arg0, offset, attr_get_bat(attr), &val); + if (ret) + return ret; + return sprintf(buf, "%d\n", mul*(s16)val+add); +} + +/** + * show_tp_ec_bat_str - show a string from EC battery status data + * @arg0: specified 1st argument of EC raw to read + * @offset: byte offset in EC raw data + * @maxlen: maximum string length + * @attr: battery attribute + * @buf: output buffer + */ +static int show_tp_ec_bat_str(u8 arg0, int offset, int maxlen, + struct device_attribute *attr, char *buf) +{ + int bat = attr_get_bat(attr); + u8 row[TP_CONTROLLER_ROW_LEN]; + int ret; + if (bat_has_status(bat)!=1) + return -ENXIO; + ret = read_tp_ec_row(arg0, bat, 0, row); + if (ret) + return ret; + strncpy(buf, (char*)row+offset, maxlen); + buf[maxlen] = 0; + strcat(buf, "\n"); + return strlen(buf); +} + +/** + * show_tp_ec_bat_power - show a power readout from EC battery status data + * @arg0: specified 1st argument of EC raw to read + * @offV: byte offset of voltage in EC raw data + * @offI: byte offset of current in EC raw data + * @attr: battery attribute + * @buf: output buffer + * Computes the power as current*voltage from the two given readout offsets. + */ +static int show_tp_ec_bat_power(u8 arg0, int offV, int offI, + struct device_attribute *attr, char *buf) +{ + u8 row[TP_CONTROLLER_ROW_LEN]; + int milliamp, millivolt, ret; + int bat = attr_get_bat(attr); + if (bat_has_status(bat)!=1) + return -ENXIO; + ret = read_tp_ec_row(1, bat, 0, row); + if (ret) + return ret; + millivolt = *(u16*)(row+offV); + milliamp = *(s16*)(row+offI); + return sprintf(buf, "%d\n", milliamp*millivolt/1000); /* type: mW */ +} + +/** + * show_tp_ec_bat_date - decode and show a date from EC battery status data + * @arg0: specified 1st argument of EC raw to read + * @offset: byte offset in EC raw data + * @attr: battery attribute + * @buf: output buffer + */ +static int show_tp_ec_bat_date(u8 arg0, int offset, + struct device_attribute *attr, char *buf) +{ + u8 row[TP_CONTROLLER_ROW_LEN]; + u16 v; + int ret; + int day, month, year; + int bat = attr_get_bat(attr); + if (bat_has_status(bat)!=1) + return -ENXIO; + ret = read_tp_ec_row(arg0, bat, 0, row); + if (ret) + return ret; + + /* Decode bit-packed: v = day | (month<<5) | ((year-1980)<<9) */ + v = *(u16*)(row+offset); + day = v & 0x1F; + month = (v >> 5) & 0xF; + year = (v >> 9) + 1980; + + return sprintf(buf, "%04d-%02d-%02d\n", year, month, day); +} + + +/********************************************************************* + * sysfs attribute I/O for batteries - + * the actual attribute show/store functions + */ + +static int show_battery_start_charge_thresh(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int thresh; + int bat = attr_get_bat(attr); + int ret = get_thresh(bat, THRESH_START, &thresh); + if (ret) + return ret; + return sprintf(buf, "%d\n", thresh); /* type: percent */ +} + +static int show_battery_stop_charge_thresh(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int thresh; + int bat = attr_get_bat(attr); + int ret = get_thresh(bat, THRESH_STOP, &thresh); + if (ret) + return ret; + return sprintf(buf, "%d\n", thresh); /* type: percent */ +} + +/** + * store_battery_start_charge_thresh - store battery_start_charge_thresh attr + * Since this is a kernel<->user interface, we ensure a valid state for + * the hardware. We do this by clamping the requested threshold to the + * valid range and, if necessary, moving the other threshold so that + * it's MIN_THRESH_DELTA away from this one. + */ +static int store_battery_start_charge_thresh(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int thresh, other_thresh, ret; + int bat = attr_get_bat(attr); + + if (sscanf(buf, "%d", &thresh)!=1 || thresh<1 || thresh>100) + return -EINVAL; + + if (thresh < MIN_THRESH_START) /* clamp up to MIN_THRESH_START */ + thresh = MIN_THRESH_START; + if (thresh > MAX_THRESH_START) /* clamp down to MAX_THRESH_START */ + thresh = MAX_THRESH_START; + + down(&smapi_mutex); + ret = get_thresh(bat, THRESH_STOP, &other_thresh); + if (ret!=-ENOSYS) { + if (ret) /* other threshold is set? */ + goto out; + ret = get_real_thresh(bat, THRESH_START, NULL); + if (ret) /* this threshold is set? */ + goto out; + if (other_thresh < thresh+MIN_THRESH_DELTA) { + /* move other thresh to keep it above this one */ + ret = set_thresh(bat, THRESH_STOP, + thresh+MIN_THRESH_DELTA); + if (ret) + goto out; + } + } + ret = set_thresh(bat, THRESH_START, thresh); +out: + up(&smapi_mutex); + return count; + +} + +/** + * store_battery_stop_charge_thresh - store battery_stop_charge_thresh attr + * Since this is a kernel<->user interface, we ensure a valid state for + * the hardware. We do this by clamping the requested threshold to the + * valid range and, if necessary, moving the other threshold so that + * it's MIN_THRESH_DELTA away from this one. + */ +static int store_battery_stop_charge_thresh(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int thresh, other_thresh, ret; + int bat = attr_get_bat(attr); + + if (sscanf(buf, "%d", &thresh)!=1 || thresh<1 || thresh>100) + return -EINVAL; + + if (thresh=thresh-MIN_THRESH_DELTA) { + /* move other thresh to be below this one */ + ret = set_thresh(bat, THRESH_START, + thresh-MIN_THRESH_DELTA); + if (ret) + goto out; + } + } + ret = set_thresh(bat, THRESH_STOP, thresh); +out: + up(&smapi_mutex); + return count; +} + +static int show_battery_inhibit_charge_minutes(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int minutes; + int bat = attr_get_bat(attr); + int ret = get_inhibit_charge_minutes(bat, &minutes); + if (ret) + return ret; + return sprintf(buf, "%d\n", minutes); /* type: minutes */ +} + +static int store_battery_inhibit_charge_minutes(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + int minutes; + int bat = attr_get_bat(attr); + if (sscanf(buf, "%d", &minutes)!=1 || minutes<0) { + TPRINTK(KERN_ERR, "inhibit_charge_minutes: " + "must be a non-negative integer"); + return -EINVAL; + } + ret = set_inhibit_charge_minutes(bat, minutes); + if (ret) + return ret; + return count; +} + +static int show_battery_force_discharge(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int enabled; + int bat = attr_get_bat(attr); + int ret = get_force_discharge(bat, &enabled); + if (ret) + return ret; + return sprintf(buf, "%d\n", enabled); /* type: boolean */ +} + +static int store_battery_force_discharge(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int ret; + int enabled; + int bat = attr_get_bat(attr); + if (sscanf(buf, "%d", &enabled)!=1 || enabled<0 || enabled>1) + return -EINVAL; + ret = set_force_discharge(bat, enabled); + if (ret) + return ret; + return count; +} + +static int show_battery_installed( + struct device *dev, struct device_attribute *attr, char *buf) +{ + int bat = attr_get_bat(attr); + int ret = power_device_present(bat); + if (ret<0) + return ret; + return sprintf(buf, "%d\n", ret); /* type: boolean */ +} + +static int show_battery_state( + struct device *dev, struct device_attribute *attr, char *buf) +{ + u8 row[TP_CONTROLLER_ROW_LEN]; + const char* txt; + int ret; + int bat = attr_get_bat(attr); + if (bat_has_status(bat)!=1) + return sprintf(buf, "none\n"); + ret = read_tp_ec_row(1, bat, 0, row); + if (ret) + return ret; + switch (row[1] & 0xf0) { + case 0xc0: txt = "idle"; break; + case 0xd0: txt = "discharging"; break; + case 0xe0: txt = "charging"; break; + default: return sprintf(buf, "unknown (0x%x)\n", row[1]); + } + return sprintf(buf, "%s\n", txt); /* type: string from fixed set */ +} + +static int show_battery_manufacturer( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return show_tp_ec_bat_str(4, 2, TP_CONTROLLER_ROW_LEN-2, attr, buf); +} + +static int show_battery_model( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return show_tp_ec_bat_str(5, 2, TP_CONTROLLER_ROW_LEN-2, attr, buf); +} + +static int show_battery_barcoding( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return show_tp_ec_bat_str(7, 2, TP_CONTROLLER_ROW_LEN-2, attr, buf); +} + +static int show_battery_chemistry( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return show_tp_ec_bat_str(6, 2, 5, attr, buf); +} + +static int show_battery_voltage( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return show_tp_ec_bat_u16(1, 6, 1, NULL, attr, buf); /* type: mV */ +} + +static int show_battery_design_voltage( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return show_tp_ec_bat_u16(3, 4, 1, NULL, attr, buf); /* type: mV */ +} + +static int show_battery_current_now( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return show_tp_ec_bat_s16(1, 8, 1, 0, attr, buf); /* type: mA */ +} + +static int show_battery_current_avg( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return show_tp_ec_bat_s16(1, 10, 1, 0, attr, buf); /* type: mA */ +} + +static int show_battery_power_now( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return show_tp_ec_bat_power(1, 6, 8, attr, buf); /* type: mW */ +} + +static int show_battery_power_avg( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return show_tp_ec_bat_power(1, 6, 10, attr, buf); /* type: mW */ +} + +static int show_battery_remaining_percent( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return show_tp_ec_bat_u16(1, 12, 1, NULL, attr, buf); /* type: % */ +} + +static int show_battery_remaining_charging_time( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return show_tp_ec_bat_u16(2, 8, 1, "not_charging", + attr, buf); /* type: minutes */ +} + +static int show_battery_remaining_running_time( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return show_tp_ec_bat_u16(2, 6, 1, "not_discharging", + attr, buf); /* type: minutes */ +} + +static int show_battery_remaining_capacity( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return show_tp_ec_bat_u16(1, 14, 10, "", attr, buf); /* type: mWh */ +} + +static int show_battery_last_full_capacity( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return show_tp_ec_bat_u16(2, 2, 10, "", attr, buf); /* type: mWh */ +} + +static int show_battery_design_capacity( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return show_tp_ec_bat_u16(3, 2, 10, "", attr, buf); /* type: mWh */ +} + +static int show_battery_cycle_count( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return show_tp_ec_bat_u16(2, 12, 1, "", attr, buf); /* type: ordinal */ +} + +static int show_battery_temperature( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return show_tp_ec_bat_s16(1, 4, 100, -273100, attr, buf); /* type: millicelsius */ +} + +static int show_battery_serial( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return show_tp_ec_bat_u16(3, 10, 1, "", attr, buf); /* type: int */ +} + +static int show_battery_manufacture_date( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return show_tp_ec_bat_date(3, 8, attr, buf); /* type: YYYY-MM-DD */ +} + +static int show_battery_first_use_date( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return show_tp_ec_bat_date(8, 2, attr, buf); /* type: YYYY-MM-DD */ +} + +/** + * show_battery_dump - show the battery_dump attribute + * The battery_dump attribute gives a hex dump of all EC readouts related to + * a battery. Some of the enumerated values don't exist (i.e., the EC function + * does not touch a register); we use a kludge to detect and denote these. + */ +#define MIN_DUMP_ARG0 0x00 +#define MAX_DUMP_ARG0 0x0a /* 0x0b is useful too but hangs old EC firmware */ +static int show_battery_dump( + struct device *dev, struct device_attribute *attr, char *buf) +{ + int i; + char* p = buf; + int bat = attr_get_bat(attr); + u8 arg0; /* first argument to EC */ + u8 rowa[TP_CONTROLLER_ROW_LEN], rowb[TP_CONTROLLER_ROW_LEN]; + const u8 junka=0xAA, junkb=0x55; /* junk values for testing changes */ + int ret; + + for (arg0=MIN_DUMP_ARG0; arg0<=MAX_DUMP_ARG0; ++arg0) { + if ( (p-buf) > PAGE_SIZE-TP_CONTROLLER_ROW_LEN*5 ) + return -ENOMEM; /* don't overflow sysfs buf */ + /* Read raw twice with different junk values, + * to detect unused output bytes which are left unchaged: */ + ret = read_tp_ec_row(arg0, bat, junka, rowa); + if (ret) + return ret; + ret = read_tp_ec_row(arg0, bat, junkb, rowb); + if (ret) + return ret; + for (i=0; i1) + return -EINVAL; + ret = set_enable_pci_power_saving_on_boot(on); + if (ret) + return ret; + return count; +} + +/********************************************************************* + * The the "smapi_request" sysfs attribute executes a raw SMAPI call. + * You write to make a request and read to get the result. The state + * is saved globally rather than per fd (sysfs limitation), so + * simultaenous requests may get each other's results! So this is for + * development and debugging only. + */ +#define MAX_SMAPI_ATTR_ANSWER_LEN 128 +static char smapi_attr_answer[MAX_SMAPI_ATTR_ANSWER_LEN] = ""; + +static int show_smapi_request(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret = snprintf(buf, PAGE_SIZE, "%s", smapi_attr_answer); + smapi_attr_answer[0] = '\0'; + return ret; +} + +static int store_smapi_request(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned int inEBX, inECX, inEDI, inESI; + u32 outEBX, outECX, outEDX, outEDI, outESI; + const char* msg; + int ret; + if (sscanf(buf, "%x %x %x %x", &inEBX, &inECX, &inEDI, &inESI) != 4) { + smapi_attr_answer[0] = '\0'; + return -EINVAL; + } + ret = smapi_request( + inEBX, inECX, inEDI, inESI, + &outEBX, &outECX, &outEDX, &outEDI, &outESI, &msg); + snprintf(smapi_attr_answer, MAX_SMAPI_ATTR_ANSWER_LEN, + "%x %x %x %x %x %d '%s'\n", + (unsigned int)outEBX, (unsigned int)outECX, (unsigned int)outEDX, + (unsigned int)outEDI, (unsigned int)outESI, ret, msg); + if (ret) + return ret; + else + return count; +} + +/********************************************************************* + * Power management: the embedded controller forgets the battery + * thresholds when the system is suspended to disk and unplugged from + * AC and battery, so we restore it upon resume. + */ + +static int saved_threshs[4] = {-1, -1, -1, -1}; /* -1 = don't know */ + +static int tp_suspend(struct platform_device *dev, pm_message_t state) +{ + if (get_real_thresh(0, THRESH_STOP , &saved_threshs[0])) + saved_threshs[0]=-1; + if (get_real_thresh(0, THRESH_START, &saved_threshs[1])) + saved_threshs[1]=-1; + if (get_real_thresh(1, THRESH_STOP , &saved_threshs[2])) + saved_threshs[2]=-1; + if (get_real_thresh(1, THRESH_START, &saved_threshs[3])) + saved_threshs[3]=-1; + DPRINTK("suspend saved: %d %d %d %d", saved_threshs[0], + saved_threshs[1], saved_threshs[2], saved_threshs[3]); + return 0; +} + +static int tp_resume(struct platform_device *dev) +{ + DPRINTK("resume restoring: %d %d %d %d", saved_threshs[0], + saved_threshs[1], saved_threshs[2], saved_threshs[3]); + if (saved_threshs[0]>=0) + set_real_thresh(0, THRESH_STOP , saved_threshs[0]); + if (saved_threshs[1]>=0) + set_real_thresh(0, THRESH_START, saved_threshs[1]); + if (saved_threshs[2]>=0) + set_real_thresh(1, THRESH_STOP , saved_threshs[2]); + if (saved_threshs[3]>=0) + set_real_thresh(1, THRESH_START, saved_threshs[3]); + return 0; +} + + +/********************************************************************* + * Driver model + */ + +static struct platform_driver tp_driver = { + .suspend = tp_suspend, + .resume = tp_resume, + .driver = { + .name = "smapi", + .owner = THIS_MODULE + }, +}; + + +/********************************************************************* + * Sysfs device model + */ + +/* Attributes in /sys/devices/platform/smapi/ */ + +static DEVICE_ATTR(ac_connected, 0444, show_ac_connected, NULL); +static DEVICE_ATTR(enable_pci_power_saving_on_boot, 0644, + show_enable_pci_power_saving_on_boot, + store_enable_pci_power_saving_on_boot); +static DEVICE_ATTR(smapi_request, 0600, show_smapi_request, + store_smapi_request); + +static struct attribute *tp_root_attributes[] = { + &dev_attr_ac_connected.attr, + &dev_attr_enable_pci_power_saving_on_boot.attr, + &dev_attr_smapi_request.attr, + NULL +}; +static struct attribute_group tp_root_attribute_group = { + .attrs = tp_root_attributes +}; + +/* Attributes under /sys/devices/platform/smapi/BAT{0,1}/ : + * Every attribute needs to be defined (i.e., statically allocated) for + * each battery, and then referenced in the attribute list of each battery. + * We use preprocessor voodoo to avoid duplicating the list of attributes 4 + * times. The preprocessor output is just normal sysfs attributes code. + */ + +/** + * FOREACH_BAT_ATTR - invoke the given macros on all our battery attributes + * @_BAT: battery number (0 or 1) + * @_ATTR_RW: macro to invoke for each read/write attribute + * @_ATTR_R: macro to invoke for each read-only attribute + */ +#define FOREACH_BAT_ATTR(_BAT, _ATTR_RW, _ATTR_R) \ + _ATTR_RW(_BAT, start_charge_thresh) \ + _ATTR_RW(_BAT, stop_charge_thresh) \ + _ATTR_RW(_BAT, inhibit_charge_minutes) \ + _ATTR_RW(_BAT, force_discharge) \ + _ATTR_R (_BAT, installed) \ + _ATTR_R (_BAT, state) \ + _ATTR_R (_BAT, manufacturer) \ + _ATTR_R (_BAT, model) \ + _ATTR_R (_BAT, barcoding) \ + _ATTR_R (_BAT, chemistry) \ + _ATTR_R (_BAT, voltage) \ + _ATTR_R (_BAT, current_now) \ + _ATTR_R (_BAT, current_avg) \ + _ATTR_R (_BAT, power_now) \ + _ATTR_R (_BAT, power_avg) \ + _ATTR_R (_BAT, remaining_percent) \ + _ATTR_R (_BAT, remaining_charging_time) \ + _ATTR_R (_BAT, remaining_running_time) \ + _ATTR_R (_BAT, remaining_capacity) \ + _ATTR_R (_BAT, last_full_capacity) \ + _ATTR_R (_BAT, design_voltage) \ + _ATTR_R (_BAT, design_capacity) \ + _ATTR_R (_BAT, cycle_count) \ + _ATTR_R (_BAT, temperature) \ + _ATTR_R (_BAT, serial) \ + _ATTR_R (_BAT, manufacture_date) \ + _ATTR_R (_BAT, first_use_date) \ + _ATTR_R (_BAT, dump) + +/* Define several macros we will feed into FOREACH_BAT_ATTR: */ + +#define DEFINE_BAT_ATTR_RW(_BAT,_NAME) \ + static struct bat_device_attribute dev_attr_##_NAME##_##_BAT = { \ + .dev_attr = __ATTR(_NAME, 0644, show_battery_##_NAME, \ + store_battery_##_NAME), \ + .bat = _BAT \ + }; + +#define DEFINE_BAT_ATTR_R(_BAT,_NAME) \ + static struct bat_device_attribute dev_attr_##_NAME##_##_BAT = { \ + .dev_attr = __ATTR(_NAME, 0644, show_battery_##_NAME, 0), \ + .bat = _BAT \ + }; + +#define REF_BAT_ATTR(_BAT,_NAME) \ + &dev_attr_##_NAME##_##_BAT.dev_attr.attr, + +/* This provide all attributes for one battery: */ + +#define PROVIDE_BAT_ATTRS(_BAT) \ + FOREACH_BAT_ATTR(_BAT, DEFINE_BAT_ATTR_RW, DEFINE_BAT_ATTR_R) \ + static struct attribute *tp_bat##_BAT##_attributes[] = { \ + FOREACH_BAT_ATTR(_BAT, REF_BAT_ATTR, REF_BAT_ATTR) \ + NULL \ + }; \ + static struct attribute_group tp_bat##_BAT##_attribute_group = { \ + .name = "BAT" #_BAT, \ + .attrs = tp_bat##_BAT##_attributes \ + }; + +/* Finally genereate the attributes: */ + +PROVIDE_BAT_ATTRS(0) +PROVIDE_BAT_ATTRS(1) + +/* List of attribute groups */ + +static struct attribute_group *attr_groups[] = { + &tp_root_attribute_group, + &tp_bat0_attribute_group, + &tp_bat1_attribute_group, + NULL +}; + + +/********************************************************************* + * Init and cleanup + */ + +static struct attribute_group **next_attr_group; /* next to register */ + +static int __init tp_init(void) +{ + int ret; + printk(KERN_INFO "tp_smapi " TP_VERSION " loading...\n"); + + ret = find_smapi_port(); + if (ret<0) + goto err; + else + smapi_port = ret; + + if (!request_region(smapi_port, 1, "smapi")) { + printk(KERN_ERR "tp_smapi cannot claim port 0x%x\n", smapi_port); + ret = -ENXIO; + goto err; + } + + if (!request_region(SMAPI_PORT2, 1, "smapi")) { + printk(KERN_ERR "tp_smapi cannot claim port 0x%x\n", SMAPI_PORT2); + ret = -ENXIO; + goto err_port1; + } + + ret = platform_driver_register(&tp_driver); + if (ret) + goto err_port2; + + pdev = platform_device_alloc("smapi", -1); + if (!pdev) { + ret = -ENOMEM; + goto err_driver; + } + + ret = platform_device_add(pdev); + if (ret) + goto err_device_free; + + for (next_attr_group = attr_groups; *next_attr_group; ++next_attr_group) { + ret = sysfs_create_group(&pdev->dev.kobj, *next_attr_group); + if (ret) + goto err_attr; + } + + printk(KERN_INFO "tp_smapi successfully loaded (smapi_port=0x%x).\n", smapi_port); + return 0; + +err_attr: + while (--next_attr_group >= attr_groups) + sysfs_remove_group(&pdev->dev.kobj, *next_attr_group); + platform_device_unregister(pdev); +err_device_free: + platform_device_put(pdev); +err_driver: + platform_driver_unregister(&tp_driver); +err_port2: + release_region(SMAPI_PORT2, 1); +err_port1: + release_region(smapi_port, 1); +err: + printk(KERN_ERR "tp_smapi init failed (ret=%d)!\n", ret); + return ret; +} + +static void __exit tp_exit(void) +{ + while (next_attr_group && --next_attr_group >= attr_groups) + sysfs_remove_group(&pdev->dev.kobj, *next_attr_group); + platform_device_unregister(pdev); + platform_driver_unregister(&tp_driver); + release_region(SMAPI_PORT2, 1); + if (smapi_port) + release_region(smapi_port, 1); + + printk(KERN_INFO "tp_smapi unloaded.\n"); +} + +module_init(tp_init); +module_exit(tp_exit); diff -urN oldtree/drivers/hwmon/Kconfig newtree/drivers/hwmon/Kconfig --- oldtree/drivers/hwmon/Kconfig 2006-09-29 14:03:20.000000000 -0400 +++ newtree/drivers/hwmon/Kconfig 2006-09-30 05:49:34.000000000 -0400 @@ -515,6 +515,7 @@ config SENSORS_HDAPS tristate "IBM Hard Drive Active Protection System (hdaps)" depends on HWMON && INPUT && X86 + select THINKPAD_EC default n help This driver provides support for the IBM Hard Drive Active Protection diff -urN oldtree/drivers/hwmon/hdaps.c newtree/drivers/hwmon/hdaps.c --- oldtree/drivers/hwmon/hdaps.c 2006-09-29 14:03:20.000000000 -0400 +++ newtree/drivers/hwmon/hdaps.c 2006-09-30 05:52:22.000000000 -0400 @@ -33,256 +33,363 @@ #include #include #include -#include +#include -#define HDAPS_LOW_PORT 0x1600 /* first port used by hdaps */ -#define HDAPS_NR_PORTS 0x30 /* number of ports: 0x1600 - 0x162f */ - -#define HDAPS_PORT_STATE 0x1611 /* device state */ -#define HDAPS_PORT_YPOS 0x1612 /* y-axis position */ -#define HDAPS_PORT_XPOS 0x1614 /* x-axis position */ -#define HDAPS_PORT_TEMP1 0x1616 /* device temperature, in Celsius */ -#define HDAPS_PORT_YVAR 0x1617 /* y-axis variance (what is this?) */ -#define HDAPS_PORT_XVAR 0x1619 /* x-axis variance (what is this?) */ -#define HDAPS_PORT_TEMP2 0x161b /* device temperature (again?) */ -#define HDAPS_PORT_UNKNOWN 0x161c /* what is this? */ -#define HDAPS_PORT_KMACT 0x161d /* keyboard or mouse activity */ - -#define STATE_FRESH 0x50 /* accelerometer data is fresh */ +/* Embedded controller accelerometer read command and its result: */ +static const struct thinkpad_ec_row ec_accel_args = + { .mask=0x0001, .val={0x11} }; +#define EC_ACCEL_IDX_READOUTS 0x1 /* readouts included in this read */ + /* First readout, if READOUTS>=1: */ +#define EC_ACCEL_IDX_YPOS1 0x2 /* y-axis position word */ +#define EC_ACCEL_IDX_XPOS1 0x4 /* x-axis position word */ +#define EC_ACCEL_IDX_TEMP1 0x6 /* device temperature in Celsius */ + /* Second readout, if READOUTS>=2: */ +#define EC_ACCEL_IDX_XPOS2 0x7 /* y-axis position word */ +#define EC_ACCEL_IDX_YPOS2 0x9 /* x-axis pisition word */ +#define EC_ACCEL_IDX_TEMP2 0xb /* device temperature in Celsius */ +#define EC_ACCEL_IDX_QUEUED 0xc /* Number of queued readouts left */ +#define EC_ACCEL_IDX_KMACT 0xd /* keyboard or mouse activity */ +#define EC_ACCEL_IDX_RETVAL 0xf /* command return value, good=0x00 */ #define KEYBD_MASK 0x20 /* set if keyboard activity */ #define MOUSE_MASK 0x40 /* set if mouse activity */ -#define KEYBD_ISSET(n) (!! (n & KEYBD_MASK)) /* keyboard used? */ -#define MOUSE_ISSET(n) (!! (n & MOUSE_MASK)) /* mouse used? */ -#define INIT_TIMEOUT_MSECS 4000 /* wait up to 4s for device init ... */ -#define INIT_WAIT_MSECS 200 /* ... in 200ms increments */ +#define READ_TIMEOUT_MSECS 100 /* wait this long for device read */ +#define RETRY_MSECS 3 /* retry delay */ -#define HDAPS_POLL_PERIOD (HZ/20) /* poll for input every 1/20s */ #define HDAPS_INPUT_FUZZ 4 /* input event threshold */ #define HDAPS_INPUT_FLAT 4 +#define KMACT_REMEMBER_PERIOD (HZ/10) /* keyboard/mouse persistance */ static struct timer_list hdaps_timer; static struct platform_device *pdev; static struct input_dev *hdaps_idev; static unsigned int hdaps_invert; -static u8 km_activity; -static int rest_x; -static int rest_y; +static int needs_calibration; -static DECLARE_MUTEX(hdaps_sem); +/* Configuration: */ +static int sampling_rate = 50; /* Sampling rate */ +static int oversampling_ratio = 5; /* Ratio between our sampling rate and + * EC accelerometer sampling rate */ +static int running_avg_filter_order = 2; /* EC running average filter order */ +static int fake_data_mode; /* Enable EC fake data mode? */ + +/* Latest state readout: */ +static int pos_x, pos_y; /* position */ +static int temperature; /* temperature */ +static int stale_readout = 1; /* last read invalid */ +static int rest_x, rest_y; /* calibrated rest position */ + +/* Last time we saw keyboard and mouse activity: */ +static u64 last_keyboard_jiffies = INITIAL_JIFFIES; +static u64 last_mouse_jiffies = INITIAL_JIFFIES; -/* - * __get_latch - Get the value from a given port. Callers must hold hdaps_sem. - */ -static inline u8 __get_latch(u16 port) +/* Some models require an axis transformation to the standard reprsentation */ +static void transform_axes(int *x, int *y) { - return inb(port) & 0xff; + if (hdaps_invert) { + *x = -*x; + *y = -*y; + } } -/* - * __check_latch - Check a port latch for a given value. Returns zero if the - * port contains the given value. Callers must hold hdaps_sem. +/** + * __hdaps_update - query current state, with locks already acquired + * @fast: if nonzero, do one quick attempt without retries. + * + * Query current accelerometer state and update global state variables. + * Also prefetches the next query. Caller must hold controller lock. */ -static inline int __check_latch(u16 port, u8 val) +static int __hdaps_update(int fast) { - if (__get_latch(port) == val) - return 0; - return -EINVAL; + /* Read data: */ + struct thinkpad_ec_row data; + int ret; + + data.mask = (1 << EC_ACCEL_IDX_READOUTS) | (1 << EC_ACCEL_IDX_KMACT) | + (3 << EC_ACCEL_IDX_YPOS1) | (3 << EC_ACCEL_IDX_XPOS1) | + (1 << EC_ACCEL_IDX_TEMP1) | (1 << EC_ACCEL_IDX_RETVAL); + if (fast) + ret = thinkpad_ec_try_read_row(&ec_accel_args, &data); + else + ret = thinkpad_ec_read_row(&ec_accel_args, &data); + thinkpad_ec_prefetch_row(&ec_accel_args); /* Prefetch even if error */ + if (ret) + return ret; + + /* Check status: */ + if (data.val[EC_ACCEL_IDX_RETVAL] != 0x00) { + printk(KERN_WARNING "hdaps: read RETVAL=0x%02x\n", + data.val[EC_ACCEL_IDX_RETVAL]); + return -EIO; + } + + if (data.val[EC_ACCEL_IDX_READOUTS] < 1) + return -EBUSY; /* no pending readout, try again later */ + + /* Parse position data: */ + pos_x = *(s16*)(data.val+EC_ACCEL_IDX_XPOS1); + pos_y = *(s16*)(data.val+EC_ACCEL_IDX_YPOS1); + transform_axes(&pos_x, &pos_y); + + /* Keyboard and mouse activity status is cleared as soon as it's read, + * so applications will eat each other's events. Thus we remember any + * event for KMACT_REMEMBER_PERIOD jiffies. + */ + if (data.val[EC_ACCEL_IDX_KMACT] & KEYBD_MASK) + last_keyboard_jiffies = get_jiffies_64(); + if (data.val[EC_ACCEL_IDX_KMACT] & MOUSE_MASK) + last_mouse_jiffies = get_jiffies_64(); + + temperature = data.val[EC_ACCEL_IDX_TEMP1]; + + stale_readout = 0; + if (needs_calibration) { + rest_x = pos_x; + rest_y = pos_y; + needs_calibration = 0; + } + + return 0; } -/* - * __wait_latch - Wait up to 100us for a port latch to get a certain value, - * returning zero if the value is obtained. Callers must hold hdaps_sem. +/** + * hdaps_update - acquire locks and query current state + * + * Query current accelerometer state and update global state variables. + * Also prefetches the next query. + * Retries until timeout if the accelerometer is not in ready status (common). + * Does its own locking. */ -static int __wait_latch(u16 port, u8 val) +static int hdaps_update(void) { - unsigned int i; + int total, ret; + if (!stale_readout) /* already updated recently? */ + return 0; + for (total=0; total>8), order} }; + struct thinkpad_ec_row data = { .mask = 0x8000 }; + int ret = thinkpad_ec_read_row(&args, &data); + printk(KERN_DEBUG "hdaps: setting ec_rate=%d, filter_order=%d\n", + ec_rate, order); + if (ret) + return ret; + if (data.val[0xF]==0x03) { + printk(KERN_WARNING "hdaps: config param out of range\n"); + return -EINVAL; + } + if (data.val[0xF]==0x06) { + printk(KERN_WARNING "hdaps: config change already pending\n"); + return -EBUSY; + } + if (data.val[0xF]!=0x00) { + printk(KERN_WARNING "hdaps: config change error, ret=%d\n", + data.val[0xF]); + return -EIO; + } + return 0; } -/* - * hdaps_readb_one - reads a byte from a single I/O port, placing the value in - * the given pointer. Returns zero on success or a negative error on failure. - * Can sleep. +/** + * hdaps_get_ec_config - get accelerometer parameters. + * @ec_rate: embedded controller sampling rate + * @order: embedded controller running average filter order + * Returns zero on success and negative error code on failure. Can sleep. */ -static int hdaps_readb_one(unsigned int port, u8 *val) +static int hdaps_get_ec_config(int *ec_rate, int *order) { - int ret; - - down(&hdaps_sem); - - /* do a sync refresh -- we need to be sure that we read fresh data */ - ret = __device_refresh_sync(); + const struct thinkpad_ec_row args = + { .mask=0x0003, .val={0x17, 0x82} }; + struct thinkpad_ec_row data = { .mask = 0x801F }; + int ret = thinkpad_ec_read_row(&args, &data); if (ret) - goto out; - - *val = inb(port); - __device_complete(); - -out: - up(&hdaps_sem); - return ret; + return ret; + if (data.val[0xF]!=0x00) + return -EIO; + if (!(data.val[0x1] & 0x01)) + return -ENXIO; /* accelerometer polling not enabled */ + if (data.val[0x1] & 0x02) + return -EBUSY; /* config change in progress, retry later */ + *ec_rate = data.val[0x2] | ((int)(data.val[0x3]) << 8); + *order = data.val[0x4]; + return 0; } -/* __hdaps_read_pair - internal lockless helper for hdaps_read_pair(). */ -static int __hdaps_read_pair(unsigned int port1, unsigned int port2, - int *x, int *y) +/** + * hdaps_get_ec_mode - get EC accelerometer mode + * Returns zero on success and negative error code on failure. Can sleep. + */ +static int hdaps_get_ec_mode(u8 *mode) { - /* do a sync refresh -- we need to be sure that we read fresh data */ - if (__device_refresh_sync()) + const struct thinkpad_ec_row args = { .mask=0x0001, .val={0x13} }; + struct thinkpad_ec_row data = { .mask = 0x8002 }; + int ret = thinkpad_ec_read_row(&args, &data); + if (ret) + return ret; + if (data.val[0xF]!=0x00) { + printk(KERN_WARNING + "accelerometer not implemented (0x%02x)\n", + data.val[0xF]); return -EIO; - - *y = inw(port2); - *x = inw(port1); - km_activity = inb(HDAPS_PORT_KMACT); - __device_complete(); - - /* if hdaps_invert is set, negate the two values */ - if (hdaps_invert) { - *x = -*x; - *y = -*y; } - + *mode = data.val[0x1]; return 0; } -/* - * hdaps_read_pair - reads the values from a pair of ports, placing the values - * in the given pointers. Returns zero on success. Can sleep. +/** + * hdaps_check_ec - checks something about the EC. + * Follows the clean-room spec for HDAPS; we don't know what it means. + * Returns zero on success and negative error code on failure. Can sleep. */ -static int hdaps_read_pair(unsigned int port1, unsigned int port2, - int *val1, int *val2) +static int hdaps_check_ec(void) { - int ret; - - down(&hdaps_sem); - ret = __hdaps_read_pair(port1, port2, val1, val2); - up(&hdaps_sem); - - return ret; + const struct thinkpad_ec_row args = + { .mask=0x0003, .val={0x17, 0x81} }; + struct thinkpad_ec_row data = { .mask = 0x800E }; + int ret = thinkpad_ec_read_row(&args, &data); + if (ret) + return ret; + if (data.val[0x1]!=0x00 || data.val[0x2]!=0x60 || + data.val[0x3]!=0x00 || data.val[0xF]!=0x00) + return -EIO; + return 0; } -/* - * hdaps_device_init - initialize the accelerometer. Returns zero on success - * and negative error code on failure. Can sleep. +/** + * hdaps_device_init - initialize the accelerometer. + * + * Call several embedded controller functions to test and initialize the + * accelerometer. + * Returns zero on success and negative error code on failure. Can sleep. */ +#define ABORT_INIT(msg) printk(KERN_ERR "hdaps init failed at: %s\n", msg) static int hdaps_device_init(void) { - int total, ret = -ENXIO; + int ret; + u8 mode; - down(&hdaps_sem); + ret = thinkpad_ec_lock(); + if (ret) + return ret; - outb(0x13, 0x1610); - outb(0x01, 0x161f); - if (__wait_latch(0x161f, 0x00)) - goto out; + if (hdaps_get_ec_mode(&mode)) + { ABORT_INIT("hdaps_get_ec_mode failed"); goto bad; } - /* - * Most ThinkPads return 0x01. - * - * Others--namely the R50p, T41p, and T42p--return 0x03. These laptops - * have "inverted" axises. - * - * The 0x02 value occurs when the chip has been previously initialized. - */ - if (__check_latch(0x1611, 0x03) && - __check_latch(0x1611, 0x02) && - __check_latch(0x1611, 0x01)) - goto out; + printk(KERN_DEBUG "hdaps: initial mode latch is 0x%02x\n", mode); + if (mode==0x00) + { ABORT_INIT("accelerometer not available"); goto bad; } - printk(KERN_DEBUG "hdaps: initial latch check good (0x%02x).\n", - __get_latch(0x1611)); + if (hdaps_check_ec()) + { ABORT_INIT("hdaps_check_ec failed"); goto bad; } - outb(0x17, 0x1610); - outb(0x81, 0x1611); - outb(0x01, 0x161f); - if (__wait_latch(0x161f, 0x00)) - goto out; - if (__wait_latch(0x1611, 0x00)) - goto out; - if (__wait_latch(0x1612, 0x60)) - goto out; - if (__wait_latch(0x1613, 0x00)) - goto out; - outb(0x14, 0x1610); - outb(0x01, 0x1611); - outb(0x01, 0x161f); - if (__wait_latch(0x161f, 0x00)) - goto out; - outb(0x10, 0x1610); - outb(0xc8, 0x1611); - outb(0x00, 0x1612); - outb(0x02, 0x1613); - outb(0x01, 0x161f); - if (__wait_latch(0x161f, 0x00)) - goto out; - if (__device_refresh_sync()) - goto out; - if (__wait_latch(0x1611, 0x00)) - goto out; + if (hdaps_set_power(1)) + { ABORT_INIT("hdaps_set_power failed"); goto bad; } - /* we have done our dance, now let's wait for the applause */ - for (total = INIT_TIMEOUT_MSECS; total > 0; total -= INIT_WAIT_MSECS) { - int x, y; - - /* a read of the device helps push it into action */ - __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y); - if (!__wait_latch(0x1611, 0x02)) { - ret = 0; - break; - } + if (hdaps_set_ec_config(sampling_rate*oversampling_ratio, + running_avg_filter_order)) + { ABORT_INIT("hdaps_set_ec_config failed"); goto bad; } - msleep(INIT_WAIT_MSECS); - } + if (hdaps_set_fake_data_mode(fake_data_mode)) + { ABORT_INIT("hdaps_set_fake_data_mode failed"); goto bad; } -out: - up(&hdaps_sem); + thinkpad_ec_invalidate(); + udelay(200); + + /* Just prefetch instead of reading, to avoid ~1sec delay on load */ + ret = thinkpad_ec_prefetch_row(&ec_accel_args); + if (ret) + { ABORT_INIT("initial prefetch failed"); goto bad; } + goto good; +bad: + thinkpad_ec_invalidate(); + ret = -ENXIO; +good: + stale_readout = 1; + thinkpad_ec_unlock(); return ret; } +/** + * hdaps_device_shutdown - power off the accelerometer + * Returns nonzero on failure. Can sleep. + */ +static int hdaps_device_shutdown(void) +{ + int ret; + ret = hdaps_set_power(0); + if (ret) { + printk(KERN_WARNING "hdaps: cannot power off\n"); + return ret; + } + ret = hdaps_set_ec_config(0, 1); + if (ret) + printk(KERN_WARNING "hdaps: cannot stop EC sampling\n"); + return ret; +} /* Device model stuff */ @@ -298,13 +405,26 @@ return 0; } +static int hdaps_suspend(struct platform_device *dev, pm_message_t state) +{ + /* Don't do hdaps polls until resume re-initializes the sensor. */ + del_timer_sync(&hdaps_timer); + hdaps_device_shutdown(); /* ignore errors, effect is negligible */ + return 0; +} + static int hdaps_resume(struct platform_device *dev) { - return hdaps_device_init(); + int ret = hdaps_device_init(); + if (ret) + return ret; + mod_timer(&hdaps_timer, jiffies + HZ/sampling_rate); + return 0; } static struct platform_driver hdaps_driver = { .probe = hdaps_probe, + .suspend = hdaps_suspend, .resume = hdaps_resume, .driver = { .name = "hdaps", @@ -312,35 +432,45 @@ }, }; -/* - * hdaps_calibrate - Set our "resting" values. Callers must hold hdaps_sem. +/** + * hdaps_calibrate - set our "resting" values. + * Does its own locking. */ static void hdaps_calibrate(void) { - __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &rest_x, &rest_y); + needs_calibration = 1; + hdaps_update(); + /* If that fails, the mousedev poll will take care of things later. */ } +/* Timer handler for updating the input device. Runs in softirq context, + * so avoid lenghty or blocking operations. + */ static void hdaps_mousedev_poll(unsigned long unused) { - int x, y; + int ret; + + stale_readout = 1; /* Cannot sleep. Try nonblockingly. If we fail, try again later. */ - if (down_trylock(&hdaps_sem)) { - mod_timer(&hdaps_timer,jiffies + HDAPS_POLL_PERIOD); + if (thinkpad_ec_try_lock()) + goto keep_active; + + ret = __hdaps_update(1); /* fast update, we're in softirq context */ + thinkpad_ec_unlock(); + /* Any of "successful", "not yet ready" and "not prefetched"? */ + if (ret!=0 && ret!=-EBUSY && ret!=-ENODATA) { + printk(KERN_ERR + "hdaps: poll failed, disabling updates\n"); return; } - if (__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y)) - goto out; - - input_report_abs(hdaps_idev, ABS_X, x - rest_x); - input_report_abs(hdaps_idev, ABS_Y, y - rest_y); +keep_active: + /* Even if we failed now, pos_x,y may have been updated earlier: */ + input_report_abs(hdaps_idev, ABS_X, pos_x - rest_x); + input_report_abs(hdaps_idev, ABS_Y, pos_y - rest_y); input_sync(hdaps_idev); - - mod_timer(&hdaps_timer, jiffies + HDAPS_POLL_PERIOD); - -out: - up(&hdaps_sem); + mod_timer(&hdaps_timer, jiffies + HZ/sampling_rate); } @@ -349,65 +479,41 @@ static ssize_t hdaps_position_show(struct device *dev, struct device_attribute *attr, char *buf) { - int ret, x, y; - - ret = hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y); + int ret = hdaps_update(); if (ret) return ret; - - return sprintf(buf, "(%d,%d)\n", x, y); -} - -static ssize_t hdaps_variance_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int ret, x, y; - - ret = hdaps_read_pair(HDAPS_PORT_XVAR, HDAPS_PORT_YVAR, &x, &y); - if (ret) - return ret; - - return sprintf(buf, "(%d,%d)\n", x, y); + return sprintf(buf, "(%d,%d)\n", pos_x, pos_y); } static ssize_t hdaps_temp1_show(struct device *dev, struct device_attribute *attr, char *buf) { - u8 temp; - int ret; - - ret = hdaps_readb_one(HDAPS_PORT_TEMP1, &temp); - if (ret < 0) - return ret; - - return sprintf(buf, "%u\n", temp); -} - -static ssize_t hdaps_temp2_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - u8 temp; - int ret; - - ret = hdaps_readb_one(HDAPS_PORT_TEMP2, &temp); - if (ret < 0) + int ret = hdaps_update(); + if (ret) return ret; - - return sprintf(buf, "%u\n", temp); + return sprintf(buf, "%d\n", temperature); } static ssize_t hdaps_keyboard_activity_show(struct device *dev, struct device_attribute *attr, char *buf) { - return sprintf(buf, "%u\n", KEYBD_ISSET(km_activity)); + int ret = hdaps_update(); + if (ret) + return ret; + return sprintf(buf, "%u\n", + get_jiffies_64() < last_keyboard_jiffies + KMACT_REMEMBER_PERIOD); } static ssize_t hdaps_mouse_activity_show(struct device *dev, struct device_attribute *attr, char *buf) { - return sprintf(buf, "%u\n", MOUSE_ISSET(km_activity)); + int ret = hdaps_update(); + if (ret) + return ret; + return sprintf(buf, "%u\n", + get_jiffies_64() < last_mouse_jiffies + KMACT_REMEMBER_PERIOD); } static ssize_t hdaps_calibrate_show(struct device *dev, @@ -420,10 +526,7 @@ struct device_attribute *attr, const char *buf, size_t count) { - down(&hdaps_sem); hdaps_calibrate(); - up(&hdaps_sem); - return count; } @@ -448,24 +551,128 @@ return count; } +static ssize_t hdaps_sampling_rate_show( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", sampling_rate); +} + +static ssize_t hdaps_sampling_rate_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int rate, ret; + if (sscanf(buf, "%d", &rate) != 1 || rate>HZ || rate<0) { + printk(KERN_WARNING + "must have 01) + return -EINVAL; + ret = hdaps_set_fake_data_mode(on); + if (ret) + return ret; + fake_data_mode = on; + return count; +} + +static ssize_t hdaps_fake_data_mode_show( + struct device *dev, struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", fake_data_mode); +} + static DEVICE_ATTR(position, 0444, hdaps_position_show, NULL); -static DEVICE_ATTR(variance, 0444, hdaps_variance_show, NULL); static DEVICE_ATTR(temp1, 0444, hdaps_temp1_show, NULL); -static DEVICE_ATTR(temp2, 0444, hdaps_temp2_show, NULL); + /* "temp1" instead of "temperature" is hwmon convention */ static DEVICE_ATTR(keyboard_activity, 0444, hdaps_keyboard_activity_show, NULL); static DEVICE_ATTR(mouse_activity, 0444, hdaps_mouse_activity_show, NULL); static DEVICE_ATTR(calibrate, 0644, hdaps_calibrate_show,hdaps_calibrate_store); static DEVICE_ATTR(invert, 0644, hdaps_invert_show, hdaps_invert_store); +static DEVICE_ATTR(sampling_rate, 0644, + hdaps_sampling_rate_show, hdaps_sampling_rate_store); +static DEVICE_ATTR(oversampling_ratio, 0644, + hdaps_oversampling_ratio_show, + hdaps_oversampling_ratio_store); +static DEVICE_ATTR(running_avg_filter_order, 0644, + hdaps_running_avg_filter_order_show, + hdaps_running_avg_filter_order_store); +static DEVICE_ATTR(fake_data_mode, 0644, + hdaps_fake_data_mode_show, hdaps_fake_data_mode_store); static struct attribute *hdaps_attributes[] = { &dev_attr_position.attr, - &dev_attr_variance.attr, &dev_attr_temp1.attr, - &dev_attr_temp2.attr, &dev_attr_keyboard_activity.attr, &dev_attr_mouse_activity.attr, &dev_attr_calibrate.attr, &dev_attr_invert.attr, + &dev_attr_sampling_rate.attr, + &dev_attr_oversampling_ratio.attr, + &dev_attr_running_avg_filter_order.attr, + &dev_attr_fake_data_mode.attr, NULL, }; @@ -476,88 +683,48 @@ /* Module stuff */ -/* hdaps_dmi_match - found a match. return one, short-circuiting the hunt. */ -static int hdaps_dmi_match(struct dmi_system_id *id) -{ - printk(KERN_INFO "hdaps: %s detected.\n", id->ident); - return 1; -} - /* hdaps_dmi_match_invert - found an inverted match. */ static int hdaps_dmi_match_invert(struct dmi_system_id *id) { hdaps_invert = 1; - printk(KERN_INFO "hdaps: inverting axis readings.\n"); - return hdaps_dmi_match(id); -} - -#define HDAPS_DMI_MATCH_NORMAL(model) { \ - .ident = "IBM " model, \ - .callback = hdaps_dmi_match, \ - .matches = { \ - DMI_MATCH(DMI_BOARD_VENDOR, "IBM"), \ - DMI_MATCH(DMI_PRODUCT_VERSION, model) \ - } \ + printk(KERN_INFO "hdaps: %s detected, inverting axes\n", + id->ident); + return 1; } -#define HDAPS_DMI_MATCH_INVERT(model) { \ - .ident = "IBM " model, \ +#define HDAPS_DMI_MATCH_INVERT(vendor,model) { \ + .ident = vendor " " model, \ .callback = hdaps_dmi_match_invert, \ .matches = { \ - DMI_MATCH(DMI_BOARD_VENDOR, "IBM"), \ + DMI_MATCH(DMI_BOARD_VENDOR, vendor), \ DMI_MATCH(DMI_PRODUCT_VERSION, model) \ } \ } -#define HDAPS_DMI_MATCH_LENOVO(model) { \ - .ident = "Lenovo " model, \ - .callback = hdaps_dmi_match_invert, \ - .matches = { \ - DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), \ - DMI_MATCH(DMI_PRODUCT_VERSION, model) \ - } \ -} - static int __init hdaps_init(void) { int ret; - /* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match + /* List of models with abnormal axis configuration. + Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match "ThinkPad T42p", so the order of the entries matters */ struct dmi_system_id hdaps_whitelist[] = { - HDAPS_DMI_MATCH_NORMAL("ThinkPad H"), - HDAPS_DMI_MATCH_INVERT("ThinkPad R50p"), - HDAPS_DMI_MATCH_NORMAL("ThinkPad R50"), - HDAPS_DMI_MATCH_NORMAL("ThinkPad R51"), - HDAPS_DMI_MATCH_NORMAL("ThinkPad R52"), - HDAPS_DMI_MATCH_NORMAL("ThinkPad H"), /* R52 (1846AQG) */ - HDAPS_DMI_MATCH_INVERT("ThinkPad T41p"), - HDAPS_DMI_MATCH_NORMAL("ThinkPad T41"), - HDAPS_DMI_MATCH_INVERT("ThinkPad T42p"), - HDAPS_DMI_MATCH_NORMAL("ThinkPad T42"), - HDAPS_DMI_MATCH_NORMAL("ThinkPad T43"), - HDAPS_DMI_MATCH_LENOVO("ThinkPad T60p"), - HDAPS_DMI_MATCH_NORMAL("ThinkPad X40"), - HDAPS_DMI_MATCH_NORMAL("ThinkPad X41"), - HDAPS_DMI_MATCH_LENOVO("ThinkPad X60"), - HDAPS_DMI_MATCH_NORMAL("ThinkPad Z60m"), + HDAPS_DMI_MATCH_INVERT("IBM","ThinkPad R50p"), + HDAPS_DMI_MATCH_INVERT("IBM","ThinkPad T41p"), + HDAPS_DMI_MATCH_INVERT("IBM","ThinkPad T42p"), + HDAPS_DMI_MATCH_INVERT("LENOVO","ThinkPad T60p"), + HDAPS_DMI_MATCH_INVERT("LENOVO","ThinkPad X60"), { .ident = NULL } }; - if (!dmi_check_system(hdaps_whitelist)) { - printk(KERN_WARNING "hdaps: supported laptop not found!\n"); - ret = -ENODEV; - goto out; - } - - if (!request_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS, "hdaps")) { - ret = -ENXIO; - goto out; - } + dmi_check_system(hdaps_whitelist); /* default to normal axes */ + /* Init timer before platform_driver_register, in case of suspend */ + init_timer(&hdaps_timer); + hdaps_timer.function = hdaps_mousedev_poll; ret = platform_driver_register(&hdaps_driver); if (ret) - goto out_region; + goto out; pdev = platform_device_register_simple("hdaps", -1, NULL, 0); if (IS_ERR(pdev)) { @@ -575,8 +742,8 @@ goto out_group; } - /* initial calibrate for the input device */ - hdaps_calibrate(); + /* calibration for the input device (deferred to avoid delay) */ + needs_calibration = 1; /* initialize the input class */ hdaps_idev->name = "hdaps"; @@ -591,13 +758,9 @@ if (ret) goto out_idev; - /* start up our timer for the input device */ - init_timer(&hdaps_timer); - hdaps_timer.function = hdaps_mousedev_poll; - hdaps_timer.expires = jiffies + HDAPS_POLL_PERIOD; - add_timer(&hdaps_timer); + mod_timer(&hdaps_timer, jiffies + HZ/sampling_rate); - printk(KERN_INFO "hdaps: driver successfully loaded.\n"); + printk(KERN_INFO "hdaps: driver successfully loaded.\n"); return 0; out_idev: @@ -608,6 +771,7 @@ platform_device_unregister(pdev); out_driver: platform_driver_unregister(&hdaps_driver); + hdaps_device_shutdown(); out_region: release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS); out: @@ -619,10 +783,10 @@ { del_timer_sync(&hdaps_timer); input_unregister_device(hdaps_idev); + hdaps_device_shutdown(); /* ignore errors, effect is negligible */ sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group); platform_device_unregister(pdev); platform_driver_unregister(&hdaps_driver); - release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS); printk(KERN_INFO "hdaps: driver unloaded.\n"); } diff -urN oldtree/include/linux/dmi.h newtree/include/linux/dmi.h --- oldtree/include/linux/dmi.h 2006-09-29 14:03:22.000000000 -0400 +++ newtree/include/linux/dmi.h 2006-09-30 05:53:53.000000000 -0400 @@ -27,8 +27,8 @@ DMI_DEV_TYPE_ETHERNET, DMI_DEV_TYPE_TOKENRING, DMI_DEV_TYPE_SOUND, - DMI_DEV_TYPE_IPMI = -1, - DMI_DEV_TYPE_OEM_STRING = -2 + DMI_DEV_TYPE_IPMI = -1, + DMI_DEV_TYPE_OEM_STRING = -2 }; struct dmi_header { diff -urN oldtree/include/linux/thinkpad_ec.h newtree/include/linux/thinkpad_ec.h --- oldtree/include/linux/thinkpad_ec.h 1969-12-31 19:00:00.000000000 -0500 +++ newtree/include/linux/thinkpad_ec.h 2006-09-30 05:49:34.000000000 -0400 @@ -0,0 +1,47 @@ +/* + * thinkpad_ec.h - interface to ThinkPad embedded controller LPC3 functions + * + * Copyright (C) 2005 Shem Multinymous + * + * 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 + */ + +#ifndef _THINKPAD_EC_H +#define _THINKPAD_EC_H + +#ifdef __KERNEL__ + +#define TP_CONTROLLER_ROW_LEN 16 + +/* EC transactions input and output (possibly partial) vectors of 16 bytes. */ +struct thinkpad_ec_row { + u16 mask; /* bitmap of which entries of val[] are meaningful */ + u8 val[TP_CONTROLLER_ROW_LEN]; +}; + +extern int thinkpad_ec_lock(void); +extern int thinkpad_ec_try_lock(void); +extern void thinkpad_ec_unlock(void); + +extern int thinkpad_ec_read_row(const struct thinkpad_ec_row *args, + struct thinkpad_ec_row *data); +extern int thinkpad_ec_try_read_row(const struct thinkpad_ec_row *args, + struct thinkpad_ec_row *mask); +extern int thinkpad_ec_prefetch_row(const struct thinkpad_ec_row *args); +extern void thinkpad_ec_invalidate(void); + + +#endif /* __KERNEL */ +#endif /* _THINKPAD_EC_H */