Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge remote-tracking branch 'github/3.7/tp_smapi' into 3.7/master

  • Loading branch information...
commit 3bea0b7d46463e6ea067a670caa77b62e7d134dc 2 parents 228dfe6 + 4eecfb4
Jan Alexander Steffens heftig authored
267 Documentation/tp_smapi.txt
View
@@ -0,0 +1,267 @@
+tp_smapi version 0.40
+IBM ThinkPad hardware functions driver
+
+Author: Shem Multinymous <multinymous@gmail.com>
+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
+-----------------
+
+thinkpad_ec module:
+ force_io=1 lets thinkpad_ec load on some recent ThinkPad models
+ (e.g., T400 and T500) whose BIOS's ACPI DSDT reserves the ports we need.
+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
+
+ (When AC is connected, forced discharging will automatically stop
+ when battery is fully depleted -- this is useful for calibration.
+ Also, this attribute can be used to control which battery is discharged
+ when both a system battery and an Ultrabay battery are connected.)
+
+Misc read-only battery status attributes (see note about HDAPS below):
+
+/sys/devices/platform/smapi/BAT0/installed # 0 or 1
+/sys/devices/platform/smapi/BAT0/state # idle/charging/discharging
+/sys/devices/platform/smapi/BAT0/cycle_count # integer counter
+/sys/devices/platform/smapi/BAT0/current_now # instantaneous current
+/sys/devices/platform/smapi/BAT0/current_avg # last minute average
+/sys/devices/platform/smapi/BAT0/power_now # instantaneous power
+/sys/devices/platform/smapi/BAT0/power_avg # last minute average
+/sys/devices/platform/smapi/BAT0/last_full_capacity # in mWh
+/sys/devices/platform/smapi/BAT0/remaining_percent # remaining percent of energy (set by calibration)
+/sys/devices/platform/smapi/BAT0/remaining_percent_error # error range of remaing_percent (not reset by calibration)
+/sys/devices/platform/smapi/BAT0/remaining_running_time # in minutes, by last minute average power
+/sys/devices/platform/smapi/BAT0/remaining_running_time_now # in minutes, by instantenous power
+/sys/devices/platform/smapi/BAT0/remaining_charging_time # in minutes
+/sys/devices/platform/smapi/BAT0/remaining_capacity # in mWh
+/sys/devices/platform/smapi/BAT0/design_capacity # in mWh
+/sys/devices/platform/smapi/BAT0/voltage # in mV
+/sys/devices/platform/smapi/BAT0/design_voltage # in mV
+/sys/devices/platform/smapi/BAT0/charging_max_current # max charging current
+/sys/devices/platform/smapi/BAT0/charging_max_voltage # max charging voltage
+/sys/devices/platform/smapi/BAT0/group{0,1,2,3}_voltage # see below
+/sys/devices/platform/smapi/BAT0/manufacturer # string
+/sys/devices/platform/smapi/BAT0/model # string
+/sys/devices/platform/smapi/BAT0/barcoding # string
+/sys/devices/platform/smapi/BAT0/chemistry # string
+/sys/devices/platform/smapi/BAT0/serial # integer
+/sys/devices/platform/smapi/BAT0/manufacture_date # YYYY-MM-DD
+/sys/devices/platform/smapi/BAT0/first_use_date # YYYY-MM-DD
+/sys/devices/platform/smapi/BAT0/temperature # in milli-Celsius
+/sys/devices/platform/smapi/BAT0/dump # see below
+/sys/devices/platform/smapi/ac_connected # 0 or 1
+
+The BAT0/group{0,1,2,3}_voltage attribute refers to the separate cell groups
+in each battery. For example, on the ThinkPad 600, X3x, T4x and R5x models,
+the battery contains 3 cell groups in series, where each group consisting of 2
+or 3 cells connected in parallel. The voltage of each group is given by these
+attributes, and their sum (roughly) equals the "voltage" attribute.
+(The effective performance of the battery is determined by the weakest group,
+i.e., the one those voltage changes most rapidly during dis/charging.)
+
+The "BAT0/dump" attribute gives a 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 "--":
+
+In all of the above, replace BAT0 with BAT1 to address the 2nd battery (e.g.
+in the UltraBay).
+
+
+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
+value, 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.
+
+
+Additional HDAPS features
+-------------------------
+
+The modified hdaps driver has several improvements on the one in mainline
+(beyond resolving the conflict with thinkpad_ec and tp_smapi):
+
+- Fixes reliability and improves support for recent ThinkPad models
+ (especially *60 and newer). Unlike the mainline driver, the modified hdaps
+ correctly follows the Embedded Controller communication protocol.
+
+- Extends the "invert" parameter to cover all possible axis orientations.
+ The possible values are as follows.
+ Let X,Y denote the hardware readouts.
+ Let R denote the laptop's roll (tilt left/right).
+ Let P denote the laptop's pitch (tilt forward/backward).
+ invert=0: R= X P= Y (same as mainline)
+ invert=1: R=-X P=-Y (same as mainline)
+ invert=2: R=-X P= Y (new)
+ invert=3: R= X P=-Y (new)
+ invert=4: R= Y P= X (new)
+ invert=5: R=-Y P=-X (new)
+ invert=6: R=-Y P= X (new)
+ invert=7: R= Y P=-X (new)
+ It's probably easiest to just try all 8 possibilities and see which yields
+ correct results (e.g., in the hdaps-gl visualisation).
+
+- Adds a whitelist which automatically sets the correct axis orientation for
+ some models. If the value for your model is wrong or missing, you can override
+ it using the "invert" parameter. Please also update the tables at
+ http://www.thinkwiki.org/wiki/tp_smapi and
+ http://www.thinkwiki.org/wiki/List_of_DMI_IDs
+ and submit a patch for the whitelist in hdaps.c.
+
+- Provides new attributes:
+ /sys/devices/platform/hdaps/sampling_rate:
+ This determines the frequency at which the host queries the embedded
+ controller for accelerometer data (and informs the hdaps input devices).
+ Default=50.
+ /sys/devices/platform/hdaps/oversampling_ratio:
+ When set to X, the embedded controller is told to do physical accelerometer
+ measurements at a rate that is X times higher than the rate at which
+ the driver reads those measurements (i.e., X*sampling_rate). This
+ makes the readouts from the embedded controller more fresh, and is also
+ useful for the running average filter (see next). Default=5
+ /sys/devices/platform/hdaps/running_avg_filter_order:
+ When set to X, reported readouts will be the average of the last X physical
+ accelerometer measurements. Current firmware allows 1<=X<=8. Setting to a
+ high value decreases readout fluctuations. The averaging is handled by the
+ embedded controller, so no CPU resources are used. Higher values make the
+ readouts smoother, since it averages out both sensor noise (good) and abrupt
+ changes (bad). Default=2.
+
+- Provides a second input device, which publishes the raw accelerometer
+ measurements (without the fuzzing needed for joystick emulation). This input
+ device can be matched by a udev rule such as the following (all on one line):
+ KERNEL=="event[0-9]*", ATTRS{phys}=="hdaps/input1",
+ ATTRS{modalias}=="input:b0019v1014p5054e4801-*",
+ SYMLINK+="input/hdaps/accelerometer-event
+
+A new version of the hdapsd userspace daemon, which uses the input device
+interface instead of polling sysfs, is available seprately. Using this reduces
+the total interrupts per second generated by hdaps+hdapsd (on tickless kernels)
+to 50, down from a value that fluctuates between 50 and 100. Set the
+sampling_rate sysfs attribute to a lower value to further reduce interrupts,
+at the expense of response latency.
+
+Licensing note: all my changes to the HDAPS driver are licensed under the
+GPL version 2 or, at your option and to the extent allowed by derivation from
+prior works, any later version. My version of hdaps is derived work from the
+mainline version, which at the time of writing is available only under
+GPL version 2.
+
+Bug reporting
+-------------
+
+Mail <multinymous@gmail.com>. 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?
+
+
+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.
+
+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 module and the H8S hardware documentation:
+http://documentation.renesas.com/eng/products/mpumcu/rej09b0300_2140bhm.pdf
BIN  arch/x86/tools/test_get_len
View
Binary file not shown
21 drivers/platform/x86/Kconfig
View
@@ -433,9 +433,30 @@ config THINKPAD_ACPI_HOTKEY_POLL
If you are not sure, say Y here. The driver enables polling only if
it is strictly necessary to do so.
+config THINKPAD_EC
+ tristate
+ depends on X86
+ ---help---
+ This is a low-level driver for accessing the ThinkPad H8S embedded
+ controller over the LPC bus (not to be confused with the ACPI Embedded
+ Controller interface).
+
+config TP_SMAPI
+ tristate "ThinkPad SMAPI Support"
+ depends on X86
+ select THINKPAD_EC
+ default n
+ help
+ This adds SMAPI support on Lenovo/IBM ThinkPads, for features such
+ as battery charging control. For more information about this driver
+ see <http://www.thinkwiki.org/wiki/tp_smapi>.
+
+ If you have a Lenovo/IBM ThinkPad laptop, say Y or M here.
+
config SENSORS_HDAPS
tristate "Thinkpad Hard Drive Active Protection System (hdaps)"
depends on INPUT && X86
+ select THINKPAD_EC
select INPUT_POLLDEV
default n
help
2  drivers/platform/x86/Makefile
View
@@ -22,6 +22,8 @@ obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o
obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o
obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o
obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
+obj-$(CONFIG_THINKPAD_EC) += thinkpad_ec.o
+obj-$(CONFIG_TP_SMAPI) += tp_smapi.o
obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o
obj-$(CONFIG_FUJITSU_LAPTOP) += fujitsu-laptop.o
obj-$(CONFIG_FUJITSU_TABLET) += fujitsu-tablet.o
926 drivers/platform/x86/hdaps.c
View
@@ -30,266 +30,384 @@
#include <linux/delay.h>
#include <linux/platform_device.h>
-#include <linux/input-polldev.h>
+#include <linux/input.h>
#include <linux/kernel.h>
-#include <linux/mutex.h>
#include <linux/module.h>
#include <linux/timer.h>
#include <linux/dmi.h>
#include <linux/jiffies.h>
-#include <linux/io.h>
-
-#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 */
+#include <linux/thinkpad_ec.h>
+#include <linux/pci_ids.h>
+#include <linux/version.h>
+
+/* 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 position 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_INTERVAL 50 /* poll for input every 1/20s (50 ms)*/
#define HDAPS_INPUT_FUZZ 4 /* input event threshold */
#define HDAPS_INPUT_FLAT 4
-
-#define HDAPS_X_AXIS (1 << 0)
-#define HDAPS_Y_AXIS (1 << 1)
-#define HDAPS_BOTH_AXES (HDAPS_X_AXIS | HDAPS_Y_AXIS)
-
+#define KMACT_REMEMBER_PERIOD (HZ/10) /* keyboard/mouse persistance */
+
+/* Input IDs */
+#define HDAPS_INPUT_VENDOR PCI_VENDOR_ID_IBM
+#define HDAPS_INPUT_PRODUCT 0x5054 /* "TP", shared with thinkpad_acpi */
+#define HDAPS_INPUT_JS_VERSION 0x6801 /* Joystick emulation input device */
+#define HDAPS_INPUT_RAW_VERSION 0x4801 /* Raw accelerometer input device */
+
+/* Axis orientation. */
+/* The unnatural bit-representation of inversions is for backward
+ * compatibility with the"invert=1" module parameter. */
+#define HDAPS_ORIENT_INVERT_XY 0x01 /* Invert both X and Y axes. */
+#define HDAPS_ORIENT_INVERT_X 0x02 /* Invert the X axis (uninvert if
+ * already inverted by INVERT_XY). */
+#define HDAPS_ORIENT_SWAP 0x04 /* Swap the axes. The swap occurs
+ * before inverting X or Y. */
+#define HDAPS_ORIENT_MAX 0x07
+#define HDAPS_ORIENT_UNDEFINED 0xFF /* Placeholder during initialization */
+#define HDAPS_ORIENT_INVERT_Y (HDAPS_ORIENT_INVERT_XY | HDAPS_ORIENT_INVERT_X)
+
+static struct timer_list hdaps_timer;
static struct platform_device *pdev;
-static struct input_polled_dev *hdaps_idev;
-static unsigned int hdaps_invert;
-static u8 km_activity;
-static int rest_x;
-static int rest_y;
-
-static DEFINE_MUTEX(hdaps_mtx);
-
-/*
- * __get_latch - Get the value from a given port. Callers must hold hdaps_mtx.
- */
-static inline u8 __get_latch(u16 port)
+static struct input_dev *hdaps_idev; /* joystick-like device with fuzz */
+static struct input_dev *hdaps_idev_raw; /* raw hdaps sensor readouts */
+static unsigned int hdaps_invert = HDAPS_ORIENT_UNDEFINED;
+static int needs_calibration;
+
+/* 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 */
+
+/* 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;
+static u64 last_update_jiffies = INITIAL_JIFFIES;
+
+/* input device use count */
+static int hdaps_users;
+static DEFINE_MUTEX(hdaps_users_mtx);
+
+/* Some models require an axis transformation to the standard representation */
+static void transform_axes(int *x, int *y)
{
- return inb(port) & 0xff;
+ if (hdaps_invert & HDAPS_ORIENT_SWAP) {
+ int z;
+ z = *x;
+ *x = *y;
+ *y = z;
+ }
+ if (hdaps_invert & HDAPS_ORIENT_INVERT_XY) {
+ *x = -*x;
+ *y = -*y;
+ }
+ if (hdaps_invert & HDAPS_ORIENT_INVERT_X)
+ *x = -*x;
}
-/*
- * __check_latch - Check a port latch for a given value. Returns zero if the
- * port contains the given value. Callers must hold hdaps_mtx.
+/**
+ * __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;
-/*
- * __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_mtx.
- */
-static int __wait_latch(u16 port, u8 val)
-{
- unsigned int i;
+ 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;
- for (i = 0; i < 20; i++) {
- if (!__check_latch(port, val))
- return 0;
- udelay(5);
+ /* Check status: */
+ if (data.val[EC_ACCEL_IDX_RETVAL] != 0x00) {
+ pr_warn("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];
+
+ last_update_jiffies = get_jiffies_64();
+ stale_readout = 0;
+ if (needs_calibration) {
+ rest_x = pos_x;
+ rest_y = pos_y;
+ needs_calibration = 0;
}
- return -EIO;
+ return 0;
}
-/*
- * __device_refresh - request a refresh from the accelerometer. Does not wait
- * for refresh to complete. Callers must hold hdaps_mtx.
+/**
+ * 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 void __device_refresh(void)
+static int hdaps_update(void)
{
- udelay(200);
- if (inb(0x1604) != STATE_FRESH) {
- outb(0x11, 0x1610);
- outb(0x01, 0x161f);
+ u64 age = get_jiffies_64() - last_update_jiffies;
+ int total, ret;
+
+ if (!stale_readout && age < (9*HZ)/(10*sampling_rate))
+ return 0; /* already updated recently */
+ for (total = 0; total < READ_TIMEOUT_MSECS; total += RETRY_MSECS) {
+ ret = thinkpad_ec_lock();
+ if (ret)
+ return ret;
+ ret = __hdaps_update(0);
+ thinkpad_ec_unlock();
+
+ if (!ret)
+ return 0;
+ if (ret != -EBUSY)
+ break;
+ msleep(RETRY_MSECS);
}
+ return ret;
}
-/*
- * __device_refresh_sync - request a synchronous refresh from the
- * accelerometer. We wait for the refresh to complete. Returns zero if
- * successful and nonzero on error. Callers must hold hdaps_mtx.
+/**
+ * hdaps_set_power - enable or disable power to the accelerometer.
+ * Returns zero on success and negative error code on failure. Can sleep.
*/
-static int __device_refresh_sync(void)
+static int hdaps_set_power(int on)
{
- __device_refresh();
- return __wait_latch(0x1604, STATE_FRESH);
+ struct thinkpad_ec_row args =
+ { .mask = 0x0003, .val = {0x14, on?0x01:0x00} };
+ struct thinkpad_ec_row data = { .mask = 0x8000 };
+ int ret = thinkpad_ec_read_row(&args, &data);
+ if (ret)
+ return ret;
+ if (data.val[0xF] != 0x00)
+ return -EIO;
+ return 0;
}
-/*
- * __device_complete - indicate to the accelerometer that we are done reading
- * data, and then initiate an async refresh. Callers must hold hdaps_mtx.
+/**
+ * hdaps_set_ec_config - set accelerometer parameters.
+ * @ec_rate: embedded controller sampling rate
+ * @order: embedded controller running average filter order
+ * (Normally we have @ec_rate = sampling_rate * oversampling_ratio.)
+ * Returns zero on success and negative error code on failure. Can sleep.
*/
-static inline void __device_complete(void)
+static int hdaps_set_ec_config(int ec_rate, int order)
{
- inb(0x161f);
- inb(0x1604);
- __device_refresh();
+ struct thinkpad_ec_row args = { .mask = 0x000F,
+ .val = {0x10, (u8)ec_rate, (u8)(ec_rate>>8), order} };
+ struct thinkpad_ec_row data = { .mask = 0x8000 };
+ int ret = thinkpad_ec_read_row(&args, &data);
+ pr_debug("setting ec_rate=%d, filter_order=%d\n", ec_rate, order);
+ if (ret)
+ return ret;
+ if (data.val[0xF] == 0x03) {
+ pr_warn("config param out of range\n");
+ return -EINVAL;
+ }
+ if (data.val[0xF] == 0x06) {
+ pr_warn("config change already pending\n");
+ return -EBUSY;
+ }
+ if (data.val[0xF] != 0x00) {
+ pr_warn("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;
-
- mutex_lock(&hdaps_mtx);
-
- /* 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:
- mutex_unlock(&hdaps_mtx);
- 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) {
+ pr_warn("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();
-
- /* hdaps_invert is a bitvector to negate the axes */
- if (hdaps_invert & HDAPS_X_AXIS)
- *x = -*x;
- if (hdaps_invert & HDAPS_Y_AXIS)
- *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;
-
- mutex_lock(&hdaps_mtx);
- ret = __hdaps_read_pair(port1, port2, val1, val2);
- mutex_unlock(&hdaps_mtx);
-
- 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) || /* cleanroom spec */
+ (data.val[0x1] == 0x01 && data.val[0x2] == 0x00)) || /* seen on T61 */
+ data.val[0x3] != 0x00 || data.val[0xF] != 0x00) {
+ pr_warn("hdaps_check_ec: bad response (0x%x,0x%x,0x%x,0x%x)\n",
+ data.val[0x1], data.val[0x2],
+ data.val[0x3], data.val[0xF]);
+ 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 FAILED_INIT(msg) pr_err("init failed at: %s\n", msg)
static int hdaps_device_init(void)
{
- int total, ret = -ENXIO;
+ int ret;
+ u8 mode;
- mutex_lock(&hdaps_mtx);
+ 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))
+ { FAILED_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 latch check good (0x%02x)\n",
- __get_latch(0x1611));
+ pr_debug("initial mode latch is 0x%02x\n", mode);
+ if (mode == 0x00)
+ { FAILED_INIT("accelerometer not available"); 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_check_ec())
+ { FAILED_INIT("hdaps_check_ec 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;
+ if (hdaps_set_power(1))
+ { FAILED_INIT("hdaps_set_power failed"); goto bad; }
- /* 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))
+ { FAILED_INIT("hdaps_set_ec_config failed"); goto bad; }
- msleep(INIT_WAIT_MSECS);
- }
+ thinkpad_ec_invalidate();
+ udelay(200);
-out:
- mutex_unlock(&hdaps_mtx);
+ /* Just prefetch instead of reading, to avoid ~1sec delay on load */
+ ret = thinkpad_ec_prefetch_row(&ec_accel_args);
+ if (ret)
+ { FAILED_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) {
+ pr_warn("cannot power off\n");
+ return ret;
+ }
+ ret = hdaps_set_ec_config(0, 1);
+ if (ret)
+ pr_warn("cannot stop EC sampling\n");
+ return ret;
+}
/* Device model stuff */
@@ -306,13 +424,29 @@ static int hdaps_probe(struct platform_device *dev)
}
#ifdef CONFIG_PM_SLEEP
+static int hdaps_suspend(struct device *dev)
+{
+ /* 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 device *dev)
{
- return hdaps_device_init();
+ int ret = hdaps_device_init();
+ if (ret)
+ return ret;
+
+ mutex_lock(&hdaps_users_mtx);
+ if (hdaps_users)
+ mod_timer(&hdaps_timer, jiffies + HZ/sampling_rate);
+ mutex_unlock(&hdaps_users_mtx);
+ return 0;
}
#endif
-static SIMPLE_DEV_PM_OPS(hdaps_pm, NULL, hdaps_resume);
+static SIMPLE_DEV_PM_OPS(hdaps_pm, hdaps_suspend, hdaps_resume);
static struct platform_driver hdaps_driver = {
.probe = hdaps_probe,
@@ -323,30 +457,47 @@ static struct platform_driver hdaps_driver = {
},
};
-/*
- * hdaps_calibrate - Set our "resting" values. Callers must hold hdaps_mtx.
+/**
+ * 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. */
}
-static void hdaps_mousedev_poll(struct input_polled_dev *dev)
+/* 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)
{
- struct input_dev *input_dev = dev->input;
- int x, y;
+ int ret;
- mutex_lock(&hdaps_mtx);
+ stale_readout = 1;
- if (__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y))
- goto out;
+ /* Cannot sleep. Try nonblockingly. If we fail, try again later. */
+ if (thinkpad_ec_try_lock())
+ goto keep_active;
- input_report_abs(input_dev, ABS_X, x - rest_x);
- input_report_abs(input_dev, ABS_Y, y - rest_y);
- input_sync(input_dev);
+ 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) {
+ pr_err("poll failed, disabling updates\n");
+ return;
+ }
-out:
- mutex_unlock(&hdaps_mtx);
+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);
+ input_report_abs(hdaps_idev_raw, ABS_X, pos_x);
+ input_report_abs(hdaps_idev_raw, ABS_Y, pos_y);
+ input_sync(hdaps_idev_raw);
+ mod_timer(&hdaps_timer, jiffies + HZ/sampling_rate);
}
@@ -355,65 +506,41 @@ static void hdaps_mousedev_poll(struct input_polled_dev *dev)
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);
- 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);
+ int ret = hdaps_update();
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 uninitialized_var(temp);
- int ret;
-
- ret = hdaps_readb_one(HDAPS_PORT_TEMP1, &temp);
- if (ret)
- return ret;
-
- return sprintf(buf, "%u\n", temp);
-}
-
-static ssize_t hdaps_temp2_show(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- u8 uninitialized_var(temp);
- int ret;
-
- ret = hdaps_readb_one(HDAPS_PORT_TEMP2, &temp);
+ 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,
@@ -426,10 +553,7 @@ static ssize_t hdaps_calibrate_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
- mutex_lock(&hdaps_mtx);
hdaps_calibrate();
- mutex_unlock(&hdaps_mtx);
-
return count;
}
@@ -446,7 +570,7 @@ static ssize_t hdaps_invert_store(struct device *dev,
int invert;
if (sscanf(buf, "%d", &invert) != 1 ||
- invert < 0 || invert > HDAPS_BOTH_AXES)
+ invert < 0 || invert > HDAPS_ORIENT_MAX)
return -EINVAL;
hdaps_invert = invert;
@@ -455,24 +579,128 @@ static ssize_t hdaps_invert_store(struct device *dev,
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) {
+ pr_warn("must have 0<input_sampling_rate<=HZ=%d\n", HZ);
+ return -EINVAL;
+ }
+ ret = hdaps_set_ec_config(rate*oversampling_ratio,
+ running_avg_filter_order);
+ if (ret)
+ return ret;
+ sampling_rate = rate;
+ return count;
+}
+
+static ssize_t hdaps_oversampling_ratio_show(
+ struct device *dev, struct device_attribute *attr, char *buf)
+{
+ int ec_rate, order;
+ int ret = hdaps_get_ec_config(&ec_rate, &order);
+ if (ret)
+ return ret;
+ return sprintf(buf, "%u\n", ec_rate / sampling_rate);
+}
+
+static ssize_t hdaps_oversampling_ratio_store(
+ struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ratio, ret;
+ if (sscanf(buf, "%d", &ratio) != 1 || ratio < 1)
+ return -EINVAL;
+ ret = hdaps_set_ec_config(sampling_rate*ratio,
+ running_avg_filter_order);
+ if (ret)
+ return ret;
+ oversampling_ratio = ratio;
+ return count;
+}
+
+static ssize_t hdaps_running_avg_filter_order_show(
+ struct device *dev, struct device_attribute *attr, char *buf)
+{
+ int rate, order;
+ int ret = hdaps_get_ec_config(&rate, &order);
+ if (ret)
+ return ret;
+ return sprintf(buf, "%u\n", order);
+}
+
+static ssize_t hdaps_running_avg_filter_order_store(
+ struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int order, ret;
+ if (sscanf(buf, "%d", &order) != 1)
+ return -EINVAL;
+ ret = hdaps_set_ec_config(sampling_rate*oversampling_ratio, order);
+ if (ret)
+ return ret;
+ running_avg_filter_order = order;
+ return count;
+}
+
+static int hdaps_mousedev_open(struct input_dev *dev)
+{
+ if (!try_module_get(THIS_MODULE))
+ return -ENODEV;
+
+ mutex_lock(&hdaps_users_mtx);
+ if (hdaps_users++ == 0) /* first input user */
+ mod_timer(&hdaps_timer, jiffies + HZ/sampling_rate);
+ mutex_unlock(&hdaps_users_mtx);
+ return 0;
+}
+
+static void hdaps_mousedev_close(struct input_dev *dev)
+{
+ mutex_lock(&hdaps_users_mtx);
+ if (--hdaps_users == 0) /* no input users left */
+ del_timer_sync(&hdaps_timer);
+ mutex_unlock(&hdaps_users_mtx);
+
+ module_put(THIS_MODULE);
+}
+
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(keyboard_activity, 0444, hdaps_keyboard_activity_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(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 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,
NULL,
};
@@ -483,84 +711,71 @@ static struct attribute_group hdaps_attribute_group = {
/* Module stuff */
-/* hdaps_dmi_match - found a match. return one, short-circuiting the hunt. */
-static int __init hdaps_dmi_match(const struct dmi_system_id *id)
-{
- pr_info("%s detected\n", id->ident);
- return 1;
-}
-
/* hdaps_dmi_match_invert - found an inverted match. */
static int __init hdaps_dmi_match_invert(const struct dmi_system_id *id)
{
- hdaps_invert = (unsigned long)id->driver_data;
- pr_info("inverting axis (%u) readings\n", hdaps_invert);
- return hdaps_dmi_match(id);
+ unsigned int orient = (kernel_ulong_t) id->driver_data;
+ hdaps_invert = orient;
+ pr_info("%s detected, setting orientation %u\n", id->ident, orient);
+ return 1; /* stop enumeration */
}
-#define HDAPS_DMI_MATCH_INVERT(vendor, model, axes) { \
+#define HDAPS_DMI_MATCH_INVERT(vendor, model, orient) { \
.ident = vendor " " model, \
.callback = hdaps_dmi_match_invert, \
- .driver_data = (void *)axes, \
+ .driver_data = (void *)(orient), \
.matches = { \
DMI_MATCH(DMI_BOARD_VENDOR, vendor), \
DMI_MATCH(DMI_PRODUCT_VERSION, model) \
} \
}
-#define HDAPS_DMI_MATCH_NORMAL(vendor, model) \
- HDAPS_DMI_MATCH_INVERT(vendor, model, 0)
-
-/* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
- "ThinkPad T42p", so the order of the entries matters.
- If your ThinkPad is not recognized, please update to latest
- BIOS. This is especially the case for some R52 ThinkPads. */
-static struct dmi_system_id __initdata hdaps_whitelist[] = {
- HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p", HDAPS_BOTH_AXES),
- HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R50"),
- HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R51"),
- HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R52"),
- HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61i", HDAPS_BOTH_AXES),
- HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61", HDAPS_BOTH_AXES),
- HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p", HDAPS_BOTH_AXES),
- HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T41"),
- HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p", HDAPS_BOTH_AXES),
- HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T42"),
- HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T43"),
- HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400", HDAPS_BOTH_AXES),
- HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60", HDAPS_BOTH_AXES),
- HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61p", HDAPS_BOTH_AXES),
- HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61", HDAPS_BOTH_AXES),
- HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X40"),
- HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X41", HDAPS_Y_AXIS),
- HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60", HDAPS_BOTH_AXES),
- HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61s", HDAPS_BOTH_AXES),
- HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61", HDAPS_BOTH_AXES),
- HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad Z60m"),
- HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61m", HDAPS_BOTH_AXES),
- HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61p", HDAPS_BOTH_AXES),
+/* List of models with abnormal axis configuration.
+ Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
+ "ThinkPad T42p", and enumeration stops after first match,
+ so the order of the entries matters. */
+struct dmi_system_id __initdata hdaps_whitelist[] = {
+ HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p", HDAPS_ORIENT_INVERT_XY),
+ HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R60", HDAPS_ORIENT_INVERT_XY),
+ HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p", HDAPS_ORIENT_INVERT_XY),
+ HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p", HDAPS_ORIENT_INVERT_XY),
+ HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X40", HDAPS_ORIENT_INVERT_Y),
+ HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X41", HDAPS_ORIENT_INVERT_Y),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R60", HDAPS_ORIENT_INVERT_XY),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61", HDAPS_ORIENT_INVERT_XY),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60", HDAPS_ORIENT_INVERT_XY),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61", HDAPS_ORIENT_INVERT_XY),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60 Tablet", HDAPS_ORIENT_INVERT_Y),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60s", HDAPS_ORIENT_INVERT_Y),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400s", HDAPS_ORIENT_INVERT_X),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400", HDAPS_ORIENT_INVERT_XY),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T410", HDAPS_ORIENT_INVERT_XY),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T500", HDAPS_ORIENT_INVERT_XY),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X200", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X | HDAPS_ORIENT_INVERT_Y),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X201 Tablet", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_XY),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X201s", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_XY),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X201", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X220", HDAPS_ORIENT_SWAP),
{ .ident = NULL }
};
static int __init hdaps_init(void)
{
- struct input_dev *idev;
int ret;
- if (!dmi_check_system(hdaps_whitelist)) {
- pr_warn("supported laptop not found!\n");
- ret = -ENODEV;
- goto out;
- }
-
- if (!request_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS, "hdaps")) {
- ret = -ENXIO;
- goto out;
- }
+ /* Determine axis orientation orientation */
+ if (hdaps_invert == HDAPS_ORIENT_UNDEFINED) /* set by module param? */
+ if (dmi_check_system(hdaps_whitelist) < 1) /* in whitelist? */
+ hdaps_invert = 0; /* default */
+ /* 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)) {
@@ -572,47 +787,79 @@ static int __init hdaps_init(void)
if (ret)
goto out_device;
- hdaps_idev = input_allocate_polled_device();
+ hdaps_idev = input_allocate_device();
if (!hdaps_idev) {
ret = -ENOMEM;
goto out_group;
}
- hdaps_idev->poll = hdaps_mousedev_poll;
- hdaps_idev->poll_interval = HDAPS_POLL_INTERVAL;
-
- /* initial calibrate for the input device */
- hdaps_calibrate();
+ hdaps_idev_raw = input_allocate_device();
+ if (!hdaps_idev_raw) {
+ ret = -ENOMEM;
+ goto out_idev_first;
+ }
- /* initialize the input class */
- idev = hdaps_idev->input;
- idev->name = "hdaps";
- idev->phys = "isa1600/input0";
- idev->id.bustype = BUS_ISA;
- idev->dev.parent = &pdev->dev;
- idev->evbit[0] = BIT_MASK(EV_ABS);
- input_set_abs_params(idev, ABS_X,
+ /* calibration for the input device (deferred to avoid delay) */
+ needs_calibration = 1;
+
+ /* initialize the joystick-like fuzzed input device */
+ hdaps_idev->name = "ThinkPad HDAPS joystick emulation";
+ hdaps_idev->phys = "hdaps/input0";
+ hdaps_idev->id.bustype = BUS_HOST;
+ hdaps_idev->id.vendor = HDAPS_INPUT_VENDOR;
+ hdaps_idev->id.product = HDAPS_INPUT_PRODUCT;
+ hdaps_idev->id.version = HDAPS_INPUT_JS_VERSION;
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
+ hdaps_idev->cdev.dev = &pdev->dev;
+#endif
+ hdaps_idev->evbit[0] = BIT(EV_ABS);
+ hdaps_idev->open = hdaps_mousedev_open;
+ hdaps_idev->close = hdaps_mousedev_close;
+ input_set_abs_params(hdaps_idev, ABS_X,
-256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
- input_set_abs_params(idev, ABS_Y,
+ input_set_abs_params(hdaps_idev, ABS_Y,
-256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
- ret = input_register_polled_device(hdaps_idev);
+ ret = input_register_device(hdaps_idev);
if (ret)
goto out_idev;
- pr_info("driver successfully loaded\n");
+ /* initialize the raw data input device */
+ hdaps_idev_raw->name = "ThinkPad HDAPS accelerometer data";
+ hdaps_idev_raw->phys = "hdaps/input1";
+ hdaps_idev_raw->id.bustype = BUS_HOST;
+ hdaps_idev_raw->id.vendor = HDAPS_INPUT_VENDOR;
+ hdaps_idev_raw->id.product = HDAPS_INPUT_PRODUCT;
+ hdaps_idev_raw->id.version = HDAPS_INPUT_RAW_VERSION;
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
+ hdaps_idev_raw->cdev.dev = &pdev->dev;
+#endif
+ hdaps_idev_raw->evbit[0] = BIT(EV_ABS);
+ hdaps_idev_raw->open = hdaps_mousedev_open;
+ hdaps_idev_raw->close = hdaps_mousedev_close;
+ input_set_abs_params(hdaps_idev_raw, ABS_X, -32768, 32767, 0, 0);
+ input_set_abs_params(hdaps_idev_raw, ABS_Y, -32768, 32767, 0, 0);
+
+ ret = input_register_device(hdaps_idev_raw);
+ if (ret)
+ goto out_idev_reg_first;
+
+ pr_info("driver successfully loaded.\n");
return 0;
+out_idev_reg_first:
+ input_unregister_device(hdaps_idev);
out_idev:
- input_free_polled_device(hdaps_idev);
+ input_free_device(hdaps_idev_raw);
+out_idev_first:
+ input_free_device(hdaps_idev);
out_group:
sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
out_device:
platform_device_unregister(pdev);
out_driver:
platform_driver_unregister(&hdaps_driver);
-out_region:
- release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
+ hdaps_device_shutdown();
out:
pr_warn("driver init failed (ret=%d)!\n", ret);
return ret;
@@ -620,12 +867,12 @@ static int __init hdaps_init(void)
static void __exit hdaps_exit(void)
{
- input_unregister_polled_device(hdaps_idev);
- input_free_polled_device(hdaps_idev);
+ input_unregister_device(hdaps_idev_raw);
+ 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);
pr_info("driver unloaded\n");
}
@@ -633,9 +880,8 @@ static void __exit hdaps_exit(void)
module_init(hdaps_init);
module_exit(hdaps_exit);
-module_param_named(invert, hdaps_invert, int, 0);
-MODULE_PARM_DESC(invert, "invert data along each axis. 1 invert x-axis, "
- "2 invert y-axis, 3 invert both axes.");
+module_param_named(invert, hdaps_invert, uint, 0);
+MODULE_PARM_DESC(invert, "axis orientation code");
MODULE_AUTHOR("Robert Love");
MODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver");
513 drivers/platform/x86/thinkpad_ec.c
View
@@ -0,0 +1,513 @@
+/*
+ * thinkpad_ec.c - ThinkPad embedded controller LPC3 functions
+ *
+ * The embedded controller on ThinkPad laptops has a non-standard interface,
+ * where LPC channel 3 of the H8S EC chip is hooked up to IO ports
+ * 0x1600-0x161F and implements (a special case of) the H8S LPC protocol.
+ * The EC LPC interface provides various system management services (currently
+ * known: battery information and accelerometer readouts). This driver
+ * provides access and mutual exclusion for the EC interface.
+*
+ * The LPC protocol and terminology are documented here:
+ * "H8S/2104B Group Hardware Manual",
+ * http://documentation.renesas.com/eng/products/mpumcu/rej09b0300_2140bhm.pdf
+ *
+ * Copyright (C) 2006-2007 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
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/dmi.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/thinkpad_ec.h>
+#include <linux/jiffies.h>
+#include <asm/io.h>
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
+ #include <asm/semaphore.h>
+#else
+ #include <linux/semaphore.h>
+#endif
+
+#define TP_VERSION "0.41"
+
+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 0<i<16) */
+
+/* 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 1000
+#define TPC_REQUEST_NDELAY 10
+#define TPC_PREFETCH_TIMEOUT (HZ/10) /* invalidate prefetch after 0.1sec */
+
+/* A few macros for printk()ing: */
+#define MSG_FMT(fmt, args...) \
+ "thinkpad_ec: %s: " fmt "\n", __func__, ## args
+#define REQ_FMT(msg, code) \
+ MSG_FMT("%s: (0x%02x:0x%02x)->0x%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: */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37)
+static DECLARE_MUTEX(thinkpad_ec_mutex);
+#else
+static DEFINE_SEMAPHORE(thinkpad_ec_mutex);
+#endif
+
+/* Kludge in case the ACPI DSDT reserves the ports we need. */
+static int force_io; /* Willing to do IO to ports we couldn't reserve? */
+static int reserved_io; /* Successfully reserved the ports? */
+module_param_named(force_io, force_io, bool, 0600);
+MODULE_PARM_DESC(force_io, "Force IO even if region already reserved (0=off, 1=on)");
+
+/**
+ * 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);
+
+/**
+ * thinkpad_ec_request_row - tell embedded controller to prepare a row
+ * @args Input register arguments
+ *
+ * Requests a data row by writing to H8S LPC registers TRW0 through TWR15 (or
+ * a subset thereof) following the protocol prescribed by the "H8S/2104B Group
+ * Hardware Manual". Does sanity checks via status register STR3.
+ */
+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(
+ "EC has result from unrequested transaction",
+ 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 is busy with unrequested transaction",
+ str3));
+ return -EBUSY; /* data will be pending in a few usecs */
+ } else if (str3 != 0x00) { /* unexpected status? */
+ printk(KERN_WARNING REQ_FMT("unexpected 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; i < TP_CONTROLLER_ROW_LEN-1; i++)
+ if ((args->mask>>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; 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)))
+ /* Normal progress (the EC hasn't seen the request
+ * yet, or is processing it). Wait it out. */
+ ndelay(TPC_REQUEST_NDELAY);
+ else { /* weird EC status */
+ printk(KERN_WARNING
+ REQ_FMT("bad end STR3", str3));
+ return -EIO;
+ }
+ }
+ printk(KERN_WARNING REQ_FMT("EC is mysteriously silent", str3));
+ return -EIO;
+}
+
+/**
+ * thinkpad_ec_read_data - read pre-requested row-data from EC
+ * @args Input register arguments of pre-requested rows
+ * @data Output register values
+ *
+ * Reads current row data from the controller, assuming it's already
+ * requested. Follows the H8S spec for register access and status checks.
+ */
+static int thinkpad_ec_read_data(const struct thinkpad_ec_row *args,
+ struct thinkpad_ec_row *data)
+{
+ int i;
+ u8 str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK;
+ /* Once we make a request, STR3 assumes the sequence of values listed
+ * in the following 'if' as it reads the request and writes its data.
+ * It takes about a few dozen nanosecs total, with very high variance.
+ */
+ if (str3 == (H8S_STR3_IBF3B|H8S_STR3_MWMF) ||
+ str3 == 0x00 || /* the 0x00 is indistinguishable from idle EC! */
+ str3 == H8S_STR3_SWMF)
+ return -EBUSY; /* not ready yet */
+ /* Finally, the EC signals output buffer full: */
+ if (str3 != (H8S_STR3_OBF3B|H8S_STR3_SWMF)) {
+ printk(KERN_WARNING
+ REQ_FMT("bad initial STR3", str3));
+ return -EIO;
+ }
+
+ /* 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
+ REQ_FMT("OBF3B=1 after read", str3));
+ /* If port 0x161F returns 0x80 too often, the EC may lock up. Warn: */
+ if (data->val[0xF] == 0x80)
+ printk(KERN_WARNING
+ REQ_FMT("0x161F reports error", data->val[0xF]));
+ return 0;
+}
+
+/**
+ * thinkpad_ec_is_row_fetched - is the given row currently prefetched?
+ *
+ * To keep things simple we compare only the first and last args;
+ * this suffices for all known cases.
+ */
+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 is specified by an
+ * array of 16 bytes, some of which may be undefined (but the first is
+ * mandatory). These bytes are given in @args->val[], where @args->val[i] is
+ * used iff (@args->mask>>i)&1). The resulting row 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&(1<<i)==0 then
+ * @data->val[i] is undefined.
+ *
+ * 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; retries < TPC_READ_RETRIES; ++retries) {
+ ret = thinkpad_ec_request_row(args);
+ if (!ret)
+ goto read_row;
+ if (ret != -EBUSY)
+ break;
+ ndelay(TPC_READ_NDELAY);
+ }
+ printk(KERN_ERR REQ_FMT("failed requesting row", ret));
+ goto out;
+
+read_row:
+ /* Read the row's data */
+ for (retries = 0; retries < TPC_READ_RETRIES; ++retries) {
+ ret = thinkpad_ec_read_data(args, data);
+ if (!ret)
+ goto out;
+ if (ret != -EBUSY)
+ break;
+ ndelay(TPC_READ_NDELAY);
+ }
+
+ printk(KERN_ERR REQ_FMT("failed waiting for data", ret));
+
+out:
+ prefetch_jiffies = TPC_PREFETCH_JUNK;
+ return ret;
+}
+EXPORT_SYMBOL_GPL(thinkpad_ec_read_row);
+
+/**
+ * thinkpad_ec_try_read_row - try reading prefetched data from ThinkPad EC
+ * @args Input register arguments
+ * @data Output register values
+ *
+ * Try reading a data row from the ThinkPad embedded controller LPC3
+ * interface, if this raw was recently prefetched using
+ * thinkpad_ec_prefetch_row(). Does not fetch, retry or block.
+ * The parameters have the same meaning as in thinkpad_ec_read_row().
+ *
+ * Returns -EBUSY is data not ready and -ENODATA if row not prefetched.
+ * Caller must hold controller lock.
+ */
+int thinkpad_ec_try_read_row(const struct thinkpad_ec_row *args,
+ struct thinkpad_ec_row *data)
+{
+ int ret;
+ if (!thinkpad_ec_is_row_fetched(args)) {
+ ret = -ENODATA;
+ } else {
+ ret = thinkpad_ec_read_data(args, data);
+ if (!ret)
+ prefetch_jiffies = TPC_PREFETCH_NONE; /* eaten up */
+ }
+ return ret;
+}
+EXPORT_SYMBOL_GPL(thinkpad_ec_try_read_row);
+
+/**
+ * thinkpad_ec_prefetch_row - prefetch data from ThinkPad EC
+ * @args Input register arguments
+ *
+ * Prefetch a data row from the ThinkPad embedded controller LCP3
+ * interface. A subsequent call to thinkpad_ec_read_row() with the
+ * same arguments will be faster, and a subsequent call to
+ * thinkpad_ec_try_read_row() stands a good chance of succeeding if
+ * done neither too soon nor too late. See
+ * thinkpad_ec_read_row() for the meaning of @args.
+ *
+ * Returns -EBUSY on transient error and -EIO on abnormal condition.
+ * Caller must hold controller lock.
+ */
+int thinkpad_ec_prefetch_row(const struct thinkpad_ec_row *args)
+{
+ int ret;
+ ret = thinkpad_ec_request_row(args);
+ if (ret) {
+ prefetch_jiffies = TPC_PREFETCH_JUNK;
+ } else {
+ prefetch_jiffies = get_jiffies_64();
+ prefetch_arg0 = args->val[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 - verify the EC is present and follows protocol
+ *
+ * Ensure the EC LPC3 channel really works on this machine by making
+ * an EC request and seeing if the EC follows the documented H8S protocol.
+ * The requested row just reads battery status, so it should be harmless to
+ * access it (on a correct EC).
+ * This test writes to IO ports, so execute only after checking DMI.
+ */
+static int __init 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)
+{
+ const 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"),
+ TP_DMI_MATCH("LENOVO", "ThinkPad"),
+ { .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")) {
+ reserved_io = 1;
+ } else {
+ printk(KERN_ERR "thinkpad_ec: cannot claim IO ports %#x-%#x... ",
+ TPC_BASE_PORT,
+ TPC_BASE_PORT + TPC_NUM_PORTS - 1);
+ if (force_io) {
+ printk("forcing use of unreserved IO ports.\n");
+ } else {
+ printk("consider using force_io=1.\n");
+ return -ENXIO;
+ }
+ }
+ prefetch_jiffies = TPC_PREFETCH_JUNK;
+ if (thinkpad_ec_test()) {
+ printk(KERN_ERR "thinkpad_ec: initial ec test failed\n");
+ if (reserved_io)
+ 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)
+{
+ if (reserved_io)
+ 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);
1,493 drivers/platform/x86/tp_smapi.c
View
@@ -0,0 +1,1493 @@
+/*
+ * 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.
+ * It also exposes battery status information, obtained from the ThinkPad
+ * embedded controller (via the thinkpad_ec module).
+ * Ancient ThinkPad models use a different interface, supported by the
+ * "thinkpad" module from "tpctl".
+ *
+ * Many of the battery status values obtained from the EC simply mirror
+ * values provided by the battery's Smart Battery System (SBS) interface, so
+ * their meaning is defined by the Smart Battery Data Specification (see
+ * http://sbs-forum.org/specs/sbdat110.pdf). References to this SBS spec
+ * are given in the code where relevant.
+ *
+ * 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
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/proc_fs.h>
+#include <linux/mc146818rtc.h> /* CMOS defines */
+#include <linux/delay.h>
+#include <linux/version.h>
+#include <linux/thinkpad_ec.h>
+#include <linux/platform_device.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#define TP_VERSION "0.41"
+#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;
+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
+
+/* 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 function is not available", -ENXIO},
+ {0x81, "Invalid parameter", -EINVAL},
+ {0x86, "Function is not supported by SMAPI BIOS", -EOPNOTSUPP},
+ {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; /* APM control port, normally 0xB2 */
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37)
+static DECLARE_MUTEX(smapi_mutex);
+#else
+static DEFINE_SEMAPHORE(smapi_mutex);
+#endif
+
+/**
+ * find_smapi_port - read SMAPI port from NVRAM
+ */
+static int __init 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 < SMAPI_MAX_RETRIES; ++retries) {
+ DPRINTK("req_in: BX=%x CX=%x DI=%x SI=%x",
+ inEBX, inECX, inEDI, inESI);
+
+ /* SMAPI's SMBIOS call and thinkpad_ec end up using use
+ * different interfaces to the same chip, so play it safe. */
+ ret = thinkpad_ec_lock();
+ if (ret)
+ return ret;
+
+ __asm__ __volatile__(
+ "movl $0x00005380,%%eax\n\t"
+ "movl %6,%%ebx\n\t"
+ "movl %7,%%ecx\n\t"
+ "movl %8,%%edi\n\t"
+ "movl %9,%%esi\n\t"
+ "xorl %%edx,%%edx\n\t"
+ "movw %10,%%dx\n\t"
+ "out %%al,%%dx\n\t" /* trigger SMI to SMBIOS */
+ "out %%al,$0x4F\n\t"
+ "movl %%eax,%0\n\t"
+ "movl %%ebx,%1\n\t"
+ "movl %%ecx,%2\n\t"
+ "movl %%edx,%3\n\t"
+ "movl %%edi,%4\n\t"
+ "movl %%esi,%5\n\t"
+ :"=m"(tmpEAX),
+ "=m"(tmpEBX),
+ "=m"(tmpECX),
+ "=m"(tmpEDX),
+ "=m"(tmpEDI),
+ "=m"(tmpESI)
+ :"m"(inEBX), "m"(inECX), "m"(inEDI), "m"(inESI),
+ "m"((u16)smapi_port)
+ :"%eax", "%ebx", "%ecx", "%edx", "%edi",
+ "%esi");
+
+ thinkpad_ec_invalidate();
+ thinkpad_ec_unlock();
+
+ /* Don't let the next SMAPI access happen too quickly,
+ * may case problems. (We're hold smapi_mutex). */
+ msleep(50);
+
+ if (outEBX) *outEBX = tmpEBX;
+ if (outECX) *outECX = tmpECX;
+ if (outEDX) *outEDX = tmpEDX;
+ if (outESI) *outESI = tmpESI;
+ if (outEDI) *outEDI = tmpEDI;
+
+ /* Look up error code */
+ rc = (tmpEAX>>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;
+}
+
+
+/*********************************************************************
+ * 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;
+ switch (bat) {
+ case 0: test = 0x40; break; /* battery 0 */
+ case 1: test = 0x20; break; /* battery 1 */
+ default: 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
+ */
+
+/* A custom device attribute struct which holds a battery number */
+struct bat_device_attribute {
+ struct device_attribute dev_attr;
+ int bat;
+};
+
+/**
+ * attr_get_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 ssize_t 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 ssize_t 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);