xref: /linux/drivers/platform/x86/hdaps.c (revision 92fd4d4d67b945c0766416284d4ab236b31542c4)
1 /*
2  * 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  *
7  * 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.
10  *
11  * This driver is based on the document by Mark A. Smith available at
12  * 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>
30 #include <linux/platform_device.h>
31 #include <linux/input-polldev.h>
32 #include <linux/kernel.h>
33 #include <linux/mutex.h>
34 #include <linux/module.h>
35 #include <linux/timer.h>
36 #include <linux/dmi.h>
37 #include <linux/jiffies.h>
38 #include <linux/io.h>
39 
40 #define HDAPS_LOW_PORT		0x1600	/* first port used by hdaps */
41 #define HDAPS_NR_PORTS		0x30	/* number of ports: 0x1600 - 0x162f */
42 
43 #define HDAPS_PORT_STATE	0x1611	/* device state */
44 #define HDAPS_PORT_YPOS		0x1612	/* y-axis position */
45 #define	HDAPS_PORT_XPOS		0x1614	/* x-axis position */
46 #define HDAPS_PORT_TEMP1	0x1616	/* device temperature, in Celsius */
47 #define HDAPS_PORT_YVAR		0x1617	/* y-axis variance (what is this?) */
48 #define HDAPS_PORT_XVAR		0x1619	/* x-axis variance (what is this?) */
49 #define HDAPS_PORT_TEMP2	0x161b	/* device temperature (again?) */
50 #define HDAPS_PORT_UNKNOWN	0x161c	/* what is this? */
51 #define HDAPS_PORT_KMACT	0x161d	/* keyboard or mouse activity */
52 
53 #define STATE_FRESH		0x50	/* accelerometer data is fresh */
54 
55 #define KEYBD_MASK		0x20	/* set if keyboard activity */
56 #define MOUSE_MASK		0x40	/* set if mouse activity */
57 #define KEYBD_ISSET(n)		(!! (n & KEYBD_MASK))	/* keyboard used? */
58 #define MOUSE_ISSET(n)		(!! (n & MOUSE_MASK))	/* mouse used? */
59 
60 #define INIT_TIMEOUT_MSECS	4000	/* wait up to 4s for device init ... */
61 #define INIT_WAIT_MSECS		200	/* ... in 200ms increments */
62 
63 #define HDAPS_POLL_INTERVAL	50	/* poll for input every 1/20s (50 ms)*/
64 #define HDAPS_INPUT_FUZZ	4	/* input event threshold */
65 #define HDAPS_INPUT_FLAT	4
66 
67 #define HDAPS_X_AXIS		(1 << 0)
68 #define HDAPS_Y_AXIS		(1 << 1)
69 #define HDAPS_BOTH_AXES		(HDAPS_X_AXIS | HDAPS_Y_AXIS)
70 
71 static struct platform_device *pdev;
72 static struct input_polled_dev *hdaps_idev;
73 static unsigned int hdaps_invert;
74 static u8 km_activity;
75 static int rest_x;
76 static int rest_y;
77 
78 static DEFINE_MUTEX(hdaps_mtx);
79 
80 /*
81  * __get_latch - Get the value from a given port.  Callers must hold hdaps_mtx.
82  */
83 static inline u8 __get_latch(u16 port)
84 {
85 	return inb(port) & 0xff;
86 }
87 
88 /*
89  * __check_latch - Check a port latch for a given value.  Returns zero if the
90  * port contains the given value.  Callers must hold hdaps_mtx.
91  */
92 static inline int __check_latch(u16 port, u8 val)
93 {
94 	if (__get_latch(port) == val)
95 		return 0;
96 	return -EINVAL;
97 }
98 
99 /*
100  * __wait_latch - Wait up to 100us for a port latch to get a certain value,
101  * returning zero if the value is obtained.  Callers must hold hdaps_mtx.
102  */
103 static int __wait_latch(u16 port, u8 val)
104 {
105 	unsigned int i;
106 
107 	for (i = 0; i < 20; i++) {
108 		if (!__check_latch(port, val))
109 			return 0;
110 		udelay(5);
111 	}
112 
113 	return -EIO;
114 }
115 
116 /*
117  * __device_refresh - request a refresh from the accelerometer.  Does not wait
118  * for refresh to complete.  Callers must hold hdaps_mtx.
119  */
120 static void __device_refresh(void)
121 {
122 	udelay(200);
123 	if (inb(0x1604) != STATE_FRESH) {
124 		outb(0x11, 0x1610);
125 		outb(0x01, 0x161f);
126 	}
127 }
128 
129 /*
130  * __device_refresh_sync - request a synchronous refresh from the
131  * accelerometer.  We wait for the refresh to complete.  Returns zero if
132  * successful and nonzero on error.  Callers must hold hdaps_mtx.
133  */
134 static int __device_refresh_sync(void)
135 {
136 	__device_refresh();
137 	return __wait_latch(0x1604, STATE_FRESH);
138 }
139 
140 /*
141  * __device_complete - indicate to the accelerometer that we are done reading
142  * data, and then initiate an async refresh.  Callers must hold hdaps_mtx.
143  */
144 static inline void __device_complete(void)
145 {
146 	inb(0x161f);
147 	inb(0x1604);
148 	__device_refresh();
149 }
150 
151 /*
152  * hdaps_readb_one - reads a byte from a single I/O port, placing the value in
153  * the given pointer.  Returns zero on success or a negative error on failure.
154  * Can sleep.
155  */
156 static int hdaps_readb_one(unsigned int port, u8 *val)
157 {
158 	int ret;
159 
160 	mutex_lock(&hdaps_mtx);
161 
162 	/* do a sync refresh -- we need to be sure that we read fresh data */
163 	ret = __device_refresh_sync();
164 	if (ret)
165 		goto out;
166 
167 	*val = inb(port);
168 	__device_complete();
169 
170 out:
171 	mutex_unlock(&hdaps_mtx);
172 	return ret;
173 }
174 
175 /* __hdaps_read_pair - internal lockless helper for hdaps_read_pair(). */
176 static int __hdaps_read_pair(unsigned int port1, unsigned int port2,
177 			     int *x, int *y)
178 {
179 	/* do a sync refresh -- we need to be sure that we read fresh data */
180 	if (__device_refresh_sync())
181 		return -EIO;
182 
183 	*y = inw(port2);
184 	*x = inw(port1);
185 	km_activity = inb(HDAPS_PORT_KMACT);
186 	__device_complete();
187 
188 	/* hdaps_invert is a bitvector to negate the axes */
189 	if (hdaps_invert & HDAPS_X_AXIS)
190 		*x = -*x;
191 	if (hdaps_invert & HDAPS_Y_AXIS)
192 		*y = -*y;
193 
194 	return 0;
195 }
196 
197 /*
198  * hdaps_read_pair - reads the values from a pair of ports, placing the values
199  * in the given pointers.  Returns zero on success.  Can sleep.
200  */
201 static int hdaps_read_pair(unsigned int port1, unsigned int port2,
202 			   int *val1, int *val2)
203 {
204 	int ret;
205 
206 	mutex_lock(&hdaps_mtx);
207 	ret = __hdaps_read_pair(port1, port2, val1, val2);
208 	mutex_unlock(&hdaps_mtx);
209 
210 	return ret;
211 }
212 
213 /*
214  * hdaps_device_init - initialize the accelerometer.  Returns zero on success
215  * and negative error code on failure.  Can sleep.
216  */
217 static int hdaps_device_init(void)
218 {
219 	int total, ret = -ENXIO;
220 
221 	mutex_lock(&hdaps_mtx);
222 
223 	outb(0x13, 0x1610);
224 	outb(0x01, 0x161f);
225 	if (__wait_latch(0x161f, 0x00))
226 		goto out;
227 
228 	/*
229 	 * Most ThinkPads return 0x01.
230 	 *
231 	 * Others--namely the R50p, T41p, and T42p--return 0x03.  These laptops
232 	 * have "inverted" axises.
233 	 *
234 	 * The 0x02 value occurs when the chip has been previously initialized.
235 	 */
236 	if (__check_latch(0x1611, 0x03) &&
237 		     __check_latch(0x1611, 0x02) &&
238 		     __check_latch(0x1611, 0x01))
239 		goto out;
240 
241 	printk(KERN_DEBUG "hdaps: initial latch check good (0x%02x).\n",
242 	       __get_latch(0x1611));
243 
244 	outb(0x17, 0x1610);
245 	outb(0x81, 0x1611);
246 	outb(0x01, 0x161f);
247 	if (__wait_latch(0x161f, 0x00))
248 		goto out;
249 	if (__wait_latch(0x1611, 0x00))
250 		goto out;
251 	if (__wait_latch(0x1612, 0x60))
252 		goto out;
253 	if (__wait_latch(0x1613, 0x00))
254 		goto out;
255 	outb(0x14, 0x1610);
256 	outb(0x01, 0x1611);
257 	outb(0x01, 0x161f);
258 	if (__wait_latch(0x161f, 0x00))
259 		goto out;
260 	outb(0x10, 0x1610);
261 	outb(0xc8, 0x1611);
262 	outb(0x00, 0x1612);
263 	outb(0x02, 0x1613);
264 	outb(0x01, 0x161f);
265 	if (__wait_latch(0x161f, 0x00))
266 		goto out;
267 	if (__device_refresh_sync())
268 		goto out;
269 	if (__wait_latch(0x1611, 0x00))
270 		goto out;
271 
272 	/* we have done our dance, now let's wait for the applause */
273 	for (total = INIT_TIMEOUT_MSECS; total > 0; total -= INIT_WAIT_MSECS) {
274 		int x, y;
275 
276 		/* a read of the device helps push it into action */
277 		__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
278 		if (!__wait_latch(0x1611, 0x02)) {
279 			ret = 0;
280 			break;
281 		}
282 
283 		msleep(INIT_WAIT_MSECS);
284 	}
285 
286 out:
287 	mutex_unlock(&hdaps_mtx);
288 	return ret;
289 }
290 
291 
292 /* Device model stuff */
293 
294 static int hdaps_probe(struct platform_device *dev)
295 {
296 	int ret;
297 
298 	ret = hdaps_device_init();
299 	if (ret)
300 		return ret;
301 
302 	printk(KERN_INFO "hdaps: device successfully initialized.\n");
303 	return 0;
304 }
305 
306 static int hdaps_resume(struct platform_device *dev)
307 {
308 	return hdaps_device_init();
309 }
310 
311 static struct platform_driver hdaps_driver = {
312 	.probe = hdaps_probe,
313 	.resume = hdaps_resume,
314 	.driver	= {
315 		.name = "hdaps",
316 		.owner = THIS_MODULE,
317 	},
318 };
319 
320 /*
321  * hdaps_calibrate - Set our "resting" values.  Callers must hold hdaps_mtx.
322  */
323 static void hdaps_calibrate(void)
324 {
325 	__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &rest_x, &rest_y);
326 }
327 
328 static void hdaps_mousedev_poll(struct input_polled_dev *dev)
329 {
330 	struct input_dev *input_dev = dev->input;
331 	int x, y;
332 
333 	mutex_lock(&hdaps_mtx);
334 
335 	if (__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y))
336 		goto out;
337 
338 	input_report_abs(input_dev, ABS_X, x - rest_x);
339 	input_report_abs(input_dev, ABS_Y, y - rest_y);
340 	input_sync(input_dev);
341 
342 out:
343 	mutex_unlock(&hdaps_mtx);
344 }
345 
346 
347 /* Sysfs Files */
348 
349 static ssize_t hdaps_position_show(struct device *dev,
350 				   struct device_attribute *attr, char *buf)
351 {
352 	int ret, x, y;
353 
354 	ret = hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
355 	if (ret)
356 		return ret;
357 
358 	return sprintf(buf, "(%d,%d)\n", x, y);
359 }
360 
361 static ssize_t hdaps_variance_show(struct device *dev,
362 				   struct device_attribute *attr, char *buf)
363 {
364 	int ret, x, y;
365 
366 	ret = hdaps_read_pair(HDAPS_PORT_XVAR, HDAPS_PORT_YVAR, &x, &y);
367 	if (ret)
368 		return ret;
369 
370 	return sprintf(buf, "(%d,%d)\n", x, y);
371 }
372 
373 static ssize_t hdaps_temp1_show(struct device *dev,
374 				struct device_attribute *attr, char *buf)
375 {
376 	u8 temp;
377 	int ret;
378 
379 	ret = hdaps_readb_one(HDAPS_PORT_TEMP1, &temp);
380 	if (ret < 0)
381 		return ret;
382 
383 	return sprintf(buf, "%u\n", temp);
384 }
385 
386 static ssize_t hdaps_temp2_show(struct device *dev,
387 				struct device_attribute *attr, char *buf)
388 {
389 	u8 temp;
390 	int ret;
391 
392 	ret = hdaps_readb_one(HDAPS_PORT_TEMP2, &temp);
393 	if (ret < 0)
394 		return ret;
395 
396 	return sprintf(buf, "%u\n", temp);
397 }
398 
399 static ssize_t hdaps_keyboard_activity_show(struct device *dev,
400 					    struct device_attribute *attr,
401 					    char *buf)
402 {
403 	return sprintf(buf, "%u\n", KEYBD_ISSET(km_activity));
404 }
405 
406 static ssize_t hdaps_mouse_activity_show(struct device *dev,
407 					 struct device_attribute *attr,
408 					 char *buf)
409 {
410 	return sprintf(buf, "%u\n", MOUSE_ISSET(km_activity));
411 }
412 
413 static ssize_t hdaps_calibrate_show(struct device *dev,
414 				    struct device_attribute *attr, char *buf)
415 {
416 	return sprintf(buf, "(%d,%d)\n", rest_x, rest_y);
417 }
418 
419 static ssize_t hdaps_calibrate_store(struct device *dev,
420 				     struct device_attribute *attr,
421 				     const char *buf, size_t count)
422 {
423 	mutex_lock(&hdaps_mtx);
424 	hdaps_calibrate();
425 	mutex_unlock(&hdaps_mtx);
426 
427 	return count;
428 }
429 
430 static ssize_t hdaps_invert_show(struct device *dev,
431 				 struct device_attribute *attr, char *buf)
432 {
433 	return sprintf(buf, "%u\n", hdaps_invert);
434 }
435 
436 static ssize_t hdaps_invert_store(struct device *dev,
437 				  struct device_attribute *attr,
438 				  const char *buf, size_t count)
439 {
440 	int invert;
441 
442 	if (sscanf(buf, "%d", &invert) != 1 ||
443 	    invert < 0 || invert > HDAPS_BOTH_AXES)
444 		return -EINVAL;
445 
446 	hdaps_invert = invert;
447 	hdaps_calibrate();
448 
449 	return count;
450 }
451 
452 static DEVICE_ATTR(position, 0444, hdaps_position_show, NULL);
453 static DEVICE_ATTR(variance, 0444, hdaps_variance_show, NULL);
454 static DEVICE_ATTR(temp1, 0444, hdaps_temp1_show, NULL);
455 static DEVICE_ATTR(temp2, 0444, hdaps_temp2_show, NULL);
456 static DEVICE_ATTR(keyboard_activity, 0444, hdaps_keyboard_activity_show, NULL);
457 static DEVICE_ATTR(mouse_activity, 0444, hdaps_mouse_activity_show, NULL);
458 static DEVICE_ATTR(calibrate, 0644, hdaps_calibrate_show,hdaps_calibrate_store);
459 static DEVICE_ATTR(invert, 0644, hdaps_invert_show, hdaps_invert_store);
460 
461 static struct attribute *hdaps_attributes[] = {
462 	&dev_attr_position.attr,
463 	&dev_attr_variance.attr,
464 	&dev_attr_temp1.attr,
465 	&dev_attr_temp2.attr,
466 	&dev_attr_keyboard_activity.attr,
467 	&dev_attr_mouse_activity.attr,
468 	&dev_attr_calibrate.attr,
469 	&dev_attr_invert.attr,
470 	NULL,
471 };
472 
473 static struct attribute_group hdaps_attribute_group = {
474 	.attrs = hdaps_attributes,
475 };
476 
477 
478 /* Module stuff */
479 
480 /* hdaps_dmi_match - found a match.  return one, short-circuiting the hunt. */
481 static int __init hdaps_dmi_match(const struct dmi_system_id *id)
482 {
483 	printk(KERN_INFO "hdaps: %s detected.\n", id->ident);
484 	return 1;
485 }
486 
487 /* hdaps_dmi_match_invert - found an inverted match. */
488 static int __init hdaps_dmi_match_invert(const struct dmi_system_id *id)
489 {
490 	hdaps_invert = (unsigned long)id->driver_data;
491 	printk(KERN_INFO "hdaps: inverting axis (%u) readings.\n",
492 	       hdaps_invert);
493 	return hdaps_dmi_match(id);
494 }
495 
496 #define HDAPS_DMI_MATCH_INVERT(vendor, model, axes) {	\
497 	.ident = vendor " " model,			\
498 	.callback = hdaps_dmi_match_invert,		\
499 	.driver_data = (void *)axes,			\
500 	.matches = {					\
501 		DMI_MATCH(DMI_BOARD_VENDOR, vendor),	\
502 		DMI_MATCH(DMI_PRODUCT_VERSION, model)	\
503 	}						\
504 }
505 
506 #define HDAPS_DMI_MATCH_NORMAL(vendor, model)		\
507 	HDAPS_DMI_MATCH_INVERT(vendor, model, 0)
508 
509 /* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
510    "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. */
513 static struct dmi_system_id __initdata hdaps_whitelist[] = {
514 	HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p", HDAPS_BOTH_AXES),
515 	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R50"),
516 	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R51"),
517 	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R52"),
518 	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61i", HDAPS_BOTH_AXES),
519 	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61", HDAPS_BOTH_AXES),
520 	HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p", HDAPS_BOTH_AXES),
521 	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T41"),
522 	HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p", HDAPS_BOTH_AXES),
523 	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T42"),
524 	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T43"),
525 	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400", HDAPS_BOTH_AXES),
526 	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60", HDAPS_BOTH_AXES),
527 	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61p", HDAPS_BOTH_AXES),
528 	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61", HDAPS_BOTH_AXES),
529 	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X40"),
530 	HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X41", HDAPS_Y_AXIS),
531 	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60", HDAPS_BOTH_AXES),
532 	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61s", HDAPS_BOTH_AXES),
533 	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61", HDAPS_BOTH_AXES),
534 	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad Z60m"),
535 	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61m", HDAPS_BOTH_AXES),
536 	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61p", HDAPS_BOTH_AXES),
537 	{ .ident = NULL }
538 };
539 
540 static int __init hdaps_init(void)
541 {
542 	struct input_dev *idev;
543 	int ret;
544 
545 	if (!dmi_check_system(hdaps_whitelist)) {
546 		printk(KERN_WARNING "hdaps: supported laptop not found!\n");
547 		ret = -ENODEV;
548 		goto out;
549 	}
550 
551 	if (!request_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS, "hdaps")) {
552 		ret = -ENXIO;
553 		goto out;
554 	}
555 
556 	ret = platform_driver_register(&hdaps_driver);
557 	if (ret)
558 		goto out_region;
559 
560 	pdev = platform_device_register_simple("hdaps", -1, NULL, 0);
561 	if (IS_ERR(pdev)) {
562 		ret = PTR_ERR(pdev);
563 		goto out_driver;
564 	}
565 
566 	ret = sysfs_create_group(&pdev->dev.kobj, &hdaps_attribute_group);
567 	if (ret)
568 		goto out_device;
569 
570 	hdaps_idev = input_allocate_polled_device();
571 	if (!hdaps_idev) {
572 		ret = -ENOMEM;
573 		goto out_group;
574 	}
575 
576 	hdaps_idev->poll = hdaps_mousedev_poll;
577 	hdaps_idev->poll_interval = HDAPS_POLL_INTERVAL;
578 
579 	/* initial calibrate for the input device */
580 	hdaps_calibrate();
581 
582 	/* initialize the input class */
583 	idev = hdaps_idev->input;
584 	idev->name = "hdaps";
585 	idev->phys = "isa1600/input0";
586 	idev->id.bustype = BUS_ISA;
587 	idev->dev.parent = &pdev->dev;
588 	idev->evbit[0] = BIT_MASK(EV_ABS);
589 	input_set_abs_params(idev, ABS_X,
590 			-256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
591 	input_set_abs_params(idev, ABS_Y,
592 			-256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
593 
594 	ret = input_register_polled_device(hdaps_idev);
595 	if (ret)
596 		goto out_idev;
597 
598 	printk(KERN_INFO "hdaps: driver successfully loaded.\n");
599 	return 0;
600 
601 out_idev:
602 	input_free_polled_device(hdaps_idev);
603 out_group:
604 	sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
605 out_device:
606 	platform_device_unregister(pdev);
607 out_driver:
608 	platform_driver_unregister(&hdaps_driver);
609 out_region:
610 	release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
611 out:
612 	printk(KERN_WARNING "hdaps: driver init failed (ret=%d)!\n", ret);
613 	return ret;
614 }
615 
616 static void __exit hdaps_exit(void)
617 {
618 	input_unregister_polled_device(hdaps_idev);
619 	input_free_polled_device(hdaps_idev);
620 	sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
621 	platform_device_unregister(pdev);
622 	platform_driver_unregister(&hdaps_driver);
623 	release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
624 
625 	printk(KERN_INFO "hdaps: driver unloaded.\n");
626 }
627 
628 module_init(hdaps_init);
629 module_exit(hdaps_exit);
630 
631 module_param_named(invert, hdaps_invert, int, 0);
632 MODULE_PARM_DESC(invert, "invert data along each axis. 1 invert x-axis, "
633 		 "2 invert y-axis, 3 invert both axes.");
634 
635 MODULE_AUTHOR("Robert Love");
636 MODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver");
637 MODULE_LICENSE("GPL v2");
638