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