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