linux/drivers/platform/x86/hdaps.c
<<
>>
Prefs
   1/*
   2 * hdaps.c - driver for IBM's Hard Drive Active Protection System
   3 *
   4 * Copyright (C) 2005 Robert Love <rml@novell.com>
   5 * Copyright (C) 2005 Jesper Juhl <jesper.juhl@gmail.com>
   6 *
   7 * The HardDisk Active Protection System (hdaps) is present in IBM ThinkPads
   8 * starting with the R40, T41, and X40.  It provides a basic two-axis
   9 * accelerometer and other data, such as the device's temperature.
  10 *
  11 * This driver is based on the document by Mark A. Smith available at
  12 * http://www.almaden.ibm.com/cs/people/marksmith/tpaps.html and a lot of trial
  13 * and error.
  14 *
  15 * This program is free software; you can redistribute it and/or modify it
  16 * under the terms of the GNU General Public License v2 as published by the
  17 * Free Software Foundation.
  18 *
  19 * This program is distributed in the hope that it will be useful, but WITHOUT
  20 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  21 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  22 * more details.
  23 *
  24 * You should have received a copy of the GNU General Public License along with
  25 * this program; if not, write to the Free Software Foundation, Inc.,
  26 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
  27 */
  28
  29#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  30
  31#include <linux/delay.h>
  32#include <linux/platform_device.h>
  33#include <linux/input-polldev.h>
  34#include <linux/kernel.h>
  35#include <linux/mutex.h>
  36#include <linux/module.h>
  37#include <linux/timer.h>
  38#include <linux/dmi.h>
  39#include <linux/jiffies.h>
  40#include <linux/io.h>
  41
  42#define HDAPS_LOW_PORT          0x1600  /* first port used by hdaps */
  43#define HDAPS_NR_PORTS          0x30    /* number of ports: 0x1600 - 0x162f */
  44
  45#define HDAPS_PORT_STATE        0x1611  /* device state */
  46#define HDAPS_PORT_YPOS         0x1612  /* y-axis position */
  47#define HDAPS_PORT_XPOS         0x1614  /* x-axis position */
  48#define HDAPS_PORT_TEMP1        0x1616  /* device temperature, in Celsius */
  49#define HDAPS_PORT_YVAR         0x1617  /* y-axis variance (what is this?) */
  50#define HDAPS_PORT_XVAR         0x1619  /* x-axis variance (what is this?) */
  51#define HDAPS_PORT_TEMP2        0x161b  /* device temperature (again?) */
  52#define HDAPS_PORT_UNKNOWN      0x161c  /* what is this? */
  53#define HDAPS_PORT_KMACT        0x161d  /* keyboard or mouse activity */
  54
  55#define STATE_FRESH             0x50    /* accelerometer data is fresh */
  56
  57#define KEYBD_MASK              0x20    /* set if keyboard activity */
  58#define MOUSE_MASK              0x40    /* set if mouse activity */
  59#define KEYBD_ISSET(n)          (!! (n & KEYBD_MASK))   /* keyboard used? */
  60#define MOUSE_ISSET(n)          (!! (n & MOUSE_MASK))   /* mouse used? */
  61
  62#define INIT_TIMEOUT_MSECS      4000    /* wait up to 4s for device init ... */
  63#define INIT_WAIT_MSECS         200     /* ... in 200ms increments */
  64
  65#define HDAPS_POLL_INTERVAL     50      /* poll for input every 1/20s (50 ms)*/
  66#define HDAPS_INPUT_FUZZ        4       /* input event threshold */
  67#define HDAPS_INPUT_FLAT        4
  68
  69#define HDAPS_X_AXIS            (1 << 0)
  70#define HDAPS_Y_AXIS            (1 << 1)
  71#define HDAPS_BOTH_AXES         (HDAPS_X_AXIS | HDAPS_Y_AXIS)
  72
  73static struct platform_device *pdev;
  74static struct input_polled_dev *hdaps_idev;
  75static unsigned int hdaps_invert;
  76static u8 km_activity;
  77static int rest_x;
  78static int rest_y;
  79
  80static DEFINE_MUTEX(hdaps_mtx);
  81
  82/*
  83 * __get_latch - Get the value from a given port.  Callers must hold hdaps_mtx.
  84 */
  85static inline u8 __get_latch(u16 port)
  86{
  87        return inb(port) & 0xff;
  88}
  89
  90/*
  91 * __check_latch - Check a port latch for a given value.  Returns zero if the
  92 * port contains the given value.  Callers must hold hdaps_mtx.
  93 */
  94static inline int __check_latch(u16 port, u8 val)
  95{
  96        if (__get_latch(port) == val)
  97                return 0;
  98        return -EINVAL;
  99}
 100
 101/*
 102 * __wait_latch - Wait up to 100us for a port latch to get a certain value,
 103 * returning zero if the value is obtained.  Callers must hold hdaps_mtx.
 104 */
 105static int __wait_latch(u16 port, u8 val)
 106{
 107        unsigned int i;
 108
 109        for (i = 0; i < 20; i++) {
 110                if (!__check_latch(port, val))
 111                        return 0;
 112                udelay(5);
 113        }
 114
 115        return -EIO;
 116}
 117
 118/*
 119 * __device_refresh - request a refresh from the accelerometer.  Does not wait
 120 * for refresh to complete.  Callers must hold hdaps_mtx.
 121 */
 122static void __device_refresh(void)
 123{
 124        udelay(200);
 125        if (inb(0x1604) != STATE_FRESH) {
 126                outb(0x11, 0x1610);
 127                outb(0x01, 0x161f);
 128        }
 129}
 130
 131/*
 132 * __device_refresh_sync - request a synchronous refresh from the
 133 * accelerometer.  We wait for the refresh to complete.  Returns zero if
 134 * successful and nonzero on error.  Callers must hold hdaps_mtx.
 135 */
 136static int __device_refresh_sync(void)
 137{
 138        __device_refresh();
 139        return __wait_latch(0x1604, STATE_FRESH);
 140}
 141
 142/*
 143 * __device_complete - indicate to the accelerometer that we are done reading
 144 * data, and then initiate an async refresh.  Callers must hold hdaps_mtx.
 145 */
 146static inline void __device_complete(void)
 147{
 148        inb(0x161f);
 149        inb(0x1604);
 150        __device_refresh();
 151}
 152
 153/*
 154 * hdaps_readb_one - reads a byte from a single I/O port, placing the value in
 155 * the given pointer.  Returns zero on success or a negative error on failure.
 156 * Can sleep.
 157 */
 158static int hdaps_readb_one(unsigned int port, u8 *val)
 159{
 160        int ret;
 161
 162        mutex_lock(&hdaps_mtx);
 163
 164        /* do a sync refresh -- we need to be sure that we read fresh data */
 165        ret = __device_refresh_sync();
 166        if (ret)
 167                goto out;
 168
 169        *val = inb(port);
 170        __device_complete();
 171
 172out:
 173        mutex_unlock(&hdaps_mtx);
 174        return ret;
 175}
 176
 177/* __hdaps_read_pair - internal lockless helper for hdaps_read_pair(). */
 178static int __hdaps_read_pair(unsigned int port1, unsigned int port2,
 179                             int *x, int *y)
 180{
 181        /* do a sync refresh -- we need to be sure that we read fresh data */
 182        if (__device_refresh_sync())
 183                return -EIO;
 184
 185        *y = inw(port2);
 186        *x = inw(port1);
 187        km_activity = inb(HDAPS_PORT_KMACT);
 188        __device_complete();
 189
 190        /* hdaps_invert is a bitvector to negate the axes */
 191        if (hdaps_invert & HDAPS_X_AXIS)
 192                *x = -*x;
 193        if (hdaps_invert & HDAPS_Y_AXIS)
 194                *y = -*y;
 195
 196        return 0;
 197}
 198
 199/*
 200 * hdaps_read_pair - reads the values from a pair of ports, placing the values
 201 * in the given pointers.  Returns zero on success.  Can sleep.
 202 */
 203static int hdaps_read_pair(unsigned int port1, unsigned int port2,
 204                           int *val1, int *val2)
 205{
 206        int ret;
 207
 208        mutex_lock(&hdaps_mtx);
 209        ret = __hdaps_read_pair(port1, port2, val1, val2);
 210        mutex_unlock(&hdaps_mtx);
 211
 212        return ret;
 213}
 214
 215/*
 216 * hdaps_device_init - initialize the accelerometer.  Returns zero on success
 217 * and negative error code on failure.  Can sleep.
 218 */
 219static int hdaps_device_init(void)
 220{
 221        int total, ret = -ENXIO;
 222
 223        mutex_lock(&hdaps_mtx);
 224
 225        outb(0x13, 0x1610);
 226        outb(0x01, 0x161f);
 227        if (__wait_latch(0x161f, 0x00))
 228                goto out;
 229
 230        /*
 231         * Most ThinkPads return 0x01.
 232         *
 233         * Others--namely the R50p, T41p, and T42p--return 0x03.  These laptops
 234         * have "inverted" axises.
 235         *
 236         * The 0x02 value occurs when the chip has been previously initialized.
 237         */
 238        if (__check_latch(0x1611, 0x03) &&
 239                     __check_latch(0x1611, 0x02) &&
 240                     __check_latch(0x1611, 0x01))
 241                goto out;
 242
 243        printk(KERN_DEBUG "hdaps: initial latch check good (0x%02x)\n",
 244               __get_latch(0x1611));
 245
 246        outb(0x17, 0x1610);
 247        outb(0x81, 0x1611);
 248        outb(0x01, 0x161f);
 249        if (__wait_latch(0x161f, 0x00))
 250                goto out;
 251        if (__wait_latch(0x1611, 0x00))
 252                goto out;
 253        if (__wait_latch(0x1612, 0x60))
 254                goto out;
 255        if (__wait_latch(0x1613, 0x00))
 256                goto out;
 257        outb(0x14, 0x1610);
 258        outb(0x01, 0x1611);
 259        outb(0x01, 0x161f);
 260        if (__wait_latch(0x161f, 0x00))
 261                goto out;
 262        outb(0x10, 0x1610);
 263        outb(0xc8, 0x1611);
 264        outb(0x00, 0x1612);
 265        outb(0x02, 0x1613);
 266        outb(0x01, 0x161f);
 267        if (__wait_latch(0x161f, 0x00))
 268                goto out;
 269        if (__device_refresh_sync())
 270                goto out;
 271        if (__wait_latch(0x1611, 0x00))
 272                goto out;
 273
 274        /* we have done our dance, now let's wait for the applause */
 275        for (total = INIT_TIMEOUT_MSECS; total > 0; total -= INIT_WAIT_MSECS) {
 276                int x, y;
 277
 278                /* a read of the device helps push it into action */
 279                __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
 280                if (!__wait_latch(0x1611, 0x02)) {
 281                        ret = 0;
 282                        break;
 283                }
 284
 285                msleep(INIT_WAIT_MSECS);
 286        }
 287
 288out:
 289        mutex_unlock(&hdaps_mtx);
 290        return ret;
 291}
 292
 293
 294/* Device model stuff */
 295
 296static int hdaps_probe(struct platform_device *dev)
 297{
 298        int ret;
 299
 300        ret = hdaps_device_init();
 301        if (ret)
 302                return ret;
 303
 304        pr_info("device successfully initialized\n");
 305        return 0;
 306}
 307
 308static int hdaps_resume(struct platform_device *dev)
 309{
 310        return hdaps_device_init();
 311}
 312
 313static struct platform_driver hdaps_driver = {
 314        .probe = hdaps_probe,
 315        .resume = hdaps_resume,
 316        .driver = {
 317                .name = "hdaps",
 318                .owner = THIS_MODULE,
 319        },
 320};
 321
 322/*
 323 * hdaps_calibrate - Set our "resting" values.  Callers must hold hdaps_mtx.
 324 */
 325static void hdaps_calibrate(void)
 326{
 327        __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &rest_x, &rest_y);
 328}
 329
 330static void hdaps_mousedev_poll(struct input_polled_dev *dev)
 331{
 332        struct input_dev *input_dev = dev->input;
 333        int x, y;
 334
 335        mutex_lock(&hdaps_mtx);
 336
 337        if (__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y))
 338                goto out;
 339
 340        input_report_abs(input_dev, ABS_X, x - rest_x);
 341        input_report_abs(input_dev, ABS_Y, y - rest_y);
 342        input_sync(input_dev);
 343
 344out:
 345        mutex_unlock(&hdaps_mtx);
 346}
 347
 348
 349/* Sysfs Files */
 350
 351static ssize_t hdaps_position_show(struct device *dev,
 352                                   struct device_attribute *attr, char *buf)
 353{
 354        int ret, x, y;
 355
 356        ret = hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
 357        if (ret)
 358                return ret;
 359
 360        return sprintf(buf, "(%d,%d)\n", x, y);
 361}
 362
 363static ssize_t hdaps_variance_show(struct device *dev,
 364                                   struct device_attribute *attr, char *buf)
 365{
 366        int ret, x, y;
 367
 368        ret = hdaps_read_pair(HDAPS_PORT_XVAR, HDAPS_PORT_YVAR, &x, &y);
 369        if (ret)
 370                return ret;
 371
 372        return sprintf(buf, "(%d,%d)\n", x, y);
 373}
 374
 375static ssize_t hdaps_temp1_show(struct device *dev,
 376                                struct device_attribute *attr, char *buf)
 377{
 378        u8 uninitialized_var(temp);
 379        int ret;
 380
 381        ret = hdaps_readb_one(HDAPS_PORT_TEMP1, &temp);
 382        if (ret)
 383                return ret;
 384
 385        return sprintf(buf, "%u\n", temp);
 386}
 387
 388static ssize_t hdaps_temp2_show(struct device *dev,
 389                                struct device_attribute *attr, char *buf)
 390{
 391        u8 uninitialized_var(temp);
 392        int ret;
 393
 394        ret = hdaps_readb_one(HDAPS_PORT_TEMP2, &temp);
 395        if (ret)
 396                return ret;
 397
 398        return sprintf(buf, "%u\n", temp);
 399}
 400
 401static ssize_t hdaps_keyboard_activity_show(struct device *dev,
 402                                            struct device_attribute *attr,
 403                                            char *buf)
 404{
 405        return sprintf(buf, "%u\n", KEYBD_ISSET(km_activity));
 406}
 407
 408static ssize_t hdaps_mouse_activity_show(struct device *dev,
 409                                         struct device_attribute *attr,
 410                                         char *buf)
 411{
 412        return sprintf(buf, "%u\n", MOUSE_ISSET(km_activity));
 413}
 414
 415static ssize_t hdaps_calibrate_show(struct device *dev,
 416                                    struct device_attribute *attr, char *buf)
 417{
 418        return sprintf(buf, "(%d,%d)\n", rest_x, rest_y);
 419}
 420
 421static ssize_t hdaps_calibrate_store(struct device *dev,
 422                                     struct device_attribute *attr,
 423                                     const char *buf, size_t count)
 424{
 425        mutex_lock(&hdaps_mtx);
 426        hdaps_calibrate();
 427        mutex_unlock(&hdaps_mtx);
 428
 429        return count;
 430}
 431
 432static ssize_t hdaps_invert_show(struct device *dev,
 433                                 struct device_attribute *attr, char *buf)
 434{
 435        return sprintf(buf, "%u\n", hdaps_invert);
 436}
 437
 438static ssize_t hdaps_invert_store(struct device *dev,
 439                                  struct device_attribute *attr,
 440                                  const char *buf, size_t count)
 441{
 442        int invert;
 443
 444        if (sscanf(buf, "%d", &invert) != 1 ||
 445            invert < 0 || invert > HDAPS_BOTH_AXES)
 446                return -EINVAL;
 447
 448        hdaps_invert = invert;
 449        hdaps_calibrate();
 450
 451        return count;
 452}
 453
 454static DEVICE_ATTR(position, 0444, hdaps_position_show, NULL);
 455static DEVICE_ATTR(variance, 0444, hdaps_variance_show, NULL);
 456static DEVICE_ATTR(temp1, 0444, hdaps_temp1_show, NULL);
 457static DEVICE_ATTR(temp2, 0444, hdaps_temp2_show, NULL);
 458static DEVICE_ATTR(keyboard_activity, 0444, hdaps_keyboard_activity_show, NULL);
 459static DEVICE_ATTR(mouse_activity, 0444, hdaps_mouse_activity_show, NULL);
 460static DEVICE_ATTR(calibrate, 0644, hdaps_calibrate_show,hdaps_calibrate_store);
 461static DEVICE_ATTR(invert, 0644, hdaps_invert_show, hdaps_invert_store);
 462
 463static struct attribute *hdaps_attributes[] = {
 464        &dev_attr_position.attr,
 465        &dev_attr_variance.attr,
 466        &dev_attr_temp1.attr,
 467        &dev_attr_temp2.attr,
 468        &dev_attr_keyboard_activity.attr,
 469        &dev_attr_mouse_activity.attr,
 470        &dev_attr_calibrate.attr,
 471        &dev_attr_invert.attr,
 472        NULL,
 473};
 474
 475static struct attribute_group hdaps_attribute_group = {
 476        .attrs = hdaps_attributes,
 477};
 478
 479
 480/* Module stuff */
 481
 482/* hdaps_dmi_match - found a match.  return one, short-circuiting the hunt. */
 483static int __init hdaps_dmi_match(const struct dmi_system_id *id)
 484{
 485        pr_info("%s detected\n", id->ident);
 486        return 1;
 487}
 488
 489/* hdaps_dmi_match_invert - found an inverted match. */
 490static int __init hdaps_dmi_match_invert(const struct dmi_system_id *id)
 491{
 492        hdaps_invert = (unsigned long)id->driver_data;
 493        pr_info("inverting axis (%u) readings\n", hdaps_invert);
 494        return hdaps_dmi_match(id);
 495}
 496
 497#define HDAPS_DMI_MATCH_INVERT(vendor, model, axes) {   \
 498        .ident = vendor " " model,                      \
 499        .callback = hdaps_dmi_match_invert,             \
 500        .driver_data = (void *)axes,                    \
 501        .matches = {                                    \
 502                DMI_MATCH(DMI_BOARD_VENDOR, vendor),    \
 503                DMI_MATCH(DMI_PRODUCT_VERSION, model)   \
 504        }                                               \
 505}
 506
 507#define HDAPS_DMI_MATCH_NORMAL(vendor, model)           \
 508        HDAPS_DMI_MATCH_INVERT(vendor, model, 0)
 509
 510/* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
 511   "ThinkPad T42p", so the order of the entries matters.
 512   If your ThinkPad is not recognized, please update to latest
 513   BIOS. This is especially the case for some R52 ThinkPads. */
 514static struct dmi_system_id __initdata hdaps_whitelist[] = {
 515        HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p", HDAPS_BOTH_AXES),
 516        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R50"),
 517        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R51"),
 518        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R52"),
 519        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61i", HDAPS_BOTH_AXES),
 520        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61", HDAPS_BOTH_AXES),
 521        HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p", HDAPS_BOTH_AXES),
 522        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T41"),
 523        HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p", HDAPS_BOTH_AXES),
 524        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T42"),
 525        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T43"),
 526        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400", HDAPS_BOTH_AXES),
 527        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60", HDAPS_BOTH_AXES),
 528        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61p", HDAPS_BOTH_AXES),
 529        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61", HDAPS_BOTH_AXES),
 530        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X40"),
 531        HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X41", HDAPS_Y_AXIS),
 532        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60", HDAPS_BOTH_AXES),
 533        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61s", HDAPS_BOTH_AXES),
 534        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61", HDAPS_BOTH_AXES),
 535        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad Z60m"),
 536        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61m", HDAPS_BOTH_AXES),
 537        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61p", HDAPS_BOTH_AXES),
 538        { .ident = NULL }
 539};
 540
 541static int __init hdaps_init(void)
 542{
 543        struct input_dev *idev;
 544        int ret;
 545
 546        if (!dmi_check_system(hdaps_whitelist)) {
 547                pr_warn("supported laptop not found!\n");
 548                ret = -ENODEV;
 549                goto out;
 550        }
 551
 552        if (!request_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS, "hdaps")) {
 553                ret = -ENXIO;
 554                goto out;
 555        }
 556
 557        ret = platform_driver_register(&hdaps_driver);
 558        if (ret)
 559                goto out_region;
 560
 561        pdev = platform_device_register_simple("hdaps", -1, NULL, 0);
 562        if (IS_ERR(pdev)) {
 563                ret = PTR_ERR(pdev);
 564                goto out_driver;
 565        }
 566
 567        ret = sysfs_create_group(&pdev->dev.kobj, &hdaps_attribute_group);
 568        if (ret)
 569                goto out_device;
 570
 571        hdaps_idev = input_allocate_polled_device();
 572        if (!hdaps_idev) {
 573                ret = -ENOMEM;
 574                goto out_group;
 575        }
 576
 577        hdaps_idev->poll = hdaps_mousedev_poll;
 578        hdaps_idev->poll_interval = HDAPS_POLL_INTERVAL;
 579
 580        /* initial calibrate for the input device */
 581        hdaps_calibrate();
 582
 583        /* initialize the input class */
 584        idev = hdaps_idev->input;
 585        idev->name = "hdaps";
 586        idev->phys = "isa1600/input0";
 587        idev->id.bustype = BUS_ISA;
 588        idev->dev.parent = &pdev->dev;
 589        idev->evbit[0] = BIT_MASK(EV_ABS);
 590        input_set_abs_params(idev, ABS_X,
 591                        -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
 592        input_set_abs_params(idev, ABS_Y,
 593                        -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
 594
 595        ret = input_register_polled_device(hdaps_idev);
 596        if (ret)
 597                goto out_idev;
 598
 599        pr_info("driver successfully loaded\n");
 600        return 0;
 601
 602out_idev:
 603        input_free_polled_device(hdaps_idev);
 604out_group:
 605        sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
 606out_device:
 607        platform_device_unregister(pdev);
 608out_driver:
 609        platform_driver_unregister(&hdaps_driver);
 610out_region:
 611        release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
 612out:
 613        pr_warn("driver init failed (ret=%d)!\n", ret);
 614        return ret;
 615}
 616
 617static void __exit hdaps_exit(void)
 618{
 619        input_unregister_polled_device(hdaps_idev);
 620        input_free_polled_device(hdaps_idev);
 621        sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
 622        platform_device_unregister(pdev);
 623        platform_driver_unregister(&hdaps_driver);
 624        release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
 625
 626        pr_info("driver unloaded\n");
 627}
 628
 629module_init(hdaps_init);
 630module_exit(hdaps_exit);
 631
 632module_param_named(invert, hdaps_invert, int, 0);
 633MODULE_PARM_DESC(invert, "invert data along each axis. 1 invert x-axis, "
 634                 "2 invert y-axis, 3 invert both axes.");
 635
 636MODULE_AUTHOR("Robert Love");
 637MODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver");
 638MODULE_LICENSE("GPL v2");
 639