linux/drivers/input/mouse/trackpoint.c
<<
>>
Prefs
   1/*
   2 * Stephen Evanchik <evanchsa@gmail.com>
   3 *
   4 * This program is free software; you can redistribute it and/or modify it
   5 * under the terms of the GNU General Public License version 2 as published by
   6 * the Free Software Foundation.
   7 *
   8 * Trademarks are the property of their respective owners.
   9 */
  10
  11#include <linux/slab.h>
  12#include <linux/delay.h>
  13#include <linux/serio.h>
  14#include <linux/module.h>
  15#include <linux/input.h>
  16#include <linux/libps2.h>
  17#include <linux/proc_fs.h>
  18#include <asm/uaccess.h>
  19#include "psmouse.h"
  20#include "trackpoint.h"
  21
  22/*
  23 * Power-on Reset: Resets all trackpoint parameters, including RAM values,
  24 * to defaults.
  25 * Returns zero on success, non-zero on failure.
  26 */
  27static int trackpoint_power_on_reset(struct ps2dev *ps2dev)
  28{
  29        unsigned char results[2];
  30        int tries = 0;
  31
  32        /* Issue POR command, and repeat up to once if 0xFC00 received */
  33        do {
  34                if (ps2_command(ps2dev, NULL, MAKE_PS2_CMD(0, 0, TP_COMMAND)) ||
  35                    ps2_command(ps2dev, results, MAKE_PS2_CMD(0, 2, TP_POR)))
  36                        return -1;
  37        } while (results[0] == 0xFC && results[1] == 0x00 && ++tries < 2);
  38
  39        /* Check for success response -- 0xAA00 */
  40        if (results[0] != 0xAA || results[1] != 0x00)
  41                return -1;
  42
  43        return 0;
  44}
  45
  46/*
  47 * Device IO: read, write and toggle bit
  48 */
  49static int trackpoint_read(struct ps2dev *ps2dev,
  50                           unsigned char loc, unsigned char *results)
  51{
  52        if (ps2_command(ps2dev, NULL, MAKE_PS2_CMD(0, 0, TP_COMMAND)) ||
  53            ps2_command(ps2dev, results, MAKE_PS2_CMD(0, 1, loc))) {
  54                return -1;
  55        }
  56
  57        return 0;
  58}
  59
  60static int trackpoint_write(struct ps2dev *ps2dev,
  61                            unsigned char loc, unsigned char val)
  62{
  63        if (ps2_command(ps2dev, NULL, MAKE_PS2_CMD(0, 0, TP_COMMAND)) ||
  64            ps2_command(ps2dev, NULL, MAKE_PS2_CMD(0, 0, TP_WRITE_MEM)) ||
  65            ps2_command(ps2dev, NULL, MAKE_PS2_CMD(0, 0, loc)) ||
  66            ps2_command(ps2dev, NULL, MAKE_PS2_CMD(0, 0, val))) {
  67                return -1;
  68        }
  69
  70        return 0;
  71}
  72
  73static int trackpoint_toggle_bit(struct ps2dev *ps2dev,
  74                                 unsigned char loc, unsigned char mask)
  75{
  76        /* Bad things will happen if the loc param isn't in this range */
  77        if (loc < 0x20 || loc >= 0x2F)
  78                return -1;
  79
  80        if (ps2_command(ps2dev, NULL, MAKE_PS2_CMD(0, 0, TP_COMMAND)) ||
  81            ps2_command(ps2dev, NULL, MAKE_PS2_CMD(0, 0, TP_TOGGLE)) ||
  82            ps2_command(ps2dev, NULL, MAKE_PS2_CMD(0, 0, loc)) ||
  83            ps2_command(ps2dev, NULL, MAKE_PS2_CMD(0, 0, mask))) {
  84                return -1;
  85        }
  86
  87        return 0;
  88}
  89
  90static int trackpoint_update_bit(struct ps2dev *ps2dev, unsigned char loc,
  91                                 unsigned char mask, unsigned char value)
  92{
  93        int retval = 0;
  94        unsigned char data;
  95
  96        trackpoint_read(ps2dev, loc, &data);
  97        if (((data & mask) == mask) != !!value)
  98                retval = trackpoint_toggle_bit(ps2dev, loc, mask);
  99
 100        return retval;
 101}
 102
 103/*
 104 * Trackpoint-specific attributes
 105 */
 106struct trackpoint_attr_data {
 107        size_t field_offset;
 108        unsigned char command;
 109        unsigned char mask;
 110        unsigned char inverted;
 111        unsigned char power_on_default;
 112};
 113
 114static ssize_t trackpoint_show_int_attr(struct psmouse *psmouse, void *data, char *buf)
 115{
 116        struct trackpoint_data *tp = psmouse->private;
 117        struct trackpoint_attr_data *attr = data;
 118        unsigned char value = *(unsigned char *)((char *)tp + attr->field_offset);
 119
 120        if (attr->inverted)
 121                value = !value;
 122
 123        return sprintf(buf, "%u\n", value);
 124}
 125
 126static ssize_t trackpoint_set_int_attr(struct psmouse *psmouse, void *data,
 127                                        const char *buf, size_t count)
 128{
 129        struct trackpoint_data *tp = psmouse->private;
 130        struct trackpoint_attr_data *attr = data;
 131        unsigned char *field = (unsigned char *)((char *)tp + attr->field_offset);
 132        unsigned char value;
 133        int err;
 134
 135        err = kstrtou8(buf, 10, &value);
 136        if (err)
 137                return err;
 138
 139        *field = value;
 140        trackpoint_write(&psmouse->ps2dev, attr->command, value);
 141
 142        return count;
 143}
 144
 145#define TRACKPOINT_INT_ATTR(_name, _command, _default)                          \
 146        static struct trackpoint_attr_data trackpoint_attr_##_name = {          \
 147                .field_offset = offsetof(struct trackpoint_data, _name),        \
 148                .command = _command,                                            \
 149                .power_on_default = _default,                                   \
 150        };                                                                      \
 151        PSMOUSE_DEFINE_ATTR(_name, S_IWUSR | S_IRUGO,                           \
 152                            &trackpoint_attr_##_name,                           \
 153                            trackpoint_show_int_attr, trackpoint_set_int_attr)
 154
 155static ssize_t trackpoint_set_bit_attr(struct psmouse *psmouse, void *data,
 156                                        const char *buf, size_t count)
 157{
 158        struct trackpoint_data *tp = psmouse->private;
 159        struct trackpoint_attr_data *attr = data;
 160        unsigned char *field = (unsigned char *)((char *)tp + attr->field_offset);
 161        unsigned int value;
 162        int err;
 163
 164        err = kstrtouint(buf, 10, &value);
 165        if (err)
 166                return err;
 167
 168        if (value > 1)
 169                return -EINVAL;
 170
 171        if (attr->inverted)
 172                value = !value;
 173
 174        if (*field != value) {
 175                *field = value;
 176                trackpoint_toggle_bit(&psmouse->ps2dev, attr->command, attr->mask);
 177        }
 178
 179        return count;
 180}
 181
 182
 183#define TRACKPOINT_BIT_ATTR(_name, _command, _mask, _inv, _default)     \
 184static struct trackpoint_attr_data trackpoint_attr_##_name = {          \
 185        .field_offset           = offsetof(struct trackpoint_data,      \
 186                                           _name),                      \
 187        .command                = _command,                             \
 188        .mask                   = _mask,                                \
 189        .inverted               = _inv,                                 \
 190        .power_on_default       = _default,                             \
 191        };                                                              \
 192PSMOUSE_DEFINE_ATTR(_name, S_IWUSR | S_IRUGO,                           \
 193                    &trackpoint_attr_##_name,                           \
 194                    trackpoint_show_int_attr, trackpoint_set_bit_attr)
 195
 196#define TRACKPOINT_UPDATE_BIT(_psmouse, _tp, _name)                     \
 197do {                                                                    \
 198        struct trackpoint_attr_data *_attr = &trackpoint_attr_##_name;  \
 199                                                                        \
 200        trackpoint_update_bit(&_psmouse->ps2dev,                        \
 201                        _attr->command, _attr->mask, _tp->_name);       \
 202} while (0)
 203
 204#define TRACKPOINT_UPDATE(_power_on, _psmouse, _tp, _name)              \
 205do {                                                                    \
 206        if (!_power_on ||                                               \
 207            _tp->_name != trackpoint_attr_##_name.power_on_default) {   \
 208                if (!trackpoint_attr_##_name.mask)                      \
 209                        trackpoint_write(&_psmouse->ps2dev,             \
 210                                 trackpoint_attr_##_name.command,       \
 211                                 _tp->_name);                           \
 212                else                                                    \
 213                        TRACKPOINT_UPDATE_BIT(_psmouse, _tp, _name);    \
 214        }                                                               \
 215} while (0)
 216
 217#define TRACKPOINT_SET_POWER_ON_DEFAULT(_tp, _name)                             \
 218        (_tp->_name = trackpoint_attr_##_name.power_on_default)
 219
 220TRACKPOINT_INT_ATTR(sensitivity, TP_SENS, TP_DEF_SENS);
 221TRACKPOINT_INT_ATTR(speed, TP_SPEED, TP_DEF_SPEED);
 222TRACKPOINT_INT_ATTR(inertia, TP_INERTIA, TP_DEF_INERTIA);
 223TRACKPOINT_INT_ATTR(reach, TP_REACH, TP_DEF_REACH);
 224TRACKPOINT_INT_ATTR(draghys, TP_DRAGHYS, TP_DEF_DRAGHYS);
 225TRACKPOINT_INT_ATTR(mindrag, TP_MINDRAG, TP_DEF_MINDRAG);
 226TRACKPOINT_INT_ATTR(thresh, TP_THRESH, TP_DEF_THRESH);
 227TRACKPOINT_INT_ATTR(upthresh, TP_UP_THRESH, TP_DEF_UP_THRESH);
 228TRACKPOINT_INT_ATTR(ztime, TP_Z_TIME, TP_DEF_Z_TIME);
 229TRACKPOINT_INT_ATTR(jenks, TP_JENKS_CURV, TP_DEF_JENKS_CURV);
 230TRACKPOINT_INT_ATTR(drift_time, TP_DRIFT_TIME, TP_DEF_DRIFT_TIME);
 231
 232TRACKPOINT_BIT_ATTR(press_to_select, TP_TOGGLE_PTSON, TP_MASK_PTSON, 0,
 233                    TP_DEF_PTSON);
 234TRACKPOINT_BIT_ATTR(skipback, TP_TOGGLE_SKIPBACK, TP_MASK_SKIPBACK, 0,
 235                    TP_DEF_SKIPBACK);
 236TRACKPOINT_BIT_ATTR(ext_dev, TP_TOGGLE_EXT_DEV, TP_MASK_EXT_DEV, 1,
 237                    TP_DEF_EXT_DEV);
 238
 239static struct attribute *trackpoint_attrs[] = {
 240        &psmouse_attr_sensitivity.dattr.attr,
 241        &psmouse_attr_speed.dattr.attr,
 242        &psmouse_attr_inertia.dattr.attr,
 243        &psmouse_attr_reach.dattr.attr,
 244        &psmouse_attr_draghys.dattr.attr,
 245        &psmouse_attr_mindrag.dattr.attr,
 246        &psmouse_attr_thresh.dattr.attr,
 247        &psmouse_attr_upthresh.dattr.attr,
 248        &psmouse_attr_ztime.dattr.attr,
 249        &psmouse_attr_jenks.dattr.attr,
 250        &psmouse_attr_drift_time.dattr.attr,
 251        &psmouse_attr_press_to_select.dattr.attr,
 252        &psmouse_attr_skipback.dattr.attr,
 253        &psmouse_attr_ext_dev.dattr.attr,
 254        NULL
 255};
 256
 257static struct attribute_group trackpoint_attr_group = {
 258        .attrs = trackpoint_attrs,
 259};
 260
 261static int trackpoint_start_protocol(struct psmouse *psmouse, unsigned char *firmware_id)
 262{
 263        unsigned char param[2] = { 0 };
 264
 265        if (ps2_command(&psmouse->ps2dev, param, MAKE_PS2_CMD(0, 2, TP_READ_ID)))
 266                return -1;
 267
 268        if (param[0] != TP_MAGIC_IDENT)
 269                return -1;
 270
 271        if (firmware_id)
 272                *firmware_id = param[1];
 273
 274        return 0;
 275}
 276
 277/*
 278 * Write parameters to trackpad.
 279 * in_power_on_state: Set to true if TP is in default / power-on state (ex. if
 280 *                    power-on reset was run). If so, values will only be
 281 *                    written to TP if they differ from power-on default.
 282 */
 283static int trackpoint_sync(struct psmouse *psmouse, bool in_power_on_state)
 284{
 285        struct trackpoint_data *tp = psmouse->private;
 286
 287        if (!in_power_on_state) {
 288                /*
 289                 * Disable features that may make device unusable
 290                 * with this driver.
 291                 */
 292                trackpoint_update_bit(&psmouse->ps2dev, TP_TOGGLE_TWOHAND,
 293                                      TP_MASK_TWOHAND, TP_DEF_TWOHAND);
 294
 295                trackpoint_update_bit(&psmouse->ps2dev, TP_TOGGLE_SOURCE_TAG,
 296                                      TP_MASK_SOURCE_TAG, TP_DEF_SOURCE_TAG);
 297
 298                trackpoint_update_bit(&psmouse->ps2dev, TP_TOGGLE_MB,
 299                                      TP_MASK_MB, TP_DEF_MB);
 300        }
 301
 302        /*
 303         * These properties can be changed in this driver. Only
 304         * configure them if the values are non-default or if the TP is in
 305         * an unknown state.
 306         */
 307        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, sensitivity);
 308        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, inertia);
 309        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, speed);
 310        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, reach);
 311        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, draghys);
 312        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, mindrag);
 313        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, thresh);
 314        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, upthresh);
 315        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, ztime);
 316        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, jenks);
 317        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, drift_time);
 318
 319        /* toggles */
 320        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, press_to_select);
 321        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, skipback);
 322        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, ext_dev);
 323
 324        return 0;
 325}
 326
 327static void trackpoint_defaults(struct trackpoint_data *tp)
 328{
 329        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, sensitivity);
 330        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, speed);
 331        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, reach);
 332        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, draghys);
 333        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, mindrag);
 334        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, thresh);
 335        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, upthresh);
 336        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, ztime);
 337        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, jenks);
 338        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, drift_time);
 339        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, inertia);
 340
 341        /* toggles */
 342        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, press_to_select);
 343        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, skipback);
 344        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, ext_dev);
 345}
 346
 347static void trackpoint_disconnect(struct psmouse *psmouse)
 348{
 349        sysfs_remove_group(&psmouse->ps2dev.serio->dev.kobj, &trackpoint_attr_group);
 350
 351        kfree(psmouse->private);
 352        psmouse->private = NULL;
 353}
 354
 355static int trackpoint_reconnect(struct psmouse *psmouse)
 356{
 357        int reset_fail;
 358
 359        if (trackpoint_start_protocol(psmouse, NULL))
 360                return -1;
 361
 362        reset_fail = trackpoint_power_on_reset(&psmouse->ps2dev);
 363        if (trackpoint_sync(psmouse, !reset_fail))
 364                return -1;
 365
 366        return 0;
 367}
 368
 369int trackpoint_detect(struct psmouse *psmouse, bool set_properties)
 370{
 371        struct ps2dev *ps2dev = &psmouse->ps2dev;
 372        unsigned char firmware_id;
 373        unsigned char button_info;
 374        int error;
 375
 376        if (trackpoint_start_protocol(psmouse, &firmware_id))
 377                return -1;
 378
 379        if (!set_properties)
 380                return 0;
 381
 382        if (trackpoint_read(&psmouse->ps2dev, TP_EXT_BTN, &button_info)) {
 383                psmouse_warn(psmouse, "failed to get extended button data\n");
 384                button_info = 0;
 385        }
 386
 387        psmouse->private = kzalloc(sizeof(struct trackpoint_data), GFP_KERNEL);
 388        if (!psmouse->private)
 389                return -ENOMEM;
 390
 391        psmouse->vendor = "IBM";
 392        psmouse->name = "TrackPoint";
 393
 394        psmouse->reconnect = trackpoint_reconnect;
 395        psmouse->disconnect = trackpoint_disconnect;
 396
 397        if ((button_info & 0x0f) >= 3)
 398                __set_bit(BTN_MIDDLE, psmouse->dev->keybit);
 399
 400        __set_bit(INPUT_PROP_POINTER, psmouse->dev->propbit);
 401        __set_bit(INPUT_PROP_POINTING_STICK, psmouse->dev->propbit);
 402
 403        trackpoint_defaults(psmouse->private);
 404
 405        error = trackpoint_power_on_reset(&psmouse->ps2dev);
 406
 407        /* Write defaults to TP only if reset fails. */
 408        if (error)
 409                trackpoint_sync(psmouse, false);
 410
 411        error = sysfs_create_group(&ps2dev->serio->dev.kobj, &trackpoint_attr_group);
 412        if (error) {
 413                psmouse_err(psmouse,
 414                            "failed to create sysfs attributes, error: %d\n",
 415                            error);
 416                kfree(psmouse->private);
 417                psmouse->private = NULL;
 418                return -1;
 419        }
 420
 421        psmouse_info(psmouse,
 422                     "IBM TrackPoint firmware: 0x%02x, buttons: %d/%d\n",
 423                     firmware_id,
 424                     (button_info & 0xf0) >> 4, button_info & 0x0f);
 425
 426        return 0;
 427}
 428
 429