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