0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025
0026
0027
0028
0029
0030
0031
0032
0033
0034
0035
0036
0037
0038
0039
0040
0041
0042
0043
0044
0045
0046
0047
0048
0049
0050
0051
0052
0053
0054
0055
0056
0057
0058
0059
0060
0061
0062
0063
0064
0065
0066
0067
0068
0069
0070
0071
0072 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0073
0074 #include <linux/module.h>
0075 #include <linux/kernel.h>
0076 #include <linux/dmi.h>
0077 #include <linux/spinlock.h>
0078 #include <linux/platform_device.h>
0079 #include <linux/input.h>
0080 #include <linux/io.h>
0081 #include <linux/ioport.h>
0082 #include <linux/i8042.h>
0083 #include <linux/serio.h>
0084
0085 #define IDEAPAD_BASE 0xff29
0086
0087 static bool force;
0088 module_param(force, bool, 0);
0089 MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
0090
0091 static DEFINE_SPINLOCK(io_lock);
0092
0093 static struct input_dev *slidebar_input_dev;
0094 static struct platform_device *slidebar_platform_dev;
0095
0096 static u8 slidebar_pos_get(void)
0097 {
0098 u8 res;
0099 unsigned long flags;
0100
0101 spin_lock_irqsave(&io_lock, flags);
0102 outb(0xf4, 0xff29);
0103 outb(0xbf, 0xff2a);
0104 res = inb(0xff2b);
0105 spin_unlock_irqrestore(&io_lock, flags);
0106
0107 return res;
0108 }
0109
0110 static u8 slidebar_mode_get(void)
0111 {
0112 u8 res;
0113 unsigned long flags;
0114
0115 spin_lock_irqsave(&io_lock, flags);
0116 outb(0xf7, 0xff29);
0117 outb(0x8b, 0xff2a);
0118 res = inb(0xff2b);
0119 spin_unlock_irqrestore(&io_lock, flags);
0120
0121 return res;
0122 }
0123
0124 static void slidebar_mode_set(u8 mode)
0125 {
0126 unsigned long flags;
0127
0128 spin_lock_irqsave(&io_lock, flags);
0129 outb(0xf7, 0xff29);
0130 outb(0x8b, 0xff2a);
0131 outb(mode, 0xff2b);
0132 spin_unlock_irqrestore(&io_lock, flags);
0133 }
0134
0135 static bool slidebar_i8042_filter(unsigned char data, unsigned char str,
0136 struct serio *port)
0137 {
0138 static bool extended = false;
0139
0140
0141 if (str & I8042_STR_AUXDATA)
0142 return false;
0143
0144
0145 if (data == 0xe0) {
0146 extended = true;
0147 return true;
0148 }
0149
0150 if (!extended)
0151 return false;
0152
0153 extended = false;
0154
0155 if (likely((data & 0x7f) != 0x3b)) {
0156 serio_interrupt(port, 0xe0, 0);
0157 return false;
0158 }
0159
0160 if (data & 0x80) {
0161 input_report_key(slidebar_input_dev, BTN_TOUCH, 0);
0162 } else {
0163 input_report_key(slidebar_input_dev, BTN_TOUCH, 1);
0164 input_report_abs(slidebar_input_dev, ABS_X, slidebar_pos_get());
0165 }
0166 input_sync(slidebar_input_dev);
0167
0168 return true;
0169 }
0170
0171 static ssize_t show_slidebar_mode(struct device *dev,
0172 struct device_attribute *attr,
0173 char *buf)
0174 {
0175 return sprintf(buf, "%x\n", slidebar_mode_get());
0176 }
0177
0178 static ssize_t store_slidebar_mode(struct device *dev,
0179 struct device_attribute *attr,
0180 const char *buf, size_t count)
0181 {
0182 u8 mode;
0183 int error;
0184
0185 error = kstrtou8(buf, 0, &mode);
0186 if (error)
0187 return error;
0188
0189 slidebar_mode_set(mode);
0190
0191 return count;
0192 }
0193
0194 static DEVICE_ATTR(slidebar_mode, S_IWUSR | S_IRUGO,
0195 show_slidebar_mode, store_slidebar_mode);
0196
0197 static struct attribute *ideapad_attrs[] = {
0198 &dev_attr_slidebar_mode.attr,
0199 NULL
0200 };
0201
0202 static struct attribute_group ideapad_attr_group = {
0203 .attrs = ideapad_attrs
0204 };
0205
0206 static const struct attribute_group *ideapad_attr_groups[] = {
0207 &ideapad_attr_group,
0208 NULL
0209 };
0210
0211 static int __init ideapad_probe(struct platform_device* pdev)
0212 {
0213 int err;
0214
0215 if (!request_region(IDEAPAD_BASE, 3, "ideapad_slidebar")) {
0216 dev_err(&pdev->dev, "IO ports are busy\n");
0217 return -EBUSY;
0218 }
0219
0220 slidebar_input_dev = input_allocate_device();
0221 if (!slidebar_input_dev) {
0222 dev_err(&pdev->dev, "Failed to allocate input device\n");
0223 err = -ENOMEM;
0224 goto err_release_ports;
0225 }
0226
0227 slidebar_input_dev->name = "IdeaPad Slidebar";
0228 slidebar_input_dev->id.bustype = BUS_HOST;
0229 slidebar_input_dev->dev.parent = &pdev->dev;
0230 input_set_capability(slidebar_input_dev, EV_KEY, BTN_TOUCH);
0231 input_set_capability(slidebar_input_dev, EV_ABS, ABS_X);
0232 input_set_abs_params(slidebar_input_dev, ABS_X, 0, 0xff, 0, 0);
0233
0234 err = i8042_install_filter(slidebar_i8042_filter);
0235 if (err) {
0236 dev_err(&pdev->dev,
0237 "Failed to install i8042 filter: %d\n", err);
0238 goto err_free_dev;
0239 }
0240
0241 err = input_register_device(slidebar_input_dev);
0242 if (err) {
0243 dev_err(&pdev->dev,
0244 "Failed to register input device: %d\n", err);
0245 goto err_remove_filter;
0246 }
0247
0248 return 0;
0249
0250 err_remove_filter:
0251 i8042_remove_filter(slidebar_i8042_filter);
0252 err_free_dev:
0253 input_free_device(slidebar_input_dev);
0254 err_release_ports:
0255 release_region(IDEAPAD_BASE, 3);
0256 return err;
0257 }
0258
0259 static int ideapad_remove(struct platform_device *pdev)
0260 {
0261 i8042_remove_filter(slidebar_i8042_filter);
0262 input_unregister_device(slidebar_input_dev);
0263 release_region(IDEAPAD_BASE, 3);
0264
0265 return 0;
0266 }
0267
0268 static struct platform_driver slidebar_drv = {
0269 .driver = {
0270 .name = "ideapad_slidebar",
0271 },
0272 .remove = ideapad_remove,
0273 };
0274
0275 static int __init ideapad_dmi_check(const struct dmi_system_id *id)
0276 {
0277 pr_info("Laptop model '%s'\n", id->ident);
0278 return 1;
0279 }
0280
0281 static const struct dmi_system_id ideapad_dmi[] __initconst = {
0282 {
0283 .ident = "Lenovo IdeaPad Y550",
0284 .matches = {
0285 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
0286 DMI_MATCH(DMI_PRODUCT_NAME, "20017"),
0287 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550")
0288 },
0289 .callback = ideapad_dmi_check
0290 },
0291 {
0292 .ident = "Lenovo IdeaPad Y550P",
0293 .matches = {
0294 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
0295 DMI_MATCH(DMI_PRODUCT_NAME, "20035"),
0296 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550P")
0297 },
0298 .callback = ideapad_dmi_check
0299 },
0300 { NULL, }
0301 };
0302 MODULE_DEVICE_TABLE(dmi, ideapad_dmi);
0303
0304 static int __init slidebar_init(void)
0305 {
0306 int err;
0307
0308 if (!force && !dmi_check_system(ideapad_dmi)) {
0309 pr_err("DMI does not match\n");
0310 return -ENODEV;
0311 }
0312
0313 slidebar_platform_dev = platform_device_alloc("ideapad_slidebar", -1);
0314 if (!slidebar_platform_dev) {
0315 pr_err("Not enough memory\n");
0316 return -ENOMEM;
0317 }
0318
0319 slidebar_platform_dev->dev.groups = ideapad_attr_groups;
0320
0321 err = platform_device_add(slidebar_platform_dev);
0322 if (err) {
0323 pr_err("Failed to register platform device\n");
0324 goto err_free_dev;
0325 }
0326
0327 err = platform_driver_probe(&slidebar_drv, ideapad_probe);
0328 if (err) {
0329 pr_err("Failed to register platform driver\n");
0330 goto err_delete_dev;
0331 }
0332
0333 return 0;
0334
0335 err_delete_dev:
0336 platform_device_del(slidebar_platform_dev);
0337 err_free_dev:
0338 platform_device_put(slidebar_platform_dev);
0339 return err;
0340 }
0341
0342 static void __exit slidebar_exit(void)
0343 {
0344 platform_device_unregister(slidebar_platform_dev);
0345 platform_driver_unregister(&slidebar_drv);
0346 }
0347
0348 module_init(slidebar_init);
0349 module_exit(slidebar_exit);
0350
0351 MODULE_AUTHOR("Andrey Moiseev <o2g.org.ru@gmail.com>");
0352 MODULE_DESCRIPTION("Slidebar input support for some Lenovo IdeaPad laptops");
0353 MODULE_LICENSE("GPL");