import of tp-smapi version 0.22

This commit is contained in:
Shem Multinymous 2006-06-21 18:32:16 +02:00 committed by Evgeni Golov
parent 987e971824
commit e696acf8b6
9 changed files with 615 additions and 363 deletions

29
CHANGES
View File

@ -1,5 +1,34 @@
Change history for tp_smapi:
0.22 2006-07-20
----------------
- hdaps: Added support for X60s (but X axis is still inverted).
- tp_base: Wait until EC starts replying before considering a request
successful (this improves stability and eliminates abnormal events.)
- hdaps: Removed /sys/devices/platform/hdaps/{variance,temp2}. They actually
show an older pending readout (if there is one), so it never makes sense
to read them via the existing interface.
- tp_base: Added many additional status and consistency checks on EC,
to give early warning on any strange behavior.
- tp_base: New constants and comments greatly clarify access to the EC.
- hdaps: Convert all init code to use tp_base transactions. Mo more direct
IO port access.
- hdaps: Move some init code to new functions hdaps_set_power() and
hdaps_set_ec_config(). These will be exposed as attributes in the future.
- hdaps: If init failed, tell at which step it happened.
- tp_base: Allow additional inputs and optional outputs to EC requests.
- tp_base: Decrease timeout for reply to request.
- Kconfig: Make TP_BASE an invisible tristate option
- tp_base: Rename tp_controller_trylock() to tp_controller_try_lock().
- tp_base: make tp_controller_lock() return an int; have it checked in
tp_smapi and hdaps.
- tp_base: Rewrote all printk()s using macros, and made debug=1 quieter.
- Makefile: work even if $SHELL is not bash.
- Makefile: Autopatch hdaps patch to work with 2.6.18-rc1
Many of the above changes were suggested or contributed by
Henrique de Moraes Holschuh.
0.21 2006-06-21
----------------
- Compatibility with kernel 2.6.17.

View File

