18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * hdaps.c - driver for IBM's Hard Drive Active Protection System
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2005 Robert Love <rml@novell.com>
68c2ecf20Sopenharmony_ci * Copyright (C) 2005 Jesper Juhl <jj@chaosbits.net>
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * The HardDisk Active Protection System (hdaps) is present in IBM ThinkPads
98c2ecf20Sopenharmony_ci * starting with the R40, T41, and X40.  It provides a basic two-axis
108c2ecf20Sopenharmony_ci * accelerometer and other data, such as the device's temperature.
118c2ecf20Sopenharmony_ci *
128c2ecf20Sopenharmony_ci * This driver is based on the document by Mark A. Smith available at
138c2ecf20Sopenharmony_ci * http://www.almaden.ibm.com/cs/people/marksmith/tpaps.html and a lot of trial
148c2ecf20Sopenharmony_ci * and error.
158c2ecf20Sopenharmony_ci */
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#include <linux/delay.h>
208c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
218c2ecf20Sopenharmony_ci#include <linux/input.h>
228c2ecf20Sopenharmony_ci#include <linux/kernel.h>
238c2ecf20Sopenharmony_ci#include <linux/mutex.h>
248c2ecf20Sopenharmony_ci#include <linux/module.h>
258c2ecf20Sopenharmony_ci#include <linux/timer.h>
268c2ecf20Sopenharmony_ci#include <linux/dmi.h>
278c2ecf20Sopenharmony_ci#include <linux/jiffies.h>
288c2ecf20Sopenharmony_ci#include <linux/io.h>
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci#define HDAPS_LOW_PORT		0x1600	/* first port used by hdaps */
318c2ecf20Sopenharmony_ci#define HDAPS_NR_PORTS		0x30	/* number of ports: 0x1600 - 0x162f */
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci#define HDAPS_PORT_STATE	0x1611	/* device state */
348c2ecf20Sopenharmony_ci#define HDAPS_PORT_YPOS		0x1612	/* y-axis position */
358c2ecf20Sopenharmony_ci#define	HDAPS_PORT_XPOS		0x1614	/* x-axis position */
368c2ecf20Sopenharmony_ci#define HDAPS_PORT_TEMP1	0x1616	/* device temperature, in Celsius */
378c2ecf20Sopenharmony_ci#define HDAPS_PORT_YVAR		0x1617	/* y-axis variance (what is this?) */
388c2ecf20Sopenharmony_ci#define HDAPS_PORT_XVAR		0x1619	/* x-axis variance (what is this?) */
398c2ecf20Sopenharmony_ci#define HDAPS_PORT_TEMP2	0x161b	/* device temperature (again?) */
408c2ecf20Sopenharmony_ci#define HDAPS_PORT_UNKNOWN	0x161c	/* what is this? */
418c2ecf20Sopenharmony_ci#define HDAPS_PORT_KMACT	0x161d	/* keyboard or mouse activity */
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci#define STATE_FRESH		0x50	/* accelerometer data is fresh */
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci#define KEYBD_MASK		0x20	/* set if keyboard activity */
468c2ecf20Sopenharmony_ci#define MOUSE_MASK		0x40	/* set if mouse activity */
478c2ecf20Sopenharmony_ci#define KEYBD_ISSET(n)		(!! (n & KEYBD_MASK))	/* keyboard used? */
488c2ecf20Sopenharmony_ci#define MOUSE_ISSET(n)		(!! (n & MOUSE_MASK))	/* mouse used? */
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci#define INIT_TIMEOUT_MSECS	4000	/* wait up to 4s for device init ... */
518c2ecf20Sopenharmony_ci#define INIT_WAIT_MSECS		200	/* ... in 200ms increments */
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci#define HDAPS_POLL_INTERVAL	50	/* poll for input every 1/20s (50 ms)*/
548c2ecf20Sopenharmony_ci#define HDAPS_INPUT_FUZZ	4	/* input event threshold */
558c2ecf20Sopenharmony_ci#define HDAPS_INPUT_FLAT	4
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci#define HDAPS_X_AXIS		(1 << 0)
588c2ecf20Sopenharmony_ci#define HDAPS_Y_AXIS		(1 << 1)
598c2ecf20Sopenharmony_ci#define HDAPS_BOTH_AXES		(HDAPS_X_AXIS | HDAPS_Y_AXIS)
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_cistatic struct platform_device *pdev;
628c2ecf20Sopenharmony_cistatic struct input_dev *hdaps_idev;
638c2ecf20Sopenharmony_cistatic unsigned int hdaps_invert;
648c2ecf20Sopenharmony_cistatic u8 km_activity;
658c2ecf20Sopenharmony_cistatic int rest_x;
668c2ecf20Sopenharmony_cistatic int rest_y;
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(hdaps_mtx);
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci/*
718c2ecf20Sopenharmony_ci * __get_latch - Get the value from a given port.  Callers must hold hdaps_mtx.
728c2ecf20Sopenharmony_ci */
738c2ecf20Sopenharmony_cistatic inline u8 __get_latch(u16 port)
748c2ecf20Sopenharmony_ci{
758c2ecf20Sopenharmony_ci	return inb(port) & 0xff;
768c2ecf20Sopenharmony_ci}
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci/*
798c2ecf20Sopenharmony_ci * __check_latch - Check a port latch for a given value.  Returns zero if the
808c2ecf20Sopenharmony_ci * port contains the given value.  Callers must hold hdaps_mtx.
818c2ecf20Sopenharmony_ci */
828c2ecf20Sopenharmony_cistatic inline int __check_latch(u16 port, u8 val)
838c2ecf20Sopenharmony_ci{
848c2ecf20Sopenharmony_ci	if (__get_latch(port) == val)
858c2ecf20Sopenharmony_ci		return 0;
868c2ecf20Sopenharmony_ci	return -EINVAL;
878c2ecf20Sopenharmony_ci}
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci/*
908c2ecf20Sopenharmony_ci * __wait_latch - Wait up to 100us for a port latch to get a certain value,
918c2ecf20Sopenharmony_ci * returning zero if the value is obtained.  Callers must hold hdaps_mtx.
928c2ecf20Sopenharmony_ci */
938c2ecf20Sopenharmony_cistatic int __wait_latch(u16 port, u8 val)
948c2ecf20Sopenharmony_ci{
958c2ecf20Sopenharmony_ci	unsigned int i;
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	for (i = 0; i < 20; i++) {
988c2ecf20Sopenharmony_ci		if (!__check_latch(port, val))
998c2ecf20Sopenharmony_ci			return 0;
1008c2ecf20Sopenharmony_ci		udelay(5);
1018c2ecf20Sopenharmony_ci	}
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	return -EIO;
1048c2ecf20Sopenharmony_ci}
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci/*
1078c2ecf20Sopenharmony_ci * __device_refresh - request a refresh from the accelerometer.  Does not wait
1088c2ecf20Sopenharmony_ci * for refresh to complete.  Callers must hold hdaps_mtx.
1098c2ecf20Sopenharmony_ci */
1108c2ecf20Sopenharmony_cistatic void __device_refresh(void)
1118c2ecf20Sopenharmony_ci{
1128c2ecf20Sopenharmony_ci	udelay(200);
1138c2ecf20Sopenharmony_ci	if (inb(0x1604) != STATE_FRESH) {
1148c2ecf20Sopenharmony_ci		outb(0x11, 0x1610);
1158c2ecf20Sopenharmony_ci		outb(0x01, 0x161f);
1168c2ecf20Sopenharmony_ci	}
1178c2ecf20Sopenharmony_ci}
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci/*
1208c2ecf20Sopenharmony_ci * __device_refresh_sync - request a synchronous refresh from the
1218c2ecf20Sopenharmony_ci * accelerometer.  We wait for the refresh to complete.  Returns zero if
1228c2ecf20Sopenharmony_ci * successful and nonzero on error.  Callers must hold hdaps_mtx.
1238c2ecf20Sopenharmony_ci */
1248c2ecf20Sopenharmony_cistatic int __device_refresh_sync(void)
1258c2ecf20Sopenharmony_ci{
1268c2ecf20Sopenharmony_ci	__device_refresh();
1278c2ecf20Sopenharmony_ci	return __wait_latch(0x1604, STATE_FRESH);
1288c2ecf20Sopenharmony_ci}
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci/*
1318c2ecf20Sopenharmony_ci * __device_complete - indicate to the accelerometer that we are done reading
1328c2ecf20Sopenharmony_ci * data, and then initiate an async refresh.  Callers must hold hdaps_mtx.
1338c2ecf20Sopenharmony_ci */
1348c2ecf20Sopenharmony_cistatic inline void __device_complete(void)
1358c2ecf20Sopenharmony_ci{
1368c2ecf20Sopenharmony_ci	inb(0x161f);
1378c2ecf20Sopenharmony_ci	inb(0x1604);
1388c2ecf20Sopenharmony_ci	__device_refresh();
1398c2ecf20Sopenharmony_ci}
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci/*
1428c2ecf20Sopenharmony_ci * hdaps_readb_one - reads a byte from a single I/O port, placing the value in
1438c2ecf20Sopenharmony_ci * the given pointer.  Returns zero on success or a negative error on failure.
1448c2ecf20Sopenharmony_ci * Can sleep.
1458c2ecf20Sopenharmony_ci */
1468c2ecf20Sopenharmony_cistatic int hdaps_readb_one(unsigned int port, u8 *val)
1478c2ecf20Sopenharmony_ci{
1488c2ecf20Sopenharmony_ci	int ret;
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	mutex_lock(&hdaps_mtx);
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	/* do a sync refresh -- we need to be sure that we read fresh data */
1538c2ecf20Sopenharmony_ci	ret = __device_refresh_sync();
1548c2ecf20Sopenharmony_ci	if (ret)
1558c2ecf20Sopenharmony_ci		goto out;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	*val = inb(port);
1588c2ecf20Sopenharmony_ci	__device_complete();
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ciout:
1618c2ecf20Sopenharmony_ci	mutex_unlock(&hdaps_mtx);
1628c2ecf20Sopenharmony_ci	return ret;
1638c2ecf20Sopenharmony_ci}
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci/* __hdaps_read_pair - internal lockless helper for hdaps_read_pair(). */
1668c2ecf20Sopenharmony_cistatic int __hdaps_read_pair(unsigned int port1, unsigned int port2,
1678c2ecf20Sopenharmony_ci			     int *x, int *y)
1688c2ecf20Sopenharmony_ci{
1698c2ecf20Sopenharmony_ci	/* do a sync refresh -- we need to be sure that we read fresh data */
1708c2ecf20Sopenharmony_ci	if (__device_refresh_sync())
1718c2ecf20Sopenharmony_ci		return -EIO;
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	*y = inw(port2);
1748c2ecf20Sopenharmony_ci	*x = inw(port1);
1758c2ecf20Sopenharmony_ci	km_activity = inb(HDAPS_PORT_KMACT);
1768c2ecf20Sopenharmony_ci	__device_complete();
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	/* hdaps_invert is a bitvector to negate the axes */
1798c2ecf20Sopenharmony_ci	if (hdaps_invert & HDAPS_X_AXIS)
1808c2ecf20Sopenharmony_ci		*x = -*x;
1818c2ecf20Sopenharmony_ci	if (hdaps_invert & HDAPS_Y_AXIS)
1828c2ecf20Sopenharmony_ci		*y = -*y;
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	return 0;
1858c2ecf20Sopenharmony_ci}
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci/*
1888c2ecf20Sopenharmony_ci * hdaps_read_pair - reads the values from a pair of ports, placing the values
1898c2ecf20Sopenharmony_ci * in the given pointers.  Returns zero on success.  Can sleep.
1908c2ecf20Sopenharmony_ci */
1918c2ecf20Sopenharmony_cistatic int hdaps_read_pair(unsigned int port1, unsigned int port2,
1928c2ecf20Sopenharmony_ci			   int *val1, int *val2)
1938c2ecf20Sopenharmony_ci{
1948c2ecf20Sopenharmony_ci	int ret;
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	mutex_lock(&hdaps_mtx);
1978c2ecf20Sopenharmony_ci	ret = __hdaps_read_pair(port1, port2, val1, val2);
1988c2ecf20Sopenharmony_ci	mutex_unlock(&hdaps_mtx);
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	return ret;
2018c2ecf20Sopenharmony_ci}
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci/*
2048c2ecf20Sopenharmony_ci * hdaps_device_init - initialize the accelerometer.  Returns zero on success
2058c2ecf20Sopenharmony_ci * and negative error code on failure.  Can sleep.
2068c2ecf20Sopenharmony_ci */
2078c2ecf20Sopenharmony_cistatic int hdaps_device_init(void)
2088c2ecf20Sopenharmony_ci{
2098c2ecf20Sopenharmony_ci	int total, ret = -ENXIO;
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci	mutex_lock(&hdaps_mtx);
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	outb(0x13, 0x1610);
2148c2ecf20Sopenharmony_ci	outb(0x01, 0x161f);
2158c2ecf20Sopenharmony_ci	if (__wait_latch(0x161f, 0x00))
2168c2ecf20Sopenharmony_ci		goto out;
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci	/*
2198c2ecf20Sopenharmony_ci	 * Most ThinkPads return 0x01.
2208c2ecf20Sopenharmony_ci	 *
2218c2ecf20Sopenharmony_ci	 * Others--namely the R50p, T41p, and T42p--return 0x03.  These laptops
2228c2ecf20Sopenharmony_ci	 * have "inverted" axises.
2238c2ecf20Sopenharmony_ci	 *
2248c2ecf20Sopenharmony_ci	 * The 0x02 value occurs when the chip has been previously initialized.
2258c2ecf20Sopenharmony_ci	 */
2268c2ecf20Sopenharmony_ci	if (__check_latch(0x1611, 0x03) &&
2278c2ecf20Sopenharmony_ci		     __check_latch(0x1611, 0x02) &&
2288c2ecf20Sopenharmony_ci		     __check_latch(0x1611, 0x01))
2298c2ecf20Sopenharmony_ci		goto out;
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	printk(KERN_DEBUG "hdaps: initial latch check good (0x%02x)\n",
2328c2ecf20Sopenharmony_ci	       __get_latch(0x1611));
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci	outb(0x17, 0x1610);
2358c2ecf20Sopenharmony_ci	outb(0x81, 0x1611);
2368c2ecf20Sopenharmony_ci	outb(0x01, 0x161f);
2378c2ecf20Sopenharmony_ci	if (__wait_latch(0x161f, 0x00))
2388c2ecf20Sopenharmony_ci		goto out;
2398c2ecf20Sopenharmony_ci	if (__wait_latch(0x1611, 0x00))
2408c2ecf20Sopenharmony_ci		goto out;
2418c2ecf20Sopenharmony_ci	if (__wait_latch(0x1612, 0x60))
2428c2ecf20Sopenharmony_ci		goto out;
2438c2ecf20Sopenharmony_ci	if (__wait_latch(0x1613, 0x00))
2448c2ecf20Sopenharmony_ci		goto out;
2458c2ecf20Sopenharmony_ci	outb(0x14, 0x1610);
2468c2ecf20Sopenharmony_ci	outb(0x01, 0x1611);
2478c2ecf20Sopenharmony_ci	outb(0x01, 0x161f);
2488c2ecf20Sopenharmony_ci	if (__wait_latch(0x161f, 0x00))
2498c2ecf20Sopenharmony_ci		goto out;
2508c2ecf20Sopenharmony_ci	outb(0x10, 0x1610);
2518c2ecf20Sopenharmony_ci	outb(0xc8, 0x1611);
2528c2ecf20Sopenharmony_ci	outb(0x00, 0x1612);
2538c2ecf20Sopenharmony_ci	outb(0x02, 0x1613);
2548c2ecf20Sopenharmony_ci	outb(0x01, 0x161f);
2558c2ecf20Sopenharmony_ci	if (__wait_latch(0x161f, 0x00))
2568c2ecf20Sopenharmony_ci		goto out;
2578c2ecf20Sopenharmony_ci	if (__device_refresh_sync())
2588c2ecf20Sopenharmony_ci		goto out;
2598c2ecf20Sopenharmony_ci	if (__wait_latch(0x1611, 0x00))
2608c2ecf20Sopenharmony_ci		goto out;
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	/* we have done our dance, now let's wait for the applause */
2638c2ecf20Sopenharmony_ci	for (total = INIT_TIMEOUT_MSECS; total > 0; total -= INIT_WAIT_MSECS) {
2648c2ecf20Sopenharmony_ci		int x, y;
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci		/* a read of the device helps push it into action */
2678c2ecf20Sopenharmony_ci		__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
2688c2ecf20Sopenharmony_ci		if (!__wait_latch(0x1611, 0x02)) {
2698c2ecf20Sopenharmony_ci			ret = 0;
2708c2ecf20Sopenharmony_ci			break;
2718c2ecf20Sopenharmony_ci		}
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci		msleep(INIT_WAIT_MSECS);
2748c2ecf20Sopenharmony_ci	}
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ciout:
2778c2ecf20Sopenharmony_ci	mutex_unlock(&hdaps_mtx);
2788c2ecf20Sopenharmony_ci	return ret;
2798c2ecf20Sopenharmony_ci}
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_ci/* Device model stuff */
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_cistatic int hdaps_probe(struct platform_device *dev)
2858c2ecf20Sopenharmony_ci{
2868c2ecf20Sopenharmony_ci	int ret;
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_ci	ret = hdaps_device_init();
2898c2ecf20Sopenharmony_ci	if (ret)
2908c2ecf20Sopenharmony_ci		return ret;
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_ci	pr_info("device successfully initialized\n");
2938c2ecf20Sopenharmony_ci	return 0;
2948c2ecf20Sopenharmony_ci}
2958c2ecf20Sopenharmony_ci
2968c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP
2978c2ecf20Sopenharmony_cistatic int hdaps_resume(struct device *dev)
2988c2ecf20Sopenharmony_ci{
2998c2ecf20Sopenharmony_ci	return hdaps_device_init();
3008c2ecf20Sopenharmony_ci}
3018c2ecf20Sopenharmony_ci#endif
3028c2ecf20Sopenharmony_ci
3038c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(hdaps_pm, NULL, hdaps_resume);
3048c2ecf20Sopenharmony_ci
3058c2ecf20Sopenharmony_cistatic struct platform_driver hdaps_driver = {
3068c2ecf20Sopenharmony_ci	.probe = hdaps_probe,
3078c2ecf20Sopenharmony_ci	.driver	= {
3088c2ecf20Sopenharmony_ci		.name = "hdaps",
3098c2ecf20Sopenharmony_ci		.pm = &hdaps_pm,
3108c2ecf20Sopenharmony_ci	},
3118c2ecf20Sopenharmony_ci};
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_ci/*
3148c2ecf20Sopenharmony_ci * hdaps_calibrate - Set our "resting" values.  Callers must hold hdaps_mtx.
3158c2ecf20Sopenharmony_ci */
3168c2ecf20Sopenharmony_cistatic void hdaps_calibrate(void)
3178c2ecf20Sopenharmony_ci{
3188c2ecf20Sopenharmony_ci	__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &rest_x, &rest_y);
3198c2ecf20Sopenharmony_ci}
3208c2ecf20Sopenharmony_ci
3218c2ecf20Sopenharmony_cistatic void hdaps_mousedev_poll(struct input_dev *input_dev)
3228c2ecf20Sopenharmony_ci{
3238c2ecf20Sopenharmony_ci	int x, y;
3248c2ecf20Sopenharmony_ci
3258c2ecf20Sopenharmony_ci	mutex_lock(&hdaps_mtx);
3268c2ecf20Sopenharmony_ci
3278c2ecf20Sopenharmony_ci	if (__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y))
3288c2ecf20Sopenharmony_ci		goto out;
3298c2ecf20Sopenharmony_ci
3308c2ecf20Sopenharmony_ci	input_report_abs(input_dev, ABS_X, x - rest_x);
3318c2ecf20Sopenharmony_ci	input_report_abs(input_dev, ABS_Y, y - rest_y);
3328c2ecf20Sopenharmony_ci	input_sync(input_dev);
3338c2ecf20Sopenharmony_ci
3348c2ecf20Sopenharmony_ciout:
3358c2ecf20Sopenharmony_ci	mutex_unlock(&hdaps_mtx);
3368c2ecf20Sopenharmony_ci}
3378c2ecf20Sopenharmony_ci
3388c2ecf20Sopenharmony_ci
3398c2ecf20Sopenharmony_ci/* Sysfs Files */
3408c2ecf20Sopenharmony_ci
3418c2ecf20Sopenharmony_cistatic ssize_t hdaps_position_show(struct device *dev,
3428c2ecf20Sopenharmony_ci				   struct device_attribute *attr, char *buf)
3438c2ecf20Sopenharmony_ci{
3448c2ecf20Sopenharmony_ci	int ret, x, y;
3458c2ecf20Sopenharmony_ci
3468c2ecf20Sopenharmony_ci	ret = hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
3478c2ecf20Sopenharmony_ci	if (ret)
3488c2ecf20Sopenharmony_ci		return ret;
3498c2ecf20Sopenharmony_ci
3508c2ecf20Sopenharmony_ci	return sprintf(buf, "(%d,%d)\n", x, y);
3518c2ecf20Sopenharmony_ci}
3528c2ecf20Sopenharmony_ci
3538c2ecf20Sopenharmony_cistatic ssize_t hdaps_variance_show(struct device *dev,
3548c2ecf20Sopenharmony_ci				   struct device_attribute *attr, char *buf)
3558c2ecf20Sopenharmony_ci{
3568c2ecf20Sopenharmony_ci	int ret, x, y;
3578c2ecf20Sopenharmony_ci
3588c2ecf20Sopenharmony_ci	ret = hdaps_read_pair(HDAPS_PORT_XVAR, HDAPS_PORT_YVAR, &x, &y);
3598c2ecf20Sopenharmony_ci	if (ret)
3608c2ecf20Sopenharmony_ci		return ret;
3618c2ecf20Sopenharmony_ci
3628c2ecf20Sopenharmony_ci	return sprintf(buf, "(%d,%d)\n", x, y);
3638c2ecf20Sopenharmony_ci}
3648c2ecf20Sopenharmony_ci
3658c2ecf20Sopenharmony_cistatic ssize_t hdaps_temp1_show(struct device *dev,
3668c2ecf20Sopenharmony_ci				struct device_attribute *attr, char *buf)
3678c2ecf20Sopenharmony_ci{
3688c2ecf20Sopenharmony_ci	u8 temp;
3698c2ecf20Sopenharmony_ci	int ret;
3708c2ecf20Sopenharmony_ci
3718c2ecf20Sopenharmony_ci	ret = hdaps_readb_one(HDAPS_PORT_TEMP1, &temp);
3728c2ecf20Sopenharmony_ci	if (ret)
3738c2ecf20Sopenharmony_ci		return ret;
3748c2ecf20Sopenharmony_ci
3758c2ecf20Sopenharmony_ci	return sprintf(buf, "%u\n", temp);
3768c2ecf20Sopenharmony_ci}
3778c2ecf20Sopenharmony_ci
3788c2ecf20Sopenharmony_cistatic ssize_t hdaps_temp2_show(struct device *dev,
3798c2ecf20Sopenharmony_ci				struct device_attribute *attr, char *buf)
3808c2ecf20Sopenharmony_ci{
3818c2ecf20Sopenharmony_ci	u8 temp;
3828c2ecf20Sopenharmony_ci	int ret;
3838c2ecf20Sopenharmony_ci
3848c2ecf20Sopenharmony_ci	ret = hdaps_readb_one(HDAPS_PORT_TEMP2, &temp);
3858c2ecf20Sopenharmony_ci	if (ret)
3868c2ecf20Sopenharmony_ci		return ret;
3878c2ecf20Sopenharmony_ci
3888c2ecf20Sopenharmony_ci	return sprintf(buf, "%u\n", temp);
3898c2ecf20Sopenharmony_ci}
3908c2ecf20Sopenharmony_ci
3918c2ecf20Sopenharmony_cistatic ssize_t hdaps_keyboard_activity_show(struct device *dev,
3928c2ecf20Sopenharmony_ci					    struct device_attribute *attr,
3938c2ecf20Sopenharmony_ci					    char *buf)
3948c2ecf20Sopenharmony_ci{
3958c2ecf20Sopenharmony_ci	return sprintf(buf, "%u\n", KEYBD_ISSET(km_activity));
3968c2ecf20Sopenharmony_ci}
3978c2ecf20Sopenharmony_ci
3988c2ecf20Sopenharmony_cistatic ssize_t hdaps_mouse_activity_show(struct device *dev,
3998c2ecf20Sopenharmony_ci					 struct device_attribute *attr,
4008c2ecf20Sopenharmony_ci					 char *buf)
4018c2ecf20Sopenharmony_ci{
4028c2ecf20Sopenharmony_ci	return sprintf(buf, "%u\n", MOUSE_ISSET(km_activity));
4038c2ecf20Sopenharmony_ci}
4048c2ecf20Sopenharmony_ci
4058c2ecf20Sopenharmony_cistatic ssize_t hdaps_calibrate_show(struct device *dev,
4068c2ecf20Sopenharmony_ci				    struct device_attribute *attr, char *buf)
4078c2ecf20Sopenharmony_ci{
4088c2ecf20Sopenharmony_ci	return sprintf(buf, "(%d,%d)\n", rest_x, rest_y);
4098c2ecf20Sopenharmony_ci}
4108c2ecf20Sopenharmony_ci
4118c2ecf20Sopenharmony_cistatic ssize_t hdaps_calibrate_store(struct device *dev,
4128c2ecf20Sopenharmony_ci				     struct device_attribute *attr,
4138c2ecf20Sopenharmony_ci				     const char *buf, size_t count)
4148c2ecf20Sopenharmony_ci{
4158c2ecf20Sopenharmony_ci	mutex_lock(&hdaps_mtx);
4168c2ecf20Sopenharmony_ci	hdaps_calibrate();
4178c2ecf20Sopenharmony_ci	mutex_unlock(&hdaps_mtx);
4188c2ecf20Sopenharmony_ci
4198c2ecf20Sopenharmony_ci	return count;
4208c2ecf20Sopenharmony_ci}
4218c2ecf20Sopenharmony_ci
4228c2ecf20Sopenharmony_cistatic ssize_t hdaps_invert_show(struct device *dev,
4238c2ecf20Sopenharmony_ci				 struct device_attribute *attr, char *buf)
4248c2ecf20Sopenharmony_ci{
4258c2ecf20Sopenharmony_ci	return sprintf(buf, "%u\n", hdaps_invert);
4268c2ecf20Sopenharmony_ci}
4278c2ecf20Sopenharmony_ci
4288c2ecf20Sopenharmony_cistatic ssize_t hdaps_invert_store(struct device *dev,
4298c2ecf20Sopenharmony_ci				  struct device_attribute *attr,
4308c2ecf20Sopenharmony_ci				  const char *buf, size_t count)
4318c2ecf20Sopenharmony_ci{
4328c2ecf20Sopenharmony_ci	int invert;
4338c2ecf20Sopenharmony_ci
4348c2ecf20Sopenharmony_ci	if (sscanf(buf, "%d", &invert) != 1 ||
4358c2ecf20Sopenharmony_ci	    invert < 0 || invert > HDAPS_BOTH_AXES)
4368c2ecf20Sopenharmony_ci		return -EINVAL;
4378c2ecf20Sopenharmony_ci
4388c2ecf20Sopenharmony_ci	hdaps_invert = invert;
4398c2ecf20Sopenharmony_ci	hdaps_calibrate();
4408c2ecf20Sopenharmony_ci
4418c2ecf20Sopenharmony_ci	return count;
4428c2ecf20Sopenharmony_ci}
4438c2ecf20Sopenharmony_ci
4448c2ecf20Sopenharmony_cistatic DEVICE_ATTR(position, 0444, hdaps_position_show, NULL);
4458c2ecf20Sopenharmony_cistatic DEVICE_ATTR(variance, 0444, hdaps_variance_show, NULL);
4468c2ecf20Sopenharmony_cistatic DEVICE_ATTR(temp1, 0444, hdaps_temp1_show, NULL);
4478c2ecf20Sopenharmony_cistatic DEVICE_ATTR(temp2, 0444, hdaps_temp2_show, NULL);
4488c2ecf20Sopenharmony_cistatic DEVICE_ATTR(keyboard_activity, 0444, hdaps_keyboard_activity_show, NULL);
4498c2ecf20Sopenharmony_cistatic DEVICE_ATTR(mouse_activity, 0444, hdaps_mouse_activity_show, NULL);
4508c2ecf20Sopenharmony_cistatic DEVICE_ATTR(calibrate, 0644, hdaps_calibrate_show,hdaps_calibrate_store);
4518c2ecf20Sopenharmony_cistatic DEVICE_ATTR(invert, 0644, hdaps_invert_show, hdaps_invert_store);
4528c2ecf20Sopenharmony_ci
4538c2ecf20Sopenharmony_cistatic struct attribute *hdaps_attributes[] = {
4548c2ecf20Sopenharmony_ci	&dev_attr_position.attr,
4558c2ecf20Sopenharmony_ci	&dev_attr_variance.attr,
4568c2ecf20Sopenharmony_ci	&dev_attr_temp1.attr,
4578c2ecf20Sopenharmony_ci	&dev_attr_temp2.attr,
4588c2ecf20Sopenharmony_ci	&dev_attr_keyboard_activity.attr,
4598c2ecf20Sopenharmony_ci	&dev_attr_mouse_activity.attr,
4608c2ecf20Sopenharmony_ci	&dev_attr_calibrate.attr,
4618c2ecf20Sopenharmony_ci	&dev_attr_invert.attr,
4628c2ecf20Sopenharmony_ci	NULL,
4638c2ecf20Sopenharmony_ci};
4648c2ecf20Sopenharmony_ci
4658c2ecf20Sopenharmony_cistatic struct attribute_group hdaps_attribute_group = {
4668c2ecf20Sopenharmony_ci	.attrs = hdaps_attributes,
4678c2ecf20Sopenharmony_ci};
4688c2ecf20Sopenharmony_ci
4698c2ecf20Sopenharmony_ci
4708c2ecf20Sopenharmony_ci/* Module stuff */
4718c2ecf20Sopenharmony_ci
4728c2ecf20Sopenharmony_ci/* hdaps_dmi_match - found a match.  return one, short-circuiting the hunt. */
4738c2ecf20Sopenharmony_cistatic int __init hdaps_dmi_match(const struct dmi_system_id *id)
4748c2ecf20Sopenharmony_ci{
4758c2ecf20Sopenharmony_ci	pr_info("%s detected\n", id->ident);
4768c2ecf20Sopenharmony_ci	return 1;
4778c2ecf20Sopenharmony_ci}
4788c2ecf20Sopenharmony_ci
4798c2ecf20Sopenharmony_ci/* hdaps_dmi_match_invert - found an inverted match. */
4808c2ecf20Sopenharmony_cistatic int __init hdaps_dmi_match_invert(const struct dmi_system_id *id)
4818c2ecf20Sopenharmony_ci{
4828c2ecf20Sopenharmony_ci	hdaps_invert = (unsigned long)id->driver_data;
4838c2ecf20Sopenharmony_ci	pr_info("inverting axis (%u) readings\n", hdaps_invert);
4848c2ecf20Sopenharmony_ci	return hdaps_dmi_match(id);
4858c2ecf20Sopenharmony_ci}
4868c2ecf20Sopenharmony_ci
4878c2ecf20Sopenharmony_ci#define HDAPS_DMI_MATCH_INVERT(vendor, model, axes) {	\
4888c2ecf20Sopenharmony_ci	.ident = vendor " " model,			\
4898c2ecf20Sopenharmony_ci	.callback = hdaps_dmi_match_invert,		\
4908c2ecf20Sopenharmony_ci	.driver_data = (void *)axes,			\
4918c2ecf20Sopenharmony_ci	.matches = {					\
4928c2ecf20Sopenharmony_ci		DMI_MATCH(DMI_BOARD_VENDOR, vendor),	\
4938c2ecf20Sopenharmony_ci		DMI_MATCH(DMI_PRODUCT_VERSION, model)	\
4948c2ecf20Sopenharmony_ci	}						\
4958c2ecf20Sopenharmony_ci}
4968c2ecf20Sopenharmony_ci
4978c2ecf20Sopenharmony_ci#define HDAPS_DMI_MATCH_NORMAL(vendor, model)		\
4988c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_INVERT(vendor, model, 0)
4998c2ecf20Sopenharmony_ci
5008c2ecf20Sopenharmony_ci/* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
5018c2ecf20Sopenharmony_ci   "ThinkPad T42p", so the order of the entries matters.
5028c2ecf20Sopenharmony_ci   If your ThinkPad is not recognized, please update to latest
5038c2ecf20Sopenharmony_ci   BIOS. This is especially the case for some R52 ThinkPads. */
5048c2ecf20Sopenharmony_cistatic const struct dmi_system_id hdaps_whitelist[] __initconst = {
5058c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p", HDAPS_BOTH_AXES),
5068c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R50"),
5078c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R51"),
5088c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R52"),
5098c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61i", HDAPS_BOTH_AXES),
5108c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61", HDAPS_BOTH_AXES),
5118c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p", HDAPS_BOTH_AXES),
5128c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T41"),
5138c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p", HDAPS_BOTH_AXES),
5148c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T42"),
5158c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T43"),
5168c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400", HDAPS_BOTH_AXES),
5178c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60", HDAPS_BOTH_AXES),
5188c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61p", HDAPS_BOTH_AXES),
5198c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61", HDAPS_BOTH_AXES),
5208c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X40"),
5218c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X41", HDAPS_Y_AXIS),
5228c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60", HDAPS_BOTH_AXES),
5238c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61s", HDAPS_BOTH_AXES),
5248c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61", HDAPS_BOTH_AXES),
5258c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad Z60m"),
5268c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61m", HDAPS_BOTH_AXES),
5278c2ecf20Sopenharmony_ci	HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61p", HDAPS_BOTH_AXES),
5288c2ecf20Sopenharmony_ci	{ .ident = NULL }
5298c2ecf20Sopenharmony_ci};
5308c2ecf20Sopenharmony_ci
5318c2ecf20Sopenharmony_cistatic int __init hdaps_init(void)
5328c2ecf20Sopenharmony_ci{
5338c2ecf20Sopenharmony_ci	int ret;
5348c2ecf20Sopenharmony_ci
5358c2ecf20Sopenharmony_ci	if (!dmi_check_system(hdaps_whitelist)) {
5368c2ecf20Sopenharmony_ci		pr_warn("supported laptop not found!\n");
5378c2ecf20Sopenharmony_ci		ret = -ENODEV;
5388c2ecf20Sopenharmony_ci		goto out;
5398c2ecf20Sopenharmony_ci	}
5408c2ecf20Sopenharmony_ci
5418c2ecf20Sopenharmony_ci	if (!request_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS, "hdaps")) {
5428c2ecf20Sopenharmony_ci		ret = -ENXIO;
5438c2ecf20Sopenharmony_ci		goto out;
5448c2ecf20Sopenharmony_ci	}
5458c2ecf20Sopenharmony_ci
5468c2ecf20Sopenharmony_ci	ret = platform_driver_register(&hdaps_driver);
5478c2ecf20Sopenharmony_ci	if (ret)
5488c2ecf20Sopenharmony_ci		goto out_region;
5498c2ecf20Sopenharmony_ci
5508c2ecf20Sopenharmony_ci	pdev = platform_device_register_simple("hdaps", -1, NULL, 0);
5518c2ecf20Sopenharmony_ci	if (IS_ERR(pdev)) {
5528c2ecf20Sopenharmony_ci		ret = PTR_ERR(pdev);
5538c2ecf20Sopenharmony_ci		goto out_driver;
5548c2ecf20Sopenharmony_ci	}
5558c2ecf20Sopenharmony_ci
5568c2ecf20Sopenharmony_ci	ret = sysfs_create_group(&pdev->dev.kobj, &hdaps_attribute_group);
5578c2ecf20Sopenharmony_ci	if (ret)
5588c2ecf20Sopenharmony_ci		goto out_device;
5598c2ecf20Sopenharmony_ci
5608c2ecf20Sopenharmony_ci	hdaps_idev = input_allocate_device();
5618c2ecf20Sopenharmony_ci	if (!hdaps_idev) {
5628c2ecf20Sopenharmony_ci		ret = -ENOMEM;
5638c2ecf20Sopenharmony_ci		goto out_group;
5648c2ecf20Sopenharmony_ci	}
5658c2ecf20Sopenharmony_ci
5668c2ecf20Sopenharmony_ci	/* initial calibrate for the input device */
5678c2ecf20Sopenharmony_ci	hdaps_calibrate();
5688c2ecf20Sopenharmony_ci
5698c2ecf20Sopenharmony_ci	/* initialize the input class */
5708c2ecf20Sopenharmony_ci	hdaps_idev->name = "hdaps";
5718c2ecf20Sopenharmony_ci	hdaps_idev->phys = "isa1600/input0";
5728c2ecf20Sopenharmony_ci	hdaps_idev->id.bustype = BUS_ISA;
5738c2ecf20Sopenharmony_ci	hdaps_idev->dev.parent = &pdev->dev;
5748c2ecf20Sopenharmony_ci	input_set_abs_params(hdaps_idev, ABS_X,
5758c2ecf20Sopenharmony_ci			-256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
5768c2ecf20Sopenharmony_ci	input_set_abs_params(hdaps_idev, ABS_Y,
5778c2ecf20Sopenharmony_ci			-256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
5788c2ecf20Sopenharmony_ci
5798c2ecf20Sopenharmony_ci	ret = input_setup_polling(hdaps_idev, hdaps_mousedev_poll);
5808c2ecf20Sopenharmony_ci	if (ret)
5818c2ecf20Sopenharmony_ci		goto out_idev;
5828c2ecf20Sopenharmony_ci
5838c2ecf20Sopenharmony_ci	input_set_poll_interval(hdaps_idev, HDAPS_POLL_INTERVAL);
5848c2ecf20Sopenharmony_ci
5858c2ecf20Sopenharmony_ci	ret = input_register_device(hdaps_idev);
5868c2ecf20Sopenharmony_ci	if (ret)
5878c2ecf20Sopenharmony_ci		goto out_idev;
5888c2ecf20Sopenharmony_ci
5898c2ecf20Sopenharmony_ci	pr_info("driver successfully loaded\n");
5908c2ecf20Sopenharmony_ci	return 0;
5918c2ecf20Sopenharmony_ci
5928c2ecf20Sopenharmony_ciout_idev:
5938c2ecf20Sopenharmony_ci	input_free_device(hdaps_idev);
5948c2ecf20Sopenharmony_ciout_group:
5958c2ecf20Sopenharmony_ci	sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
5968c2ecf20Sopenharmony_ciout_device:
5978c2ecf20Sopenharmony_ci	platform_device_unregister(pdev);
5988c2ecf20Sopenharmony_ciout_driver:
5998c2ecf20Sopenharmony_ci	platform_driver_unregister(&hdaps_driver);
6008c2ecf20Sopenharmony_ciout_region:
6018c2ecf20Sopenharmony_ci	release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
6028c2ecf20Sopenharmony_ciout:
6038c2ecf20Sopenharmony_ci	pr_warn("driver init failed (ret=%d)!\n", ret);
6048c2ecf20Sopenharmony_ci	return ret;
6058c2ecf20Sopenharmony_ci}
6068c2ecf20Sopenharmony_ci
6078c2ecf20Sopenharmony_cistatic void __exit hdaps_exit(void)
6088c2ecf20Sopenharmony_ci{
6098c2ecf20Sopenharmony_ci	input_unregister_device(hdaps_idev);
6108c2ecf20Sopenharmony_ci	sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
6118c2ecf20Sopenharmony_ci	platform_device_unregister(pdev);
6128c2ecf20Sopenharmony_ci	platform_driver_unregister(&hdaps_driver);
6138c2ecf20Sopenharmony_ci	release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
6148c2ecf20Sopenharmony_ci
6158c2ecf20Sopenharmony_ci	pr_info("driver unloaded\n");
6168c2ecf20Sopenharmony_ci}
6178c2ecf20Sopenharmony_ci
6188c2ecf20Sopenharmony_cimodule_init(hdaps_init);
6198c2ecf20Sopenharmony_cimodule_exit(hdaps_exit);
6208c2ecf20Sopenharmony_ci
6218c2ecf20Sopenharmony_cimodule_param_named(invert, hdaps_invert, int, 0);
6228c2ecf20Sopenharmony_ciMODULE_PARM_DESC(invert, "invert data along each axis. 1 invert x-axis, "
6238c2ecf20Sopenharmony_ci		 "2 invert y-axis, 3 invert both axes.");
6248c2ecf20Sopenharmony_ci
6258c2ecf20Sopenharmony_ciMODULE_AUTHOR("Robert Love");
6268c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver");
6278c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
628