blob: 88e89653daafe0bad48ba50c3127097cde10ce59 [file] [log] [blame]
Robert Love860e1d62005-08-31 23:57:59 -04001/*
2 * drivers/hwmon/hdaps.c - driver for IBM's Hard Drive Active Protection System
3 *
4 * Copyright (C) 2005 Robert Love <rml@novell.com>
5 * Copyright (C) 2005 Jesper Juhl <jesper.juhl@gmail.com>
6 *
Robert Love4c87b742005-09-22 21:44:00 -07007 * The HardDisk Active Protection System (hdaps) is present in IBM ThinkPads
8 * starting with the R40, T41, and X40. It provides a basic two-axis
9 * accelerometer and other data, such as the device's temperature.
Robert Love860e1d62005-08-31 23:57:59 -040010 *
Robert Love393ad292005-09-16 19:28:07 -070011 * This driver is based on the document by Mark A. Smith available at
Robert Love860e1d62005-08-31 23:57:59 -040012 * http://www.almaden.ibm.com/cs/people/marksmith/tpaps.html and a lot of trial
13 * and error.
14 *
15 * This program is free software; you can redistribute it and/or modify it
16 * under the terms of the GNU General Public License v2 as published by the
17 * Free Software Foundation.
18 *
19 * This program is distributed in the hope that it will be useful, but WITHOUT
20 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
22 * more details.
23 *
24 * You should have received a copy of the GNU General Public License along with
25 * this program; if not, write to the Free Software Foundation, Inc.,
26 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
27 */
28
29#include <linux/delay.h>
Russell Kingd052d1b2005-10-29 19:07:23 +010030#include <linux/platform_device.h>
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -040031#include <linux/input-polldev.h>
Robert Love860e1d62005-08-31 23:57:59 -040032#include <linux/kernel.h>
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -070033#include <linux/mutex.h>
Robert Love860e1d62005-08-31 23:57:59 -040034#include <linux/module.h>
35#include <linux/timer.h>
36#include <linux/dmi.h>
Al Viro914e2632006-10-18 13:55:46 -040037#include <linux/jiffies.h>
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -070038
Robert Love860e1d62005-08-31 23:57:59 -040039#include <asm/io.h>
40
41#define HDAPS_LOW_PORT 0x1600 /* first port used by hdaps */
Robert Love393ad292005-09-16 19:28:07 -070042#define HDAPS_NR_PORTS 0x30 /* number of ports: 0x1600 - 0x162f */
Robert Love860e1d62005-08-31 23:57:59 -040043
44#define HDAPS_PORT_STATE 0x1611 /* device state */
45#define HDAPS_PORT_YPOS 0x1612 /* y-axis position */
46#define HDAPS_PORT_XPOS 0x1614 /* x-axis position */
Jean Delvare0a704682006-06-04 20:10:55 +020047#define HDAPS_PORT_TEMP1 0x1616 /* device temperature, in Celsius */
Robert Love860e1d62005-08-31 23:57:59 -040048#define HDAPS_PORT_YVAR 0x1617 /* y-axis variance (what is this?) */
49#define HDAPS_PORT_XVAR 0x1619 /* x-axis variance (what is this?) */
50#define HDAPS_PORT_TEMP2 0x161b /* device temperature (again?) */
51#define HDAPS_PORT_UNKNOWN 0x161c /* what is this? */
52#define HDAPS_PORT_KMACT 0x161d /* keyboard or mouse activity */
53
Robert Love393ad292005-09-16 19:28:07 -070054#define STATE_FRESH 0x50 /* accelerometer data is fresh */
Robert Love860e1d62005-08-31 23:57:59 -040055
56#define KEYBD_MASK 0x20 /* set if keyboard activity */
57#define MOUSE_MASK 0x40 /* set if mouse activity */
58#define KEYBD_ISSET(n) (!! (n & KEYBD_MASK)) /* keyboard used? */
59#define MOUSE_ISSET(n) (!! (n & MOUSE_MASK)) /* mouse used? */
60
61#define INIT_TIMEOUT_MSECS 4000 /* wait up to 4s for device init ... */
62#define INIT_WAIT_MSECS 200 /* ... in 200ms increments */
63
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -040064#define HDAPS_POLL_INTERVAL 50 /* poll for input every 1/20s (50 ms)*/
Robert Love393ad292005-09-16 19:28:07 -070065#define HDAPS_INPUT_FUZZ 4 /* input event threshold */
Dmitry Torokhovd12eb7e2005-11-10 22:10:55 -050066#define HDAPS_INPUT_FLAT 4
Robert Love393ad292005-09-16 19:28:07 -070067
Robert Love393ad292005-09-16 19:28:07 -070068static struct platform_device *pdev;
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -040069static struct input_polled_dev *hdaps_idev;
Robert Love860e1d62005-08-31 23:57:59 -040070static unsigned int hdaps_invert;
71static u8 km_activity;
72static int rest_x;
73static int rest_y;
74
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -070075static DEFINE_MUTEX(hdaps_mtx);
Robert Love860e1d62005-08-31 23:57:59 -040076
77/*
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -070078 * __get_latch - Get the value from a given port. Callers must hold hdaps_mtx.
Robert Love860e1d62005-08-31 23:57:59 -040079 */
80static inline u8 __get_latch(u16 port)
81{
Robert Love393ad292005-09-16 19:28:07 -070082 return inb(port) & 0xff;
Robert Love860e1d62005-08-31 23:57:59 -040083}
84
85/*
Robert Love393ad292005-09-16 19:28:07 -070086 * __check_latch - Check a port latch for a given value. Returns zero if the
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -070087 * port contains the given value. Callers must hold hdaps_mtx.
Robert Love860e1d62005-08-31 23:57:59 -040088 */
Robert Love393ad292005-09-16 19:28:07 -070089static inline int __check_latch(u16 port, u8 val)
Robert Love860e1d62005-08-31 23:57:59 -040090{
91 if (__get_latch(port) == val)
92 return 0;
93 return -EINVAL;
94}
95
96/*
97 * __wait_latch - Wait up to 100us for a port latch to get a certain value,
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -070098 * returning zero if the value is obtained. Callers must hold hdaps_mtx.
Robert Love860e1d62005-08-31 23:57:59 -040099 */
Robert Love393ad292005-09-16 19:28:07 -0700100static int __wait_latch(u16 port, u8 val)
Robert Love860e1d62005-08-31 23:57:59 -0400101{
102 unsigned int i;
103
104 for (i = 0; i < 20; i++) {
105 if (!__check_latch(port, val))
106 return 0;
107 udelay(5);
108 }
109
Robert Love393ad292005-09-16 19:28:07 -0700110 return -EIO;
Robert Love860e1d62005-08-31 23:57:59 -0400111}
112
113/*
Robert Love393ad292005-09-16 19:28:07 -0700114 * __device_refresh - request a refresh from the accelerometer. Does not wait
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700115 * for refresh to complete. Callers must hold hdaps_mtx.
Robert Love860e1d62005-08-31 23:57:59 -0400116 */
Robert Love393ad292005-09-16 19:28:07 -0700117static void __device_refresh(void)
Robert Love860e1d62005-08-31 23:57:59 -0400118{
Robert Love393ad292005-09-16 19:28:07 -0700119 udelay(200);
120 if (inb(0x1604) != STATE_FRESH) {
121 outb(0x11, 0x1610);
122 outb(0x01, 0x161f);
123 }
124}
Robert Love860e1d62005-08-31 23:57:59 -0400125
Robert Love393ad292005-09-16 19:28:07 -0700126/*
127 * __device_refresh_sync - request a synchronous refresh from the
128 * accelerometer. We wait for the refresh to complete. Returns zero if
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700129 * successful and nonzero on error. Callers must hold hdaps_mtx.
Robert Love393ad292005-09-16 19:28:07 -0700130 */
131static int __device_refresh_sync(void)
132{
133 __device_refresh();
Robert Love860e1d62005-08-31 23:57:59 -0400134 return __wait_latch(0x1604, STATE_FRESH);
135}
136
137/*
Robert Love393ad292005-09-16 19:28:07 -0700138 * __device_complete - indicate to the accelerometer that we are done reading
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700139 * data, and then initiate an async refresh. Callers must hold hdaps_mtx.
Robert Love860e1d62005-08-31 23:57:59 -0400140 */
141static inline void __device_complete(void)
142{
143 inb(0x161f);
144 inb(0x1604);
Robert Love393ad292005-09-16 19:28:07 -0700145 __device_refresh();
Robert Love860e1d62005-08-31 23:57:59 -0400146}
147
148/*
149 * hdaps_readb_one - reads a byte from a single I/O port, placing the value in
150 * the given pointer. Returns zero on success or a negative error on failure.
151 * Can sleep.
152 */
153static int hdaps_readb_one(unsigned int port, u8 *val)
154{
155 int ret;
156
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700157 mutex_lock(&hdaps_mtx);
Robert Love860e1d62005-08-31 23:57:59 -0400158
Robert Love393ad292005-09-16 19:28:07 -0700159 /* do a sync refresh -- we need to be sure that we read fresh data */
160 ret = __device_refresh_sync();
161 if (ret)
162 goto out;
163
164 *val = inb(port);
165 __device_complete();
166
167out:
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700168 mutex_unlock(&hdaps_mtx);
Robert Love860e1d62005-08-31 23:57:59 -0400169 return ret;
170}
171
Robert Love393ad292005-09-16 19:28:07 -0700172/* __hdaps_read_pair - internal lockless helper for hdaps_read_pair(). */
Robert Love860e1d62005-08-31 23:57:59 -0400173static int __hdaps_read_pair(unsigned int port1, unsigned int port2,
174 int *x, int *y)
175{
176 /* do a sync refresh -- we need to be sure that we read fresh data */
Robert Love393ad292005-09-16 19:28:07 -0700177 if (__device_refresh_sync())
Robert Love860e1d62005-08-31 23:57:59 -0400178 return -EIO;
179
180 *y = inw(port2);
181 *x = inw(port1);
182 km_activity = inb(HDAPS_PORT_KMACT);
183 __device_complete();
184
185 /* if hdaps_invert is set, negate the two values */
186 if (hdaps_invert) {
187 *x = -*x;
188 *y = -*y;
189 }
190
191 return 0;
192}
193
194/*
195 * hdaps_read_pair - reads the values from a pair of ports, placing the values
196 * in the given pointers. Returns zero on success. Can sleep.
197 */
198static int hdaps_read_pair(unsigned int port1, unsigned int port2,
199 int *val1, int *val2)
200{
201 int ret;
202
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700203 mutex_lock(&hdaps_mtx);
Robert Love860e1d62005-08-31 23:57:59 -0400204 ret = __hdaps_read_pair(port1, port2, val1, val2);
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700205 mutex_unlock(&hdaps_mtx);
Robert Love860e1d62005-08-31 23:57:59 -0400206
207 return ret;
208}
209
Robert Love393ad292005-09-16 19:28:07 -0700210/*
211 * hdaps_device_init - initialize the accelerometer. Returns zero on success
212 * and negative error code on failure. Can sleep.
213 */
Robert Love860e1d62005-08-31 23:57:59 -0400214static int hdaps_device_init(void)
215{
Robert Love393ad292005-09-16 19:28:07 -0700216 int total, ret = -ENXIO;
Robert Love860e1d62005-08-31 23:57:59 -0400217
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700218 mutex_lock(&hdaps_mtx);
Robert Love860e1d62005-08-31 23:57:59 -0400219
220 outb(0x13, 0x1610);
221 outb(0x01, 0x161f);
222 if (__wait_latch(0x161f, 0x00))
223 goto out;
224
225 /*
Robert Love393ad292005-09-16 19:28:07 -0700226 * Most ThinkPads return 0x01.
227 *
228 * Others--namely the R50p, T41p, and T42p--return 0x03. These laptops
229 * have "inverted" axises.
Robert Love860e1d62005-08-31 23:57:59 -0400230 *
231 * The 0x02 value occurs when the chip has been previously initialized.
232 */
233 if (__check_latch(0x1611, 0x03) &&
234 __check_latch(0x1611, 0x02) &&
235 __check_latch(0x1611, 0x01))
236 goto out;
237
238 printk(KERN_DEBUG "hdaps: initial latch check good (0x%02x).\n",
239 __get_latch(0x1611));
240
241 outb(0x17, 0x1610);
242 outb(0x81, 0x1611);
243 outb(0x01, 0x161f);
244 if (__wait_latch(0x161f, 0x00))
245 goto out;
246 if (__wait_latch(0x1611, 0x00))
247 goto out;
248 if (__wait_latch(0x1612, 0x60))
249 goto out;
250 if (__wait_latch(0x1613, 0x00))
251 goto out;
252 outb(0x14, 0x1610);
253 outb(0x01, 0x1611);
254 outb(0x01, 0x161f);
255 if (__wait_latch(0x161f, 0x00))
256 goto out;
257 outb(0x10, 0x1610);
258 outb(0xc8, 0x1611);
259 outb(0x00, 0x1612);
260 outb(0x02, 0x1613);
261 outb(0x01, 0x161f);
262 if (__wait_latch(0x161f, 0x00))
263 goto out;
Robert Love393ad292005-09-16 19:28:07 -0700264 if (__device_refresh_sync())
Robert Love860e1d62005-08-31 23:57:59 -0400265 goto out;
266 if (__wait_latch(0x1611, 0x00))
267 goto out;
268
269 /* we have done our dance, now let's wait for the applause */
Robert Love393ad292005-09-16 19:28:07 -0700270 for (total = INIT_TIMEOUT_MSECS; total > 0; total -= INIT_WAIT_MSECS) {
271 int x, y;
Robert Love860e1d62005-08-31 23:57:59 -0400272
273 /* a read of the device helps push it into action */
Robert Love393ad292005-09-16 19:28:07 -0700274 __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
Robert Love860e1d62005-08-31 23:57:59 -0400275 if (!__wait_latch(0x1611, 0x02)) {
276 ret = 0;
277 break;
278 }
279
280 msleep(INIT_WAIT_MSECS);
Robert Love860e1d62005-08-31 23:57:59 -0400281 }
282
283out:
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700284 mutex_unlock(&hdaps_mtx);
Robert Love860e1d62005-08-31 23:57:59 -0400285 return ret;
286}
287
288
Robert Love860e1d62005-08-31 23:57:59 -0400289/* Device model stuff */
290
Russell King3ae5eae2005-11-09 22:32:44 +0000291static int hdaps_probe(struct platform_device *dev)
Robert Love860e1d62005-08-31 23:57:59 -0400292{
293 int ret;
294
295 ret = hdaps_device_init();
296 if (ret)
297 return ret;
298
299 printk(KERN_INFO "hdaps: device successfully initialized.\n");
300 return 0;
301}
302
Russell King3ae5eae2005-11-09 22:32:44 +0000303static int hdaps_resume(struct platform_device *dev)
Robert Love860e1d62005-08-31 23:57:59 -0400304{
Russell King9480e302005-10-28 09:52:56 -0700305 return hdaps_device_init();
Robert Love860e1d62005-08-31 23:57:59 -0400306}
307
Russell King3ae5eae2005-11-09 22:32:44 +0000308static struct platform_driver hdaps_driver = {
Robert Love860e1d62005-08-31 23:57:59 -0400309 .probe = hdaps_probe,
Russell King3ae5eae2005-11-09 22:32:44 +0000310 .resume = hdaps_resume,
311 .driver = {
312 .name = "hdaps",
313 .owner = THIS_MODULE,
314 },
Robert Love860e1d62005-08-31 23:57:59 -0400315};
316
Robert Love393ad292005-09-16 19:28:07 -0700317/*
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700318 * hdaps_calibrate - Set our "resting" values. Callers must hold hdaps_mtx.
Robert Love393ad292005-09-16 19:28:07 -0700319 */
320static void hdaps_calibrate(void)
321{
322 __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &rest_x, &rest_y);
323}
324
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -0400325static void hdaps_mousedev_poll(struct input_polled_dev *dev)
Robert Love393ad292005-09-16 19:28:07 -0700326{
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -0400327 struct input_dev *input_dev = dev->input;
Robert Love393ad292005-09-16 19:28:07 -0700328 int x, y;
329
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -0400330 mutex_lock(&hdaps_mtx);
Robert Love393ad292005-09-16 19:28:07 -0700331
332 if (__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y))
333 goto out;
334
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -0400335 input_report_abs(input_dev, ABS_X, x - rest_x);
336 input_report_abs(input_dev, ABS_Y, y - rest_y);
337 input_sync(input_dev);
Robert Love393ad292005-09-16 19:28:07 -0700338
339out:
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700340 mutex_unlock(&hdaps_mtx);
Robert Love393ad292005-09-16 19:28:07 -0700341}
342
Robert Love860e1d62005-08-31 23:57:59 -0400343
344/* Sysfs Files */
345
346static ssize_t hdaps_position_show(struct device *dev,
347 struct device_attribute *attr, char *buf)
348{
349 int ret, x, y;
350
351 ret = hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
352 if (ret)
353 return ret;
354
355 return sprintf(buf, "(%d,%d)\n", x, y);
356}
357
358static ssize_t hdaps_variance_show(struct device *dev,
359 struct device_attribute *attr, char *buf)
360{
361 int ret, x, y;
362
363 ret = hdaps_read_pair(HDAPS_PORT_XVAR, HDAPS_PORT_YVAR, &x, &y);
364 if (ret)
365 return ret;
366
367 return sprintf(buf, "(%d,%d)\n", x, y);
368}
369
370static ssize_t hdaps_temp1_show(struct device *dev,
371 struct device_attribute *attr, char *buf)
372{
373 u8 temp;
374 int ret;
375
376 ret = hdaps_readb_one(HDAPS_PORT_TEMP1, &temp);
377 if (ret < 0)
378 return ret;
379
380 return sprintf(buf, "%u\n", temp);
381}
382
383static ssize_t hdaps_temp2_show(struct device *dev,
384 struct device_attribute *attr, char *buf)
385{
386 u8 temp;
387 int ret;
388
389 ret = hdaps_readb_one(HDAPS_PORT_TEMP2, &temp);
390 if (ret < 0)
391 return ret;
392
393 return sprintf(buf, "%u\n", temp);
394}
395
396static ssize_t hdaps_keyboard_activity_show(struct device *dev,
397 struct device_attribute *attr,
398 char *buf)
399{
400 return sprintf(buf, "%u\n", KEYBD_ISSET(km_activity));
401}
402
403static ssize_t hdaps_mouse_activity_show(struct device *dev,
404 struct device_attribute *attr,
405 char *buf)
406{
407 return sprintf(buf, "%u\n", MOUSE_ISSET(km_activity));
408}
409
410static ssize_t hdaps_calibrate_show(struct device *dev,
411 struct device_attribute *attr, char *buf)
412{
413 return sprintf(buf, "(%d,%d)\n", rest_x, rest_y);
414}
415
416static ssize_t hdaps_calibrate_store(struct device *dev,
417 struct device_attribute *attr,
418 const char *buf, size_t count)
419{
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700420 mutex_lock(&hdaps_mtx);
Robert Love860e1d62005-08-31 23:57:59 -0400421 hdaps_calibrate();
Matthias Kaehlcke6acc3692007-05-08 00:32:05 -0700422 mutex_unlock(&hdaps_mtx);
Robert Love860e1d62005-08-31 23:57:59 -0400423
424 return count;
425}
426
427static ssize_t hdaps_invert_show(struct device *dev,
428 struct device_attribute *attr, char *buf)
429{
430 return sprintf(buf, "%u\n", hdaps_invert);
431}
432
433static ssize_t hdaps_invert_store(struct device *dev,
434 struct device_attribute *attr,
435 const char *buf, size_t count)
436{
437 int invert;
438
439 if (sscanf(buf, "%d", &invert) != 1 || (invert != 1 && invert != 0))
440 return -EINVAL;
441
442 hdaps_invert = invert;
443 hdaps_calibrate();
444
445 return count;
446}
447
Robert Love860e1d62005-08-31 23:57:59 -0400448static DEVICE_ATTR(position, 0444, hdaps_position_show, NULL);
449static DEVICE_ATTR(variance, 0444, hdaps_variance_show, NULL);
450static DEVICE_ATTR(temp1, 0444, hdaps_temp1_show, NULL);
451static DEVICE_ATTR(temp2, 0444, hdaps_temp2_show, NULL);
452static DEVICE_ATTR(keyboard_activity, 0444, hdaps_keyboard_activity_show, NULL);
453static DEVICE_ATTR(mouse_activity, 0444, hdaps_mouse_activity_show, NULL);
454static DEVICE_ATTR(calibrate, 0644, hdaps_calibrate_show,hdaps_calibrate_store);
455static DEVICE_ATTR(invert, 0644, hdaps_invert_show, hdaps_invert_store);
Robert Love860e1d62005-08-31 23:57:59 -0400456
457static struct attribute *hdaps_attributes[] = {
458 &dev_attr_position.attr,
459 &dev_attr_variance.attr,
460 &dev_attr_temp1.attr,
461 &dev_attr_temp2.attr,
462 &dev_attr_keyboard_activity.attr,
463 &dev_attr_mouse_activity.attr,
464 &dev_attr_calibrate.attr,
Robert Love860e1d62005-08-31 23:57:59 -0400465 &dev_attr_invert.attr,
466 NULL,
467};
468
469static struct attribute_group hdaps_attribute_group = {
470 .attrs = hdaps_attributes,
471};
472
473
474/* Module stuff */
475
Robert Love4c87b742005-09-22 21:44:00 -0700476/* hdaps_dmi_match - found a match. return one, short-circuiting the hunt. */
Jeff Garzik18552562007-10-03 15:15:40 -0400477static int __init hdaps_dmi_match(const struct dmi_system_id *id)
Robert Love860e1d62005-08-31 23:57:59 -0400478{
479 printk(KERN_INFO "hdaps: %s detected.\n", id->ident);
Robert Love4c87b742005-09-22 21:44:00 -0700480 return 1;
Robert Love860e1d62005-08-31 23:57:59 -0400481}
482
Robert Love4c87b742005-09-22 21:44:00 -0700483/* hdaps_dmi_match_invert - found an inverted match. */
Jeff Garzik18552562007-10-03 15:15:40 -0400484static int __init hdaps_dmi_match_invert(const struct dmi_system_id *id)
Robert Love860e1d62005-08-31 23:57:59 -0400485{
486 hdaps_invert = 1;
487 printk(KERN_INFO "hdaps: inverting axis readings.\n");
Robert Love4c87b742005-09-22 21:44:00 -0700488 return hdaps_dmi_match(id);
Robert Love860e1d62005-08-31 23:57:59 -0400489}
490
Jean Delvare509a5e82006-12-12 18:18:28 +0100491#define HDAPS_DMI_MATCH_NORMAL(vendor, model) { \
492 .ident = vendor " " model, \
Robert Love860e1d62005-08-31 23:57:59 -0400493 .callback = hdaps_dmi_match, \
494 .matches = { \
Jean Delvare509a5e82006-12-12 18:18:28 +0100495 DMI_MATCH(DMI_BOARD_VENDOR, vendor), \
Robert Love860e1d62005-08-31 23:57:59 -0400496 DMI_MATCH(DMI_PRODUCT_VERSION, model) \
497 } \
498}
499
Jean Delvare509a5e82006-12-12 18:18:28 +0100500#define HDAPS_DMI_MATCH_INVERT(vendor, model) { \
501 .ident = vendor " " model, \
Robert Love860e1d62005-08-31 23:57:59 -0400502 .callback = hdaps_dmi_match_invert, \
503 .matches = { \
Jean Delvare509a5e82006-12-12 18:18:28 +0100504 DMI_MATCH(DMI_BOARD_VENDOR, vendor), \
Robert Love860e1d62005-08-31 23:57:59 -0400505 DMI_MATCH(DMI_PRODUCT_VERSION, model) \
506 } \
507}
508
Jean Delvare509a5e82006-12-12 18:18:28 +0100509/* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
Stephan Berberig0f23e502006-12-12 18:18:28 +0100510 "ThinkPad T42p", so the order of the entries matters.
511 If your ThinkPad is not recognized, please update to latest
512 BIOS. This is especially the case for some R52 ThinkPads. */
Jean Delvare509a5e82006-12-12 18:18:28 +0100513static struct dmi_system_id __initdata hdaps_whitelist[] = {
Jean Delvare509a5e82006-12-12 18:18:28 +0100514 HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p"),
515 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R50"),
516 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R51"),
517 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R52"),
Gabor Czigolaca68d0a2008-05-23 13:04:23 -0700518 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61i"),
Jean Delvare509a5e82006-12-12 18:18:28 +0100519 HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p"),
520 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T41"),
521 HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p"),
522 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T42"),
523 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T43"),
524 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60"),
525 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X40"),
526 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X41"),
527 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60"),
528 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad Z60m"),
529 { .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)) {
538 printk(KERN_WARNING "hdaps: 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 King3ae5eae2005-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";
577 idev->dev.parent = &pdev->dev;
Jiri Slaby7b19ada2007-10-18 23:40:32 -0700578 idev->evbit[0] = BIT_MASK(EV_ABS);
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -0400579 input_set_abs_params(idev, ABS_X,
Dmitry Torokhovd12eb7e2005-11-10 22:10:55 -0500580 -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -0400581 input_set_abs_params(idev, ABS_Y,
Dmitry Torokhovd12eb7e2005-11-10 22:10:55 -0500582 -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
583
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -0400584 ret = input_register_polled_device(hdaps_idev);
Dmitry Torokhovb25a1062006-08-28 14:21:42 +0200585 if (ret)
586 goto out_idev;
Robert Love393ad292005-09-16 19:28:07 -0700587
Robert Love860e1d62005-08-31 23:57:59 -0400588 printk(KERN_INFO "hdaps: driver successfully loaded.\n");
589 return 0;
590
Dmitry Torokhovb25a1062006-08-28 14:21:42 +0200591out_idev:
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -0400592 input_free_polled_device(hdaps_idev);
Dmitry Torokhovd12eb7e2005-11-10 22:10:55 -0500593out_group:
594 sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
Robert Love860e1d62005-08-31 23:57:59 -0400595out_device:
596 platform_device_unregister(pdev);
597out_driver:
Russell King3ae5eae2005-11-09 22:32:44 +0000598 platform_driver_unregister(&hdaps_driver);
Robert Love860e1d62005-08-31 23:57:59 -0400599out_region:
600 release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
601out:
602 printk(KERN_WARNING "hdaps: driver init failed (ret=%d)!\n", ret);
603 return ret;
604}
605
606static void __exit hdaps_exit(void)
607{
Dmitry Torokhovaefca8b2007-10-12 21:30:36 -0400608 input_unregister_polled_device(hdaps_idev);
609 input_free_polled_device(hdaps_idev);
Robert Love860e1d62005-08-31 23:57:59 -0400610 sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
611 platform_device_unregister(pdev);
Russell King3ae5eae2005-11-09 22:32:44 +0000612 platform_driver_unregister(&hdaps_driver);
Robert Love860e1d62005-08-31 23:57:59 -0400613 release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
614
615 printk(KERN_INFO "hdaps: driver unloaded.\n");
616}
617
618module_init(hdaps_init);
619module_exit(hdaps_exit);
620
Robert Love860e1d62005-08-31 23:57:59 -0400621module_param_named(invert, hdaps_invert, bool, 0);
622MODULE_PARM_DESC(invert, "invert data along each axis");
623
624MODULE_AUTHOR("Robert Love");
625MODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver");
626MODULE_LICENSE("GPL v2");