root/drivers/input/misc/ideapad_slidebar.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. slidebar_pos_get
  2. slidebar_mode_get
  3. slidebar_mode_set
  4. slidebar_i8042_filter
  5. show_slidebar_mode
  6. store_slidebar_mode
  7. ideapad_probe
  8. ideapad_remove
  9. ideapad_dmi_check
  10. slidebar_init
  11. slidebar_exit

   1 // SPDX-License-Identifier: GPL-2.0-or-later
   2 /*
   3  * Input driver for slidebars on some Lenovo IdeaPad laptops
   4  *
   5  * Copyright (C) 2013 Andrey Moiseev <o2g.org.ru@gmail.com>
   6  *
   7  * Reverse-engineered from Lenovo SlideNav software (SBarHook.dll).
   8  *
   9  * Trademarks are the property of their respective owners.
  10  */
  11 
  12 /*
  13  * Currently tested and works on:
  14  *      Lenovo IdeaPad Y550
  15  *      Lenovo IdeaPad Y550P
  16  *
  17  * Other models can be added easily. To test,
  18  * load with 'force' parameter set 'true'.
  19  *
  20  * LEDs blinking and input mode are managed via sysfs,
  21  * (hex, unsigned byte value):
  22  * /sys/devices/platform/ideapad_slidebar/slidebar_mode
  23  *
  24  * The value is in byte range, however, I only figured out
  25  * how bits 0b10011001 work. Some other bits, probably,
  26  * are meaningfull too.
  27  *
  28  * Possible states:
  29  *
  30  * STD_INT, ONMOV_INT, OFF_INT, LAST_POLL, OFF_POLL
  31  *
  32  * Meaning:
  33  *           released      touched
  34  * STD       'heartbeat'   lights follow the finger
  35  * ONMOV     no lights     lights follow the finger
  36  * LAST      at last pos   lights follow the finger
  37  * OFF       no lights     no lights
  38  *
  39  * INT       all input events are generated, interrupts are used
  40  * POLL      no input events by default, to get them,
  41  *           send 0b10000000 (read below)
  42  *
  43  * Commands: write
  44  *
  45  * All      |  0b01001 -> STD_INT
  46  * possible |  0b10001 -> ONMOV_INT
  47  * states   |  0b01000 -> OFF_INT
  48  *
  49  *                      |  0b0 -> LAST_POLL
  50  * STD_INT or ONMOV_INT |
  51  *                      |  0b1 -> STD_INT
  52  *
  53  *                      |  0b0 -> OFF_POLL
  54  * OFF_INT or OFF_POLL  |
  55  *                      |  0b1 -> OFF_INT
  56  *
  57  * Any state |   0b10000000 ->  if the slidebar has updated data,
  58  *                              produce one input event (last position),
  59  *                              switch to respective POLL mode
  60  *                              (like 0x0), if not in POLL mode yet.
  61  *
  62  * Get current state: read
  63  *
  64  * masked by 0x11 read value means:
  65  *
  66  * 0x00   LAST
  67  * 0x01   STD
  68  * 0x10   OFF
  69  * 0x11   ONMOV
  70  */
  71 
  72 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  73 
  74 #include <linux/module.h>
  75 #include <linux/kernel.h>
  76 #include <linux/dmi.h>
  77 #include <linux/spinlock.h>
  78 #include <linux/platform_device.h>
  79 #include <linux/input.h>
  80 #include <linux/io.h>
  81 #include <linux/ioport.h>
  82 #include <linux/i8042.h>
  83 #include <linux/serio.h>
  84 
  85 #define IDEAPAD_BASE    0xff29
  86 
  87 static bool force;
  88 module_param(force, bool, 0);
  89 MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
  90 
  91 static DEFINE_SPINLOCK(io_lock);
  92 
  93 static struct input_dev *slidebar_input_dev;
  94 static struct platform_device *slidebar_platform_dev;
  95 
  96 static u8 slidebar_pos_get(void)
  97 {
  98         u8 res;
  99         unsigned long flags;
 100 
 101         spin_lock_irqsave(&io_lock, flags);
 102         outb(0xf4, 0xff29);
 103         outb(0xbf, 0xff2a);
 104         res = inb(0xff2b);
 105         spin_unlock_irqrestore(&io_lock, flags);
 106 
 107         return res;
 108 }
 109 
 110 static u8 slidebar_mode_get(void)
 111 {
 112         u8 res;
 113         unsigned long flags;
 114 
 115         spin_lock_irqsave(&io_lock, flags);
 116         outb(0xf7, 0xff29);
 117         outb(0x8b, 0xff2a);
 118         res = inb(0xff2b);
 119         spin_unlock_irqrestore(&io_lock, flags);
 120 
 121         return res;
 122 }
 123 
 124 static void slidebar_mode_set(u8 mode)
 125 {
 126         unsigned long flags;
 127 
 128         spin_lock_irqsave(&io_lock, flags);
 129         outb(0xf7, 0xff29);
 130         outb(0x8b, 0xff2a);
 131         outb(mode, 0xff2b);
 132         spin_unlock_irqrestore(&io_lock, flags);
 133 }
 134 
 135 static bool slidebar_i8042_filter(unsigned char data, unsigned char str,
 136                                   struct serio *port)
 137 {
 138         static bool extended = false;
 139 
 140         /* We are only interested in data coming form KBC port */
 141         if (str & I8042_STR_AUXDATA)
 142                 return false;
 143 
 144         /* Scancodes: e03b on move, e0bb on release. */
 145         if (data == 0xe0) {
 146                 extended = true;
 147                 return true;
 148         }
 149 
 150         if (!extended)
 151                 return false;
 152 
 153         extended = false;
 154 
 155         if (likely((data & 0x7f) != 0x3b)) {
 156                 serio_interrupt(port, 0xe0, 0);
 157                 return false;
 158         }
 159 
 160         if (data & 0x80) {
 161                 input_report_key(slidebar_input_dev, BTN_TOUCH, 0);
 162         } else {
 163                 input_report_key(slidebar_input_dev, BTN_TOUCH, 1);
 164                 input_report_abs(slidebar_input_dev, ABS_X, slidebar_pos_get());
 165         }
 166         input_sync(slidebar_input_dev);
 167 
 168         return true;
 169 }
 170 
 171 static ssize_t show_slidebar_mode(struct device *dev,
 172                                   struct device_attribute *attr,
 173                                   char *buf)
 174 {
 175         return sprintf(buf, "%x\n", slidebar_mode_get());
 176 }
 177 
 178 static ssize_t store_slidebar_mode(struct device *dev,
 179                                    struct device_attribute *attr,
 180                                    const char *buf, size_t count)
 181 {
 182         u8 mode;
 183         int error;
 184 
 185         error = kstrtou8(buf, 0, &mode);
 186         if (error)
 187                 return error;
 188 
 189         slidebar_mode_set(mode);
 190 
 191         return count;
 192 }
 193 
 194 static DEVICE_ATTR(slidebar_mode, S_IWUSR | S_IRUGO,
 195                    show_slidebar_mode, store_slidebar_mode);
 196 
 197 static struct attribute *ideapad_attrs[] = {
 198         &dev_attr_slidebar_mode.attr,
 199         NULL
 200 };
 201 
 202 static struct attribute_group ideapad_attr_group = {
 203         .attrs = ideapad_attrs
 204 };
 205 
 206 static const struct attribute_group *ideapad_attr_groups[] = {
 207         &ideapad_attr_group,
 208         NULL
 209 };
 210 
 211 static int __init ideapad_probe(struct platform_device* pdev)
 212 {
 213         int err;
 214 
 215         if (!request_region(IDEAPAD_BASE, 3, "ideapad_slidebar")) {
 216                 dev_err(&pdev->dev, "IO ports are busy\n");
 217                 return -EBUSY;
 218         }
 219 
 220         slidebar_input_dev = input_allocate_device();
 221         if (!slidebar_input_dev) {
 222                 dev_err(&pdev->dev, "Failed to allocate input device\n");
 223                 err = -ENOMEM;
 224                 goto err_release_ports;
 225         }
 226 
 227         slidebar_input_dev->name = "IdeaPad Slidebar";
 228         slidebar_input_dev->id.bustype = BUS_HOST;
 229         slidebar_input_dev->dev.parent = &pdev->dev;
 230         input_set_capability(slidebar_input_dev, EV_KEY, BTN_TOUCH);
 231         input_set_capability(slidebar_input_dev, EV_ABS, ABS_X);
 232         input_set_abs_params(slidebar_input_dev, ABS_X, 0, 0xff, 0, 0);
 233 
 234         err = i8042_install_filter(slidebar_i8042_filter);
 235         if (err) {
 236                 dev_err(&pdev->dev,
 237                         "Failed to install i8042 filter: %d\n", err);
 238                 goto err_free_dev;
 239         }
 240 
 241         err = input_register_device(slidebar_input_dev);
 242         if (err) {
 243                 dev_err(&pdev->dev,
 244                         "Failed to register input device: %d\n", err);
 245                 goto err_remove_filter;
 246         }
 247 
 248         return 0;
 249 
 250 err_remove_filter:
 251         i8042_remove_filter(slidebar_i8042_filter);
 252 err_free_dev:
 253         input_free_device(slidebar_input_dev);
 254 err_release_ports:
 255         release_region(IDEAPAD_BASE, 3);
 256         return err;
 257 }
 258 
 259 static int ideapad_remove(struct platform_device *pdev)
 260 {
 261         i8042_remove_filter(slidebar_i8042_filter);
 262         input_unregister_device(slidebar_input_dev);
 263         release_region(IDEAPAD_BASE, 3);
 264 
 265         return 0;
 266 }
 267 
 268 static struct platform_driver slidebar_drv = {
 269         .driver = {
 270                 .name = "ideapad_slidebar",
 271         },
 272         .remove = ideapad_remove,
 273 };
 274 
 275 static int __init ideapad_dmi_check(const struct dmi_system_id *id)
 276 {
 277         pr_info("Laptop model '%s'\n", id->ident);
 278         return 1;
 279 }
 280 
 281 static const struct dmi_system_id ideapad_dmi[] __initconst = {
 282         {
 283                 .ident = "Lenovo IdeaPad Y550",
 284                 .matches = {
 285                         DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
 286                         DMI_MATCH(DMI_PRODUCT_NAME, "20017"),
 287                         DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550")
 288                 },
 289                 .callback = ideapad_dmi_check
 290         },
 291         {
 292                 .ident = "Lenovo IdeaPad Y550P",
 293                 .matches = {
 294                         DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
 295                         DMI_MATCH(DMI_PRODUCT_NAME, "20035"),
 296                         DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550P")
 297                 },
 298                 .callback = ideapad_dmi_check
 299         },
 300         { NULL, }
 301 };
 302 MODULE_DEVICE_TABLE(dmi, ideapad_dmi);
 303 
 304 static int __init slidebar_init(void)
 305 {
 306         int err;
 307 
 308         if (!force && !dmi_check_system(ideapad_dmi)) {
 309                 pr_err("DMI does not match\n");
 310                 return -ENODEV;
 311         }
 312 
 313         slidebar_platform_dev = platform_device_alloc("ideapad_slidebar", -1);
 314         if (!slidebar_platform_dev) {
 315                 pr_err("Not enough memory\n");
 316                 return -ENOMEM;
 317         }
 318 
 319         slidebar_platform_dev->dev.groups = ideapad_attr_groups;
 320 
 321         err = platform_device_add(slidebar_platform_dev);
 322         if (err) {
 323                 pr_err("Failed to register platform device\n");
 324                 goto err_free_dev;
 325         }
 326 
 327         err = platform_driver_probe(&slidebar_drv, ideapad_probe);
 328         if (err) {
 329                 pr_err("Failed to register platform driver\n");
 330                 goto err_delete_dev;
 331         }
 332 
 333         return 0;
 334 
 335 err_delete_dev:
 336         platform_device_del(slidebar_platform_dev);
 337 err_free_dev:
 338         platform_device_put(slidebar_platform_dev);
 339         return err;
 340 }
 341 
 342 static void __exit slidebar_exit(void)
 343 {
 344         platform_device_unregister(slidebar_platform_dev);
 345         platform_driver_unregister(&slidebar_drv);
 346 }
 347 
 348 module_init(slidebar_init);
 349 module_exit(slidebar_exit);
 350 
 351 MODULE_AUTHOR("Andrey Moiseev <o2g.org.ru@gmail.com>");
 352 MODULE_DESCRIPTION("Slidebar input support for some Lenovo IdeaPad laptops");
 353 MODULE_LICENSE("GPL");

/* [<][>][^][v][top][bottom][index][help] */