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