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