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 <jj@chaosbits.net>
   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
 308#ifdef CONFIG_PM_SLEEP
 309static int hdaps_resume(struct device *dev)
 310{
 311        return hdaps_device_init();
 312}
 313#endif
 314
 315static SIMPLE_DEV_PM_OPS(hdaps_pm, NULL, hdaps_resume);
 316
 317static struct platform_driver hdaps_driver = {
 318        .probe = hdaps_probe,
 319        .driver = {
 320                .name = "hdaps",
 321                .pm = &hdaps_pm,
 322        },
 323};
 324
 325/*
 326 * hdaps_calibrate - Set our "resting" values.  Callers must hold hdaps_mtx.
 327 */
 328static void hdaps_calibrate(void)
 329{
 330        __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &rest_x, &rest_y);
 331}
 332
 333static void hdaps_mousedev_poll(struct input_polled_dev *dev)
 334{
 335        struct input_dev *input_dev = dev->input;
 336        int x, y;
 337
 338        mutex_lock(&hdaps_mtx);
 339
 340        if (__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y))
 341                goto out;
 342
 343        input_report_abs(input_dev, ABS_X, x - rest_x);
 344        input_report_abs(input_dev, ABS_Y, y - rest_y);
 345        input_sync(input_dev);
 346
 347out:
 348        mutex_unlock(&hdaps_mtx);
 349}
 350
 351
 352/* Sysfs Files */
 353
 354static ssize_t hdaps_position_show(struct device *dev,
 355                                   struct device_attribute *attr, char *buf)
 356{
 357        int ret, x, y;
 358
 359        ret = hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
 360        if (ret)
 361                return ret;
 362
 363        return sprintf(buf, "(%d,%d)\n", x, y);
 364}
 365
 366static ssize_t hdaps_variance_show(struct device *dev,
 367                                   struct device_attribute *attr, char *buf)
 368{
 369        int ret, x, y;
 370
 371        ret = hdaps_read_pair(HDAPS_PORT_XVAR, HDAPS_PORT_YVAR, &x, &y);
 372        if (ret)
 373                return ret;
 374
 375        return sprintf(buf, "(%d,%d)\n", x, y);
 376}
 377
 378static ssize_t hdaps_temp1_show(struct device *dev,
 379                                struct device_attribute *attr, char *buf)
 380{
 381        u8 uninitialized_var(temp);
 382        int ret;
 383
 384        ret = hdaps_readb_one(HDAPS_PORT_TEMP1, &temp);
 385        if (ret)
 386                return ret;
 387
 388        return sprintf(buf, "%u\n", temp);
 389}
 390
 391static ssize_t hdaps_temp2_show(struct device *dev,
 392                                struct device_attribute *attr, char *buf)
 393{
 394        u8 uninitialized_var(temp);
 395        int ret;
 396
 397        ret = hdaps_readb_one(HDAPS_PORT_TEMP2, &temp);
 398        if (ret)
 399                return ret;
 400
 401        return sprintf(buf, "%u\n", temp);
 402}
 403
 404static ssize_t hdaps_keyboard_activity_show(struct device *dev,
 405                                            struct device_attribute *attr,
 406                                            char *buf)
 407{
 408        return sprintf(buf, "%u\n", KEYBD_ISSET(km_activity));
 409}
 410
 411static ssize_t hdaps_mouse_activity_show(struct device *dev,
 412                                         struct device_attribute *attr,
 413                                         char *buf)
 414{
 415        return sprintf(buf, "%u\n", MOUSE_ISSET(km_activity));
 416}
 417
 418static ssize_t hdaps_calibrate_show(struct device *dev,
 419                                    struct device_attribute *attr, char *buf)
 420{
 421        return sprintf(buf, "(%d,%d)\n", rest_x, rest_y);
 422}
 423
 424static ssize_t hdaps_calibrate_store(struct device *dev,
 425                                     struct device_attribute *attr,
 426                                     const char *buf, size_t count)
 427{
 428        mutex_lock(&hdaps_mtx);
 429        hdaps_calibrate();
 430        mutex_unlock(&hdaps_mtx);
 431
 432        return count;
 433}
 434
 435static ssize_t hdaps_invert_show(struct device *dev,
 436                                 struct device_attribute *attr, char *buf)
 437{
 438        return sprintf(buf, "%u\n", hdaps_invert);
 439}
 440
 441static ssize_t hdaps_invert_store(struct device *dev,
 442                                  struct device_attribute *attr,
 443                                  const char *buf, size_t count)
 444{
 445        int invert;
 446
 447        if (sscanf(buf, "%d", &invert) != 1 ||
 448            invert < 0 || invert > HDAPS_BOTH_AXES)
 449                return -EINVAL;
 450
 451        hdaps_invert = invert;
 452        hdaps_calibrate();
 453
 454        return count;
 455}
 456
 457static DEVICE_ATTR(position, 0444, hdaps_position_show, NULL);
 458static DEVICE_ATTR(variance, 0444, hdaps_variance_show, NULL);
 459static DEVICE_ATTR(temp1, 0444, hdaps_temp1_show, NULL);
 460static DEVICE_ATTR(temp2, 0444, hdaps_temp2_show, NULL);
 461static DEVICE_ATTR(keyboard_activity, 0444, hdaps_keyboard_activity_show, NULL);
 462static DEVICE_ATTR(mouse_activity, 0444, hdaps_mouse_activity_show, NULL);
 463static DEVICE_ATTR(calibrate, 0644, hdaps_calibrate_show,hdaps_calibrate_store);
 464static DEVICE_ATTR(invert, 0644, hdaps_invert_show, hdaps_invert_store);
 465
 466static struct attribute *hdaps_attributes[] = {
 467        &dev_attr_position.attr,
 468        &dev_attr_variance.attr,
 469        &dev_attr_temp1.attr,
 470        &dev_attr_temp2.attr,
 471        &dev_attr_keyboard_activity.attr,
 472        &dev_attr_mouse_activity.attr,
 473        &dev_attr_calibrate.attr,
 474        &dev_attr_invert.attr,
 475        NULL,
 476};
 477
 478static struct attribute_group hdaps_attribute_group = {
 479        .attrs = hdaps_attributes,
 480};
 481
 482
 483/* Module stuff */
 484
 485/* hdaps_dmi_match - found a match.  return one, short-circuiting the hunt. */
 486static int __init hdaps_dmi_match(const struct dmi_system_id *id)
 487{
 488        pr_info("%s detected\n", id->ident);
 489        return 1;
 490}
 491
 492/* hdaps_dmi_match_invert - found an inverted match. */
 493static int __init hdaps_dmi_match_invert(const struct dmi_system_id *id)
 494{
 495        hdaps_invert = (unsigned long)id->driver_data;
 496        pr_info("inverting axis (%u) readings\n", hdaps_invert);
 497        return hdaps_dmi_match(id);
 498}
 499
 500#define HDAPS_DMI_MATCH_INVERT(vendor, model, axes) {   \
 501        .ident = vendor " " model,                      \
 502        .callback = hdaps_dmi_match_invert,             \
 503        .driver_data = (void *)axes,                    \
 504        .matches = {                                    \
 505                DMI_MATCH(DMI_BOARD_VENDOR, vendor),    \
 506                DMI_MATCH(DMI_PRODUCT_VERSION, model)   \
 507        }                                               \
 508}
 509
 510#define HDAPS_DMI_MATCH_NORMAL(vendor, model)           \
 511        HDAPS_DMI_MATCH_INVERT(vendor, model, 0)
 512
 513/* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
 514   "ThinkPad T42p", so the order of the entries matters.
 515   If your ThinkPad is not recognized, please update to latest
 516   BIOS. This is especially the case for some R52 ThinkPads. */
 517static struct dmi_system_id __initdata hdaps_whitelist[] = {
 518        HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p", HDAPS_BOTH_AXES),
 519        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R50"),
 520        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R51"),
 521        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R52"),
 522        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61i", HDAPS_BOTH_AXES),
 523        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61", HDAPS_BOTH_AXES),
 524        HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p", HDAPS_BOTH_AXES),
 525        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T41"),
 526        HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p", HDAPS_BOTH_AXES),
 527        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T42"),
 528        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T43"),
 529        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400", HDAPS_BOTH_AXES),
 530        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60", HDAPS_BOTH_AXES),
 531        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61p", HDAPS_BOTH_AXES),
 532        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61", HDAPS_BOTH_AXES),
 533        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X40"),
 534        HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X41", HDAPS_Y_AXIS),
 535        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60", HDAPS_BOTH_AXES),
 536        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61s", HDAPS_BOTH_AXES),
 537        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61", HDAPS_BOTH_AXES),
 538        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad Z60m"),
 539        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61m", HDAPS_BOTH_AXES),
 540        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61p", HDAPS_BOTH_AXES),
 541        { .ident = NULL }
 542};
 543
 544static int __init hdaps_init(void)
 545{
 546        struct input_dev *idev;
 547        int ret;
 548
 549        if (!dmi_check_system(hdaps_whitelist)) {
 550                pr_warn("supported laptop not found!\n");
 551                ret = -ENODEV;
 552                goto out;
 553        }
 554
 555        if (!request_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS, "hdaps")) {
 556                ret = -ENXIO;
 557                goto out;
 558        }
 559
 560        ret = platform_driver_register(&hdaps_driver);
 561        if (ret)
 562                goto out_region;
 563
 564        pdev = platform_device_register_simple("hdaps", -1, NULL, 0);
 565        if (IS_ERR(pdev)) {
 566                ret = PTR_ERR(pdev);
 567                goto out_driver;
 568        }
 569
 570        ret = sysfs_create_group(&pdev->dev.kobj, &hdaps_attribute_group);
 571        if (ret)
 572                goto out_device;
 573
 574        hdaps_idev = input_allocate_polled_device();
 575        if (!hdaps_idev) {
 576                ret = -ENOMEM;
 577                goto out_group;
 578        }
 579
 580        hdaps_idev->poll = hdaps_mousedev_poll;
 581        hdaps_idev->poll_interval = HDAPS_POLL_INTERVAL;
 582
 583        /* initial calibrate for the input device */
 584        hdaps_calibrate();
 585
 586        /* initialize the input class */
 587        idev = hdaps_idev->input;
 588        idev->name = "hdaps";
 589        idev->phys = "isa1600/input0";
 590        idev->id.bustype = BUS_ISA;
 591        idev->dev.parent = &pdev->dev;
 592        idev->evbit[0] = BIT_MASK(EV_ABS);
 593        input_set_abs_params(idev, ABS_X,
 594                        -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
 595        input_set_abs_params(idev, ABS_Y,
 596                        -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
 597
 598        ret = input_register_polled_device(hdaps_idev);
 599        if (ret)
 600                goto out_idev;
 601
 602        pr_info("driver successfully loaded\n");
 603        return 0;
 604
 605out_idev:
 606        input_free_polled_device(hdaps_idev);
 607out_group:
 608        sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
 609out_device:
 610        platform_device_unregister(pdev);
 611out_driver:
 612        platform_driver_unregister(&hdaps_driver);
 613out_region:
 614        release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
 615out:
 616        pr_warn("driver init failed (ret=%d)!\n", ret);
 617        return ret;
 618}
 619
 620static void __exit hdaps_exit(void)
 621{
 622        input_unregister_polled_device(hdaps_idev);
 623        input_free_polled_device(hdaps_idev);
 624        sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
 625        platform_device_unregister(pdev);
 626        platform_driver_unregister(&hdaps_driver);
 627        release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
 628
 629        pr_info("driver unloaded\n");
 630}
 631
 632module_init(hdaps_init);
 633module_exit(hdaps_exit);
 634
 635module_param_named(invert, hdaps_invert, int, 0);
 636MODULE_PARM_DESC(invert, "invert data along each axis. 1 invert x-axis, "
 637                 "2 invert y-axis, 3 invert both axes.");
 638
 639MODULE_AUTHOR("Robert Love");
 640MODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver");
 641MODULE_LICENSE("GPL v2");
 642