Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * hdaps.c - driver for IBM's Hard Drive Active Protection System
0004  *
0005  * Copyright (C) 2005 Robert Love <rml@novell.com>
0006  * Copyright (C) 2005 Jesper Juhl <jj@chaosbits.net>
0007  *
0008  * The HardDisk Active Protection System (hdaps) is present in IBM ThinkPads
0009  * starting with the R40, T41, and X40.  It provides a basic two-axis
0010  * accelerometer and other data, such as the device's temperature.
0011  *
0012  * This driver is based on the document by Mark A. Smith available at
0013  * http://www.almaden.ibm.com/cs/people/marksmith/tpaps.html and a lot of trial
0014  * and error.
0015  */
0016 
0017 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0018 
0019 #include <linux/delay.h>
0020 #include <linux/platform_device.h>
0021 #include <linux/input.h>
0022 #include <linux/kernel.h>
0023 #include <linux/mutex.h>
0024 #include <linux/module.h>
0025 #include <linux/timer.h>
0026 #include <linux/dmi.h>
0027 #include <linux/jiffies.h>
0028 #include <linux/io.h>
0029 
0030 #define HDAPS_LOW_PORT      0x1600  /* first port used by hdaps */
0031 #define HDAPS_NR_PORTS      0x30    /* number of ports: 0x1600 - 0x162f */
0032 
0033 #define HDAPS_PORT_STATE    0x1611  /* device state */
0034 #define HDAPS_PORT_YPOS     0x1612  /* y-axis position */
0035 #define HDAPS_PORT_XPOS     0x1614  /* x-axis position */
0036 #define HDAPS_PORT_TEMP1    0x1616  /* device temperature, in Celsius */
0037 #define HDAPS_PORT_YVAR     0x1617  /* y-axis variance (what is this?) */
0038 #define HDAPS_PORT_XVAR     0x1619  /* x-axis variance (what is this?) */
0039 #define HDAPS_PORT_TEMP2    0x161b  /* device temperature (again?) */
0040 #define HDAPS_PORT_UNKNOWN  0x161c  /* what is this? */
0041 #define HDAPS_PORT_KMACT    0x161d  /* keyboard or mouse activity */
0042 
0043 #define STATE_FRESH     0x50    /* accelerometer data is fresh */
0044 
0045 #define KEYBD_MASK      0x20    /* set if keyboard activity */
0046 #define MOUSE_MASK      0x40    /* set if mouse activity */
0047 #define KEYBD_ISSET(n)      (!! (n & KEYBD_MASK))   /* keyboard used? */
0048 #define MOUSE_ISSET(n)      (!! (n & MOUSE_MASK))   /* mouse used? */
0049 
0050 #define INIT_TIMEOUT_MSECS  4000    /* wait up to 4s for device init ... */
0051 #define INIT_WAIT_MSECS     200 /* ... in 200ms increments */
0052 
0053 #define HDAPS_POLL_INTERVAL 50  /* poll for input every 1/20s (50 ms)*/
0054 #define HDAPS_INPUT_FUZZ    4   /* input event threshold */
0055 #define HDAPS_INPUT_FLAT    4
0056 
0057 #define HDAPS_X_AXIS        (1 << 0)
0058 #define HDAPS_Y_AXIS        (1 << 1)
0059 #define HDAPS_BOTH_AXES     (HDAPS_X_AXIS | HDAPS_Y_AXIS)
0060 
0061 static struct platform_device *pdev;
0062 static struct input_dev *hdaps_idev;
0063 static unsigned int hdaps_invert;
0064 static u8 km_activity;
0065 static int rest_x;
0066 static int rest_y;
0067 
0068 static DEFINE_MUTEX(hdaps_mtx);
0069 
0070 /*
0071  * __get_latch - Get the value from a given port.  Callers must hold hdaps_mtx.
0072  */
0073 static inline u8 __get_latch(u16 port)
0074 {
0075     return inb(port) & 0xff;
0076 }
0077 
0078 /*
0079  * __check_latch - Check a port latch for a given value.  Returns zero if the
0080  * port contains the given value.  Callers must hold hdaps_mtx.
0081  */
0082 static inline int __check_latch(u16 port, u8 val)
0083 {
0084     if (__get_latch(port) == val)
0085         return 0;
0086     return -EINVAL;
0087 }
0088 
0089 /*
0090  * __wait_latch - Wait up to 100us for a port latch to get a certain value,
0091  * returning zero if the value is obtained.  Callers must hold hdaps_mtx.
0092  */
0093 static int __wait_latch(u16 port, u8 val)
0094 {
0095     unsigned int i;
0096 
0097     for (i = 0; i < 20; i++) {
0098         if (!__check_latch(port, val))
0099             return 0;
0100         udelay(5);
0101     }
0102 
0103     return -EIO;
0104 }
0105 
0106 /*
0107  * __device_refresh - request a refresh from the accelerometer.  Does not wait
0108  * for refresh to complete.  Callers must hold hdaps_mtx.
0109  */
0110 static void __device_refresh(void)
0111 {
0112     udelay(200);
0113     if (inb(0x1604) != STATE_FRESH) {
0114         outb(0x11, 0x1610);
0115         outb(0x01, 0x161f);
0116     }
0117 }
0118 
0119 /*
0120  * __device_refresh_sync - request a synchronous refresh from the
0121  * accelerometer.  We wait for the refresh to complete.  Returns zero if
0122  * successful and nonzero on error.  Callers must hold hdaps_mtx.
0123  */
0124 static int __device_refresh_sync(void)
0125 {
0126     __device_refresh();
0127     return __wait_latch(0x1604, STATE_FRESH);
0128 }
0129 
0130 /*
0131  * __device_complete - indicate to the accelerometer that we are done reading
0132  * data, and then initiate an async refresh.  Callers must hold hdaps_mtx.
0133  */
0134 static inline void __device_complete(void)
0135 {
0136     inb(0x161f);
0137     inb(0x1604);
0138     __device_refresh();
0139 }
0140 
0141 /*
0142  * hdaps_readb_one - reads a byte from a single I/O port, placing the value in
0143  * the given pointer.  Returns zero on success or a negative error on failure.
0144  * Can sleep.
0145  */
0146 static int hdaps_readb_one(unsigned int port, u8 *val)
0147 {
0148     int ret;
0149 
0150     mutex_lock(&hdaps_mtx);
0151 
0152     /* do a sync refresh -- we need to be sure that we read fresh data */
0153     ret = __device_refresh_sync();
0154     if (ret)
0155         goto out;
0156 
0157     *val = inb(port);
0158     __device_complete();
0159 
0160 out:
0161     mutex_unlock(&hdaps_mtx);
0162     return ret;
0163 }
0164 
0165 /* __hdaps_read_pair - internal lockless helper for hdaps_read_pair(). */
0166 static int __hdaps_read_pair(unsigned int port1, unsigned int port2,
0167                  int *x, int *y)
0168 {
0169     /* do a sync refresh -- we need to be sure that we read fresh data */
0170     if (__device_refresh_sync())
0171         return -EIO;
0172 
0173     *y = inw(port2);
0174     *x = inw(port1);
0175     km_activity = inb(HDAPS_PORT_KMACT);
0176     __device_complete();
0177 
0178     /* hdaps_invert is a bitvector to negate the axes */
0179     if (hdaps_invert & HDAPS_X_AXIS)
0180         *x = -*x;
0181     if (hdaps_invert & HDAPS_Y_AXIS)
0182         *y = -*y;
0183 
0184     return 0;
0185 }
0186 
0187 /*
0188  * hdaps_read_pair - reads the values from a pair of ports, placing the values
0189  * in the given pointers.  Returns zero on success.  Can sleep.
0190  */
0191 static int hdaps_read_pair(unsigned int port1, unsigned int port2,
0192                int *val1, int *val2)
0193 {
0194     int ret;
0195 
0196     mutex_lock(&hdaps_mtx);
0197     ret = __hdaps_read_pair(port1, port2, val1, val2);
0198     mutex_unlock(&hdaps_mtx);
0199 
0200     return ret;
0201 }
0202 
0203 /*
0204  * hdaps_device_init - initialize the accelerometer.  Returns zero on success
0205  * and negative error code on failure.  Can sleep.
0206  */
0207 static int hdaps_device_init(void)
0208 {
0209     int total, ret = -ENXIO;
0210 
0211     mutex_lock(&hdaps_mtx);
0212 
0213     outb(0x13, 0x1610);
0214     outb(0x01, 0x161f);
0215     if (__wait_latch(0x161f, 0x00))
0216         goto out;
0217 
0218     /*
0219      * Most ThinkPads return 0x01.
0220      *
0221      * Others--namely the R50p, T41p, and T42p--return 0x03.  These laptops
0222      * have "inverted" axises.
0223      *
0224      * The 0x02 value occurs when the chip has been previously initialized.
0225      */
0226     if (__check_latch(0x1611, 0x03) &&
0227              __check_latch(0x1611, 0x02) &&
0228              __check_latch(0x1611, 0x01))
0229         goto out;
0230 
0231     printk(KERN_DEBUG "hdaps: initial latch check good (0x%02x)\n",
0232            __get_latch(0x1611));
0233 
0234     outb(0x17, 0x1610);
0235     outb(0x81, 0x1611);
0236     outb(0x01, 0x161f);
0237     if (__wait_latch(0x161f, 0x00))
0238         goto out;
0239     if (__wait_latch(0x1611, 0x00))
0240         goto out;
0241     if (__wait_latch(0x1612, 0x60))
0242         goto out;
0243     if (__wait_latch(0x1613, 0x00))
0244         goto out;
0245     outb(0x14, 0x1610);
0246     outb(0x01, 0x1611);
0247     outb(0x01, 0x161f);
0248     if (__wait_latch(0x161f, 0x00))
0249         goto out;
0250     outb(0x10, 0x1610);
0251     outb(0xc8, 0x1611);
0252     outb(0x00, 0x1612);
0253     outb(0x02, 0x1613);
0254     outb(0x01, 0x161f);
0255     if (__wait_latch(0x161f, 0x00))
0256         goto out;
0257     if (__device_refresh_sync())
0258         goto out;
0259     if (__wait_latch(0x1611, 0x00))
0260         goto out;
0261 
0262     /* we have done our dance, now let's wait for the applause */
0263     for (total = INIT_TIMEOUT_MSECS; total > 0; total -= INIT_WAIT_MSECS) {
0264         int x, y;
0265 
0266         /* a read of the device helps push it into action */
0267         __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
0268         if (!__wait_latch(0x1611, 0x02)) {
0269             ret = 0;
0270             break;
0271         }
0272 
0273         msleep(INIT_WAIT_MSECS);
0274     }
0275 
0276 out:
0277     mutex_unlock(&hdaps_mtx);
0278     return ret;
0279 }
0280 
0281 
0282 /* Device model stuff */
0283 
0284 static int hdaps_probe(struct platform_device *dev)
0285 {
0286     int ret;
0287 
0288     ret = hdaps_device_init();
0289     if (ret)
0290         return ret;
0291 
0292     pr_info("device successfully initialized\n");
0293     return 0;
0294 }
0295 
0296 #ifdef CONFIG_PM_SLEEP
0297 static int hdaps_resume(struct device *dev)
0298 {
0299     return hdaps_device_init();
0300 }
0301 #endif
0302 
0303 static SIMPLE_DEV_PM_OPS(hdaps_pm, NULL, hdaps_resume);
0304 
0305 static struct platform_driver hdaps_driver = {
0306     .probe = hdaps_probe,
0307     .driver = {
0308         .name = "hdaps",
0309         .pm = &hdaps_pm,
0310     },
0311 };
0312 
0313 /*
0314  * hdaps_calibrate - Set our "resting" values.  Callers must hold hdaps_mtx.
0315  */
0316 static void hdaps_calibrate(void)
0317 {
0318     __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &rest_x, &rest_y);
0319 }
0320 
0321 static void hdaps_mousedev_poll(struct input_dev *input_dev)
0322 {
0323     int x, y;
0324 
0325     mutex_lock(&hdaps_mtx);
0326 
0327     if (__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y))
0328         goto out;
0329 
0330     input_report_abs(input_dev, ABS_X, x - rest_x);
0331     input_report_abs(input_dev, ABS_Y, y - rest_y);
0332     input_sync(input_dev);
0333 
0334 out:
0335     mutex_unlock(&hdaps_mtx);
0336 }
0337 
0338 
0339 /* Sysfs Files */
0340 
0341 static ssize_t hdaps_position_show(struct device *dev,
0342                    struct device_attribute *attr, char *buf)
0343 {
0344     int ret, x, y;
0345 
0346     ret = hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
0347     if (ret)
0348         return ret;
0349 
0350     return sprintf(buf, "(%d,%d)\n", x, y);
0351 }
0352 
0353 static ssize_t hdaps_variance_show(struct device *dev,
0354                    struct device_attribute *attr, char *buf)
0355 {
0356     int ret, x, y;
0357 
0358     ret = hdaps_read_pair(HDAPS_PORT_XVAR, HDAPS_PORT_YVAR, &x, &y);
0359     if (ret)
0360         return ret;
0361 
0362     return sprintf(buf, "(%d,%d)\n", x, y);
0363 }
0364 
0365 static ssize_t hdaps_temp1_show(struct device *dev,
0366                 struct device_attribute *attr, char *buf)
0367 {
0368     u8 temp;
0369     int ret;
0370 
0371     ret = hdaps_readb_one(HDAPS_PORT_TEMP1, &temp);
0372     if (ret)
0373         return ret;
0374 
0375     return sprintf(buf, "%u\n", temp);
0376 }
0377 
0378 static ssize_t hdaps_temp2_show(struct device *dev,
0379                 struct device_attribute *attr, char *buf)
0380 {
0381     u8 temp;
0382     int ret;
0383 
0384     ret = hdaps_readb_one(HDAPS_PORT_TEMP2, &temp);
0385     if (ret)
0386         return ret;
0387 
0388     return sprintf(buf, "%u\n", temp);
0389 }
0390 
0391 static ssize_t hdaps_keyboard_activity_show(struct device *dev,
0392                         struct device_attribute *attr,
0393                         char *buf)
0394 {
0395     return sprintf(buf, "%u\n", KEYBD_ISSET(km_activity));
0396 }
0397 
0398 static ssize_t hdaps_mouse_activity_show(struct device *dev,
0399                      struct device_attribute *attr,
0400                      char *buf)
0401 {
0402     return sprintf(buf, "%u\n", MOUSE_ISSET(km_activity));
0403 }
0404 
0405 static ssize_t hdaps_calibrate_show(struct device *dev,
0406                     struct device_attribute *attr, char *buf)
0407 {
0408     return sprintf(buf, "(%d,%d)\n", rest_x, rest_y);
0409 }
0410 
0411 static ssize_t hdaps_calibrate_store(struct device *dev,
0412                      struct device_attribute *attr,
0413                      const char *buf, size_t count)
0414 {
0415     mutex_lock(&hdaps_mtx);
0416     hdaps_calibrate();
0417     mutex_unlock(&hdaps_mtx);
0418 
0419     return count;
0420 }
0421 
0422 static ssize_t hdaps_invert_show(struct device *dev,
0423                  struct device_attribute *attr, char *buf)
0424 {
0425     return sprintf(buf, "%u\n", hdaps_invert);
0426 }
0427 
0428 static ssize_t hdaps_invert_store(struct device *dev,
0429                   struct device_attribute *attr,
0430                   const char *buf, size_t count)
0431 {
0432     int invert;
0433 
0434     if (sscanf(buf, "%d", &invert) != 1 ||
0435         invert < 0 || invert > HDAPS_BOTH_AXES)
0436         return -EINVAL;
0437 
0438     hdaps_invert = invert;
0439     hdaps_calibrate();
0440 
0441     return count;
0442 }
0443 
0444 static DEVICE_ATTR(position, 0444, hdaps_position_show, NULL);
0445 static DEVICE_ATTR(variance, 0444, hdaps_variance_show, NULL);
0446 static DEVICE_ATTR(temp1, 0444, hdaps_temp1_show, NULL);
0447 static DEVICE_ATTR(temp2, 0444, hdaps_temp2_show, NULL);
0448 static DEVICE_ATTR(keyboard_activity, 0444, hdaps_keyboard_activity_show, NULL);
0449 static DEVICE_ATTR(mouse_activity, 0444, hdaps_mouse_activity_show, NULL);
0450 static DEVICE_ATTR(calibrate, 0644, hdaps_calibrate_show,hdaps_calibrate_store);
0451 static DEVICE_ATTR(invert, 0644, hdaps_invert_show, hdaps_invert_store);
0452 
0453 static struct attribute *hdaps_attributes[] = {
0454     &dev_attr_position.attr,
0455     &dev_attr_variance.attr,
0456     &dev_attr_temp1.attr,
0457     &dev_attr_temp2.attr,
0458     &dev_attr_keyboard_activity.attr,
0459     &dev_attr_mouse_activity.attr,
0460     &dev_attr_calibrate.attr,
0461     &dev_attr_invert.attr,
0462     NULL,
0463 };
0464 
0465 static const struct attribute_group hdaps_attribute_group = {
0466     .attrs = hdaps_attributes,
0467 };
0468 
0469 
0470 /* Module stuff */
0471 
0472 /* hdaps_dmi_match - found a match.  return one, short-circuiting the hunt. */
0473 static int __init hdaps_dmi_match(const struct dmi_system_id *id)
0474 {
0475     pr_info("%s detected\n", id->ident);
0476     return 1;
0477 }
0478 
0479 /* hdaps_dmi_match_invert - found an inverted match. */
0480 static int __init hdaps_dmi_match_invert(const struct dmi_system_id *id)
0481 {
0482     hdaps_invert = (unsigned long)id->driver_data;
0483     pr_info("inverting axis (%u) readings\n", hdaps_invert);
0484     return hdaps_dmi_match(id);
0485 }
0486 
0487 #define HDAPS_DMI_MATCH_INVERT(vendor, model, axes) {   \
0488     .ident = vendor " " model,          \
0489     .callback = hdaps_dmi_match_invert,     \
0490     .driver_data = (void *)axes,            \
0491     .matches = {                    \
0492         DMI_MATCH(DMI_BOARD_VENDOR, vendor),    \
0493         DMI_MATCH(DMI_PRODUCT_VERSION, model)   \
0494     }                       \
0495 }
0496 
0497 #define HDAPS_DMI_MATCH_NORMAL(vendor, model)       \
0498     HDAPS_DMI_MATCH_INVERT(vendor, model, 0)
0499 
0500 /* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
0501    "ThinkPad T42p", so the order of the entries matters.
0502    If your ThinkPad is not recognized, please update to latest
0503    BIOS. This is especially the case for some R52 ThinkPads. */
0504 static const struct dmi_system_id hdaps_whitelist[] __initconst = {
0505     HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p", HDAPS_BOTH_AXES),
0506     HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R50"),
0507     HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R51"),
0508     HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R52"),
0509     HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61i", HDAPS_BOTH_AXES),
0510     HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61", HDAPS_BOTH_AXES),
0511     HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p", HDAPS_BOTH_AXES),
0512     HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T41"),
0513     HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p", HDAPS_BOTH_AXES),
0514     HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T42"),
0515     HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T43"),
0516     HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400", HDAPS_BOTH_AXES),
0517     HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60", HDAPS_BOTH_AXES),
0518     HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61p", HDAPS_BOTH_AXES),
0519     HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61", HDAPS_BOTH_AXES),
0520     HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X40"),
0521     HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X41", HDAPS_Y_AXIS),
0522     HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60", HDAPS_BOTH_AXES),
0523     HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61s", HDAPS_BOTH_AXES),
0524     HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61", HDAPS_BOTH_AXES),
0525     HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad Z60m"),
0526     HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61m", HDAPS_BOTH_AXES),
0527     HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61p", HDAPS_BOTH_AXES),
0528     { .ident = NULL }
0529 };
0530 
0531 static int __init hdaps_init(void)
0532 {
0533     int ret;
0534 
0535     if (!dmi_check_system(hdaps_whitelist)) {
0536         pr_warn("supported laptop not found!\n");
0537         ret = -ENODEV;
0538         goto out;
0539     }
0540 
0541     if (!request_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS, "hdaps")) {
0542         ret = -ENXIO;
0543         goto out;
0544     }
0545 
0546     ret = platform_driver_register(&hdaps_driver);
0547     if (ret)
0548         goto out_region;
0549 
0550     pdev = platform_device_register_simple("hdaps", -1, NULL, 0);
0551     if (IS_ERR(pdev)) {
0552         ret = PTR_ERR(pdev);
0553         goto out_driver;
0554     }
0555 
0556     ret = sysfs_create_group(&pdev->dev.kobj, &hdaps_attribute_group);
0557     if (ret)
0558         goto out_device;
0559 
0560     hdaps_idev = input_allocate_device();
0561     if (!hdaps_idev) {
0562         ret = -ENOMEM;
0563         goto out_group;
0564     }
0565 
0566     /* initial calibrate for the input device */
0567     hdaps_calibrate();
0568 
0569     /* initialize the input class */
0570     hdaps_idev->name = "hdaps";
0571     hdaps_idev->phys = "isa1600/input0";
0572     hdaps_idev->id.bustype = BUS_ISA;
0573     hdaps_idev->dev.parent = &pdev->dev;
0574     input_set_abs_params(hdaps_idev, ABS_X,
0575             -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
0576     input_set_abs_params(hdaps_idev, ABS_Y,
0577             -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
0578 
0579     ret = input_setup_polling(hdaps_idev, hdaps_mousedev_poll);
0580     if (ret)
0581         goto out_idev;
0582 
0583     input_set_poll_interval(hdaps_idev, HDAPS_POLL_INTERVAL);
0584 
0585     ret = input_register_device(hdaps_idev);
0586     if (ret)
0587         goto out_idev;
0588 
0589     pr_info("driver successfully loaded\n");
0590     return 0;
0591 
0592 out_idev:
0593     input_free_device(hdaps_idev);
0594 out_group:
0595     sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
0596 out_device:
0597     platform_device_unregister(pdev);
0598 out_driver:
0599     platform_driver_unregister(&hdaps_driver);
0600 out_region:
0601     release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
0602 out:
0603     pr_warn("driver init failed (ret=%d)!\n", ret);
0604     return ret;
0605 }
0606 
0607 static void __exit hdaps_exit(void)
0608 {
0609     input_unregister_device(hdaps_idev);
0610     sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
0611     platform_device_unregister(pdev);
0612     platform_driver_unregister(&hdaps_driver);
0613     release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
0614 
0615     pr_info("driver unloaded\n");
0616 }
0617 
0618 module_init(hdaps_init);
0619 module_exit(hdaps_exit);
0620 
0621 module_param_named(invert, hdaps_invert, int, 0);
0622 MODULE_PARM_DESC(invert, "invert data along each axis. 1 invert x-axis, "
0623          "2 invert y-axis, 3 invert both axes.");
0624 
0625 MODULE_AUTHOR("Robert Love");
0626 MODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver");
0627 MODULE_LICENSE("GPL v2");