tp_smapi/hdaps.c
Jan Nordholz 76c5120f7b Adapt HDAPS driver to use the new timer_setup() interface.
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
2017-12-27 14:41:34 +01:00

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");