diff options
Diffstat (limited to 'drivers/platform/x86/ideapad-laptop.c')
-rw-r--r-- | drivers/platform/x86/ideapad-laptop.c | 259 |
1 files changed, 179 insertions, 80 deletions
diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 5ff12205aa6b..114d95247cdf 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * ideapad_acpi.c - Lenovo IdeaPad ACPI Extras | 2 | * ideapad-laptop.c - Lenovo IdeaPad ACPI Extras |
3 | * | 3 | * |
4 | * Copyright © 2010 Intel Corporation | 4 | * Copyright © 2010 Intel Corporation |
5 | * Copyright © 2010 David Woodhouse <dwmw2@infradead.org> | 5 | * Copyright © 2010 David Woodhouse <dwmw2@infradead.org> |
@@ -27,31 +27,19 @@ | |||
27 | #include <acpi/acpi_bus.h> | 27 | #include <acpi/acpi_bus.h> |
28 | #include <acpi/acpi_drivers.h> | 28 | #include <acpi/acpi_drivers.h> |
29 | #include <linux/rfkill.h> | 29 | #include <linux/rfkill.h> |
30 | #include <linux/platform_device.h> | ||
31 | #include <linux/input.h> | ||
32 | #include <linux/input/sparse-keymap.h> | ||
30 | 33 | ||
31 | #define IDEAPAD_DEV_CAMERA 0 | 34 | #define IDEAPAD_RFKILL_DEV_NUM (3) |
32 | #define IDEAPAD_DEV_WLAN 1 | ||
33 | #define IDEAPAD_DEV_BLUETOOTH 2 | ||
34 | #define IDEAPAD_DEV_3G 3 | ||
35 | #define IDEAPAD_DEV_KILLSW 4 | ||
36 | 35 | ||
37 | struct ideapad_private { | 36 | struct ideapad_private { |
38 | acpi_handle handle; | 37 | struct rfkill *rfk[IDEAPAD_RFKILL_DEV_NUM]; |
39 | struct rfkill *rfk[5]; | 38 | struct platform_device *platform_device; |
40 | } *ideapad_priv; | 39 | struct input_dev *inputdev; |
41 | |||
42 | static struct { | ||
43 | char *name; | ||
44 | int cfgbit; | ||
45 | int opcode; | ||
46 | int type; | ||
47 | } ideapad_rfk_data[] = { | ||
48 | { "ideapad_camera", 19, 0x1E, NUM_RFKILL_TYPES }, | ||
49 | { "ideapad_wlan", 18, 0x15, RFKILL_TYPE_WLAN }, | ||
50 | { "ideapad_bluetooth", 16, 0x17, RFKILL_TYPE_BLUETOOTH }, | ||
51 | { "ideapad_3g", 17, 0x20, RFKILL_TYPE_WWAN }, | ||
52 | { "ideapad_killsw", 0, 0, RFKILL_TYPE_WLAN } | ||
53 | }; | 40 | }; |
54 | 41 | ||
42 | static acpi_handle ideapad_handle; | ||
55 | static bool no_bt_rfkill; | 43 | static bool no_bt_rfkill; |
56 | module_param(no_bt_rfkill, bool, 0444); | 44 | module_param(no_bt_rfkill, bool, 0444); |
57 | MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth."); | 45 | MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth."); |
@@ -163,17 +151,17 @@ static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data) | |||
163 | pr_err("timeout in write_ec_cmd\n"); | 151 | pr_err("timeout in write_ec_cmd\n"); |
164 | return -1; | 152 | return -1; |
165 | } | 153 | } |
166 | /* the above is ACPI helpers */ | ||
167 | 154 | ||
155 | /* | ||
156 | * camera power | ||
157 | */ | ||
168 | static ssize_t show_ideapad_cam(struct device *dev, | 158 | static ssize_t show_ideapad_cam(struct device *dev, |
169 | struct device_attribute *attr, | 159 | struct device_attribute *attr, |
170 | char *buf) | 160 | char *buf) |
171 | { | 161 | { |
172 | struct ideapad_private *priv = dev_get_drvdata(dev); | ||
173 | acpi_handle handle = priv->handle; | ||
174 | unsigned long result; | 162 | unsigned long result; |
175 | 163 | ||
176 | if (read_ec_data(handle, 0x1D, &result)) | 164 | if (read_ec_data(ideapad_handle, 0x1D, &result)) |
177 | return sprintf(buf, "-1\n"); | 165 | return sprintf(buf, "-1\n"); |
178 | return sprintf(buf, "%lu\n", result); | 166 | return sprintf(buf, "%lu\n", result); |
179 | } | 167 | } |
@@ -182,15 +170,13 @@ static ssize_t store_ideapad_cam(struct device *dev, | |||
182 | struct device_attribute *attr, | 170 | struct device_attribute *attr, |
183 | const char *buf, size_t count) | 171 | const char *buf, size_t count) |
184 | { | 172 | { |
185 | struct ideapad_private *priv = dev_get_drvdata(dev); | ||
186 | acpi_handle handle = priv->handle; | ||
187 | int ret, state; | 173 | int ret, state; |
188 | 174 | ||
189 | if (!count) | 175 | if (!count) |
190 | return 0; | 176 | return 0; |
191 | if (sscanf(buf, "%i", &state) != 1) | 177 | if (sscanf(buf, "%i", &state) != 1) |
192 | return -EINVAL; | 178 | return -EINVAL; |
193 | ret = write_ec_cmd(handle, 0x1E, state); | 179 | ret = write_ec_cmd(ideapad_handle, 0x1E, state); |
194 | if (ret < 0) | 180 | if (ret < 0) |
195 | return ret; | 181 | return ret; |
196 | return count; | 182 | return count; |
@@ -198,16 +184,27 @@ static ssize_t store_ideapad_cam(struct device *dev, | |||
198 | 184 | ||
199 | static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam); | 185 | static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam); |
200 | 186 | ||
187 | /* | ||
188 | * Rfkill | ||
189 | */ | ||
190 | struct ideapad_rfk_data { | ||
191 | char *name; | ||
192 | int cfgbit; | ||
193 | int opcode; | ||
194 | int type; | ||
195 | }; | ||
196 | |||
197 | const struct ideapad_rfk_data ideapad_rfk_data[] = { | ||
198 | { "ideapad_wlan", 18, 0x15, RFKILL_TYPE_WLAN }, | ||
199 | { "ideapad_bluetooth", 16, 0x17, RFKILL_TYPE_BLUETOOTH }, | ||
200 | { "ideapad_3g", 17, 0x20, RFKILL_TYPE_WWAN }, | ||
201 | }; | ||
202 | |||
201 | static int ideapad_rfk_set(void *data, bool blocked) | 203 | static int ideapad_rfk_set(void *data, bool blocked) |
202 | { | 204 | { |
203 | int device = (unsigned long)data; | 205 | unsigned long opcode = (unsigned long)data; |
204 | 206 | ||
205 | if (device == IDEAPAD_DEV_KILLSW) | 207 | return write_ec_cmd(ideapad_handle, opcode, !blocked); |
206 | return -EINVAL; | ||
207 | |||
208 | return write_ec_cmd(ideapad_priv->handle, | ||
209 | ideapad_rfk_data[device].opcode, | ||
210 | !blocked); | ||
211 | } | 208 | } |
212 | 209 | ||
213 | static struct rfkill_ops ideapad_rfk_ops = { | 210 | static struct rfkill_ops ideapad_rfk_ops = { |
@@ -217,20 +214,20 @@ static struct rfkill_ops ideapad_rfk_ops = { | |||
217 | static void ideapad_sync_rfk_state(struct acpi_device *adevice) | 214 | static void ideapad_sync_rfk_state(struct acpi_device *adevice) |
218 | { | 215 | { |
219 | struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); | 216 | struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); |
220 | acpi_handle handle = priv->handle; | ||
221 | unsigned long hw_blocked; | 217 | unsigned long hw_blocked; |
222 | int i; | 218 | int i; |
223 | 219 | ||
224 | if (read_ec_data(handle, 0x23, &hw_blocked)) | 220 | if (read_ec_data(ideapad_handle, 0x23, &hw_blocked)) |
225 | return; | 221 | return; |
226 | hw_blocked = !hw_blocked; | 222 | hw_blocked = !hw_blocked; |
227 | 223 | ||
228 | for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++) | 224 | for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) |
229 | if (priv->rfk[i]) | 225 | if (priv->rfk[i]) |
230 | rfkill_set_hw_state(priv->rfk[i], hw_blocked); | 226 | rfkill_set_hw_state(priv->rfk[i], hw_blocked); |
231 | } | 227 | } |
232 | 228 | ||
233 | static int ideapad_register_rfkill(struct acpi_device *adevice, int dev) | 229 | static int __devinit ideapad_register_rfkill(struct acpi_device *adevice, |
230 | int dev) | ||
234 | { | 231 | { |
235 | struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); | 232 | struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); |
236 | int ret; | 233 | int ret; |
@@ -239,7 +236,7 @@ static int ideapad_register_rfkill(struct acpi_device *adevice, int dev) | |||
239 | if (no_bt_rfkill && | 236 | if (no_bt_rfkill && |
240 | (ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH)) { | 237 | (ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH)) { |
241 | /* Force to enable bluetooth when no_bt_rfkill=1 */ | 238 | /* Force to enable bluetooth when no_bt_rfkill=1 */ |
242 | write_ec_cmd(ideapad_priv->handle, | 239 | write_ec_cmd(ideapad_handle, |
243 | ideapad_rfk_data[dev].opcode, 1); | 240 | ideapad_rfk_data[dev].opcode, 1); |
244 | return 0; | 241 | return 0; |
245 | } | 242 | } |
@@ -250,7 +247,7 @@ static int ideapad_register_rfkill(struct acpi_device *adevice, int dev) | |||
250 | if (!priv->rfk[dev]) | 247 | if (!priv->rfk[dev]) |
251 | return -ENOMEM; | 248 | return -ENOMEM; |
252 | 249 | ||
253 | if (read_ec_data(ideapad_priv->handle, ideapad_rfk_data[dev].opcode-1, | 250 | if (read_ec_data(ideapad_handle, ideapad_rfk_data[dev].opcode-1, |
254 | &sw_blocked)) { | 251 | &sw_blocked)) { |
255 | rfkill_init_sw_state(priv->rfk[dev], 0); | 252 | rfkill_init_sw_state(priv->rfk[dev], 0); |
256 | } else { | 253 | } else { |
@@ -266,7 +263,8 @@ static int ideapad_register_rfkill(struct acpi_device *adevice, int dev) | |||
266 | return 0; | 263 | return 0; |
267 | } | 264 | } |
268 | 265 | ||
269 | static void ideapad_unregister_rfkill(struct acpi_device *adevice, int dev) | 266 | static void __devexit ideapad_unregister_rfkill(struct acpi_device *adevice, |
267 | int dev) | ||
270 | { | 268 | { |
271 | struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); | 269 | struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); |
272 | 270 | ||
@@ -277,73 +275,177 @@ static void ideapad_unregister_rfkill(struct acpi_device *adevice, int dev) | |||
277 | rfkill_destroy(priv->rfk[dev]); | 275 | rfkill_destroy(priv->rfk[dev]); |
278 | } | 276 | } |
279 | 277 | ||
278 | /* | ||
279 | * Platform device | ||
280 | */ | ||
281 | static struct attribute *ideapad_attributes[] = { | ||
282 | &dev_attr_camera_power.attr, | ||
283 | NULL | ||
284 | }; | ||
285 | |||
286 | static struct attribute_group ideapad_attribute_group = { | ||
287 | .attrs = ideapad_attributes | ||
288 | }; | ||
289 | |||
290 | static int __devinit ideapad_platform_init(struct ideapad_private *priv) | ||
291 | { | ||
292 | int result; | ||
293 | |||
294 | priv->platform_device = platform_device_alloc("ideapad", -1); | ||
295 | if (!priv->platform_device) | ||
296 | return -ENOMEM; | ||
297 | platform_set_drvdata(priv->platform_device, priv); | ||
298 | |||
299 | result = platform_device_add(priv->platform_device); | ||
300 | if (result) | ||
301 | goto fail_platform_device; | ||
302 | |||
303 | result = sysfs_create_group(&priv->platform_device->dev.kobj, | ||
304 | &ideapad_attribute_group); | ||
305 | if (result) | ||
306 | goto fail_sysfs; | ||
307 | return 0; | ||
308 | |||
309 | fail_sysfs: | ||
310 | platform_device_del(priv->platform_device); | ||
311 | fail_platform_device: | ||
312 | platform_device_put(priv->platform_device); | ||
313 | return result; | ||
314 | } | ||
315 | |||
316 | static void ideapad_platform_exit(struct ideapad_private *priv) | ||
317 | { | ||
318 | sysfs_remove_group(&priv->platform_device->dev.kobj, | ||
319 | &ideapad_attribute_group); | ||
320 | platform_device_unregister(priv->platform_device); | ||
321 | } | ||
322 | |||
323 | /* | ||
324 | * input device | ||
325 | */ | ||
326 | static const struct key_entry ideapad_keymap[] = { | ||
327 | { KE_KEY, 0x06, { KEY_SWITCHVIDEOMODE } }, | ||
328 | { KE_KEY, 0x0D, { KEY_WLAN } }, | ||
329 | { KE_END, 0 }, | ||
330 | }; | ||
331 | |||
332 | static int __devinit ideapad_input_init(struct ideapad_private *priv) | ||
333 | { | ||
334 | struct input_dev *inputdev; | ||
335 | int error; | ||
336 | |||
337 | inputdev = input_allocate_device(); | ||
338 | if (!inputdev) { | ||
339 | pr_info("Unable to allocate input device\n"); | ||
340 | return -ENOMEM; | ||
341 | } | ||
342 | |||
343 | inputdev->name = "Ideapad extra buttons"; | ||
344 | inputdev->phys = "ideapad/input0"; | ||
345 | inputdev->id.bustype = BUS_HOST; | ||
346 | inputdev->dev.parent = &priv->platform_device->dev; | ||
347 | |||
348 | error = sparse_keymap_setup(inputdev, ideapad_keymap, NULL); | ||
349 | if (error) { | ||
350 | pr_err("Unable to setup input device keymap\n"); | ||
351 | goto err_free_dev; | ||
352 | } | ||
353 | |||
354 | error = input_register_device(inputdev); | ||
355 | if (error) { | ||
356 | pr_err("Unable to register input device\n"); | ||
357 | goto err_free_keymap; | ||
358 | } | ||
359 | |||
360 | priv->inputdev = inputdev; | ||
361 | return 0; | ||
362 | |||
363 | err_free_keymap: | ||
364 | sparse_keymap_free(inputdev); | ||
365 | err_free_dev: | ||
366 | input_free_device(inputdev); | ||
367 | return error; | ||
368 | } | ||
369 | |||
370 | static void __devexit ideapad_input_exit(struct ideapad_private *priv) | ||
371 | { | ||
372 | sparse_keymap_free(priv->inputdev); | ||
373 | input_unregister_device(priv->inputdev); | ||
374 | priv->inputdev = NULL; | ||
375 | } | ||
376 | |||
377 | static void ideapad_input_report(struct ideapad_private *priv, | ||
378 | unsigned long scancode) | ||
379 | { | ||
380 | sparse_keymap_report_event(priv->inputdev, scancode, 1, true); | ||
381 | } | ||
382 | |||
383 | /* | ||
384 | * module init/exit | ||
385 | */ | ||
280 | static const struct acpi_device_id ideapad_device_ids[] = { | 386 | static const struct acpi_device_id ideapad_device_ids[] = { |
281 | { "VPC2004", 0}, | 387 | { "VPC2004", 0}, |
282 | { "", 0}, | 388 | { "", 0}, |
283 | }; | 389 | }; |
284 | MODULE_DEVICE_TABLE(acpi, ideapad_device_ids); | 390 | MODULE_DEVICE_TABLE(acpi, ideapad_device_ids); |
285 | 391 | ||
286 | static int ideapad_acpi_add(struct acpi_device *adevice) | 392 | static int __devinit ideapad_acpi_add(struct acpi_device *adevice) |
287 | { | 393 | { |
288 | int i, cfg; | 394 | int ret, i, cfg; |
289 | int devs_present[5]; | ||
290 | struct ideapad_private *priv; | 395 | struct ideapad_private *priv; |
291 | 396 | ||
292 | if (read_method_int(adevice->handle, "_CFG", &cfg)) | 397 | if (read_method_int(adevice->handle, "_CFG", &cfg)) |
293 | return -ENODEV; | 398 | return -ENODEV; |
294 | 399 | ||
295 | for (i = IDEAPAD_DEV_CAMERA; i < IDEAPAD_DEV_KILLSW; i++) { | ||
296 | if (test_bit(ideapad_rfk_data[i].cfgbit, (unsigned long *)&cfg)) | ||
297 | devs_present[i] = 1; | ||
298 | else | ||
299 | devs_present[i] = 0; | ||
300 | } | ||
301 | |||
302 | /* The hardware switch is always present */ | ||
303 | devs_present[IDEAPAD_DEV_KILLSW] = 1; | ||
304 | |||
305 | priv = kzalloc(sizeof(*priv), GFP_KERNEL); | 400 | priv = kzalloc(sizeof(*priv), GFP_KERNEL); |
306 | if (!priv) | 401 | if (!priv) |
307 | return -ENOMEM; | 402 | return -ENOMEM; |
403 | dev_set_drvdata(&adevice->dev, priv); | ||
404 | ideapad_handle = adevice->handle; | ||
308 | 405 | ||
309 | if (devs_present[IDEAPAD_DEV_CAMERA]) { | 406 | ret = ideapad_platform_init(priv); |
310 | int ret = device_create_file(&adevice->dev, &dev_attr_camera_power); | 407 | if (ret) |
311 | if (ret) { | 408 | goto platform_failed; |
312 | kfree(priv); | ||
313 | return ret; | ||
314 | } | ||
315 | } | ||
316 | 409 | ||
317 | priv->handle = adevice->handle; | 410 | ret = ideapad_input_init(priv); |
318 | dev_set_drvdata(&adevice->dev, priv); | 411 | if (ret) |
319 | ideapad_priv = priv; | 412 | goto input_failed; |
320 | for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++) { | ||
321 | if (!devs_present[i]) | ||
322 | continue; | ||
323 | 413 | ||
324 | ideapad_register_rfkill(adevice, i); | 414 | for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) { |
415 | if (test_bit(ideapad_rfk_data[i].cfgbit, (unsigned long *)&cfg)) | ||
416 | ideapad_register_rfkill(adevice, i); | ||
417 | else | ||
418 | priv->rfk[i] = NULL; | ||
325 | } | 419 | } |
326 | ideapad_sync_rfk_state(adevice); | 420 | ideapad_sync_rfk_state(adevice); |
421 | |||
327 | return 0; | 422 | return 0; |
423 | |||
424 | input_failed: | ||
425 | ideapad_platform_exit(priv); | ||
426 | platform_failed: | ||
427 | kfree(priv); | ||
428 | return ret; | ||
328 | } | 429 | } |
329 | 430 | ||
330 | static int ideapad_acpi_remove(struct acpi_device *adevice, int type) | 431 | static int __devexit ideapad_acpi_remove(struct acpi_device *adevice, int type) |
331 | { | 432 | { |
332 | struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); | 433 | struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); |
333 | int i; | 434 | int i; |
334 | 435 | ||
335 | device_remove_file(&adevice->dev, &dev_attr_camera_power); | 436 | for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) |
336 | |||
337 | for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++) | ||
338 | ideapad_unregister_rfkill(adevice, i); | 437 | ideapad_unregister_rfkill(adevice, i); |
339 | 438 | ideapad_input_exit(priv); | |
439 | ideapad_platform_exit(priv); | ||
340 | dev_set_drvdata(&adevice->dev, NULL); | 440 | dev_set_drvdata(&adevice->dev, NULL); |
341 | kfree(priv); | 441 | kfree(priv); |
442 | |||
342 | return 0; | 443 | return 0; |
343 | } | 444 | } |
344 | 445 | ||
345 | static void ideapad_acpi_notify(struct acpi_device *adevice, u32 event) | 446 | static void ideapad_acpi_notify(struct acpi_device *adevice, u32 event) |
346 | { | 447 | { |
448 | struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); | ||
347 | acpi_handle handle = adevice->handle; | 449 | acpi_handle handle = adevice->handle; |
348 | unsigned long vpc1, vpc2, vpc_bit; | 450 | unsigned long vpc1, vpc2, vpc_bit; |
349 | 451 | ||
@@ -357,6 +459,8 @@ static void ideapad_acpi_notify(struct acpi_device *adevice, u32 event) | |||
357 | if (test_bit(vpc_bit, &vpc1)) { | 459 | if (test_bit(vpc_bit, &vpc1)) { |
358 | if (vpc_bit == 9) | 460 | if (vpc_bit == 9) |
359 | ideapad_sync_rfk_state(adevice); | 461 | ideapad_sync_rfk_state(adevice); |
462 | else | ||
463 | ideapad_input_report(priv, vpc_bit); | ||
360 | } | 464 | } |
361 | } | 465 | } |
362 | } | 466 | } |
@@ -371,19 +475,14 @@ static struct acpi_driver ideapad_acpi_driver = { | |||
371 | .owner = THIS_MODULE, | 475 | .owner = THIS_MODULE, |
372 | }; | 476 | }; |
373 | 477 | ||
374 | |||
375 | static int __init ideapad_acpi_module_init(void) | 478 | static int __init ideapad_acpi_module_init(void) |
376 | { | 479 | { |
377 | acpi_bus_register_driver(&ideapad_acpi_driver); | 480 | return acpi_bus_register_driver(&ideapad_acpi_driver); |
378 | |||
379 | return 0; | ||
380 | } | 481 | } |
381 | 482 | ||
382 | |||
383 | static void __exit ideapad_acpi_module_exit(void) | 483 | static void __exit ideapad_acpi_module_exit(void) |
384 | { | 484 | { |
385 | acpi_bus_unregister_driver(&ideapad_acpi_driver); | 485 | acpi_bus_unregister_driver(&ideapad_acpi_driver); |
386 | |||
387 | } | 486 | } |
388 | 487 | ||
389 | MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); | 488 | MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); |