mirror of
https://github.com/linux-thinkpad/tp_smapi.git
synced 2024-09-11 07:05:28 +03:00
76c5120f7b
Linux 4.15 removed the ancient init_timer() API and changed the signature of the timer handler function. Signed-off-by: Jan Nordholz <jnordholz@sec.t-labs.tu-berlin.de> Closes: #31
905 lines
28 KiB
C
905 lines
28 KiB
C
/*
|
|
* drivers/platform/x86/hdaps.c - driver for IBM's Hard Drive Active Protection System
|
|
*
|
|
* Copyright (C) 2005 Robert Love <rml@novell.com>
|
|
* Copyright (C) 2005 Jesper Juhl <jesper.juhl@gmail.com>
|
|
*
|
|
* The HardDisk Active Protection System (hdaps) is present in IBM ThinkPads
|
|
* starting with the R40, T41, and X40. It provides a basic two-axis
|
|
* accelerometer and other data, such as the device's temperature.
|
|
*
|
|
* This driver is based on the document by Mark A. Smith available at
|
|
* http://www.almaden.ibm.com/cs/people/marksmith/tpaps.html and a lot of trial
|
|
* and error.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License v2 as published by the
|
|
* Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include <linux/delay.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/input.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/dmi.h>
|
|
#include <linux/jiffies.h>
|
|
#include "thinkpad_ec.h"
|
|
#include <linux/pci_ids.h>
|
|
#include <linux/version.h>
|
|
|
|
/* Embedded controller accelerometer read command and its result: */
|
|
static const struct thinkpad_ec_row ec_accel_args =
|
|
{ .mask = 0x0001, .val = {0x11} };
|
|
#define EC_ACCEL_IDX_READOUTS 0x1 /* readouts included in this read */
|
|
/* First readout, if READOUTS>=1: */
|
|
#define EC_ACCEL_IDX_YPOS1 0x2 /* y-axis position word */
|
|
#define EC_ACCEL_IDX_XPOS1 0x4 /* x-axis position word */
|
|
#define EC_ACCEL_IDX_TEMP1 0x6 /* device temperature in Celsius */
|
|
/* Second readout, if READOUTS>=2: */
|
|
#define EC_ACCEL_IDX_XPOS2 0x7 /* y-axis position word */
|
|
#define EC_ACCEL_IDX_YPOS2 0x9 /* x-axis position word */
|
|
#define EC_ACCEL_IDX_TEMP2 0xb /* device temperature in Celsius */
|
|
#define EC_ACCEL_IDX_QUEUED 0xc /* Number of queued readouts left */
|
|
#define EC_ACCEL_IDX_KMACT 0xd /* keyboard or mouse activity */
|
|
#define EC_ACCEL_IDX_RETVAL 0xf /* command return value, good=0x00 */
|
|
|
|
#define KEYBD_MASK 0x20 /* set if keyboard activity */
|
|
#define MOUSE_MASK 0x40 /* set if mouse activity */
|
|
|
|
#define READ_TIMEOUT_MSECS 100 /* wait this long for device read */
|
|
#define RETRY_MSECS 3 /* retry delay */
|
|
|
|
#define HDAPS_INPUT_FUZZ 4 /* input event threshold */
|
|
#define HDAPS_INPUT_FLAT 4
|
|
#define KMACT_REMEMBER_PERIOD (HZ/10) /* keyboard/mouse persistence */
|
|
|
|
/* Input IDs */
|
|
#define HDAPS_INPUT_VENDOR PCI_VENDOR_ID_IBM
|
|
#define HDAPS_INPUT_PRODUCT 0x5054 /* "TP", shared with thinkpad_acpi */
|
|
#define HDAPS_INPUT_JS_VERSION 0x6801 /* Joystick emulation input device */
|
|
#define HDAPS_INPUT_RAW_VERSION 0x4801 /* Raw accelerometer input device */
|
|
|
|
/* Axis orientation. */
|
|
/* The unnatural bit-representation of inversions is for backward
|
|
* compatibility with the"invert=1" module parameter. */
|
|
#define HDAPS_ORIENT_INVERT_XY 0x01 /* Invert both X and Y axes. */
|
|
#define HDAPS_ORIENT_INVERT_X 0x02 /* Invert the X axis (uninvert if
|
|
* already inverted by INVERT_XY). */
|
|
#define HDAPS_ORIENT_SWAP 0x04 /* Swap the axes. The swap occurs
|
|
* before inverting X or Y. */
|
|
#define HDAPS_ORIENT_MAX 0x07
|
|
#define HDAPS_ORIENT_UNDEFINED 0xFF /* Placeholder during initialization */
|
|
#define HDAPS_ORIENT_INVERT_Y (HDAPS_ORIENT_INVERT_XY | HDAPS_ORIENT_INVERT_X)
|
|
|
|
static struct timer_list hdaps_timer;
|
|
static struct platform_device *pdev;
|
|
static struct input_dev *hdaps_idev; /* joystick-like device with fuzz */
|
|
static struct input_dev *hdaps_idev_raw; /* raw hdaps sensor readouts */
|
|
static unsigned int hdaps_invert = HDAPS_ORIENT_UNDEFINED;
|
|
static int needs_calibration;
|
|
|
|
/* Configuration: */
|
|
static int sampling_rate = 50; /* Sampling rate */
|
|
static int oversampling_ratio = 5; /* Ratio between our sampling rate and
|
|
* EC accelerometer sampling rate */
|
|
static int running_avg_filter_order = 2; /* EC running average filter order */
|
|
|
|
/* Latest state readout: */
|
|
static int pos_x, pos_y; /* position */
|
|
static int temperature; /* temperature */
|
|
static int stale_readout = 1; /* last read invalid */
|
|
static int rest_x, rest_y; /* calibrated rest position */
|
|
|
|
/* Last time we saw keyboard and mouse activity: */
|
|
static u64 last_keyboard_jiffies = INITIAL_JIFFIES;
|
|
static u64 last_mouse_jiffies = INITIAL_JIFFIES;
|
|
static u64 last_update_jiffies = INITIAL_JIFFIES;
|
|
|
|
/* input device use count */
|
|
static int hdaps_users;
|
|
static DEFINE_MUTEX(hdaps_users_mtx);
|
|
|
|
/* Some models require an axis transformation to the standard representation */
|
|
static void transform_axes(int *x, int *y)
|
|
{
|
|
if (hdaps_invert & HDAPS_ORIENT_SWAP) {
|
|
int z;
|
|
z = *x;
|
|
*x = *y;
|
|
*y = z;
|
|
}
|
|
if (hdaps_invert & HDAPS_ORIENT_INVERT_XY) {
|
|
*x = -*x;
|
|
*y = -*y;
|
|
}
|
|
if (hdaps_invert & HDAPS_ORIENT_INVERT_X)
|
|
*x = -*x;
|
|
}
|
|
|
|
/**
|
|
* __hdaps_update - query current state, with locks already acquired
|
|
* @fast: if nonzero, do one quick attempt without retries.
|
|
*
|
|
* Query current accelerometer state and update global state variables.
|
|
* Also prefetches the next query. Caller must hold controller lock.
|
|
*/
|
|
static int __hdaps_update(int fast)
|
|
{
|
|
/* Read data: */
|
|
struct thinkpad_ec_row data;
|
|
int ret;
|
|
|
|
data.mask = (1 << EC_ACCEL_IDX_READOUTS) | (1 << EC_ACCEL_IDX_KMACT) |
|
|
(3 << EC_ACCEL_IDX_YPOS1) | (3 << EC_ACCEL_IDX_XPOS1) |
|
|
(1 << EC_ACCEL_IDX_TEMP1) | (1 << EC_ACCEL_IDX_RETVAL);
|
|
if (fast)
|
|
ret = thinkpad_ec_try_read_row(&ec_accel_args, &data);
|
|
else
|
|
ret = thinkpad_ec_read_row(&ec_accel_args, &data);
|
|
thinkpad_ec_prefetch_row(&ec_accel_args); /* Prefetch even if error */
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Check status: */
|
|
if (data.val[EC_ACCEL_IDX_RETVAL] != 0x00) {
|
|
printk(KERN_WARNING "hdaps: read RETVAL=0x%02x\n",
|
|
data.val[EC_ACCEL_IDX_RETVAL]);
|
|
return -EIO;
|
|
}
|
|
|
|
if (data.val[EC_ACCEL_IDX_READOUTS] < 1)
|
|
return -EBUSY; /* no pending readout, try again later */
|
|
|
|
/* Parse position data: */
|
|
pos_x = *(s16 *)(data.val+EC_ACCEL_IDX_XPOS1);
|
|
pos_y = *(s16 *)(data.val+EC_ACCEL_IDX_YPOS1);
|
|
transform_axes(&pos_x, &pos_y);
|
|
|
|
/* Keyboard and mouse activity status is cleared as soon as it's read,
|
|
* so applications will eat each other's events. Thus we remember any
|
|
* event for KMACT_REMEMBER_PERIOD jiffies.
|
|
*/
|
|
if (data.val[EC_ACCEL_IDX_KMACT] & KEYBD_MASK)
|
|
last_keyboard_jiffies = get_jiffies_64();
|
|
if (data.val[EC_ACCEL_IDX_KMACT] & MOUSE_MASK)
|
|
last_mouse_jiffies = get_jiffies_64();
|
|
|
|
temperature = data.val[EC_ACCEL_IDX_TEMP1];
|
|
|
|
last_update_jiffies = get_jiffies_64();
|
|
stale_readout = 0;
|
|
if (needs_calibration) {
|
|
rest_x = pos_x;
|
|
rest_y = pos_y;
|
|
needs_calibration = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* hdaps_update - acquire locks and query current state
|
|
*
|
|
* Query current accelerometer state and update global state variables.
|
|
* Also prefetches the next query.
|
|
* Retries until timeout if the accelerometer is not in ready status (common).
|
|
* Does its own locking.
|
|
*/
|
|
static int hdaps_update(void)
|
|
{
|
|
u64 age = get_jiffies_64() - last_update_jiffies;
|
|
int total, ret;
|
|
|
|
if (!stale_readout && age < (9*HZ)/(10*sampling_rate))
|
|
return 0; /* already updated recently */
|
|
for (total = 0; total < READ_TIMEOUT_MSECS; total += RETRY_MSECS) {
|
|
ret = thinkpad_ec_lock();
|
|
if (ret)
|
|
return ret;
|
|
ret = __hdaps_update(0);
|
|
thinkpad_ec_unlock();
|
|
|
|
if (!ret)
|
|
return 0;
|
|
if (ret != -EBUSY)
|
|
break;
|
|
msleep(RETRY_MSECS);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* hdaps_set_power - enable or disable power to the accelerometer.
|
|
* Returns zero on success and negative error code on failure. Can sleep.
|
|
*/
|
|
static int hdaps_set_power(int on)
|
|
{
|
|
struct thinkpad_ec_row args =
|
|
{ .mask = 0x0003, .val = {0x14, on?0x01:0x00} };
|
|
struct thinkpad_ec_row data = { .mask = 0x8000 };
|
|
int ret = thinkpad_ec_read_row(&args, &data);
|
|
if (ret)
|
|
return ret;
|
|
if (data.val[0xF] != 0x00)
|
|
return -EIO;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* hdaps_set_ec_config - set accelerometer parameters.
|
|
* @ec_rate: embedded controller sampling rate
|
|
* @order: embedded controller running average filter order
|
|
* (Normally we have @ec_rate = sampling_rate * oversampling_ratio.)
|
|
* Returns zero on success and negative error code on failure. Can sleep.
|
|
*/
|
|
static int hdaps_set_ec_config(int ec_rate, int order)
|
|
{
|
|
struct thinkpad_ec_row args = { .mask = 0x000F,
|
|
.val = {0x10, (u8)ec_rate, (u8)(ec_rate>>8), order} };
|
|
struct thinkpad_ec_row data = { .mask = 0x8000 };
|
|
int ret = thinkpad_ec_read_row(&args, &data);
|
|
printk(KERN_DEBUG "hdaps: setting ec_rate=%d, filter_order=%d\n",
|
|
ec_rate, order);
|
|
if (ret)
|
|
return ret;
|
|
if (data.val[0xF] == 0x03) {
|
|
printk(KERN_WARNING "hdaps: config param out of range\n");
|
|
return -EINVAL;
|
|
}
|
|
if (data.val[0xF] == 0x06) {
|
|
printk(KERN_WARNING "hdaps: config change already pending\n");
|
|
return -EBUSY;
|
|
}
|
|
if (data.val[0xF] != 0x00) {
|
|
printk(KERN_WARNING "hdaps: config change error, ret=%d\n",
|
|
data.val[0xF]);
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* hdaps_get_ec_config - get accelerometer parameters.
|
|
* @ec_rate: embedded controller sampling rate
|
|
* @order: embedded controller running average filter order
|
|
* Returns zero on success and negative error code on failure. Can sleep.
|
|
*/
|
|
static int hdaps_get_ec_config(int *ec_rate, int *order)
|
|
{
|
|
const struct thinkpad_ec_row args =
|
|
{ .mask = 0x0003, .val = {0x17, 0x82} };
|
|
struct thinkpad_ec_row data = { .mask = 0x801F };
|
|
int ret = thinkpad_ec_read_row(&args, &data);
|
|
if (ret)
|
|
return ret;
|
|
if (data.val[0xF] != 0x00)
|
|
return -EIO;
|
|
if (!(data.val[0x1] & 0x01))
|
|
return -ENXIO; /* accelerometer polling not enabled */
|
|
if (data.val[0x1] & 0x02)
|
|
return -EBUSY; /* config change in progress, retry later */
|
|
*ec_rate = data.val[0x2] | ((int)(data.val[0x3]) << 8);
|
|
*order = data.val[0x4];
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* hdaps_get_ec_mode - get EC accelerometer mode
|
|
* Returns zero on success and negative error code on failure. Can sleep.
|
|
*/
|
|
static int hdaps_get_ec_mode(u8 *mode)
|
|
{
|
|
const struct thinkpad_ec_row args =
|
|
{ .mask = 0x0001, .val = {0x13} };
|
|
struct thinkpad_ec_row data = { .mask = 0x8002 };
|
|
int ret = thinkpad_ec_read_row(&args, &data);
|
|
if (ret)
|
|
return ret;
|
|
if (data.val[0xF] != 0x00) {
|
|
printk(KERN_WARNING
|
|
"accelerometer not implemented (0x%02x)\n",
|
|
data.val[0xF]);
|
|
return -EIO;
|
|
}
|
|
*mode = data.val[0x1];
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* hdaps_check_ec - checks something about the EC.
|
|
* Follows the clean-room spec for HDAPS; we don't know what it means.
|
|
* Returns zero on success and negative error code on failure. Can sleep.
|
|
*/
|
|
static int hdaps_check_ec(void)
|
|
{
|
|
const struct thinkpad_ec_row args =
|
|
{ .mask = 0x0003, .val = {0x17, 0x81} };
|
|
struct thinkpad_ec_row data = { .mask = 0x800E };
|
|
int ret = thinkpad_ec_read_row(&args, &data);
|
|
if (ret)
|
|
return ret;
|
|
if (!((data.val[0x1] == 0x00 && data.val[0x2] == 0x60) || /* cleanroom spec */
|
|
(data.val[0x1] == 0x01 && data.val[0x2] == 0x00)) || /* seen on T61 */
|
|
data.val[0x3] != 0x00 || data.val[0xF] != 0x00) {
|
|
printk(KERN_WARNING
|
|
"hdaps_check_ec: bad response (0x%x,0x%x,0x%x,0x%x)\n",
|
|
data.val[0x1], data.val[0x2],
|
|
data.val[0x3], data.val[0xF]);
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* hdaps_device_init - initialize the accelerometer.
|
|
*
|
|
* Call several embedded controller functions to test and initialize the
|
|
* accelerometer.
|
|
* Returns zero on success and negative error code on failure. Can sleep.
|
|
*/
|
|
#define FAILED_INIT(msg) printk(KERN_ERR "hdaps init failed at: %s\n", msg)
|
|
static int hdaps_device_init(void)
|
|
{
|
|
int ret;
|
|
u8 mode;
|
|
|
|
ret = thinkpad_ec_lock();
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (hdaps_get_ec_mode(&mode))
|
|
{ FAILED_INIT("hdaps_get_ec_mode failed"); goto bad; }
|
|
|
|
printk(KERN_DEBUG "hdaps: initial mode latch is 0x%02x\n", mode);
|
|
if (mode == 0x00)
|
|
{ FAILED_INIT("accelerometer not available"); goto bad; }
|
|
|
|
if (hdaps_check_ec())
|
|
{ FAILED_INIT("hdaps_check_ec failed"); goto bad; }
|
|
|
|
if (hdaps_set_power(1))
|
|
{ FAILED_INIT("hdaps_set_power failed"); goto bad; }
|
|
|
|
if (hdaps_set_ec_config(sampling_rate*oversampling_ratio,
|
|
running_avg_filter_order))
|
|
{ FAILED_INIT("hdaps_set_ec_config failed"); goto bad; }
|
|
|
|
thinkpad_ec_invalidate();
|
|
udelay(200);
|
|
|
|
/* Just prefetch instead of reading, to avoid ~1sec delay on load */
|
|
ret = thinkpad_ec_prefetch_row(&ec_accel_args);
|
|
if (ret)
|
|
{ FAILED_INIT("initial prefetch failed"); goto bad; }
|
|
goto good;
|
|
bad:
|
|
thinkpad_ec_invalidate();
|
|
ret = -ENXIO;
|
|
good:
|
|
stale_readout = 1;
|
|
thinkpad_ec_unlock();
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* hdaps_device_shutdown - power off the accelerometer
|
|
* Returns nonzero on failure. Can sleep.
|
|
*/
|
|
static int hdaps_device_shutdown(void)
|
|
{
|
|
int ret;
|
|
ret = hdaps_set_power(0);
|
|
if (ret) {
|
|
printk(KERN_WARNING "hdaps: cannot power off\n");
|
|
return ret;
|
|
}
|
|
ret = hdaps_set_ec_config(0, 1);
|
|
if (ret)
|
|
printk(KERN_WARNING "hdaps: cannot stop EC sampling\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Device model stuff */
|
|
|
|
static int hdaps_probe(struct platform_device *dev)
|
|
{
|
|
int ret;
|
|
|
|
ret = hdaps_device_init();
|
|
if (ret)
|
|
return ret;
|
|
|
|
printk(KERN_INFO "hdaps: device successfully initialized.\n");
|
|
return 0;
|
|
}
|
|
|
|
static int hdaps_suspend(struct platform_device *dev, pm_message_t state)
|
|
{
|
|
/* Don't do hdaps polls until resume re-initializes the sensor. */
|
|
del_timer_sync(&hdaps_timer);
|
|
hdaps_device_shutdown(); /* ignore errors, effect is negligible */
|
|
return 0;
|
|
}
|
|
|
|
static int hdaps_resume(struct platform_device *dev)
|
|
{
|
|
int ret = hdaps_device_init();
|
|
if (ret)
|
|
return ret;
|
|
|
|
mutex_lock(&hdaps_users_mtx);
|
|
if (hdaps_users)
|
|
mod_timer(&hdaps_timer, jiffies + HZ/sampling_rate);
|
|
mutex_unlock(&hdaps_users_mtx);
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver hdaps_driver = {
|
|
.probe = hdaps_probe,
|
|
.suspend = hdaps_suspend,
|
|
.resume = hdaps_resume,
|
|
.driver = {
|
|
.name = "hdaps",
|
|
.owner = THIS_MODULE,
|
|
},
|
|
};
|
|
|
|
/**
|
|
* hdaps_calibrate - set our "resting" values.
|
|
* Does its own locking.
|
|
*/
|
|
static void hdaps_calibrate(void)
|
|
{
|
|
needs_calibration = 1;
|
|
hdaps_update();
|
|
/* If that fails, the mousedev poll will take care of things later. */
|
|
}
|
|
|
|
/* Timer handler for updating the input device. Runs in softirq context,
|
|
* so avoid lenghty or blocking operations.
|
|
*/
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,15,0)
|
|
static void hdaps_mousedev_poll(unsigned long unused)
|
|
#else
|
|
static void hdaps_mousedev_poll(struct timer_list *unused)
|
|
#endif
|
|
{
|
|
int ret;
|
|
|
|
stale_readout = 1;
|
|
|
|
/* Cannot sleep. Try nonblockingly. If we fail, try again later. */
|
|
if (thinkpad_ec_try_lock())
|
|
goto keep_active;
|
|
|
|
ret = __hdaps_update(1); /* fast update, we're in softirq context */
|
|
thinkpad_ec_unlock();
|
|
/* Any of "successful", "not yet ready" and "not prefetched"? */
|
|
if (ret != 0 && ret != -EBUSY && ret != -ENODATA) {
|
|
printk(KERN_ERR
|
|
"hdaps: poll failed, disabling updates\n");
|
|
return;
|
|
}
|
|
|
|
keep_active:
|
|
/* Even if we failed now, pos_x,y may have been updated earlier: */
|
|
input_report_abs(hdaps_idev, ABS_X, pos_x - rest_x);
|
|
input_report_abs(hdaps_idev, ABS_Y, pos_y - rest_y);
|
|
input_sync(hdaps_idev);
|
|
input_report_abs(hdaps_idev_raw, ABS_X, pos_x);
|
|
input_report_abs(hdaps_idev_raw, ABS_Y, pos_y);
|
|
input_sync(hdaps_idev_raw);
|
|
mod_timer(&hdaps_timer, jiffies + HZ/sampling_rate);
|
|
}
|
|
|
|
|
|
/* Sysfs Files */
|
|
|
|
static ssize_t hdaps_position_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int ret = hdaps_update();
|
|
if (ret)
|
|
return ret;
|
|
return sprintf(buf, "(%d,%d)\n", pos_x, pos_y);
|
|
}
|
|
|
|
static ssize_t hdaps_temp1_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int ret = hdaps_update();
|
|
if (ret)
|
|
return ret;
|
|
return sprintf(buf, "%d\n", temperature);
|
|
}
|
|
|
|
static ssize_t hdaps_keyboard_activity_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
int ret = hdaps_update();
|
|
if (ret)
|
|
return ret;
|
|
return sprintf(buf, "%u\n",
|
|
get_jiffies_64() < last_keyboard_jiffies + KMACT_REMEMBER_PERIOD);
|
|
}
|
|
|
|
static ssize_t hdaps_mouse_activity_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
int ret = hdaps_update();
|
|
if (ret)
|
|
return ret;
|
|
return sprintf(buf, "%u\n",
|
|
get_jiffies_64() < last_mouse_jiffies + KMACT_REMEMBER_PERIOD);
|
|
}
|
|
|
|
static ssize_t hdaps_calibrate_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return sprintf(buf, "(%d,%d)\n", rest_x, rest_y);
|
|
}
|
|
|
|
static ssize_t hdaps_calibrate_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
hdaps_calibrate();
|
|
return count;
|
|
}
|
|
|
|
static ssize_t hdaps_invert_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return sprintf(buf, "%u\n", hdaps_invert);
|
|
}
|
|
|
|
static ssize_t hdaps_invert_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
int invert;
|
|
|
|
if (sscanf(buf, "%d", &invert) != 1 ||
|
|
invert < 0 || invert > HDAPS_ORIENT_MAX)
|
|
return -EINVAL;
|
|
|
|
hdaps_invert = invert;
|
|
hdaps_calibrate();
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t hdaps_sampling_rate_show(
|
|
struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
return sprintf(buf, "%d\n", sampling_rate);
|
|
}
|
|
|
|
static ssize_t hdaps_sampling_rate_store(
|
|
struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
int rate, ret;
|
|
if (sscanf(buf, "%d", &rate) != 1 || rate > HZ || rate <= 0) {
|
|
printk(KERN_WARNING
|
|
"must have 0<input_sampling_rate<=HZ=%d\n", HZ);
|
|
return -EINVAL;
|
|
}
|
|
ret = hdaps_set_ec_config(rate*oversampling_ratio,
|
|
running_avg_filter_order);
|
|
if (ret)
|
|
return ret;
|
|
sampling_rate = rate;
|
|
return count;
|
|
}
|
|
|
|
static ssize_t hdaps_oversampling_ratio_show(
|
|
struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
int ec_rate, order;
|
|
int ret = hdaps_get_ec_config(&ec_rate, &order);
|
|
if (ret)
|
|
return ret;
|
|
return sprintf(buf, "%u\n", ec_rate / sampling_rate);
|
|
}
|
|
|
|
static ssize_t hdaps_oversampling_ratio_store(
|
|
struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
int ratio, ret;
|
|
if (sscanf(buf, "%d", &ratio) != 1 || ratio < 1)
|
|
return -EINVAL;
|
|
ret = hdaps_set_ec_config(sampling_rate*ratio,
|
|
running_avg_filter_order);
|
|
if (ret)
|
|
return ret;
|
|
oversampling_ratio = ratio;
|
|
return count;
|
|
}
|
|
|
|
static ssize_t hdaps_running_avg_filter_order_show(
|
|
struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
int rate, order;
|
|
int ret = hdaps_get_ec_config(&rate, &order);
|
|
if (ret)
|
|
return ret;
|
|
return sprintf(buf, "%u\n", order);
|
|
}
|
|
|
|
static ssize_t hdaps_running_avg_filter_order_store(
|
|
struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
int order, ret;
|
|
if (sscanf(buf, "%d", &order) != 1)
|
|
return -EINVAL;
|
|
ret = hdaps_set_ec_config(sampling_rate*oversampling_ratio, order);
|
|
if (ret)
|
|
return ret;
|
|
running_avg_filter_order = order;
|
|
return count;
|
|
}
|
|
|
|
static int hdaps_mousedev_open(struct input_dev *dev)
|
|
{
|
|
if (!try_module_get(THIS_MODULE))
|
|
return -ENODEV;
|
|
|
|
mutex_lock(&hdaps_users_mtx);
|
|
if (hdaps_users++ == 0) /* first input user */
|
|
mod_timer(&hdaps_timer, jiffies + HZ/sampling_rate);
|
|
mutex_unlock(&hdaps_users_mtx);
|
|
return 0;
|
|
}
|
|
|
|
static void hdaps_mousedev_close(struct input_dev *dev)
|
|
{
|
|
mutex_lock(&hdaps_users_mtx);
|
|
if (--hdaps_users == 0) /* no input users left */
|
|
del_timer_sync(&hdaps_timer);
|
|
mutex_unlock(&hdaps_users_mtx);
|
|
|
|
module_put(THIS_MODULE);
|
|
}
|
|
|
|
static DEVICE_ATTR(position, 0444, hdaps_position_show, NULL);
|
|
static DEVICE_ATTR(temp1, 0444, hdaps_temp1_show, NULL);
|
|
/* "temp1" instead of "temperature" is hwmon convention */
|
|
static DEVICE_ATTR(keyboard_activity, 0444,
|
|
hdaps_keyboard_activity_show, NULL);
|
|
static DEVICE_ATTR(mouse_activity, 0444, hdaps_mouse_activity_show, NULL);
|
|
static DEVICE_ATTR(calibrate, 0644,
|
|
hdaps_calibrate_show, hdaps_calibrate_store);
|
|
static DEVICE_ATTR(invert, 0644, hdaps_invert_show, hdaps_invert_store);
|
|
static DEVICE_ATTR(sampling_rate, 0644,
|
|
hdaps_sampling_rate_show, hdaps_sampling_rate_store);
|
|
static DEVICE_ATTR(oversampling_ratio, 0644,
|
|
hdaps_oversampling_ratio_show,
|
|
hdaps_oversampling_ratio_store);
|
|
static DEVICE_ATTR(running_avg_filter_order, 0644,
|
|
hdaps_running_avg_filter_order_show,
|
|
hdaps_running_avg_filter_order_store);
|
|
|
|
static struct attribute *hdaps_attributes[] = {
|
|
&dev_attr_position.attr,
|
|
&dev_attr_temp1.attr,
|
|
&dev_attr_keyboard_activity.attr,
|
|
&dev_attr_mouse_activity.attr,
|
|
&dev_attr_calibrate.attr,
|
|
&dev_attr_invert.attr,
|
|
&dev_attr_sampling_rate.attr,
|
|
&dev_attr_oversampling_ratio.attr,
|
|
&dev_attr_running_avg_filter_order.attr,
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group hdaps_attribute_group = {
|
|
.attrs = hdaps_attributes,
|
|
};
|
|
|
|
|
|
/* Module stuff */
|
|
|
|
/* hdaps_dmi_match_invert - found an inverted match. */
|
|
static int __init hdaps_dmi_match_invert(const struct dmi_system_id *id)
|
|
{
|
|
unsigned int orient = (kernel_ulong_t) id->driver_data;
|
|
hdaps_invert = orient;
|
|
printk(KERN_INFO "hdaps: %s detected, setting orientation %u\n",
|
|
id->ident, orient);
|
|
return 1; /* stop enumeration */
|
|
}
|
|
|
|
#define HDAPS_DMI_MATCH_INVERT(vendor, model, orient) { \
|
|
.ident = vendor " " model, \
|
|
.callback = hdaps_dmi_match_invert, \
|
|
.driver_data = (void *)(orient), \
|
|
.matches = { \
|
|
DMI_MATCH(DMI_BOARD_VENDOR, vendor), \
|
|
DMI_MATCH(DMI_PRODUCT_VERSION, model) \
|
|
} \
|
|
}
|
|
|
|
/* List of models with abnormal axis configuration.
|
|
Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
|
|
"ThinkPad T42p", and enumeration stops after first match,
|
|
so the order of the entries matters. */
|
|
struct dmi_system_id __initconst hdaps_whitelist[] = {
|
|
HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p", HDAPS_ORIENT_INVERT_XY),
|
|
HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R60", HDAPS_ORIENT_INVERT_XY),
|
|
HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p", HDAPS_ORIENT_INVERT_XY),
|
|
HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p", HDAPS_ORIENT_INVERT_XY),
|
|
HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X40", HDAPS_ORIENT_INVERT_Y),
|
|
HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X41", HDAPS_ORIENT_INVERT_Y),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R60", HDAPS_ORIENT_INVERT_XY),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61", HDAPS_ORIENT_INVERT_XY),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R400", HDAPS_ORIENT_INVERT_XY),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R500", HDAPS_ORIENT_INVERT_XY),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60", HDAPS_ORIENT_INVERT_XY),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61", HDAPS_ORIENT_INVERT_XY),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60 Tablet", HDAPS_ORIENT_INVERT_Y),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60s", HDAPS_ORIENT_INVERT_Y),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400s", HDAPS_ORIENT_INVERT_X),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400", HDAPS_ORIENT_INVERT_XY),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T410s", HDAPS_ORIENT_SWAP),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T410", HDAPS_ORIENT_INVERT_XY),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T500", HDAPS_ORIENT_INVERT_XY),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T510", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X | HDAPS_ORIENT_INVERT_Y),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad W510", HDAPS_ORIENT_MAX),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad W520", HDAPS_ORIENT_MAX),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X200s", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_XY),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X200", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X | HDAPS_ORIENT_INVERT_Y),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X201 Tablet", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_XY),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X201s", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_XY),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X201", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X),
|
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X220", HDAPS_ORIENT_SWAP),
|
|
{ .ident = NULL }
|
|
};
|
|
|
|
static int __init hdaps_init(void)
|
|
{
|
|
int ret;
|
|
|
|
/* Determine axis orientation orientation */
|
|
if (hdaps_invert == HDAPS_ORIENT_UNDEFINED) /* set by module param? */
|
|
if (dmi_check_system(hdaps_whitelist) < 1) /* in whitelist? */
|
|
hdaps_invert = 0; /* default */
|
|
|
|
/* Init timer before platform_driver_register, in case of suspend */
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,15,0)
|
|
init_timer(&hdaps_timer);
|
|
hdaps_timer.function = hdaps_mousedev_poll;
|
|
#else
|
|
timer_setup(&hdaps_timer, hdaps_mousedev_poll, 0);
|
|
#endif
|
|
ret = platform_driver_register(&hdaps_driver);
|
|
if (ret)
|
|
goto out;
|
|
|
|
pdev = platform_device_register_simple("hdaps", -1, NULL, 0);
|
|
if (IS_ERR(pdev)) {
|
|
ret = PTR_ERR(pdev);
|
|
goto out_driver;
|
|
}
|
|
|
|
ret = sysfs_create_group(&pdev->dev.kobj, &hdaps_attribute_group);
|
|
if (ret)
|
|
goto out_device;
|
|
|
|
hdaps_idev = input_allocate_device();
|
|
if (!hdaps_idev) {
|
|
ret = -ENOMEM;
|
|
goto out_group;
|
|
}
|
|
|
|
hdaps_idev_raw = input_allocate_device();
|
|
if (!hdaps_idev_raw) {
|
|
ret = -ENOMEM;
|
|
goto out_idev_first;
|
|
}
|
|
|
|
/* calibration for the input device (deferred to avoid delay) */
|
|
needs_calibration = 1;
|
|
|
|
/* initialize the joystick-like fuzzed input device */
|
|
hdaps_idev->name = "ThinkPad HDAPS joystick emulation";
|
|
hdaps_idev->phys = "hdaps/input0";
|
|
hdaps_idev->id.bustype = BUS_HOST;
|
|
hdaps_idev->id.vendor = HDAPS_INPUT_VENDOR;
|
|
hdaps_idev->id.product = HDAPS_INPUT_PRODUCT;
|
|
hdaps_idev->id.version = HDAPS_INPUT_JS_VERSION;
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
|
|
hdaps_idev->cdev.dev = &pdev->dev;
|
|
#endif
|
|
hdaps_idev->evbit[0] = BIT(EV_ABS);
|
|
hdaps_idev->open = hdaps_mousedev_open;
|
|
hdaps_idev->close = hdaps_mousedev_close;
|
|
input_set_abs_params(hdaps_idev, ABS_X,
|
|
-256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
|
|
input_set_abs_params(hdaps_idev, ABS_Y,
|
|
-256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
|
|
|
|
ret = input_register_device(hdaps_idev);
|
|
if (ret)
|
|
goto out_idev;
|
|
|
|
/* initialize the raw data input device */
|
|
hdaps_idev_raw->name = "ThinkPad HDAPS accelerometer data";
|
|
hdaps_idev_raw->phys = "hdaps/input1";
|
|
hdaps_idev_raw->id.bustype = BUS_HOST;
|
|
hdaps_idev_raw->id.vendor = HDAPS_INPUT_VENDOR;
|
|
hdaps_idev_raw->id.product = HDAPS_INPUT_PRODUCT;
|
|
hdaps_idev_raw->id.version = HDAPS_INPUT_RAW_VERSION;
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
|
|
hdaps_idev_raw->cdev.dev = &pdev->dev;
|
|
#endif
|
|
hdaps_idev_raw->evbit[0] = BIT(EV_ABS);
|
|
hdaps_idev_raw->open = hdaps_mousedev_open;
|
|
hdaps_idev_raw->close = hdaps_mousedev_close;
|
|
input_set_abs_params(hdaps_idev_raw, ABS_X, -32768, 32767, 0, 0);
|
|
input_set_abs_params(hdaps_idev_raw, ABS_Y, -32768, 32767, 0, 0);
|
|
|
|
ret = input_register_device(hdaps_idev_raw);
|
|
if (ret)
|
|
goto out_idev_reg_first;
|
|
|
|
printk(KERN_INFO "hdaps: driver successfully loaded.\n");
|
|
return 0;
|
|
|
|
out_idev_reg_first:
|
|
input_unregister_device(hdaps_idev);
|
|
out_idev:
|
|
input_free_device(hdaps_idev_raw);
|
|
out_idev_first:
|
|
input_free_device(hdaps_idev);
|
|
out_group:
|
|
sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
|
|
out_device:
|
|
platform_device_unregister(pdev);
|
|
out_driver:
|
|
platform_driver_unregister(&hdaps_driver);
|
|
hdaps_device_shutdown();
|
|
out:
|
|
printk(KERN_WARNING "hdaps: driver init failed (ret=%d)!\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
static void __exit hdaps_exit(void)
|
|
{
|
|
input_unregister_device(hdaps_idev_raw);
|
|
input_unregister_device(hdaps_idev);
|
|
hdaps_device_shutdown(); /* ignore errors, effect is negligible */
|
|
sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
|
|
platform_device_unregister(pdev);
|
|
platform_driver_unregister(&hdaps_driver);
|
|
|
|
printk(KERN_INFO "hdaps: driver unloaded.\n");
|
|
}
|
|
|
|
module_init(hdaps_init);
|
|
module_exit(hdaps_exit);
|
|
|
|
module_param_named(invert, hdaps_invert, uint, 0);
|
|
MODULE_PARM_DESC(invert, "axis orientation code");
|
|
|
|
MODULE_AUTHOR("Robert Love");
|
|
MODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver");
|
|
MODULE_LICENSE("GPL v2");
|