Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v2.6.38-rc3 493 lines 12 kB view raw
1/* 2 * ideapad-laptop.c - Lenovo IdeaPad ACPI Extras 3 * 4 * Copyright © 2010 Intel Corporation 5 * Copyright © 2010 David Woodhouse <dwmw2@infradead.org> 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation; either version 2 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 20 * 02110-1301, USA. 21 */ 22 23#include <linux/kernel.h> 24#include <linux/module.h> 25#include <linux/init.h> 26#include <linux/types.h> 27#include <acpi/acpi_bus.h> 28#include <acpi/acpi_drivers.h> 29#include <linux/rfkill.h> 30#include <linux/platform_device.h> 31#include <linux/input.h> 32#include <linux/input/sparse-keymap.h> 33 34#define IDEAPAD_RFKILL_DEV_NUM (3) 35 36struct ideapad_private { 37 struct rfkill *rfk[IDEAPAD_RFKILL_DEV_NUM]; 38 struct platform_device *platform_device; 39 struct input_dev *inputdev; 40}; 41 42static acpi_handle ideapad_handle; 43static bool no_bt_rfkill; 44module_param(no_bt_rfkill, bool, 0444); 45MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth."); 46 47/* 48 * ACPI Helpers 49 */ 50#define IDEAPAD_EC_TIMEOUT (100) /* in ms */ 51 52static int read_method_int(acpi_handle handle, const char *method, int *val) 53{ 54 acpi_status status; 55 unsigned long long result; 56 57 status = acpi_evaluate_integer(handle, (char *)method, NULL, &result); 58 if (ACPI_FAILURE(status)) { 59 *val = -1; 60 return -1; 61 } else { 62 *val = result; 63 return 0; 64 } 65} 66 67static int method_vpcr(acpi_handle handle, int cmd, int *ret) 68{ 69 acpi_status status; 70 unsigned long long result; 71 struct acpi_object_list params; 72 union acpi_object in_obj; 73 74 params.count = 1; 75 params.pointer = &in_obj; 76 in_obj.type = ACPI_TYPE_INTEGER; 77 in_obj.integer.value = cmd; 78 79 status = acpi_evaluate_integer(handle, "VPCR", &params, &result); 80 81 if (ACPI_FAILURE(status)) { 82 *ret = -1; 83 return -1; 84 } else { 85 *ret = result; 86 return 0; 87 } 88} 89 90static int method_vpcw(acpi_handle handle, int cmd, int data) 91{ 92 struct acpi_object_list params; 93 union acpi_object in_obj[2]; 94 acpi_status status; 95 96 params.count = 2; 97 params.pointer = in_obj; 98 in_obj[0].type = ACPI_TYPE_INTEGER; 99 in_obj[0].integer.value = cmd; 100 in_obj[1].type = ACPI_TYPE_INTEGER; 101 in_obj[1].integer.value = data; 102 103 status = acpi_evaluate_object(handle, "VPCW", &params, NULL); 104 if (status != AE_OK) 105 return -1; 106 return 0; 107} 108 109static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data) 110{ 111 int val; 112 unsigned long int end_jiffies; 113 114 if (method_vpcw(handle, 1, cmd)) 115 return -1; 116 117 for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1; 118 time_before(jiffies, end_jiffies);) { 119 schedule(); 120 if (method_vpcr(handle, 1, &val)) 121 return -1; 122 if (val == 0) { 123 if (method_vpcr(handle, 0, &val)) 124 return -1; 125 *data = val; 126 return 0; 127 } 128 } 129 pr_err("timeout in read_ec_cmd\n"); 130 return -1; 131} 132 133static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data) 134{ 135 int val; 136 unsigned long int end_jiffies; 137 138 if (method_vpcw(handle, 0, data)) 139 return -1; 140 if (method_vpcw(handle, 1, cmd)) 141 return -1; 142 143 for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1; 144 time_before(jiffies, end_jiffies);) { 145 schedule(); 146 if (method_vpcr(handle, 1, &val)) 147 return -1; 148 if (val == 0) 149 return 0; 150 } 151 pr_err("timeout in write_ec_cmd\n"); 152 return -1; 153} 154 155/* 156 * camera power 157 */ 158static ssize_t show_ideapad_cam(struct device *dev, 159 struct device_attribute *attr, 160 char *buf) 161{ 162 unsigned long result; 163 164 if (read_ec_data(ideapad_handle, 0x1D, &result)) 165 return sprintf(buf, "-1\n"); 166 return sprintf(buf, "%lu\n", result); 167} 168 169static ssize_t store_ideapad_cam(struct device *dev, 170 struct device_attribute *attr, 171 const char *buf, size_t count) 172{ 173 int ret, state; 174 175 if (!count) 176 return 0; 177 if (sscanf(buf, "%i", &state) != 1) 178 return -EINVAL; 179 ret = write_ec_cmd(ideapad_handle, 0x1E, state); 180 if (ret < 0) 181 return ret; 182 return count; 183} 184 185static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam); 186 187/* 188 * Rfkill 189 */ 190struct ideapad_rfk_data { 191 char *name; 192 int cfgbit; 193 int opcode; 194 int type; 195}; 196 197const 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 203static int ideapad_rfk_set(void *data, bool blocked) 204{ 205 unsigned long opcode = (unsigned long)data; 206 207 return write_ec_cmd(ideapad_handle, opcode, !blocked); 208} 209 210static struct rfkill_ops ideapad_rfk_ops = { 211 .set_block = ideapad_rfk_set, 212}; 213 214static void ideapad_sync_rfk_state(struct acpi_device *adevice) 215{ 216 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); 217 unsigned long hw_blocked; 218 int i; 219 220 if (read_ec_data(ideapad_handle, 0x23, &hw_blocked)) 221 return; 222 hw_blocked = !hw_blocked; 223 224 for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 225 if (priv->rfk[i]) 226 rfkill_set_hw_state(priv->rfk[i], hw_blocked); 227} 228 229static int __devinit ideapad_register_rfkill(struct acpi_device *adevice, 230 int dev) 231{ 232 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); 233 int ret; 234 unsigned long sw_blocked; 235 236 if (no_bt_rfkill && 237 (ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH)) { 238 /* Force to enable bluetooth when no_bt_rfkill=1 */ 239 write_ec_cmd(ideapad_handle, 240 ideapad_rfk_data[dev].opcode, 1); 241 return 0; 242 } 243 244 priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name, &adevice->dev, 245 ideapad_rfk_data[dev].type, &ideapad_rfk_ops, 246 (void *)(long)dev); 247 if (!priv->rfk[dev]) 248 return -ENOMEM; 249 250 if (read_ec_data(ideapad_handle, ideapad_rfk_data[dev].opcode-1, 251 &sw_blocked)) { 252 rfkill_init_sw_state(priv->rfk[dev], 0); 253 } else { 254 sw_blocked = !sw_blocked; 255 rfkill_init_sw_state(priv->rfk[dev], sw_blocked); 256 } 257 258 ret = rfkill_register(priv->rfk[dev]); 259 if (ret) { 260 rfkill_destroy(priv->rfk[dev]); 261 return ret; 262 } 263 return 0; 264} 265 266static void __devexit ideapad_unregister_rfkill(struct acpi_device *adevice, 267 int dev) 268{ 269 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); 270 271 if (!priv->rfk[dev]) 272 return; 273 274 rfkill_unregister(priv->rfk[dev]); 275 rfkill_destroy(priv->rfk[dev]); 276} 277 278/* 279 * Platform device 280 */ 281static struct attribute *ideapad_attributes[] = { 282 &dev_attr_camera_power.attr, 283 NULL 284}; 285 286static struct attribute_group ideapad_attribute_group = { 287 .attrs = ideapad_attributes 288}; 289 290static 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 309fail_sysfs: 310 platform_device_del(priv->platform_device); 311fail_platform_device: 312 platform_device_put(priv->platform_device); 313 return result; 314} 315 316static 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 */ 326static const struct key_entry ideapad_keymap[] = { 327 { KE_KEY, 0x06, { KEY_SWITCHVIDEOMODE } }, 328 { KE_KEY, 0x0D, { KEY_WLAN } }, 329 { KE_END, 0 }, 330}; 331 332static 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 363err_free_keymap: 364 sparse_keymap_free(inputdev); 365err_free_dev: 366 input_free_device(inputdev); 367 return error; 368} 369 370static 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 377static 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 */ 386static const struct acpi_device_id ideapad_device_ids[] = { 387 { "VPC2004", 0}, 388 { "", 0}, 389}; 390MODULE_DEVICE_TABLE(acpi, ideapad_device_ids); 391 392static int __devinit ideapad_acpi_add(struct acpi_device *adevice) 393{ 394 int ret, i, cfg; 395 struct ideapad_private *priv; 396 397 if (read_method_int(adevice->handle, "_CFG", &cfg)) 398 return -ENODEV; 399 400 priv = kzalloc(sizeof(*priv), GFP_KERNEL); 401 if (!priv) 402 return -ENOMEM; 403 dev_set_drvdata(&adevice->dev, priv); 404 ideapad_handle = adevice->handle; 405 406 ret = ideapad_platform_init(priv); 407 if (ret) 408 goto platform_failed; 409 410 ret = ideapad_input_init(priv); 411 if (ret) 412 goto input_failed; 413 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; 419 } 420 ideapad_sync_rfk_state(adevice); 421 422 return 0; 423 424input_failed: 425 ideapad_platform_exit(priv); 426platform_failed: 427 kfree(priv); 428 return ret; 429} 430 431static int __devexit ideapad_acpi_remove(struct acpi_device *adevice, int type) 432{ 433 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); 434 int i; 435 436 for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 437 ideapad_unregister_rfkill(adevice, i); 438 ideapad_input_exit(priv); 439 ideapad_platform_exit(priv); 440 dev_set_drvdata(&adevice->dev, NULL); 441 kfree(priv); 442 443 return 0; 444} 445 446static void ideapad_acpi_notify(struct acpi_device *adevice, u32 event) 447{ 448 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); 449 acpi_handle handle = adevice->handle; 450 unsigned long vpc1, vpc2, vpc_bit; 451 452 if (read_ec_data(handle, 0x10, &vpc1)) 453 return; 454 if (read_ec_data(handle, 0x1A, &vpc2)) 455 return; 456 457 vpc1 = (vpc2 << 8) | vpc1; 458 for (vpc_bit = 0; vpc_bit < 16; vpc_bit++) { 459 if (test_bit(vpc_bit, &vpc1)) { 460 if (vpc_bit == 9) 461 ideapad_sync_rfk_state(adevice); 462 else 463 ideapad_input_report(priv, vpc_bit); 464 } 465 } 466} 467 468static struct acpi_driver ideapad_acpi_driver = { 469 .name = "ideapad_acpi", 470 .class = "IdeaPad", 471 .ids = ideapad_device_ids, 472 .ops.add = ideapad_acpi_add, 473 .ops.remove = ideapad_acpi_remove, 474 .ops.notify = ideapad_acpi_notify, 475 .owner = THIS_MODULE, 476}; 477 478static int __init ideapad_acpi_module_init(void) 479{ 480 return acpi_bus_register_driver(&ideapad_acpi_driver); 481} 482 483static void __exit ideapad_acpi_module_exit(void) 484{ 485 acpi_bus_unregister_driver(&ideapad_acpi_driver); 486} 487 488MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); 489MODULE_DESCRIPTION("IdeaPad ACPI Extras"); 490MODULE_LICENSE("GPL"); 491 492module_init(ideapad_acpi_module_init); 493module_exit(ideapad_acpi_module_exit);