Merge branches 'pxa-ian' and 'pxa-xm270' into pxa
[GitHub/mt8127/android_kernel_alcatel_ttab.git] / drivers / hwmon / hdaps.c
CommitLineData
860e1d6b
RL
1/*
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 *
4c87b74c
RL
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.
860e1d6b 10 *
393ad299 11 * This driver is based on the document by Mark A. Smith available at
860e1d6b
RL
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>
d052d1be 30#include <linux/platform_device.h>
aefca8ba 31#include <linux/input-polldev.h>
860e1d6b 32#include <linux/kernel.h>
6acc369a 33#include <linux/mutex.h>
860e1d6b
RL
34#include <linux/module.h>
35#include <linux/timer.h>
36#include <linux/dmi.h>
914e2637 37#include <linux/jiffies.h>
6acc369a 38
860e1d6b
RL
39#include <asm/io.h>
40
41#define HDAPS_LOW_PORT 0x1600 /* first port used by hdaps */
393ad299 42#define HDAPS_NR_PORTS 0x30 /* number of ports: 0x1600 - 0x162f */
860e1d6b
RL
43
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 */
0a704681 47#define HDAPS_PORT_TEMP1 0x1616 /* device temperature, in Celsius */
860e1d6b
RL
48#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
393ad299 54#define STATE_FRESH 0x50 /* accelerometer data is fresh */
860e1d6b
RL
55
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
aefca8ba 64#define HDAPS_POLL_INTERVAL 50 /* poll for input every 1/20s (50 ms)*/
393ad299 65#define HDAPS_INPUT_FUZZ 4 /* input event threshold */
d12eb7e1 66#define HDAPS_INPUT_FLAT 4
393ad299 67
393ad299 68static struct platform_device *pdev;
aefca8ba 69static struct input_polled_dev *hdaps_idev;
860e1d6b
RL
70static unsigned int hdaps_invert;
71static u8 km_activity;
72static int rest_x;
73static int rest_y;
74
6acc369a 75static DEFINE_MUTEX(hdaps_mtx);
860e1d6b
RL
76
77/*
6acc369a 78 * __get_latch - Get the value from a given port. Callers must hold hdaps_mtx.
860e1d6b
RL
79 */
80static inline u8 __get_latch(u16 port)
81{
393ad299 82 return inb(port) & 0xff;
860e1d6b
RL
83}
84
85/*
393ad299 86 * __check_latch - Check a port latch for a given value. Returns zero if the
6acc369a 87 * port contains the given value. Callers must hold hdaps_mtx.
860e1d6b 88 */
393ad299 89static inline int __check_latch(u16 port, u8 val)
860e1d6b
RL
90{
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,
6acc369a 98 * returning zero if the value is obtained. Callers must hold hdaps_mtx.
860e1d6b 99 */
393ad299 100static int __wait_latch(u16 port, u8 val)
860e1d6b
RL
101{
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
393ad299 110 return -EIO;
860e1d6b
RL
111}
112
113/*
393ad299 114 * __device_refresh - request a refresh from the accelerometer. Does not wait
6acc369a 115 * for refresh to complete. Callers must hold hdaps_mtx.
860e1d6b 116 */
393ad299 117static void __device_refresh(void)
860e1d6b 118{
393ad299
RL
119 udelay(200);
120 if (inb(0x1604) != STATE_FRESH) {
121 outb(0x11, 0x1610);
122 outb(0x01, 0x161f);
123 }
124}
860e1d6b 125
393ad299
RL
126/*
127 * __device_refresh_sync - request a synchronous refresh from the
128 * accelerometer. We wait for the refresh to complete. Returns zero if
6acc369a 129 * successful and nonzero on error. Callers must hold hdaps_mtx.
393ad299
RL
130 */
131static int __device_refresh_sync(void)
132{
133 __device_refresh();
860e1d6b
RL
134 return __wait_latch(0x1604, STATE_FRESH);
135}
136
137/*
393ad299 138 * __device_complete - indicate to the accelerometer that we are done reading
6acc369a 139 * data, and then initiate an async refresh. Callers must hold hdaps_mtx.
860e1d6b
RL
140 */
141static inline void __device_complete(void)
142{
143 inb(0x161f);
144 inb(0x1604);
393ad299 145 __device_refresh();
860e1d6b
RL
146}
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
6acc369a 157 mutex_lock(&hdaps_mtx);
860e1d6b 158
393ad299
RL
159 /* 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:
6acc369a 168 mutex_unlock(&hdaps_mtx);
860e1d6b
RL
169 return ret;
170}
171
393ad299 172/* __hdaps_read_pair - internal lockless helper for hdaps_read_pair(). */
860e1d6b
RL
173static 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 */
393ad299 177 if (__device_refresh_sync())
860e1d6b
RL
178 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
6acc369a 203 mutex_lock(&hdaps_mtx);
860e1d6b 204 ret = __hdaps_read_pair(port1, port2, val1, val2);
6acc369a 205 mutex_unlock(&hdaps_mtx);
860e1d6b
RL
206
207 return ret;
208}
209
393ad299
RL
210/*
211 * hdaps_device_init - initialize the accelerometer. Returns zero on success
212 * and negative error code on failure. Can sleep.
213 */
860e1d6b
RL
214static int hdaps_device_init(void)
215{
393ad299 216 int total, ret = -ENXIO;
860e1d6b 217
6acc369a 218 mutex_lock(&hdaps_mtx);
860e1d6b
RL
219
220 outb(0x13, 0x1610);
221 outb(0x01, 0x161f);
222 if (__wait_latch(0x161f, 0x00))
223 goto out;
224
225 /*
393ad299
RL
226 * Most ThinkPads return 0x01.
227 *
228 * Others--namely the R50p, T41p, and T42p--return 0x03. These laptops
229 * have "inverted" axises.
860e1d6b
RL
230 *
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;
393ad299 264 if (__device_refresh_sync())
860e1d6b
RL
265 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 */
393ad299
RL
270 for (total = INIT_TIMEOUT_MSECS; total > 0; total -= INIT_WAIT_MSECS) {
271 int x, y;
860e1d6b
RL
272
273 /* a read of the device helps push it into action */
393ad299 274 __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
860e1d6b
RL
275 if (!__wait_latch(0x1611, 0x02)) {
276 ret = 0;
277 break;
278 }
279
280 msleep(INIT_WAIT_MSECS);
860e1d6b
RL
281 }
282
283out:
6acc369a 284 mutex_unlock(&hdaps_mtx);
860e1d6b
RL
285 return ret;
286}
287
288
860e1d6b
RL
289/* Device model stuff */
290
3ae5eaec 291static int hdaps_probe(struct platform_device *dev)
860e1d6b
RL
292{
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
3ae5eaec 303static int hdaps_resume(struct platform_device *dev)
860e1d6b 304{
9480e307 305 return hdaps_device_init();
860e1d6b
RL
306}
307
3ae5eaec 308static struct platform_driver hdaps_driver = {
860e1d6b 309 .probe = hdaps_probe,
3ae5eaec
RK
310 .resume = hdaps_resume,
311 .driver = {
312 .name = "hdaps",
313 .owner = THIS_MODULE,
314 },
860e1d6b
RL
315};
316
393ad299 317/*
6acc369a 318 * hdaps_calibrate - Set our "resting" values. Callers must hold hdaps_mtx.
393ad299
RL
319 */
320static void hdaps_calibrate(void)
321{
322 __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &rest_x, &rest_y);
323}
324
aefca8ba 325static void hdaps_mousedev_poll(struct input_polled_dev *dev)
393ad299 326{
aefca8ba 327 struct input_dev *input_dev = dev->input;
393ad299
RL
328 int x, y;
329
aefca8ba 330 mutex_lock(&hdaps_mtx);
393ad299
RL
331
332 if (__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y))
333 goto out;
334
aefca8ba
DT
335 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);
393ad299
RL
338
339out:
6acc369a 340 mutex_unlock(&hdaps_mtx);
393ad299
RL
341}
342
860e1d6b
RL
343
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{
6acc369a 420 mutex_lock(&hdaps_mtx);
860e1d6b 421 hdaps_calibrate();
6acc369a 422 mutex_unlock(&hdaps_mtx);
860e1d6b
RL
423
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
860e1d6b
RL
448static 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);
860e1d6b
RL
456
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,
860e1d6b
RL
465 &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
4c87b74c 476/* hdaps_dmi_match - found a match. return one, short-circuiting the hunt. */
1855256c 477static int __init hdaps_dmi_match(const struct dmi_system_id *id)
860e1d6b
RL
478{
479 printk(KERN_INFO "hdaps: %s detected.\n", id->ident);
4c87b74c 480 return 1;
860e1d6b
RL
481}
482
4c87b74c 483/* hdaps_dmi_match_invert - found an inverted match. */
1855256c 484static int __init hdaps_dmi_match_invert(const struct dmi_system_id *id)
860e1d6b
RL
485{
486 hdaps_invert = 1;
487 printk(KERN_INFO "hdaps: inverting axis readings.\n");
4c87b74c 488 return hdaps_dmi_match(id);
860e1d6b
RL
489}
490
509a5e85
JD
491#define HDAPS_DMI_MATCH_NORMAL(vendor, model) { \
492 .ident = vendor " " model, \
860e1d6b
RL
493 .callback = hdaps_dmi_match, \
494 .matches = { \
509a5e85 495 DMI_MATCH(DMI_BOARD_VENDOR, vendor), \
860e1d6b
RL
496 DMI_MATCH(DMI_PRODUCT_VERSION, model) \
497 } \
498}
499
509a5e85
JD
500#define HDAPS_DMI_MATCH_INVERT(vendor, model) { \
501 .ident = vendor " " model, \
860e1d6b
RL
502 .callback = hdaps_dmi_match_invert, \
503 .matches = { \
509a5e85 504 DMI_MATCH(DMI_BOARD_VENDOR, vendor), \
860e1d6b
RL
505 DMI_MATCH(DMI_PRODUCT_VERSION, model) \
506 } \
507}
508
509a5e85 509/* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
0f23e50a
SB
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. */
509a5e85 513static struct dmi_system_id __initdata hdaps_whitelist[] = {
509a5e85
JD
514 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"),
ca68d0ac 518 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61i"),
509a5e85
JD
519 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"),
a4fa7ef0 525 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61"),
509a5e85
JD
526 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X40"),
527 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X41"),
528 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60"),
529 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad Z60m"),
530 { .ident = NULL }
531};
0f6c840d 532
860e1d6b
RL
533static int __init hdaps_init(void)
534{
aefca8ba 535 struct input_dev *idev;
860e1d6b
RL
536 int ret;
537
860e1d6b
RL
538 if (!dmi_check_system(hdaps_whitelist)) {
539 printk(KERN_WARNING "hdaps: supported laptop not found!\n");
b3f28a9a 540 ret = -ENODEV;
860e1d6b
RL
541 goto out;
542 }
543
544 if (!request_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS, "hdaps")) {
545 ret = -ENXIO;
546 goto out;
547 }
548
3ae5eaec 549 ret = platform_driver_register(&hdaps_driver);
860e1d6b
RL
550 if (ret)
551 goto out_region;
552
553 pdev = platform_device_register_simple("hdaps", -1, NULL, 0);
554 if (IS_ERR(pdev)) {
555 ret = PTR_ERR(pdev);
556 goto out_driver;
557 }
558
559 ret = sysfs_create_group(&pdev->dev.kobj, &hdaps_attribute_group);
560 if (ret)
561 goto out_device;
562
aefca8ba 563 hdaps_idev = input_allocate_polled_device();
d12eb7e1
DT
564 if (!hdaps_idev) {
565 ret = -ENOMEM;
566 goto out_group;
567 }
568
aefca8ba
DT
569 hdaps_idev->poll = hdaps_mousedev_poll;
570 hdaps_idev->poll_interval = HDAPS_POLL_INTERVAL;
571
393ad299
RL
572 /* initial calibrate for the input device */
573 hdaps_calibrate();
574
575 /* initialize the input class */
aefca8ba
DT
576 idev = hdaps_idev->input;
577 idev->name = "hdaps";
578 idev->dev.parent = &pdev->dev;
7b19ada2 579 idev->evbit[0] = BIT_MASK(EV_ABS);
aefca8ba 580 input_set_abs_params(idev, ABS_X,
d12eb7e1 581 -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
aefca8ba 582 input_set_abs_params(idev, ABS_Y,
d12eb7e1
DT
583 -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
584
aefca8ba 585 ret = input_register_polled_device(hdaps_idev);
b25a1063
DT
586 if (ret)
587 goto out_idev;
393ad299 588
860e1d6b
RL
589 printk(KERN_INFO "hdaps: driver successfully loaded.\n");
590 return 0;
591
b25a1063 592out_idev:
aefca8ba 593 input_free_polled_device(hdaps_idev);
d12eb7e1
DT
594out_group:
595 sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
860e1d6b
RL
596out_device:
597 platform_device_unregister(pdev);
598out_driver:
3ae5eaec 599 platform_driver_unregister(&hdaps_driver);
860e1d6b
RL
600out_region:
601 release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
602out:
603 printk(KERN_WARNING "hdaps: driver init failed (ret=%d)!\n", ret);
604 return ret;
605}
606
607static void __exit hdaps_exit(void)
608{
aefca8ba
DT
609 input_unregister_polled_device(hdaps_idev);
610 input_free_polled_device(hdaps_idev);
860e1d6b
RL
611 sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
612 platform_device_unregister(pdev);
3ae5eaec 613 platform_driver_unregister(&hdaps_driver);
860e1d6b
RL
614 release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
615
616 printk(KERN_INFO "hdaps: driver unloaded.\n");
617}
618
619module_init(hdaps_init);
620module_exit(hdaps_exit);
621
860e1d6b
RL
622module_param_named(invert, hdaps_invert, bool, 0);
623MODULE_PARM_DESC(invert, "invert data along each axis");
624
625MODULE_AUTHOR("Robert Love");
626MODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver");
627MODULE_LICENSE("GPL v2");