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