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);
 230
 231TRACKPOINT_BIT_ATTR(press_to_select, TP_TOGGLE_PTSON, TP_MASK_PTSON, 0,
 232                    TP_DEF_PTSON);
 233TRACKPOINT_BIT_ATTR(skipback, TP_TOGGLE_SKIPBACK, TP_MASK_SKIPBACK, 0,
 234                    TP_DEF_SKIPBACK);
 235TRACKPOINT_BIT_ATTR(ext_dev, TP_TOGGLE_EXT_DEV, TP_MASK_EXT_DEV, 1,
 236                    TP_DEF_EXT_DEV);
 237
 238static struct attribute *trackpoint_attrs[] = {
 239        &psmouse_attr_sensitivity.dattr.attr,
 240        &psmouse_attr_speed.dattr.attr,
 241        &psmouse_attr_inertia.dattr.attr,
 242        &psmouse_attr_reach.dattr.attr,
 243        &psmouse_attr_draghys.dattr.attr,
 244        &psmouse_attr_mindrag.dattr.attr,
 245        &psmouse_attr_thresh.dattr.attr,
 246        &psmouse_attr_upthresh.dattr.attr,
 247        &psmouse_attr_ztime.dattr.attr,
 248        &psmouse_attr_jenks.dattr.attr,
 249        &psmouse_attr_press_to_select.dattr.attr,
 250        &psmouse_attr_skipback.dattr.attr,
 251        &psmouse_attr_ext_dev.dattr.attr,
 252        NULL
 253};
 254
 255static struct attribute_group trackpoint_attr_group = {
 256        .attrs = trackpoint_attrs,
 257};
 258
 259static int trackpoint_start_protocol(struct psmouse *psmouse, unsigned char *firmware_id)
 260{
 261        unsigned char param[2] = { 0 };
 262
 263        if (ps2_command(&psmouse->ps2dev, param, MAKE_PS2_CMD(0, 2, TP_READ_ID)))
 264                return -1;
 265
 266        if (param[0] != TP_MAGIC_IDENT)
 267                return -1;
 268
 269        if (firmware_id)
 270                *firmware_id = param[1];
 271
 272        return 0;
 273}
 274
 275/*
 276 * Write parameters to trackpad.
 277 * in_power_on_state: Set to true if TP is in default / power-on state (ex. if
 278 *                    power-on reset was run). If so, values will only be
 279 *                    written to TP if they differ from power-on default.
 280 */
 281static int trackpoint_sync(struct psmouse *psmouse, bool in_power_on_state)
 282{
 283        struct trackpoint_data *tp = psmouse->private;
 284
 285        if (!in_power_on_state) {
 286                /*
 287                 * Disable features that may make device unusable
 288                 * with this driver.
 289                 */
 290                trackpoint_update_bit(&psmouse->ps2dev, TP_TOGGLE_TWOHAND,
 291                                      TP_MASK_TWOHAND, TP_DEF_TWOHAND);
 292
 293                trackpoint_update_bit(&psmouse->ps2dev, TP_TOGGLE_SOURCE_TAG,
 294                                      TP_MASK_SOURCE_TAG, TP_DEF_SOURCE_TAG);
 295
 296                trackpoint_update_bit(&psmouse->ps2dev, TP_TOGGLE_MB,
 297                                      TP_MASK_MB, TP_DEF_MB);
 298        }
 299
 300        /*
 301         * These properties can be changed in this driver. Only
 302         * configure them if the values are non-default or if the TP is in
 303         * an unknown state.
 304         */
 305        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, sensitivity);
 306        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, inertia);
 307        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, speed);
 308        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, reach);
 309        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, draghys);
 310        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, mindrag);
 311        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, thresh);
 312        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, upthresh);
 313        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, ztime);
 314        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, jenks);
 315
 316        /* toggles */
 317        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, press_to_select);
 318        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, skipback);
 319        TRACKPOINT_UPDATE(in_power_on_state, psmouse, tp, ext_dev);
 320
 321        return 0;
 322}
 323
 324static void trackpoint_defaults(struct trackpoint_data *tp)
 325{
 326        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, sensitivity);
 327        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, speed);
 328        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, reach);
 329        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, draghys);
 330        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, mindrag);
 331        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, thresh);
 332        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, upthresh);
 333        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, ztime);
 334        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, jenks);
 335        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, inertia);
 336
 337        /* toggles */
 338        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, press_to_select);
 339        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, skipback);
 340        TRACKPOINT_SET_POWER_ON_DEFAULT(tp, ext_dev);
 341}
 342
 343static void trackpoint_disconnect(struct psmouse *psmouse)
 344{
 345        sysfs_remove_group(&psmouse->ps2dev.serio->dev.kobj, &trackpoint_attr_group);
 346
 347        kfree(psmouse->private);
 348        psmouse->private = NULL;
 349}
 350
 351static int trackpoint_reconnect(struct psmouse *psmouse)
 352{
 353        int reset_fail;
 354
 355        if (trackpoint_start_protocol(psmouse, NULL))
 356                return -1;
 357
 358        reset_fail = trackpoint_power_on_reset(&psmouse->ps2dev);
 359        if (trackpoint_sync(psmouse, !reset_fail))
 360                return -1;
 361
 362        return 0;
 363}
 364
 365int trackpoint_detect(struct psmouse *psmouse, bool set_properties)
 366{
 367        struct ps2dev *ps2dev = &psmouse->ps2dev;
 368        unsigned char firmware_id;
 369        unsigned char button_info;
 370        int error;
 371
 372        if (trackpoint_start_protocol(psmouse, &firmware_id))
 373                return -1;
 374
 375        if (!set_properties)
 376                return 0;
 377
 378        if (trackpoint_read(&psmouse->ps2dev, TP_EXT_BTN, &button_info)) {
 379                psmouse_warn(psmouse, "failed to get extended button data\n");
 380                button_info = 0;
 381        }
 382
 383        psmouse->private = kzalloc(sizeof(struct trackpoint_data), GFP_KERNEL);
 384        if (!psmouse->private)
 385                return -ENOMEM;
 386
 387        psmouse->vendor = "IBM";
 388        psmouse->name = "TrackPoint";
 389
 390        psmouse->reconnect = trackpoint_reconnect;
 391        psmouse->disconnect = trackpoint_disconnect;
 392
 393        if ((button_info & 0x0f) >= 3)
 394                __set_bit(BTN_MIDDLE, psmouse->dev->keybit);
 395
 396        trackpoint_defaults(psmouse->private);
 397
 398        error = trackpoint_power_on_reset(&psmouse->ps2dev);
 399
 400        /* Write defaults to TP only if reset fails. */
 401        if (error)
 402                trackpoint_sync(psmouse, false);
 403
 404        error = sysfs_create_group(&ps2dev->serio->dev.kobj, &trackpoint_attr_group);
 405        if (error) {
 406                psmouse_err(psmouse,
 407                            "failed to create sysfs attributes, error: %d\n",
 408                            error);
 409                kfree(psmouse->private);
 410                psmouse->private = NULL;
 411                return -1;
 412        }
 413
 414        psmouse_info(psmouse,
 415                     "IBM TrackPoint firmware: 0x%02x, buttons: %d/%d\n",
 416                     firmware_id,
 417                     (button_info & 0xf0) >> 4, button_info & 0x0f);
 418
 419        return 0;
 420}
 421
 422