blob: 3adcb0de01935f079e1453b73a085dc5d1b195a6 [file] [log] [blame]
Thomas Gleixner21eb0be2019-05-29 07:12:45 -07001// SPDX-License-Identifier: GPL-2.0-only
Robert Love860e1d62005-08-31 23:57:59 -04002/*
Jean Delvarebd9fc3a2010-10-05 12:08:57 +02003 * hdaps.c - driver for IBM's Hard Drive Active Protection System
Robert Love860e1d62005-08-31 23:57:59 -04004 *
5 * Copyright (C) 2005 Robert Love <rml@novell.com>
Jesper Juhle4332e82012-04-29 02:01:54 +02006 * Copyright (C) 2005 Jesper Juhl <jj@chaosbits.net>
Robert Love860e1d62005-08-31 23:57:59 -04007 *
Robert Love4c87b742005-09-22 21:44:00 -07008 * The HardDisk Active Protection System (hdaps) is present in IBM ThinkPads
9 * starting with the R40, T41, and X40. It provides a basic two-axis
10 * accelerometer and other data, such as the device's temperature.
Robert Love860e1d62005-08-31 23:57:59 -040011 *
Robert Love393ad292005-09-16 19:28:07 -070012 * This driver is based on the document by Mark A. Smith available at
Robert Love860e1d62005-08-31 23:57:59 -040013 * http://www.almaden.ibm.com/cs/people/marksmith/tpaps.html and a lot of trial
14 * and error.
Robert Love860e1d62005-08-31 23:57:59 -040015 */
16
Joe Perches611f5762011-03-29 15:21:40 -070017#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
18
Robert Love860e1d62005-08-31 23:57:59 -040019#include <linux/delay.h>
Russell Kingd052d1b2005-10-29 19:07:23 +010020#include <linux/platform_device.h>
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -040021#include <linux/input-polldev.h>
Robert Love860e1d62005-08-31 23:57:59 -040022#include <linux/kernel.h>
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -070023#include <linux/mutex.h>
Robert Love860e1d62005-08-31 23:57:59 -040024#include <linux/module.h>
25#include <linux/timer.h>
26#include <linux/dmi.h>
Al Viro914e2632006-10-18 13:55:46 -040027#include <linux/jiffies.h>
H Hartley Sweeten6055fae2009-09-15 17:18:13 +020028#include <linux/io.h>
Robert Love860e1d62005-08-31 23:57:59 -040029
30#define HDAPS_LOW_PORT 0x1600 /* first port used by hdaps */
Robert Love393ad292005-09-16 19:28:07 -070031#define HDAPS_NR_PORTS 0x30 /* number of ports: 0x1600 - 0x162f */
Robert Love860e1d62005-08-31 23:57:59 -040032
33#define HDAPS_PORT_STATE 0x1611 /* device state */
34#define HDAPS_PORT_YPOS 0x1612 /* y-axis position */
35#define HDAPS_PORT_XPOS 0x1614 /* x-axis position */
Jean Delvare0a704682006-06-04 20:10:55 +020036#define HDAPS_PORT_TEMP1 0x1616 /* device temperature, in Celsius */
Robert Love860e1d62005-08-31 23:57:59 -040037#define HDAPS_PORT_YVAR 0x1617 /* y-axis variance (what is this?) */
38#define HDAPS_PORT_XVAR 0x1619 /* x-axis variance (what is this?) */
39#define HDAPS_PORT_TEMP2 0x161b /* device temperature (again?) */
40#define HDAPS_PORT_UNKNOWN 0x161c /* what is this? */
41#define HDAPS_PORT_KMACT 0x161d /* keyboard or mouse activity */
42
Robert Love393ad292005-09-16 19:28:07 -070043#define STATE_FRESH 0x50 /* accelerometer data is fresh */
Robert Love860e1d62005-08-31 23:57:59 -040044
45#define KEYBD_MASK 0x20 /* set if keyboard activity */
46#define MOUSE_MASK 0x40 /* set if mouse activity */
47#define KEYBD_ISSET(n) (!! (n & KEYBD_MASK)) /* keyboard used? */
48#define MOUSE_ISSET(n) (!! (n & MOUSE_MASK)) /* mouse used? */
49
50#define INIT_TIMEOUT_MSECS 4000 /* wait up to 4s for device init ... */
51#define INIT_WAIT_MSECS 200 /* ... in 200ms increments */
52
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -040053#define HDAPS_POLL_INTERVAL 50 /* poll for input every 1/20s (50 ms)*/
Robert Love393ad292005-09-16 19:28:07 -070054#define HDAPS_INPUT_FUZZ 4 /* input event threshold */
Dmitry Torokhovd12eb7e2005-11-10 22:10:55 -050055#define HDAPS_INPUT_FLAT 4
Robert Love393ad292005-09-16 19:28:07 -070056
Frank Seidel2b8cf3e2009-03-30 21:46:41 +020057#define HDAPS_X_AXIS (1 << 0)
58#define HDAPS_Y_AXIS (1 << 1)
59#define HDAPS_BOTH_AXES (HDAPS_X_AXIS | HDAPS_Y_AXIS)
60
Robert Love393ad292005-09-16 19:28:07 -070061static struct platform_device *pdev;
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -040062static struct input_polled_dev *hdaps_idev;
Robert Love860e1d62005-08-31 23:57:59 -040063static unsigned int hdaps_invert;
64static u8 km_activity;
65static int rest_x;
66static int rest_y;
67
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -070068static DEFINE_MUTEX(hdaps_mtx);
Robert Love860e1d62005-08-31 23:57:59 -040069
70/*
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -070071 * __get_latch - Get the value from a given port. Callers must hold hdaps_mtx.
Robert Love860e1d62005-08-31 23:57:59 -040072 */
73static inline u8 __get_latch(u16 port)
74{
Robert Love393ad292005-09-16 19:28:07 -070075 return inb(port) & 0xff;
Robert Love860e1d62005-08-31 23:57:59 -040076}
77
78/*
Robert Love393ad292005-09-16 19:28:07 -070079 * __check_latch - Check a port latch for a given value. Returns zero if the
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -070080 * port contains the given value. Callers must hold hdaps_mtx.
Robert Love860e1d62005-08-31 23:57:59 -040081 */
Robert Love393ad292005-09-16 19:28:07 -070082static inline int __check_latch(u16 port, u8 val)
Robert Love860e1d62005-08-31 23:57:59 -040083{
84 if (__get_latch(port) == val)
85 return 0;
86 return -EINVAL;
87}
88
89/*
90 * __wait_latch - Wait up to 100us for a port latch to get a certain value,
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -070091 * returning zero if the value is obtained. Callers must hold hdaps_mtx.
Robert Love860e1d62005-08-31 23:57:59 -040092 */
Robert Love393ad292005-09-16 19:28:07 -070093static int __wait_latch(u16 port, u8 val)
Robert Love860e1d62005-08-31 23:57:59 -040094{
95 unsigned int i;
96
97 for (i = 0; i < 20; i++) {
98 if (!__check_latch(port, val))
99 return 0;
100 udelay(5);
101 }
102
Robert Love393ad292005-09-16 19:28:07 -0700103 return -EIO;
Robert Love860e1d62005-08-31 23:57:59 -0400104}
105
106/*
Robert Love393ad292005-09-16 19:28:07 -0700107 * __device_refresh - request a refresh from the accelerometer. Does not wait
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700108 * for refresh to complete. Callers must hold hdaps_mtx.
Robert Love860e1d62005-08-31 23:57:59 -0400109 */
Robert Love393ad292005-09-16 19:28:07 -0700110static void __device_refresh(void)
Robert Love860e1d62005-08-31 23:57:59 -0400111{
Robert Love393ad292005-09-16 19:28:07 -0700112 udelay(200);
113 if (inb(0x1604) != STATE_FRESH) {
114 outb(0x11, 0x1610);
115 outb(0x01, 0x161f);
116 }
117}
Robert Love860e1d62005-08-31 23:57:59 -0400118
Robert Love393ad292005-09-16 19:28:07 -0700119/*
120 * __device_refresh_sync - request a synchronous refresh from the
121 * accelerometer. We wait for the refresh to complete. Returns zero if
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700122 * successful and nonzero on error. Callers must hold hdaps_mtx.
Robert Love393ad292005-09-16 19:28:07 -0700123 */
124static int __device_refresh_sync(void)
125{
126 __device_refresh();
Robert Love860e1d62005-08-31 23:57:59 -0400127 return __wait_latch(0x1604, STATE_FRESH);
128}
129
130/*
Robert Love393ad292005-09-16 19:28:07 -0700131 * __device_complete - indicate to the accelerometer that we are done reading
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700132 * data, and then initiate an async refresh. Callers must hold hdaps_mtx.
Robert Love860e1d62005-08-31 23:57:59 -0400133 */
134static inline void __device_complete(void)
135{
136 inb(0x161f);
137 inb(0x1604);
Robert Love393ad292005-09-16 19:28:07 -0700138 __device_refresh();
Robert Love860e1d62005-08-31 23:57:59 -0400139}
140
141/*
142 * hdaps_readb_one - reads a byte from a single I/O port, placing the value in
143 * the given pointer. Returns zero on success or a negative error on failure.
144 * Can sleep.
145 */
146static int hdaps_readb_one(unsigned int port, u8 *val)
147{
148 int ret;
149
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700150 mutex_lock(&hdaps_mtx);
Robert Love860e1d62005-08-31 23:57:59 -0400151
Robert Love393ad292005-09-16 19:28:07 -0700152 /* do a sync refresh -- we need to be sure that we read fresh data */
153 ret = __device_refresh_sync();
154 if (ret)
155 goto out;
156
157 *val = inb(port);
158 __device_complete();
159
160out:
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700161 mutex_unlock(&hdaps_mtx);
Robert Love860e1d62005-08-31 23:57:59 -0400162 return ret;
163}
164
Robert Love393ad292005-09-16 19:28:07 -0700165/* __hdaps_read_pair - internal lockless helper for hdaps_read_pair(). */
Robert Love860e1d62005-08-31 23:57:59 -0400166static int __hdaps_read_pair(unsigned int port1, unsigned int port2,
167 int *x, int *y)
168{
169 /* do a sync refresh -- we need to be sure that we read fresh data */
Robert Love393ad292005-09-16 19:28:07 -0700170 if (__device_refresh_sync())
Robert Love860e1d62005-08-31 23:57:59 -0400171 return -EIO;
172
173 *y = inw(port2);
174 *x = inw(port1);
175 km_activity = inb(HDAPS_PORT_KMACT);
176 __device_complete();
177
Frank Seidel2b8cf3e2009-03-30 21:46:41 +0200178 /* hdaps_invert is a bitvector to negate the axes */
179 if (hdaps_invert & HDAPS_X_AXIS)
Robert Love860e1d62005-08-31 23:57:59 -0400180 *x = -*x;
Frank Seidel2b8cf3e2009-03-30 21:46:41 +0200181 if (hdaps_invert & HDAPS_Y_AXIS)
Robert Love860e1d62005-08-31 23:57:59 -0400182 *y = -*y;
Robert Love860e1d62005-08-31 23:57:59 -0400183
184 return 0;
185}
186
187/*
188 * hdaps_read_pair - reads the values from a pair of ports, placing the values
189 * in the given pointers. Returns zero on success. Can sleep.
190 */
191static int hdaps_read_pair(unsigned int port1, unsigned int port2,
192 int *val1, int *val2)
193{
194 int ret;
195
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700196 mutex_lock(&hdaps_mtx);
Robert Love860e1d62005-08-31 23:57:59 -0400197 ret = __hdaps_read_pair(port1, port2, val1, val2);
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700198 mutex_unlock(&hdaps_mtx);
Robert Love860e1d62005-08-31 23:57:59 -0400199
200 return ret;
201}
202
Robert Love393ad292005-09-16 19:28:07 -0700203/*
204 * hdaps_device_init - initialize the accelerometer. Returns zero on success
205 * and negative error code on failure. Can sleep.
206 */
Robert Love860e1d62005-08-31 23:57:59 -0400207static int hdaps_device_init(void)
208{
Robert Love393ad292005-09-16 19:28:07 -0700209 int total, ret = -ENXIO;
Robert Love860e1d62005-08-31 23:57:59 -0400210
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700211 mutex_lock(&hdaps_mtx);
Robert Love860e1d62005-08-31 23:57:59 -0400212
213 outb(0x13, 0x1610);
214 outb(0x01, 0x161f);
215 if (__wait_latch(0x161f, 0x00))
216 goto out;
217
218 /*
Robert Love393ad292005-09-16 19:28:07 -0700219 * Most ThinkPads return 0x01.
220 *
221 * Others--namely the R50p, T41p, and T42p--return 0x03. These laptops
222 * have "inverted" axises.
Robert Love860e1d62005-08-31 23:57:59 -0400223 *
224 * The 0x02 value occurs when the chip has been previously initialized.
225 */
226 if (__check_latch(0x1611, 0x03) &&
227 __check_latch(0x1611, 0x02) &&
228 __check_latch(0x1611, 0x01))
229 goto out;
230
Joe Perches611f5762011-03-29 15:21:40 -0700231 printk(KERN_DEBUG "hdaps: initial latch check good (0x%02x)\n",
Robert Love860e1d62005-08-31 23:57:59 -0400232 __get_latch(0x1611));
233
234 outb(0x17, 0x1610);
235 outb(0x81, 0x1611);
236 outb(0x01, 0x161f);
237 if (__wait_latch(0x161f, 0x00))
238 goto out;
239 if (__wait_latch(0x1611, 0x00))
240 goto out;
241 if (__wait_latch(0x1612, 0x60))
242 goto out;
243 if (__wait_latch(0x1613, 0x00))
244 goto out;
245 outb(0x14, 0x1610);
246 outb(0x01, 0x1611);
247 outb(0x01, 0x161f);
248 if (__wait_latch(0x161f, 0x00))
249 goto out;
250 outb(0x10, 0x1610);
251 outb(0xc8, 0x1611);
252 outb(0x00, 0x1612);
253 outb(0x02, 0x1613);
254 outb(0x01, 0x161f);
255 if (__wait_latch(0x161f, 0x00))
256 goto out;
Robert Love393ad292005-09-16 19:28:07 -0700257 if (__device_refresh_sync())
Robert Love860e1d62005-08-31 23:57:59 -0400258 goto out;
259 if (__wait_latch(0x1611, 0x00))
260 goto out;
261
262 /* we have done our dance, now let's wait for the applause */
Robert Love393ad292005-09-16 19:28:07 -0700263 for (total = INIT_TIMEOUT_MSECS; total > 0; total -= INIT_WAIT_MSECS) {
264 int x, y;
Robert Love860e1d62005-08-31 23:57:59 -0400265
266 /* a read of the device helps push it into action */
Robert Love393ad292005-09-16 19:28:07 -0700267 __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
Robert Love860e1d62005-08-31 23:57:59 -0400268 if (!__wait_latch(0x1611, 0x02)) {
269 ret = 0;
270 break;
271 }
272
273 msleep(INIT_WAIT_MSECS);
Robert Love860e1d62005-08-31 23:57:59 -0400274 }
275
276out:
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700277 mutex_unlock(&hdaps_mtx);
Robert Love860e1d62005-08-31 23:57:59 -0400278 return ret;
279}
280
281
Robert Love860e1d62005-08-31 23:57:59 -0400282/* Device model stuff */
283
Russell King3ae5eaec2005-11-09 22:32:44 +0000284static int hdaps_probe(struct platform_device *dev)
Robert Love860e1d62005-08-31 23:57:59 -0400285{
286 int ret;
287
288 ret = hdaps_device_init();
289 if (ret)
290 return ret;
291
Joe Perches611f5762011-03-29 15:21:40 -0700292 pr_info("device successfully initialized\n");
Robert Love860e1d62005-08-31 23:57:59 -0400293 return 0;
294}
295
Rafael J. Wysocki3567a4e22012-08-09 23:00:13 +0200296#ifdef CONFIG_PM_SLEEP
Rafael J. Wysockie25d5c12012-06-30 23:50:47 +0200297static int hdaps_resume(struct device *dev)
Robert Love860e1d62005-08-31 23:57:59 -0400298{
Russell King9480e302005-10-28 09:52:56 -0700299 return hdaps_device_init();
Robert Love860e1d62005-08-31 23:57:59 -0400300}
Rafael J. Wysocki3567a4e22012-08-09 23:00:13 +0200301#endif
Robert Love860e1d62005-08-31 23:57:59 -0400302
Rafael J. Wysockie25d5c12012-06-30 23:50:47 +0200303static SIMPLE_DEV_PM_OPS(hdaps_pm, NULL, hdaps_resume);
304
Russell King3ae5eaec2005-11-09 22:32:44 +0000305static struct platform_driver hdaps_driver = {
Robert Love860e1d62005-08-31 23:57:59 -0400306 .probe = hdaps_probe,
Russell King3ae5eaec2005-11-09 22:32:44 +0000307 .driver = {
308 .name = "hdaps",
Rafael J. Wysockie25d5c12012-06-30 23:50:47 +0200309 .pm = &hdaps_pm,
Russell King3ae5eaec2005-11-09 22:32:44 +0000310 },
Robert Love860e1d62005-08-31 23:57:59 -0400311};
312
Robert Love393ad292005-09-16 19:28:07 -0700313/*
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700314 * hdaps_calibrate - Set our "resting" values. Callers must hold hdaps_mtx.
Robert Love393ad292005-09-16 19:28:07 -0700315 */
316static void hdaps_calibrate(void)
317{
318 __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &rest_x, &rest_y);
319}
320
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -0400321static void hdaps_mousedev_poll(struct input_polled_dev *dev)
Robert Love393ad292005-09-16 19:28:07 -0700322{
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -0400323 struct input_dev *input_dev = dev->input;
Robert Love393ad292005-09-16 19:28:07 -0700324 int x, y;
325
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -0400326 mutex_lock(&hdaps_mtx);
Robert Love393ad292005-09-16 19:28:07 -0700327
328 if (__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y))
329 goto out;
330
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -0400331 input_report_abs(input_dev, ABS_X, x - rest_x);
332 input_report_abs(input_dev, ABS_Y, y - rest_y);
333 input_sync(input_dev);
Robert Love393ad292005-09-16 19:28:07 -0700334
335out:
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700336 mutex_unlock(&hdaps_mtx);
Robert Love393ad292005-09-16 19:28:07 -0700337}
338
Robert Love860e1d62005-08-31 23:57:59 -0400339
340/* Sysfs Files */
341
342static ssize_t hdaps_position_show(struct device *dev,
343 struct device_attribute *attr, char *buf)
344{
345 int ret, x, y;
346
347 ret = hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
348 if (ret)
349 return ret;
350
351 return sprintf(buf, "(%d,%d)\n", x, y);
352}
353
354static ssize_t hdaps_variance_show(struct device *dev,
355 struct device_attribute *attr, char *buf)
356{
357 int ret, x, y;
358
359 ret = hdaps_read_pair(HDAPS_PORT_XVAR, HDAPS_PORT_YVAR, &x, &y);
360 if (ret)
361 return ret;
362
363 return sprintf(buf, "(%d,%d)\n", x, y);
364}
365
366static ssize_t hdaps_temp1_show(struct device *dev,
367 struct device_attribute *attr, char *buf)
368{
Borislav Petkovb94e88b2011-11-24 15:01:31 +0100369 u8 uninitialized_var(temp);
Robert Love860e1d62005-08-31 23:57:59 -0400370 int ret;
371
372 ret = hdaps_readb_one(HDAPS_PORT_TEMP1, &temp);
Danny Kukawkaa9384062012-01-30 23:00:11 +0100373 if (ret)
Robert Love860e1d62005-08-31 23:57:59 -0400374 return ret;
375
376 return sprintf(buf, "%u\n", temp);
377}
378
379static ssize_t hdaps_temp2_show(struct device *dev,
380 struct device_attribute *attr, char *buf)
381{
Borislav Petkovb94e88b2011-11-24 15:01:31 +0100382 u8 uninitialized_var(temp);
Robert Love860e1d62005-08-31 23:57:59 -0400383 int ret;
384
385 ret = hdaps_readb_one(HDAPS_PORT_TEMP2, &temp);
Danny Kukawkaa9384062012-01-30 23:00:11 +0100386 if (ret)
Robert Love860e1d62005-08-31 23:57:59 -0400387 return ret;
388
389 return sprintf(buf, "%u\n", temp);
390}
391
392static ssize_t hdaps_keyboard_activity_show(struct device *dev,
393 struct device_attribute *attr,
394 char *buf)
395{
396 return sprintf(buf, "%u\n", KEYBD_ISSET(km_activity));
397}
398
399static ssize_t hdaps_mouse_activity_show(struct device *dev,
400 struct device_attribute *attr,
401 char *buf)
402{
403 return sprintf(buf, "%u\n", MOUSE_ISSET(km_activity));
404}
405
406static ssize_t hdaps_calibrate_show(struct device *dev,
407 struct device_attribute *attr, char *buf)
408{
409 return sprintf(buf, "(%d,%d)\n", rest_x, rest_y);
410}
411
412static ssize_t hdaps_calibrate_store(struct device *dev,
413 struct device_attribute *attr,
414 const char *buf, size_t count)
415{
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700416 mutex_lock(&hdaps_mtx);
Robert Love860e1d62005-08-31 23:57:59 -0400417 hdaps_calibrate();
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700418 mutex_unlock(&hdaps_mtx);
Robert Love860e1d62005-08-31 23:57:59 -0400419
420 return count;
421}
422
423static ssize_t hdaps_invert_show(struct device *dev,
424 struct device_attribute *attr, char *buf)
425{
426 return sprintf(buf, "%u\n", hdaps_invert);
427}
428
429static ssize_t hdaps_invert_store(struct device *dev,
430 struct device_attribute *attr,
431 const char *buf, size_t count)
432{
433 int invert;
434
Frank Seidel2b8cf3e2009-03-30 21:46:41 +0200435 if (sscanf(buf, "%d", &invert) != 1 ||
436 invert < 0 || invert > HDAPS_BOTH_AXES)
Robert Love860e1d62005-08-31 23:57:59 -0400437 return -EINVAL;
438
439 hdaps_invert = invert;
440 hdaps_calibrate();
441
442 return count;
443}
444
Robert Love860e1d62005-08-31 23:57:59 -0400445static DEVICE_ATTR(position, 0444, hdaps_position_show, NULL);
446static DEVICE_ATTR(variance, 0444, hdaps_variance_show, NULL);
447static DEVICE_ATTR(temp1, 0444, hdaps_temp1_show, NULL);
448static DEVICE_ATTR(temp2, 0444, hdaps_temp2_show, NULL);
449static DEVICE_ATTR(keyboard_activity, 0444, hdaps_keyboard_activity_show, NULL);
450static DEVICE_ATTR(mouse_activity, 0444, hdaps_mouse_activity_show, NULL);
451static DEVICE_ATTR(calibrate, 0644, hdaps_calibrate_show,hdaps_calibrate_store);
452static DEVICE_ATTR(invert, 0644, hdaps_invert_show, hdaps_invert_store);
Robert Love860e1d62005-08-31 23:57:59 -0400453
454static struct attribute *hdaps_attributes[] = {
455 &dev_attr_position.attr,
456 &dev_attr_variance.attr,
457 &dev_attr_temp1.attr,
458 &dev_attr_temp2.attr,
459 &dev_attr_keyboard_activity.attr,
460 &dev_attr_mouse_activity.attr,
461 &dev_attr_calibrate.attr,
Robert Love860e1d62005-08-31 23:57:59 -0400462 &dev_attr_invert.attr,
463 NULL,
464};
465
466static struct attribute_group hdaps_attribute_group = {
467 .attrs = hdaps_attributes,
468};
469
470
471/* Module stuff */
472
Robert Love4c87b742005-09-22 21:44:00 -0700473/* hdaps_dmi_match - found a match. return one, short-circuiting the hunt. */
Jeff Garzik18552562007-10-03 15:15:40 -0400474static int __init hdaps_dmi_match(const struct dmi_system_id *id)
Robert Love860e1d62005-08-31 23:57:59 -0400475{
Joe Perches611f5762011-03-29 15:21:40 -0700476 pr_info("%s detected\n", id->ident);
Robert Love4c87b742005-09-22 21:44:00 -0700477 return 1;
Robert Love860e1d62005-08-31 23:57:59 -0400478}
479
Robert Love4c87b742005-09-22 21:44:00 -0700480/* hdaps_dmi_match_invert - found an inverted match. */
Jeff Garzik18552562007-10-03 15:15:40 -0400481static int __init hdaps_dmi_match_invert(const struct dmi_system_id *id)
Robert Love860e1d62005-08-31 23:57:59 -0400482{
Frank Seidel2b8cf3e2009-03-30 21:46:41 +0200483 hdaps_invert = (unsigned long)id->driver_data;
Joe Perches611f5762011-03-29 15:21:40 -0700484 pr_info("inverting axis (%u) readings\n", hdaps_invert);
Robert Love4c87b742005-09-22 21:44:00 -0700485 return hdaps_dmi_match(id);
Robert Love860e1d62005-08-31 23:57:59 -0400486}
487
Frank Seidel2b8cf3e2009-03-30 21:46:41 +0200488#define HDAPS_DMI_MATCH_INVERT(vendor, model, axes) { \
Jean Delvare509a5e82006-12-12 18:18:28 +0100489 .ident = vendor " " model, \
Frank Seidel2b8cf3e2009-03-30 21:46:41 +0200490 .callback = hdaps_dmi_match_invert, \
491 .driver_data = (void *)axes, \
Robert Love860e1d62005-08-31 23:57:59 -0400492 .matches = { \
Jean Delvare509a5e82006-12-12 18:18:28 +0100493 DMI_MATCH(DMI_BOARD_VENDOR, vendor), \
Robert Love860e1d62005-08-31 23:57:59 -0400494 DMI_MATCH(DMI_PRODUCT_VERSION, model) \
495 } \
496}
497
Frank Seidel2b8cf3e2009-03-30 21:46:41 +0200498#define HDAPS_DMI_MATCH_NORMAL(vendor, model) \
499 HDAPS_DMI_MATCH_INVERT(vendor, model, 0)
Robert Love860e1d62005-08-31 23:57:59 -0400500
Jean Delvare509a5e82006-12-12 18:18:28 +0100501/* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
Stephan Berberig0f23e502006-12-12 18:18:28 +0100502 "ThinkPad T42p", so the order of the entries matters.
503 If your ThinkPad is not recognized, please update to latest
504 BIOS. This is especially the case for some R52 ThinkPads. */
Christoph Hellwig6faadbb2017-09-14 11:59:30 +0200505static const struct dmi_system_id hdaps_whitelist[] __initconst = {
Frank Seidel2b8cf3e2009-03-30 21:46:41 +0200506 HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p", HDAPS_BOTH_AXES),
Jean Delvare509a5e82006-12-12 18:18:28 +0100507 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R50"),
508 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R51"),
509 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R52"),
Frank Seidel2b8cf3e2009-03-30 21:46:41 +0200510 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61i", HDAPS_BOTH_AXES),
511 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61", HDAPS_BOTH_AXES),
512 HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p", HDAPS_BOTH_AXES),
Jean Delvare509a5e82006-12-12 18:18:28 +0100513 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T41"),
Frank Seidel2b8cf3e2009-03-30 21:46:41 +0200514 HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p", HDAPS_BOTH_AXES),
Jean Delvare509a5e82006-12-12 18:18:28 +0100515 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T42"),
516 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T43"),
Ritesh Raj Sarraf5f1209a2010-08-09 17:21:04 -0700517 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400", HDAPS_BOTH_AXES),
Frank Seidel2b8cf3e2009-03-30 21:46:41 +0200518 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60", HDAPS_BOTH_AXES),
519 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61p", HDAPS_BOTH_AXES),
520 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61", HDAPS_BOTH_AXES),
Jean Delvare509a5e82006-12-12 18:18:28 +0100521 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X40"),
Frank Seidelb6a33fe2009-03-30 21:46:42 +0200522 HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X41", HDAPS_Y_AXIS),
Frank Seidel2b8cf3e2009-03-30 21:46:41 +0200523 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60", HDAPS_BOTH_AXES),
524 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61s", HDAPS_BOTH_AXES),
525 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61", HDAPS_BOTH_AXES),
Jean Delvare509a5e82006-12-12 18:18:28 +0100526 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad Z60m"),
Frank Seidel2b8cf3e2009-03-30 21:46:41 +0200527 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61m", HDAPS_BOTH_AXES),
528 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61p", HDAPS_BOTH_AXES),
Jean Delvare509a5e82006-12-12 18:18:28 +0100529 { .ident = NULL }
530};
Robert Love0f6c8402006-04-10 22:54:11 -0700531
Robert Love860e1d62005-08-31 23:57:59 -0400532static int __init hdaps_init(void)
533{
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -0400534 struct input_dev *idev;
Robert Love860e1d62005-08-31 23:57:59 -0400535 int ret;
536
Robert Love860e1d62005-08-31 23:57:59 -0400537 if (!dmi_check_system(hdaps_whitelist)) {
Joe Perches611f5762011-03-29 15:21:40 -0700538 pr_warn("supported laptop not found!\n");
Andrew Mortonb3f28a92006-04-10 22:54:32 -0700539 ret = -ENODEV;
Robert Love860e1d62005-08-31 23:57:59 -0400540 goto out;
541 }
542
543 if (!request_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS, "hdaps")) {
544 ret = -ENXIO;
545 goto out;
546 }
547
Russell King3ae5eaec2005-11-09 22:32:44 +0000548 ret = platform_driver_register(&hdaps_driver);
Robert Love860e1d62005-08-31 23:57:59 -0400549 if (ret)
550 goto out_region;
551
552 pdev = platform_device_register_simple("hdaps", -1, NULL, 0);
553 if (IS_ERR(pdev)) {
554 ret = PTR_ERR(pdev);
555 goto out_driver;
556 }
557
558 ret = sysfs_create_group(&pdev->dev.kobj, &hdaps_attribute_group);
559 if (ret)
560 goto out_device;
561
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -0400562 hdaps_idev = input_allocate_polled_device();
Dmitry Torokhovd12eb7e2005-11-10 22:10:55 -0500563 if (!hdaps_idev) {
564 ret = -ENOMEM;
565 goto out_group;
566 }
567
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -0400568 hdaps_idev->poll = hdaps_mousedev_poll;
569 hdaps_idev->poll_interval = HDAPS_POLL_INTERVAL;
570
Robert Love393ad292005-09-16 19:28:07 -0700571 /* initial calibrate for the input device */
572 hdaps_calibrate();
573
574 /* initialize the input class */
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -0400575 idev = hdaps_idev->input;
576 idev->name = "hdaps";
Dmitry Torokhovd2fc60d2008-05-05 11:53:45 -0400577 idev->phys = "isa1600/input0";
578 idev->id.bustype = BUS_ISA;
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -0400579 idev->dev.parent = &pdev->dev;
Jiri Slaby7b19ada2007-10-18 23:40:32 -0700580 idev->evbit[0] = BIT_MASK(EV_ABS);
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -0400581 input_set_abs_params(idev, ABS_X,
Dmitry Torokhovd12eb7e2005-11-10 22:10:55 -0500582 -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -0400583 input_set_abs_params(idev, ABS_Y,
Dmitry Torokhovd12eb7e2005-11-10 22:10:55 -0500584 -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
585
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -0400586 ret = input_register_polled_device(hdaps_idev);
Dmitry Torokhovb25a1062006-08-28 14:21:42 +0200587 if (ret)
588 goto out_idev;
Robert Love393ad292005-09-16 19:28:07 -0700589
Joe Perches611f5762011-03-29 15:21:40 -0700590 pr_info("driver successfully loaded\n");
Robert Love860e1d62005-08-31 23:57:59 -0400591 return 0;
592
Dmitry Torokhovb25a1062006-08-28 14:21:42 +0200593out_idev:
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -0400594 input_free_polled_device(hdaps_idev);
Dmitry Torokhovd12eb7e2005-11-10 22:10:55 -0500595out_group:
596 sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
Robert Love860e1d62005-08-31 23:57:59 -0400597out_device:
598 platform_device_unregister(pdev);
599out_driver:
Russell King3ae5eaec2005-11-09 22:32:44 +0000600 platform_driver_unregister(&hdaps_driver);
Robert Love860e1d62005-08-31 23:57:59 -0400601out_region:
602 release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
603out:
Joe Perches611f5762011-03-29 15:21:40 -0700604 pr_warn("driver init failed (ret=%d)!\n", ret);
Robert Love860e1d62005-08-31 23:57:59 -0400605 return ret;
606}
607
608static void __exit hdaps_exit(void)
609{
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -0400610 input_unregister_polled_device(hdaps_idev);
611 input_free_polled_device(hdaps_idev);
Robert Love860e1d62005-08-31 23:57:59 -0400612 sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
613 platform_device_unregister(pdev);
Russell King3ae5eaec2005-11-09 22:32:44 +0000614 platform_driver_unregister(&hdaps_driver);
Robert Love860e1d62005-08-31 23:57:59 -0400615 release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
616
Joe Perches611f5762011-03-29 15:21:40 -0700617 pr_info("driver unloaded\n");
Robert Love860e1d62005-08-31 23:57:59 -0400618}
619
620module_init(hdaps_init);
621module_exit(hdaps_exit);
622
Frank Seidel2b8cf3e2009-03-30 21:46:41 +0200623module_param_named(invert, hdaps_invert, int, 0);
624MODULE_PARM_DESC(invert, "invert data along each axis. 1 invert x-axis, "
625 "2 invert y-axis, 3 invert both axes.");
Robert Love860e1d62005-08-31 23:57:59 -0400626
627MODULE_AUTHOR("Robert Love");
628MODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver");
629MODULE_LICENSE("GPL v2");