@ -8,6 +8,7 @@ PWD := $(shell pwd)
IDIR := include/linux
TP_DIR := drivers/firmware
TP_MODULES := tp_base.o tp_smapi.o
SHELL := /bin/bash
ifeq ($(HDAPS),1)
TP_MODULES += hdaps.o
@ -22,7 +23,7 @@ ifneq ($(shell [ -f $(KSRC)/include/linux/platform_device.h ] && echo 1),1)
$(error This driver requires kernel 2.6.15 or newer.)
endif
.PHONY: default clean modules load unload install patch check_hdaps
.PHONY: default clean modules load unload install patch check_hdaps mk-hdaps.diff
export TP_MODULES
#####################################################################
@ -40,7 +41,7 @@ clean:
rm -f hdaps.mod.* hdaps.o hdaps.ko .hdaps.*.cmd
rm -f *~ diff/*~ *.orig diff/*.orig *.rej diff/*.rej
rm -f tp_smapi-*-for-*.patch
rm -fr .tmp_versions Modules.symvers
rm -fr .tmp_versions Modules.symvers diff/hdaps.diff.tmp
@if [ -f hdaps.c -a hdaps.c.flag -ot hdaps.c ]; then \
echo 'WARNING: hdaps.c has changed since autogeneration, will not delete.'; \
else \
@ -75,12 +76,14 @@ endif
depmod -a
# Match hdaps.c from kernel tree into local copy.
# (First do a small change in our own patch if kernel 2.6.17.)
# (First do a small change in our own patch if kernel != 2.6.17.x)
hdaps.c: $(KSRC)/drivers/hwmon/hdaps.c diff/hdaps.diff
cat $(PWD)/diff/hdaps.diff \
| if grep -q 'ret = -ENODEV' $(KSRC)/drivers/hwmon/hdaps.c; then \
perl -pe 'm/^ \t\tret = -ENXIO;/ && s/NXIO/NODEV/'; else cat; fi \
| patch -d $(KSRC) -p1 -o $(PWD)/hdaps.c
| if ! grep -q 'ret = -ENODEV' $(KSRC)/drivers/hwmon/hdaps.c; then \
perl -0777 -pe 's/(laptop not found.*\n.*)ENODEV/$$1ENXIO/'; else cat; fi \
| if grep -q 'Celsius' $(KSRC)/drivers/hwmon/hdaps.c; then \
perl -0777 -pe 's/ celcius / Celsius /'; else cat; fi \
| patch -d $(KSRC) -p1 -o $(PWD)/hdaps.c || { rm -v hdaps.c; exit 1; }
@touch -r hdaps.c hdaps.c.flag
@ -123,7 +126,7 @@ patch: hdaps.c
perl -0777 -pe 's/\n(Installation\n---+|Conflict with HDAPS\n---+|Files in this package\n---+|Setting and getting CD-ROM speed:\n).*?\n(?=[^\n]*\n-----)/\n/gs' $(PWD)/README > $(NEW)/Documentation/tp_smapi.txt \
; fi &&\
\
{ diff -Nurp $(ORG) $(NEW) > patch \
{ diff -dNurp $(ORG) $(NEW) > patch \
|| [ $$? -lt 2 ]; } &&\
{ diffstat patch; echo; echo; cat patch; } \
> $(PWD)/${PATCH} &&\
@ -131,6 +134,13 @@ patch: hdaps.c
@echo -e "\nPatch file created:\n ${PATCH}"
@echo -e "To apply, use:\n patch -p1 -d ${KSRC} < ${PATCH}"
#####################################################################
# Tools for preparing a release. Ignore these.
mk-hdaps.diff: diff/hdaps.diff $(KSRC)/drivers/hwmon/hdaps.c
{ head -2 diff/hdaps.diff; diff -up -U3 $(KSRC)/drivers/hwmon/hdaps.c hdaps.c | tail -n +3; } > diff/hdaps.diff.tmp || :
mv diff/hdaps.diff.tmp diff/hdaps.diff
else
#####################################################################
# This part runs as a submake in kernel Makefile context:

14
README
View File

@ -1,4 +1,4 @@
IBM ThinkPad SMAPI Driver v0.21
IBM ThinkPad SMAPI Driver v0.22
Author: Shem Multinymous <multinymous@gmail.com>
Project: http://sourceforge.net/projects/tpctl
Wiki: http://thinkwiki.org/wiki/tp_smapi
@ -258,9 +258,9 @@ 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 to the above, battery information is accessible through IO ports
0x1604-0x161F (these provide a virtual address space, which exposes both
battery information and HDAPS accelerometer data). It's not clear which
hardware component handles these ports; maybe it's another interface to the
embedded controller (in addition to the above SMAPI protocol and the standard
EC interface at ports 0x62,0x66).
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
tp_base modul and the H8S hardware documentation:
http://documentation.renesas.com/eng/products/mpumcu/rej09b0300_2140bhm.pdf

View File

@ -1,21 +1,20 @@
--- linux-2.6.15-rc5-orig/drivers/firmware/Kconfig 2005-12-13 19:13:57.000000000 +0200
+++ linux-2.6.15-rc5-patched/drivers/firmware/Kconfig 2005-12-21 00:35:13.000000000 +0200
@@ -87,2 +87,11 @@
@@ -85,4 +85,7 @@
Say Y or M here to enable the driver for use by Dell systems
management software such as Dell OpenManage.
+config TP_BASE
+ tristate "ThinkPad-specific hardware support"
+ depends on X86
+ default n
+ help
+ This provides coordination of access to ThinkPad-specific hardware.
+
+ If you have an IBM ThinkPad laptop, say Y or M here
+ tristate
+
endmenu
--- linux-2.6.15-rc5-orig/drivers/hwmon/Kconfig 2005-10-28 02:02:08.000000000 +0200
+++ linux-2.6.15-rc5-patched/drivers/hwmon/Kconfig 2005-12-21 00:42:22.000000000 +0200
@@ -415,3 +415,3 @@
@@ -437,6 +437,7 @@
config SENSORS_HDAPS
tristate "IBM Hard Drive Active Protection System (hdaps)"
- depends on HWMON && INPUT && X86
+ depends on HWMON && INPUT && X86 && TP_BASE
depends on HWMON && INPUT && X86
+ select TP_BASE
default n
help
This driver provides support for the IBM Hard Drive Active Protection

View File

@ -1,15 +1,18 @@
--- linux-2.6.15-rc5-orig/drivers/firmware/Kconfig 2005-12-13 19:13:57.000000000 +0200
+++ linux-2.6.15-rc5-patched/drivers/firmware/Kconfig 2005-12-21 00:35:13.000000000 +0200
@@ -87,2 +87,13 @@
@@ -88,4 +88,16 @@
config TP_BASE
tristate
+config TP_SMAPI
+ tristate "ThinkPad SMAPI Support"
+ depends on X86 && TP_BASE
+ depends on X86
+ select TP_BASE
+ default n
+ help
+ This adds SMAPI support on IBM ThinkPads, mostly used for battery
+ charge control. For more information about this driver see
+ <http://www.thinkwiki.org/wiki/tp_smapi> .
+ <http://www.thinkwiki.org/wiki/tp_smapi>.
+
+ If you have an IBM ThinkPad laptop, say Y or M here.
+

View File

@ -1,12 +1,12 @@
--- a/drivers/hwmon/hdaps.c 2006-01-01 00:00:00.000000000 +0000
+++ b/drivers/hwmon/hdaps.c 2006-01-01 00:00:00.000000000 +0000
@@ -33,74 +33,62 @@
@@ -33,253 +33,256 @@
#include <linux/module.h>
#include <linux/timer.h>
#include <linux/dmi.h>
-#include <asm/io.h>
+#include <linux/tp_base.h>
+#include <linux/jiffies.h>
#include <asm/io.h>
-#define HDAPS_LOW_PORT 0x1600 /* first port used by hdaps */
-#define HDAPS_NR_PORTS 0x30 /* number of ports: 0x1600 - 0x162f */
@ -22,14 +22,16 @@
-#define HDAPS_PORT_KMACT 0x161d /* keyboard or mouse activity */
-
-#define STATE_FRESH 0x50 /* accelerometer data is fresh */
+#define HDAPS_IDX_STATE 0x1 /* device state */
+#define HDAPS_IDX_YPOS 0x2 /* y-axis position */
+#define HDAPS_IDX_XPOS 0x4 /* x-axis position */
+#define HDAPS_IDX_TEMP1 0x6 /* device temperature, in celcius */
+#define HDAPS_IDX_YVAR 0x7 /* y-axis variance (what is this?) */
+#define HDAPS_IDX_XVAR 0x9 /* x-axis variance (what is this?) */
+#define HDAPS_IDX_TEMP2 0xb /* device temperature (again?) */
+#define HDAPS_IDX_UNKNOWN 0xc /* what is this? */
+#define HDAPS_IDX_READOUTS 0x1 /* readouts included in this read */
+ /* First readout, if READOUTS>=1: */
+#define HDAPS_IDX_YPOS1 0x2 /* y-axis position word */
+#define HDAPS_IDX_XPOS1 0x4 /* x-axis position word */
+#define HDAPS_IDX_TEMP1 0x6 /* device temperature in Celsius */
+ /* Second readout, if READOUTS>=2: */
+#define HDAPS_IDX_XPOS2 0x7 /* y-axis position word */
+#define HDAPS_IDX_YPOS2 0x9 /* x-axis pisition word */
+#define HDAPS_IDX_TEMP2 0xb /* device temperature in Celsius */
+#define HDAPS_IDX_QUEUED 0xc /* Number of queued readouts left */
+#define HDAPS_IDX_KMACT 0xd /* keyboard or mouse activity */
#define KEYBD_MASK 0x20 /* set if keyboard activity */
@ -39,8 +41,8 @@
-#define INIT_TIMEOUT_MSECS 4000 /* wait up to 4s for device init ... */
-#define INIT_WAIT_MSECS 200 /* ... in 200ms increments */
+#define STATE_HAVE_POS 0x01 /* have position data */
+#define STATE_HAVE_VAR 0x02 /* have position and variance data */
+#define EC_POLL_HZ 200 /* EC accelerometer sampling rate */
+#define EC_FILTER_DEPTH 2 /* EC running average filter depth */
+
+#define READ_TIMEOUT_MSECS 100 /* wait this long for device read */
+#define RETRY_MSECS 3 /* retry delay */
@ -49,7 +51,7 @@
#define HDAPS_INPUT_FUZZ 4 /* input event threshold */
#define HDAPS_INPUT_FLAT 4
+#define KMACT_REMEMBER_PERIOD (HDAPS_POLL_PERIOD*2) /* k/m persistance */
+#define KMACT_REMEMBER_PERIOD (HZ/10) /* k/m persistance */
+
static struct timer_list hdaps_timer;
static struct platform_device *pdev;
@ -58,19 +60,35 @@
-static u8 km_activity;
-static int rest_x;
-static int rest_y;
-
-static DECLARE_MUTEX(hdaps_sem);
+static unsigned int hdaps_force;
+static int needs_calibration = 0;
-static DECLARE_MUTEX(hdaps_sem);
-
-/*
- * __get_latch - Get the value from a given port. Callers must hold hdaps_sem.
- */
+/* Latest state read */
+static int pos_x, pos_y; /* position */
+static u8 temperature; /* temperature */
+static int rest_x, rest_y; /* calibrated rest position */
+
+/* Last time we saw keyboard and mouse activity */
+u64 last_keyboard_jiffies = INITIAL_JIFFIES;
+u64 last_mouse_jiffies = INITIAL_JIFFIES;
+
+/* hdaps_read_row - read a row of data from the controller.
+ * Also prefetches the next read, to reduce udelay busy-waiting.
+ * If fast, do one quick attempt without retries.
+ * Caller must hold controller lock.
*/
-static inline u8 __get_latch(u16 port)
-{
- return inb(port) & 0xff;
-}
-
+static int hdaps_read_row(int fast, u8 *dataval) {
+ int ret;
+ struct tp_controller_row args, data;
-/*
- * __check_latch - Check a port latch for a given value. Returns zero if the
- * port contains the given value. Callers must hold hdaps_sem.
@ -80,37 +98,46 @@
- if (__get_latch(port) == val)
- return 0;
- return -EINVAL;
-}
+/* Latest state read */
+static int pos_x, pos_y; /* position */
+static int var_x, var_y; /* variance (what is this?) */
+static int rest_x, rest_y; /* calibrated rest position */
+static u8 temp1, temp2; /* temperatures */
+
+/* Last time we saw keyboard and mouse activity */
+u64 last_keyboard_jiffies = INITIAL_JIFFIES;
+u64 last_mouse_jiffies = INITIAL_JIFFIES;
+ args.val[0x0] = 0x11;
+ args.mask = 0x0001;
+ data.mask = (1 << HDAPS_IDX_READOUTS) | (1 << HDAPS_IDX_KMACT) |
+ (3 << HDAPS_IDX_YPOS1) | (3 << HDAPS_IDX_XPOS1) |
+ (1 << HDAPS_IDX_TEMP1);
+ if (fast)
+ ret = tp_controller_try_read_row(&args, &data);
+ else
+ ret = tp_controller_read_row(&args, &data);
+ memcpy(dataval, &data.val, TP_CONTROLLER_ROW_LEN);
+ tp_controller_prefetch_row(&args);
+ return ret;
}
/*
-/*
- * __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.
+ * __wait_latch - Wait for a port latch to get a certain value,
+ * returning zero if the value is obtained. Callers must hold controller lock.
+/* __hdaps_update - read current state and update global state variables.
+ * Caller must hold controller lock.
*/
static int __wait_latch(u16 port, u8 val)
-static int __wait_latch(u16 port, u8 val)
+static int __hdaps_update(int fast)
{
unsigned int i;
- unsigned int i;
+ u8 row[TP_CONTROLLER_ROW_LEN];
+ int ret;
- for (i = 0; i < 20; i++) {
- if (!__check_latch(port, val))
+ for (i = 0; i < 200; i++) {
+ if (inb(port)==val)
return 0;
udelay(5);
}
@@ -108,82 +96,63 @@ static int __wait_latch(u16 port, u8 val
return -EIO;
}
- return 0;
- udelay(5);
- }
+ ret = hdaps_read_row(fast, row);
+ if (ret)
+ return ret;
- return -EIO;
-}
+ if (row[HDAPS_IDX_READOUTS]<1)
+ return -EBUSY; /* no pending readout, try again later */
-/*
- * __device_refresh - request a refresh from the accelerometer. Does not wait
@ -122,7 +149,26 @@
- if (inb(0x1604) != STATE_FRESH) {
- outb(0x11, 0x1610);
- outb(0x01, 0x161f);
- }
+ pos_x = *(s16*)(row+HDAPS_IDX_XPOS1) * (hdaps_invert?-1:1);
+ pos_y = *(s16*)(row+HDAPS_IDX_YPOS1) * (hdaps_invert?-1:1);
+
+ /* 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 (row[HDAPS_IDX_KMACT] & KEYBD_MASK)
+ last_keyboard_jiffies = get_jiffies_64();
+ if (row[HDAPS_IDX_KMACT] & MOUSE_MASK)
+ last_mouse_jiffies = get_jiffies_64();
+
+ /* Temperatures */
+ temperature = row[HDAPS_IDX_TEMP1];
+
+ if (needs_calibration) {
+ rest_x = pos_x;
+ rest_y = pos_y;
+ needs_calibration = 0;
}
-}
-/*
@ -134,79 +180,82 @@
-{
- __device_refresh();
- return __wait_latch(0x1604, STATE_FRESH);
-}
-
-/*
+ return 0;
}
/*
- * __device_complete - indicate to the accelerometer that we are done reading
- * data, and then initiate an async refresh. Callers must hold hdaps_sem.
+/* hdaps_read_row - read a row of data from the controller.
+ * Also prefetches the next read, to reduce udelay busy-waiting.
+ * If fast, do one quick attempt without retries.
+ * Caller must hold controller lock.
+ * hdaps_read_pair - reads the values from a pair of ports, placing the values
+ * in the given pointers. Returns zero on success. Can sleep.
+ * Retries until timeout if the accelerometer is not in ready status (common).
+ * Does internal locking.
*/
-static inline void __device_complete(void)
-{
+static int hdaps_update(void)
{
- inb(0x161f);
- inb(0x1604);
- __device_refresh();
+static int hdaps_read_row(int fast, u8* row) {
+ int ret;
+ if (fast)
+ ret = tp_controller_try_read_row(0x11, 0x01, row);
+ else
+ ret = tp_controller_read_row(0x11, 0x01, row);
+ tp_controller_prefetch_row(0x11, 0x01);
+ int total, ret;
+ for (total=READ_TIMEOUT_MSECS; total>0; total-=RETRY_MSECS) {
+ ret = tp_controller_lock();
+ if (ret)
+ return ret;
+ ret = __hdaps_update(0);
+ tp_controller_unlock();
+
+ if (!ret)
+ return 0;
+ if (ret != -EBUSY)
+ break;
+ msleep(RETRY_MSECS);
+ }
+ return ret;
}
-/*
/*
- * 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_update - read current state and update global state variables.
+ * Caller must hold controller lock.
+ * hdaps_set_power - enable or disable power to the accelerometer.
+ * 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_update(int fast)
{
+ u8 row[TP_CONTROLLER_ROW_LEN];
-{
+static int hdaps_set_power(int on) {
int ret;
-
- down(&hdaps_sem);
-
- /* do a sync refresh -- we need to be sure that we read fresh data */
- ret = __device_refresh_sync();
+ ret = hdaps_read_row(fast, row);
+ struct tp_controller_row args, data;
+ args.val[0x0] = 0x14;
+ args.val[0x1] = on ? 0x01 : 0x00;
+ args.mask = 0x0003;
+ data.mask = 0x8000;
+ ret = tp_controller_read_row(&args, &data);
if (ret)
- goto out;
-
- *val = inb(port);
- __device_complete();
+ return ret;
-
-out:
- up(&hdaps_sem);
- return ret;
-}
+ if (row[HDAPS_IDX_STATE]>=STATE_HAVE_POS) {
+ pos_x = *(s16*)(row+HDAPS_IDX_XPOS) * (hdaps_invert?-1:1);
+ pos_y = *(s16*)(row+HDAPS_IDX_YPOS) * (hdaps_invert?-1:1);
+ } else
+ return -EBUSY;
+
+ /* Don't insist on a "variance" readout; it's useless anyway. */
+ if (row[HDAPS_IDX_STATE]>=STATE_HAVE_VAR) {
+ var_x = *(s16*)(row+HDAPS_IDX_XVAR) * (hdaps_invert?-1:1);
+ var_y = *(s16*)(row+HDAPS_IDX_YVAR) * (hdaps_invert?-1:1);
+ }
-
-/* __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)
-{
- /* do a sync refresh -- we need to be sure that we read fresh data */
- if (__device_refresh_sync())
- return -EIO;
+ return ret;
+ if (data.val[0xF]!=0x00)
return -EIO;
-
- *y = inw(port2);
- *x = inw(port1);
@ -217,70 +266,68 @@
- if (hdaps_invert) {
- *x = -*x;
- *y = -*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 (row[HDAPS_IDX_KMACT] & KEYBD_MASK)
+ last_keyboard_jiffies = get_jiffies_64();
+ if (row[HDAPS_IDX_KMACT] & MOUSE_MASK)
+ last_mouse_jiffies = get_jiffies_64();
+
+ /* Temperatures */
+ temp1 = row[HDAPS_IDX_TEMP1];
+ temp2 = row[HDAPS_IDX_TEMP2];
+
+ if (needs_calibration) {
+ rest_x = pos_x;
+ rest_y = pos_y;
+ needs_calibration = 0;
}
- }
-
return 0;
@@ -192,16 +161,23 @@ static int __hdaps_read_pair(unsigned in
}
/*
* hdaps_read_pair - reads the values from a pair of ports, placing the values
* in the given pointers. Returns zero on success. Can sleep.
+ * Retries until timeout if the accelerometer is not in ready status (common).
+ * Does internal locking.
- * 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_set_ec_config - set accelerometer parameters.
+ * freq - embedded controller sampling rate
+ * depth - embedded controller running average filter depth
+ * 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_update(void)
{
- int ret;
-{
+static int hdaps_set_ec_config(int freq, int depth) {
int ret;
-
- down(&hdaps_sem);
- ret = __hdaps_read_pair(port1, port2, val1, val2);
- up(&hdaps_sem);
+ int total, ret;
+ for (total=READ_TIMEOUT_MSECS; total>0; total-=RETRY_MSECS) {
+ tp_controller_lock();
+ ret = __hdaps_update(0);
+ tp_controller_unlock();
+ if (!ret)
+ return 0;
+ if (ret != -EBUSY)
+ break;
+ msleep(RETRY_MSECS);
+ }
return ret;
-
- return ret;
+ struct tp_controller_row args, data;
+ args.val[0x0] = 0x10;
+ args.val[0x1] = (u8)freq;
+ args.val[0x2] = (u8)(freq>>8);
+ args.val[0x3] = depth;
+ args.mask = 0x000F;
+ data.mask = 0x8000;
+ ret = tp_controller_read_row(&args, &data);
+ if (ret)
+ return ret;
+ if (data.val[0xF]!=0x00)
+ return -EIO;
+ return 0;
}
@@ -211,30 +187,30 @@ static int hdaps_read_pair(unsigned int
/*
* hdaps_device_init - initialize the accelerometer. Returns zero on success
* and negative error code on failure. Can sleep.
*/
+#define BAD_INIT(msg) do { err_msg = msg; goto bad; } while (0)
static int hdaps_device_init(void)
{
- int total, ret = -ENXIO;
-
- down(&hdaps_sem);
-
+ int ret;
+ struct tp_controller_row args, data;
+ u8 status;
+ const char *err_msg = "";
- outb(0x13, 0x1610);
- outb(0x01, 0x161f);
- if (__wait_latch(0x161f, 0x00))
- goto out;
-
+ ret = tp_controller_lock();
+ if (ret)
+ return ret;
- /*
- * Most ThinkPads return 0x01.
- *
@ -292,43 +339,72 @@
- if (__check_latch(0x1611, 0x03) &&
- __check_latch(0x1611, 0x02) &&
- __check_latch(0x1611, 0x01))
+ int ret = -ENXIO;
+ u8 row[TP_CONTROLLER_ROW_LEN];
+ u8 status;
+
+ tp_controller_lock();
+
+ if (tp_controller_read_row(0x13, 0x01, row))
+ goto out;
+ if (row[0xf]!=0x00)
+ goto out;
+ status = row[1];
- goto out;
+ args.val[0x0] = 0x13; /* Get accelerometer info */
+ args.mask=0x0001;
+ data.mask=0x8002;
+ if (tp_controller_read_row(&args, &data))
+ BAD_INIT("read1");
+ if (data.val[0xF]==0x80 || data.val[0x1]==0x00)
+ BAD_INIT("accelerometer not available");
+ if (data.val[0xF]!=0x00)
+ BAD_INIT("check1");
+ status = data.val[0x1];
+
+ if (status != 0x04 && /* Semi-invertex axes (ThinkPad T60) */
+ if (status != 0x01 && /* Normal axes (ThinkPad T43) */
+ status != 0x02 && /* Already initialized */
+ status != 0x03 && /* Invertex axes (ThinkPad R50p, T41p, R42p) */
+ status != 0x02 && /* Chip already initialized */
+ status != 0x01 ) /* Normal axes */
+ status != 0x04 && /* Swapped axes (ThinkPad T60) */
+ status != 0x05 ) /* Inverted X axis (ThinkPad X60s) */
+ {
+ printk(KERN_ERR "hdaps: initial latch check bad (0x%02x).\n",
+ status);
goto out;
+ BAD_INIT("latch");
+ }
printk(KERN_DEBUG "hdaps: initial latch check good (0x%02x).\n",
- __get_latch(0x1611));
+ status);
outb(0x17, 0x1610);
outb(0x81, 0x1611);
@@ -259,27 +235,18 @@ static int hdaps_device_init(void)
outb(0x01, 0x161f);
if (__wait_latch(0x161f, 0x00))
goto out;
- 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;
-
+ args.val[0x0] = 0x17; /* Function unknown */
+ args.val[0x1] = 0x81;
+ args.mask = 0x0003;
+ data.mask = 0x800E;
+ if (tp_controller_read_row(&args, &data))
+ BAD_INIT("read2");
+ if (data.val[0x1]!=0x00 ||
+ data.val[0x2]!=0x60 ||
+ data.val[0x3]!=0x00 ||
+ data.val[0xF]!=0x00)
+ BAD_INIT("check2");
- /* 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;
@ -339,25 +415,36 @@
- ret = 0;
- break;
- }
+ tp_controller_invalidate();
+ udelay(200);
+ if (hdaps_set_power(1))
+ BAD_INIT("hdaps_set_power");
- msleep(INIT_WAIT_MSECS);
- }
+ /* Just prefetch instead of reading, to avoid ~1sec delay on load */
+ ret = tp_controller_prefetch_row(0x11, 0x01);
+ goto good;
+ if (hdaps_set_ec_config(EC_POLL_HZ, EC_FILTER_DEPTH))
+ BAD_INIT("hdaps_set_ec_config");
out:
-out:
- up(&hdaps_sem);
+ printk(KERN_ERR "hdaps: init failed!\n");
+ tp_controller_invalidate();
+ udelay(200);
+
+ /* Just prefetch instead of reading, to avoid ~1sec delay on load */
+ args.val[0x0] = 0x11;
+ args.mask = 0x0001;
+ ret = tp_controller_prefetch_row(&args);
+ if (ret)
+ BAD_INIT("prefetch");
+ goto good;
+bad:
+ ret = -ENXIO;
+ printk(KERN_ERR "hdaps: init failed (%s)\n", err_msg);
+good:
+ tp_controller_invalidate();
+ tp_controller_unlock();
return ret;
}
@@ -298,13 +265,25 @@ static int hdaps_probe(struct platform_d
@@ -298,13 +301,25 @@ static int hdaps_probe(struct platform_d
return 0;
}
@ -384,7 +471,7 @@
.resume = hdaps_resume,
.driver = {
.name = "hdaps",
@@ -313,34 +292,42 @@ static struct platform_driver hdaps_driv
@@ -313,34 +328,42 @@ static struct platform_driver hdaps_driv
};
/*
@ -411,7 +498,7 @@
/* Cannot sleep. Try nonblockingly. If we fail, try again later. */
- if (down_trylock(&hdaps_sem)) {
- mod_timer(&hdaps_timer,jiffies + HDAPS_POLL_PERIOD);
+ if (tp_controller_trylock())
+ if (tp_controller_try_lock())
+ goto keep_active;
+
+ ret = __hdaps_update(1); /* fast update, we're in softirq context */
@ -428,38 +515,35 @@
-
- input_report_abs(hdaps_idev, ABS_X, x - rest_x);
- input_report_abs(hdaps_idev, ABS_Y, y - rest_y);
- input_sync(hdaps_idev);
-
+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);
+ /* 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);
}
@@ -349,65 +336,55 @@ out:
@@ -349,65 +372,37 @@ out:
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;
- 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_variance_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
-}
-
-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);
@ -468,26 +552,24 @@
return ret;
-
- return sprintf(buf, "(%d,%d)\n", x, y);
+ return sprintf(buf, "(%d,%d)\n", var_x, var_y);
}
static ssize_t hdaps_temp1_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
-}
-
-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)
+ int ret = hdaps_update();
+ if (ret)
return ret;
- return ret;
-
- return sprintf(buf, "%u\n", temp);
+ return sprintf(buf, "%u\n", temp1);
+ return sprintf(buf, "(%d,%d)\n", pos_x, pos_y);
}
static ssize_t hdaps_temp2_show(struct device *dev,
-static ssize_t hdaps_temp2_show(struct device *dev,
+static ssize_t hdaps_temperature_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
- u8 temp;
@ -500,7 +582,7 @@
return ret;
-
- return sprintf(buf, "%u\n", temp);
+ return sprintf(buf, "%u\n", temp2);
+ return sprintf(buf, "%u\n", temperature);
}
static ssize_t hdaps_keyboard_activity_show(struct device *dev,
@ -524,7 +606,7 @@
}
static ssize_t hdaps_calibrate_show(struct device *dev,
@@ -420,10 +397,7 @@ static ssize_t hdaps_calibrate_store(str
@@ -420,10 +415,7 @@ static ssize_t hdaps_calibrate_store(str
struct device_attribute *attr,
const char *buf, size_t count)
{
@ -535,14 +617,36 @@
return count;
}
@@ -530,20 +504,18 @@ static int __init hdaps_init(void)
@@ -449,9 +441,8 @@ static ssize_t hdaps_invert_store(struct
}
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);
+static DEVICE_ATTR(temp1, 0444, hdaps_temperature_show, NULL);
+ /* named temp1 instead of temperature for backward compatibility */
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);
@@ -459,9 +450,7 @@ static DEVICE_ATTR(invert, 0644, hdaps_i
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,
@@ -542,20 +531,18 @@ static int __init hdaps_init(void)
{ .ident = NULL }
};
- if (!dmi_check_system(hdaps_whitelist)) {
+ if (!(dmi_check_system(hdaps_whitelist) || hdaps_force)) {
printk(KERN_WARNING "hdaps: supported laptop not found!\n");
ret = -ENXIO;
ret = -ENODEV;
goto out;
}
@ -561,7 +665,7 @@
pdev = platform_device_register_simple("hdaps", -1, NULL, 0);
if (IS_ERR(pdev)) {
@@ -561,8 +533,8 @@ static int __init hdaps_init(void)
@@ -573,8 +560,8 @@ static int __init hdaps_init(void)
goto out_group;
}
@ -572,7 +676,7 @@
/* initialize the input class */
hdaps_idev->name = "hdaps";
@@ -575,11 +547,7 @@ static int __init hdaps_init(void)
@@ -587,11 +574,7 @@ static int __init hdaps_init(void)
input_register_device(hdaps_idev);
@ -585,7 +689,7 @@
printk(KERN_INFO "hdaps: driver successfully loaded.\n");
return 0;
@@ -590,8 +558,6 @@ out_device:
@@ -602,8 +585,6 @@ out_device:
platform_device_unregister(pdev);
out_driver:
platform_driver_unregister(&hdaps_driver);
@ -594,7 +698,7 @@
out:
printk(KERN_WARNING "hdaps: driver init failed (ret=%d)!\n", ret);
return ret;
@@ -604,7 +570,6 @@ static void __exit hdaps_exit(void)
@@ -616,7 +597,6 @@ static void __exit hdaps_exit(void)
sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
platform_device_unregister(pdev);
platform_driver_unregister(&hdaps_driver);
@ -602,7 +706,7 @@
printk(KERN_INFO "hdaps: driver unloaded.\n");
}
@@ -615,6 +580,9 @@ module_exit(hdaps_exit);
@@ -627,6 +607,9 @@ module_exit(hdaps_exit);
module_param_named(invert, hdaps_invert, bool, 0);
MODULE_PARM_DESC(invert, "invert data along each axis");

