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