308
tp_base.c
View File

@ -1,13 +1,16 @@
/*
* tp_base.c - coordinate access to ThinkPad-specific hardware resources
*
* ThinkPad laptops have a controller, accessible at ports 0x1600-0x161F,
* which provides system management services (currently known: battery
* information and accelerometer readouts). This driver coordinates access
* to the controller, and abstracts it to the extent possible.
* 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.
* H8S hardware documentation and terminology is used in this file:
* "H8S/2104B Group Hardware Manual",
* http://documentation.renesas.com/eng/products/mpumcu/rej09b0300_2140bhm.pdf
*
*
* Copyright (C) 2005 Shem Multinymous <multinymous@gmail.com>
* Copyright (C) 2006 Shem Multinymous <multinymous@gmail.com>
*
* 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
@ -33,188 +36,260 @@
#include <linux/jiffies.h>
#include <asm/io.h>
#define TP_VERSION "0.21"
#define TP_VERSION "0.22"
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 */
/* port 1610+i is mapped to H8S reg TWRi for 0<i<16 */
#define TPC_READ_RETRIES 100
#define TPC_READ_UDELAY 5
/* H8S STR3 status flags (see H8S/2104B Group Hardware Manual p.549) */
#define H8S_STR3_IBF3B 0x80 /* Bidi. Data Register Input Buffer Full */
#define H8S_STR3_OBF3B 0x40 /* Bidi. Data Register Output Buffer Full */
#define H8S_STR3_MWMF 0x20 /* Master Write Mode Flag */
#define H8S_STR3_SWMF 0x10 /* Slave Write Mode Flag */
#define H8S_STR3_MASK 0xF0 /* All bits we care about in STR3 */
/* Timeouts and retries */
#define TPC_READ_RETRIES 150
#define TPC_READ_NDELAY 500
#define TPC_REQUEST_RETRIES 100
#define TPC_REQUEST_NDELAY 10
#define TPC_PREFETCH_TIMEOUT (HZ/10) /* invalidate prefetch after 0.1sec */
#define TPC_PREFETCH_INVALID INITIAL_JIFFIES
static DECLARE_MUTEX(tp_controller_sem);
/* Module parameters: */
static int tp_debug = 0;
module_param_named(debug, tp_debug, int, 0600);
MODULE_PARM_DESC(debug, "Debug level (0=off, 1=on)");
#define DPRINTK(fmt, args...) { if (tp_debug) printk(KERN_DEBUG "tp_base: " fmt, ## args); }
/* A few macros for printk()ing: */
#define DPRINTK(fmt, args...) \
do { if (tp_debug) printk(KERN_DEBUG fmt, ## args); } while (0)
#define MSG_FMT(fmt, args...) \
"tp_base: %s: " fmt "\n", __func__, ## args
#define ARG_FMT(msg,code) \
MSG_FMT("%s: (0x%02x:0x%02x)->0x%02x", \
msg, args->val[0x0], args->val[0xF], code)
static u64 prefetch_jiffies = TPC_PREFETCH_INVALID; /* time of prefetch */
static u8 prefetch_arg1610, prefetch_arg161F; /* args of last prefetch */
/* 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 */
/*** Functionality ***/
/* Locking: */
void tp_controller_lock(void) {
down(&tp_controller_sem);
static DECLARE_MUTEX(tp_controller_mutex);
int tp_controller_lock(void)
{
int ret;
ret = down_interruptible(&tp_controller_mutex);
if (ret)
DPRINTK("tp_controller mutex down interrupted: %d\n", ret);
return ret;
}
EXPORT_SYMBOL_GPL(tp_controller_lock);
int tp_controller_trylock(void) {
return down_trylock(&tp_controller_sem);
int tp_controller_try_lock(void)
{
return down_trylock(&tp_controller_mutex);
}
EXPORT_SYMBOL_GPL(tp_controller_trylock);
EXPORT_SYMBOL_GPL(tp_controller_try_lock);
void tp_controller_unlock(void) {
up(&tp_controller_sem);
void tp_controller_unlock(void)
{
up(&tp_controller_mutex);
}
EXPORT_SYMBOL_GPL(tp_controller_unlock);
/* Tell embedded controller to prepare a row */
static int tp_controller_request_row(u8 arg1610, u8 arg161F) {
u8 status;
status = inb(0x1604);
if (status&0x40) { /* readout data already pending? */
inb(0x161F); /* marks end of previous transaction */
DPRINTK("readout already pending (0x%02x,0x%02x)->0x%02x\n",
arg1610, arg161F, status);
static int tp_controller_request_row(struct tp_controller_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;
}
/* EC protocol requires write to TWR15. Default to 0x01: */
if (!(args->mask & 0x8000))
args->val[0xF] = 0x01;
/* 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
ARG_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
ARG_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 ARG_FMT("bad initial STR3", str3));
return -EIO;
}
/* send 1st argument */
outb(arg1610, 0x1610);
status = inb(0x1604);
if (status!=0x20) { /* not accepted? */
DPRINTK("arg1610 not accepted (0x%02x,0x%02x)->0x%02x\n",
arg1610, arg161F, status);
if (tp_debug) { /* Someone else touching the EC? */
static int dumps_left=20;
if (dumps_left) { /* May occur often, don't flood. */
printk("" "tp_base: arg1610 not accepted:\n");
dump_stack();
dumps_left--;
}
/* 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 ARG_FMT("arg0 rejected", str3));
return -EIO;
}
/* Send TWR1 through TWR14: */
for (i=1; i<TP_CONTROLLER_ROW_LEN-1; i++)
if ((args->mask>>i)&1)
outb(args->val[i], TPC_TWR0_PORT+i);
/* Send TWR15. This marks end of command. */
outb(args->val[0xF], 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; i<TPC_REQUEST_RETRIES; ++i) {
str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK;
if (str3 & H8S_STR3_SWMF) /* EC started replying */
return 0;
else if (str3==(H8S_STR3_IBF3B|H8S_STR3_MWMF) ||
str3==0x00) /* normal progress, wait it out */
ndelay(TPC_REQUEST_NDELAY);
else { /* weird EC status */
printk(KERN_WARNING
ARG_FMT("bad end STR3", str3));
return -EIO;
}
return -EBUSY; /* the EC is handling a prior request */
}
/* send 2nd argument */
outb(arg161F, 0x161F);
status = inb(0x1604);
if (status==0x20) { /* not responding? */
printk(KERN_WARNING
"tp_base: 161F rejected (0x%02x,0x%02x)->0x%02x\n",
arg1610, arg161F, status);
return -EIO; /* this is abnormal */
}
return 0;
printk(KERN_WARNING ARG_FMT("EC is mysteriously silent", str3));
return -EIO;
}
/* Read current row data from the controller, assuming it's already
* requested.
*/
static int tp_controller_read_data(u8* buf) {
static int tp_controller_read_data(struct tp_controller_row *data)
{
int i;
u8 status = inb(0x1604);
/* After writing to ports 0x1610 and 0x161F, the status register at
* IO port 0x1604 assumes the sequence of values 0xA0, 0x00, 0x10 and
* finally bit 0x40 goes up (usually 0x50) signalling data ready.
* It takes about a dozen nanosecs total, with very high variance.
u8 str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK;
/* Once we make a request, STR3 assumes the following sequence of
* values as it reads the request and writes its data.
* It takes about a few dozen nanosecs total, with very high variance.
*/
if (status==0xA0 || status==0x00 || status==0x10) {
if (str3==(H8S_STR3_IBF3B|H8S_STR3_MWMF) ||
str3==0x00 ||
str3==H8S_STR3_SWMF )
return -EBUSY; /* not ready yet */
}
if (!(status&0x40)) {
printk(KERN_WARNING "tp_base: bad status (0x%02x) in read\n",
status);
/* Finally, it signals output buffer full: */
if (str3 != (H8S_STR3_OBF3B|H8S_STR3_SWMF)) {
printk(KERN_WARNING
MSG_FMT("bad initial STR3 (0x%02x)", str3));
return -EIO;
}
/* Data in IO ports 0x1610-0x161F. Reading 0x161F ends transaction. */
for (i=0; i<TP_CONTROLLER_ROW_LEN; ++i)
buf[i] = inb(0x1610+i);
if (inb(0x1604)&0x40) /* readout still pending? */
printk(KERN_WARNING "tp_base: data pending after read\n");
/* Read first byte (signals start of read transactions): */
data->val[0] = inb(TPC_TWR0_PORT);
/* Optionally read 14 more bytes: */
for (i=1; i<TP_CONTROLLER_ROW_LEN-1; ++i)
if ((data->mask >> 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
MSG_FMT("OBF3B=1 after read (0x%02x)", str3));
return 0;
}
/* Is the given row currently prefetched? */
static int tp_controller_is_row_fetched(u8 arg1610, u8 arg161F) {
return (prefetch_jiffies != TPC_PREFETCH_INVALID) &&
(prefetch_arg1610 == arg1610) &&
(prefetch_arg161F == arg161F) &&
/* Is the given row currently prefetched?
* To keep things simple we compare only the first and last args;
* in practice this suffices .*/
static int tp_controller_is_row_fetched(struct tp_controller_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);
}
/* Read a row from the embedded controller */
int tp_controller_read_row(u8 arg1610, u8 arg161F, u8* buf) {
int tp_controller_read_row(struct tp_controller_row *args,
struct tp_controller_row *data)
{
int retries, ret;
if (tp_controller_is_row_fetched(arg1610,arg161F))
if (tp_controller_is_row_fetched(args))
goto read_row; /* already requested */
/* Request the row */
for (retries=0; retries<TPC_READ_RETRIES; ++retries) {
ret = tp_controller_request_row(arg1610, arg161F);
ret = tp_controller_request_row(args);
if (!ret)
goto read_row;
if (ret != -EBUSY)
break;
udelay(TPC_READ_UDELAY);
ndelay(TPC_READ_NDELAY);
}
printk(KERN_ERR
"thinkpad controller read(0x%02x,0x%02x): failed requesting row\n",
arg1610, arg161F);
printk(KERN_ERR ARG_FMT("failed requesting row", ret));
goto out;
read_row:
/* Read the row's data */
for (retries=0; retries<TPC_READ_RETRIES; ++retries) {
ret = tp_controller_read_data(buf);
ret = tp_controller_read_data(data);
if (!ret)
goto out;
if (ret!=-EBUSY)
break;
udelay(TPC_READ_UDELAY);
ndelay(TPC_READ_NDELAY);
}
printk(KERN_ERR
"thinkpad controller read(0x%02x,0x%02x): failed waiting for data\n",
arg1610, arg161F);
printk(KERN_ERR ARG_FMT("failed waiting for data", ret));
out:
prefetch_jiffies = TPC_PREFETCH_INVALID;
prefetch_jiffies = TPC_PREFETCH_JUNK;
return ret;
}
EXPORT_SYMBOL_GPL(tp_controller_read_row);
/* Read a prefetched row from the controller. Don't fetch, don't retry. */
int tp_controller_try_read_row(u8 arg1610, u8 arg161F, u8* buf) {
int tp_controller_try_read_row(struct tp_controller_row *args,
struct tp_controller_row *data)
{
int ret;
if (!tp_controller_is_row_fetched(arg1610,arg161F)) {
DPRINTK("try_read_row(0x%02x,0x%02x): row not fetched\n",
arg1610, arg161F);
return -ENODATA;
if (!tp_controller_is_row_fetched(args)) {
ret = -ENODATA;
} else {
prefetch_jiffies = TPC_PREFETCH_INVALID; /* data eaten up */
ret = tp_controller_read_data(buf);
if (ret)
DPRINTK("try_read_row(0x%02x,0x%02x): "
"read failed with %d\n",
arg1610, arg161F, ret);
return ret;
ret = tp_controller_read_data(data);
if (!ret)
prefetch_jiffies = TPC_PREFETCH_NONE; /* eaten up */
}
return ret;
}
EXPORT_SYMBOL_GPL(tp_controller_try_read_row);
@ -222,22 +297,25 @@ EXPORT_SYMBOL_GPL(tp_controller_try_read_row);
/* Prefech a row from the controller. This is a one-shot prefetch
* attempt, without retries or delays.
*/
int tp_controller_prefetch_row(u8 arg1610, u8 arg161F) {
int ret = tp_controller_request_row(arg1610, arg161F);
int tp_controller_prefetch_row(struct tp_controller_row *args)
{
int ret;
ret = tp_controller_request_row(args);
if (ret) {
prefetch_jiffies = TPC_PREFETCH_INVALID;
prefetch_jiffies = TPC_PREFETCH_JUNK;
} else {
prefetch_jiffies = get_jiffies_64();
prefetch_arg1610 = arg1610;
prefetch_arg161F = arg161F;
prefetch_arg0 = args->val[0x0];
prefetch_argF = args->val[0xF];
}
return ret;
}
EXPORT_SYMBOL_GPL(tp_controller_prefetch_row);
void tp_controller_invalidate(void) {
prefetch_jiffies = TPC_PREFETCH_INVALID;
void tp_controller_invalidate(void)
{
prefetch_jiffies = TPC_PREFETCH_JUNK;
}
EXPORT_SYMBOL_GPL(tp_controller_invalidate);
@ -253,7 +331,8 @@ EXPORT_SYMBOL_GPL(tp_controller_invalidate);
} \
}
static int is_thinkpad(void) {
static int is_thinkpad(void)
{
struct dmi_system_id tp_whitelist[] = {
TP_DMI_MATCH("LENOVO","ThinkPad"),
TP_DMI_MATCH("IBM","ThinkPad"),
@ -266,7 +345,8 @@ static int is_thinkpad(void) {
/*** Init and cleanup ***/
static int __init tp_base_init(void) {
static int __init tp_base_init(void)
{
if (!is_thinkpad()) {
printk(KERN_ERR "tp_base: not a ThinkPad!\n");
return -ENODEV;
@ -277,14 +357,16 @@ static int __init tp_base_init(void) {
printk(KERN_ERR "tp_base: cannot claim ports %#x-%#x"
" (conflict with old hdaps driver?)\n",
TPC_BASE_PORT,
TPC_BASE_PORT+TPC_NUM_PORTS -1);
TPC_BASE_PORT + TPC_NUM_PORTS -1);
return -ENXIO;
}
prefetch_jiffies = TPC_PREFETCH_JUNK;
printk(KERN_INFO "tp_base: tp_base " TP_VERSION " loaded.\n");
return 0;
}
static void __exit tp_base_exit(void) {
static void __exit tp_base_exit(void)
{
release_region(TPC_BASE_PORT, TPC_NUM_PORTS );
printk(KERN_INFO "tp_base: unloaded.\n");
}

View File

@ -31,15 +31,20 @@
#define TP_CONTROLLER_ROW_LEN 16
struct tp_controller_row {
u8 val[TP_CONTROLLER_ROW_LEN];
u16 mask;
};
/* tp_controller_lock:
* Get exclusive lock for accesing the controller.
*/
extern void tp_controller_lock(void);
extern int tp_controller_lock(void);
/* tp_controller_trylock:
* Likewise, but non-blocking. Returns 0 if acquired lock.
*/
extern int tp_controller_trylock(void);
extern int tp_controller_try_lock(void);
/* tp_controller_unlock:
* Release lock.
@ -48,27 +53,37 @@ extern void tp_controller_unlock(void);
/* tp_controller_read_row:
* Read a data row from the controller, fetching and retrying if needed.
* The row args are specified by 16 byte arguments, some of which may be
* missing (but the first and last are mandatory). These are given in
* args->val[], 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->maska. 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.
*/
extern int tp_controller_read_row(u8 arg1610, u8 arg161F, u8* buf);
extern int tp_controller_read_row(struct tp_controller_row *args,
struct tp_controller_row *data);
/* tp_controller_try_read_row:
* Try read a prefetched row from the controller. Don't fetch or retry.
* See tp_controller_read_row above for the meaning of the arguments.
* Returns -EBUSY is data not ready and -ENODATA if row not prefetched.
* Caller must hold controller lock.
*/
extern int tp_controller_try_read_row(u8 arg1610, u8 arg161F, u8* buf);
extern int tp_controller_try_read_row(struct tp_controller_row *args,
struct tp_controller_row *mask);
/* tp_controller_prefetch_row:
* Prefetch data row from the controller. A subsequent call to
* tp_controller_read_row() with the same arguments will be faster,
* and a subsequent call to tp_controller_try_read_row stands a
* good chance of succeeding if done neither too soon nor too late.
* See tp_controller_read_row above for the meaning of the arguments.
* Returns -EBUSY on transient error and -EIO on abnormal condition.
* Caller must hold controller lock.
*/
extern int tp_controller_prefetch_row(u8 arg1610, u8 arg161F);
extern int tp_controller_prefetch_row(struct tp_controller_row *args);
/* tp_controller_invalidate:
* Invalidate the prefetched controller data.

View File

@ -10,11 +10,8 @@
* embedded controller (via the tp_base module).
*
*
* Copyright (C) 2005 Shem Multinymous <multinymous@gmail.com>.
* SMAPI access code based on the mwave driver by Mike Sullivan,
* Copyright (C) 1999 IBM Corporation.
* Module interface and DMI whitelist based on the hdaps module by
* Robert Love and Jesper Juhl.
* Copyright (C) 2006 Shem Multinymous <multinymous@gmail.com>.
* 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
@ -44,7 +41,7 @@
#include <asm/uaccess.h>
#include <asm/io.h>
#define TP_VERSION "0.21"
#define TP_VERSION "0.22"
#define TP_DESC "ThinkPad SMAPI Support"
#define TP_DIR "smapi"
@ -133,7 +130,7 @@ static struct {u8 rc; char *msg; int ret;} smapi_rc[]=
#define SMAPI_PORT2 0x4F /* fixed port, meaning unclear */
static unsigned short smapi_port = 0; /* APM control port, normally 0xB2 */
static DECLARE_MUTEX(smapi_sem);
static DECLARE_MUTEX(smapi_mutex);
#define SMAPI_MAX_RETRIES 10
@ -181,8 +178,12 @@ retry:
DPRINTK("req_in: BX=%x CX=%x DI=%x SI=%x\n",
inBX, inCX, inDI, inSI);
tp_controller_lock(); /* SMAPI and tp_base use different interfaces to
* the same chip, so stay on the safe side. */
/* SMAPI's SMI call and tp_base end up using the same interface
* (ports 0x1600-0x161F).use different interfaces to the same chip,
* they must be excluded from each other. */
ret = tp_controller_lock();
if (ret)
return ret;
__asm__ __volatile__(
"movl $0x00005380,%%eax\n\t"
@ -269,11 +270,20 @@ static int smapi_write(u32 inBX, u32 inCX,
*/
/* Lock controller and read row */
static int tpc_read_row(u8 arg1610, u8 arg161F, u8* buf) {
static int tpc_read_row(u8 arg0, int bat, u8* dataval) {
int ret;
tp_controller_lock();
ret = tp_controller_read_row(arg1610, arg161F, buf);
struct tp_controller_row args, data;
ret = tp_controller_lock();
if (ret)
return ret;
args.val[0x0] = arg0;
args.val[0xF] = (u8)bat;
args.mask = 0x8001;
data.mask = 0xFFFF;
ret = tp_controller_read_row(&args, &data);
tp_controller_unlock();
memcpy(dataval, &data.val, TP_CONTROLLER_ROW_LEN);
return ret;
}
@ -579,7 +589,7 @@ static int attr_get_bat(struct device_attribute *attr) {
return container_of(attr, struct bat_device_attribute, dev_attr)->bat;
}
static int show_tpc_bat_word(const char* fmt, u8 arg1610, int off,
static int show_tpc_bat_word(const char* fmt, u8 arg0, int off,
struct device_attribute *attr, char *buf)
{
u8 row[TP_CONTROLLER_ROW_LEN];
@ -587,13 +597,13 @@ static int show_tpc_bat_word(const char* fmt, u8 arg1610, int off,
int ret;
if (bat_has_extended_status(bat)!=1)
return -ENXIO;
ret = tpc_read_row(arg1610, bat, row);
ret = tpc_read_row(arg0, bat, row);
if (ret)
return ret;
return sprintf(buf, fmt, (int)*(u16*)(row+off));
}
static int show_tpc_bat_str(u8 arg1610, int off, int maxlen,
static int show_tpc_bat_str(u8 arg0, int off, int maxlen,
struct device_attribute *attr, char *buf)
{
int bat = attr_get_bat(attr);
@ -601,7 +611,7 @@ static int show_tpc_bat_str(u8 arg1610, int off, int maxlen,
int ret;
if (bat_has_extended_status(bat)!=1)
return -ENXIO;
ret = tpc_read_row(arg1610, bat, row);
ret = tpc_read_row(arg0, bat, row);
if (ret)
return ret;
strncpy(buf, (char*)row+off, maxlen);
@ -610,7 +620,7 @@ static int show_tpc_bat_str(u8 arg1610, int off, int maxlen,
return strlen(buf);
}
static int show_tpc_bat_power(u8 arg1610, int offV, int offA,
static int show_tpc_bat_power(u8 arg0, int offV, int offA,
struct device_attribute *attr, char *buf)
{
u8 row[TP_CONTROLLER_ROW_LEN];
@ -626,7 +636,7 @@ static int show_tpc_bat_power(u8 arg1610, int offV, int offA,
return sprintf(buf, "%d mW\n", milliamp*millivolt/1000);
}
static int show_tpc_bat_date(u8 arg1610, int off,
static int show_tpc_bat_date(u8 arg0, int off,
struct device_attribute *attr, char *buf)
{
u8 row[TP_CONTROLLER_ROW_LEN];
@ -636,7 +646,7 @@ static int show_tpc_bat_date(u8 arg1610, int off,
int bat = attr_get_bat(attr);
if (bat_has_extended_status(bat)!=1)
return -ENXIO;
ret = tpc_read_row(arg1610, bat, row);
ret = tpc_read_row(arg0, bat, row);
if (ret)
return ret;
@ -692,7 +702,7 @@ static int battery_start_charge_thresh_store(struct device *dev,
if (thresh > MAX_THRESH_START) /* clamp down to MAX_THRESH_START */
thresh = MAX_THRESH_START;
down(&smapi_sem);
down(&smapi_mutex);
ret = get_thresh(bat, THRESH_STOP, &other_thresh);
if (ret!=-ENOSYS) {
if (ret) /* other threshold is set? */
@ -710,7 +720,7 @@ static int battery_start_charge_thresh_store(struct device *dev,
}
ret = set_thresh(bat, THRESH_START, thresh);
out:
up(&smapi_sem);
up(&smapi_mutex);
return count;
}
@ -731,7 +741,7 @@ static int battery_stop_charge_thresh_store(struct device *dev,
if (thresh<MIN_THRESH_STOP) /* clamp up to MIN_THRESH_STOP */
thresh = MIN_THRESH_STOP;
down(&smapi_sem);
down(&smapi_mutex);
ret = get_thresh(bat, THRESH_START, &other_thresh);
if (ret!=-ENOSYS) { /* other threshold exists? */
if (ret)
@ -750,7 +760,7 @@ static int battery_stop_charge_thresh_store(struct device *dev,
}
ret = set_thresh(bat, THRESH_STOP, thresh);
out:
up(&smapi_sem);
up(&smapi_mutex);
return count;
}
@ -960,18 +970,18 @@ static int battery_dump_show(
int j;
char* p = buf;
int bat = attr_get_bat(attr);
u8 arg1610;
u8 arg0;
u8 row[TP_CONTROLLER_ROW_LEN];
int ret;
for (arg1610=1; arg1610<=8; ++arg1610) {
ret = tpc_read_row(arg1610, bat, row);
for (arg0=1; arg0<=8; ++arg0) {
ret = tpc_read_row(arg0, bat, row);
if (ret)
return ret;
for (j=0; j<TP_CONTROLLER_ROW_LEN; j++)
p += sprintf(p, "%02x ", row[j]);
p += sprintf(p, "\n");
if (arg1610==1 && bat_has_extended_status(bat)!=1)
if (arg0==1 && bat_has_extended_status(bat)!=1)
goto done;
if ( (p-buf)>PAGE_SIZE-256 ) {
printk(TP_ERR "aps_dump_show: too much output!\n");