at v3.2-rc2 479 lines 11 kB view raw
1/* 2 * quickstart.c - ACPI Direct App Launch driver 3 * 4 * 5 * Copyright (C) 2007-2010 Angelo Arrifano <miknix@gmail.com> 6 * 7 * Information gathered from disassebled dsdt and from here: 8 * <http://www.microsoft.com/whdc/system/platform/firmware/DirAppLaunch.mspx> 9 * 10 * This program is free software; you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License as published by 12 * the Free Software Foundation; either version 2 of the License, or 13 * (at your option) any later version. 14 * 15 * This program is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License for more details. 19 * 20 * You should have received a copy of the GNU General Public License 21 * along with this program; if not, write to the Free Software 22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 23 * 24 */ 25 26#define QUICKSTART_VERSION "1.03" 27 28#include <linux/kernel.h> 29#include <linux/module.h> 30#include <linux/init.h> 31#include <linux/types.h> 32#include <acpi/acpi_drivers.h> 33#include <linux/platform_device.h> 34#include <linux/input.h> 35 36MODULE_AUTHOR("Angelo Arrifano"); 37MODULE_DESCRIPTION("ACPI Direct App Launch driver"); 38MODULE_LICENSE("GPL"); 39 40#define QUICKSTART_ACPI_DEVICE_NAME "quickstart" 41#define QUICKSTART_ACPI_CLASS "quickstart" 42#define QUICKSTART_ACPI_HID "PNP0C32" 43 44#define QUICKSTART_PF_DRIVER_NAME "quickstart" 45#define QUICKSTART_PF_DEVICE_NAME "quickstart" 46#define QUICKSTART_PF_DEVATTR_NAME "pressed_button" 47 48#define QUICKSTART_MAX_BTN_NAME_LEN 16 49 50/* There will be two events: 51 * 0x02 - A hot button was pressed while device was off/sleeping. 52 * 0x80 - A hot button was pressed while device was up. */ 53#define QUICKSTART_EVENT_WAKE 0x02 54#define QUICKSTART_EVENT_RUNTIME 0x80 55 56struct quickstart_btn { 57 char *name; 58 unsigned int id; 59 struct quickstart_btn *next; 60}; 61 62static struct quickstart_driver_data { 63 struct quickstart_btn *btn_lst; 64 struct quickstart_btn *pressed; 65} quickstart_data; 66 67/* ACPI driver Structs */ 68struct quickstart_acpi { 69 struct acpi_device *device; 70 struct quickstart_btn *btn; 71}; 72static int quickstart_acpi_add(struct acpi_device *device); 73static int quickstart_acpi_remove(struct acpi_device *device, int type); 74static const struct acpi_device_id quickstart_device_ids[] = { 75 {QUICKSTART_ACPI_HID, 0}, 76 {"", 0}, 77}; 78 79static struct acpi_driver quickstart_acpi_driver = { 80 .name = "quickstart", 81 .class = QUICKSTART_ACPI_CLASS, 82 .ids = quickstart_device_ids, 83 .ops = { 84 .add = quickstart_acpi_add, 85 .remove = quickstart_acpi_remove, 86 }, 87}; 88 89/* Input device structs */ 90struct input_dev *quickstart_input; 91 92/* Platform driver structs */ 93static ssize_t buttons_show(struct device *dev, 94 struct device_attribute *attr, 95 char *buf); 96static ssize_t pressed_button_show(struct device *dev, 97 struct device_attribute *attr, 98 char *buf); 99static ssize_t pressed_button_store(struct device *dev, 100 struct device_attribute *attr, 101 const char *buf, 102 size_t count); 103static DEVICE_ATTR(pressed_button, 0666, pressed_button_show, 104 pressed_button_store); 105static DEVICE_ATTR(buttons, 0444, buttons_show, NULL); 106static struct platform_device *pf_device; 107static struct platform_driver pf_driver = { 108 .driver = { 109 .name = QUICKSTART_PF_DRIVER_NAME, 110 .owner = THIS_MODULE, 111 } 112}; 113 114/* 115 * Platform driver functions 116 */ 117static ssize_t buttons_show(struct device *dev, 118 struct device_attribute *attr, 119 char *buf) 120{ 121 int count = 0; 122 struct quickstart_btn *ptr = quickstart_data.btn_lst; 123 124 if (!ptr) 125 return snprintf(buf, PAGE_SIZE, "none"); 126 127 while (ptr && (count < PAGE_SIZE)) { 128 if (ptr->name) { 129 count += snprintf(buf + count, 130 PAGE_SIZE - count, 131 "%d\t%s\n", ptr->id, ptr->name); 132 } 133 ptr = ptr->next; 134 } 135 136 return count; 137} 138 139static ssize_t pressed_button_show(struct device *dev, 140 struct device_attribute *attr, 141 char *buf) 142{ 143 return snprintf(buf, PAGE_SIZE, "%s\n", 144 (quickstart_data.pressed ? 145 quickstart_data.pressed->name : "none")); 146} 147 148 149static ssize_t pressed_button_store(struct device *dev, 150 struct device_attribute *attr, 151 const char *buf, size_t count) 152{ 153 if (count < 2) 154 return -EINVAL; 155 156 if (strncasecmp(buf, "none", 4) != 0) 157 return -EINVAL; 158 159 quickstart_data.pressed = NULL; 160 return count; 161} 162 163/* Hotstart Helper functions */ 164static int quickstart_btnlst_add(struct quickstart_btn **data) 165{ 166 struct quickstart_btn **ptr = &quickstart_data.btn_lst; 167 168 while (*ptr) 169 ptr = &((*ptr)->next); 170 171 *ptr = kzalloc(sizeof(struct quickstart_btn), GFP_KERNEL); 172 if (!*ptr) { 173 *data = NULL; 174 return -ENOMEM; 175 } 176 *data = *ptr; 177 178 return 0; 179} 180 181static void quickstart_btnlst_del(struct quickstart_btn *data) 182{ 183 struct quickstart_btn **ptr = &quickstart_data.btn_lst; 184 185 if (!data) 186 return; 187 188 while (*ptr) { 189 if (*ptr == data) { 190 *ptr = (*ptr)->next; 191 kfree(data); 192 return; 193 } 194 ptr = &((*ptr)->next); 195 } 196 197 return; 198} 199 200static void quickstart_btnlst_free(void) 201{ 202 struct quickstart_btn *ptr = quickstart_data.btn_lst; 203 struct quickstart_btn *lptr = NULL; 204 205 while (ptr) { 206 lptr = ptr; 207 ptr = ptr->next; 208 kfree(lptr->name); 209 kfree(lptr); 210 } 211 212 return; 213} 214 215/* ACPI Driver functions */ 216static void quickstart_acpi_notify(acpi_handle handle, u32 event, void *data) 217{ 218 struct quickstart_acpi *quickstart = data; 219 220 if (!quickstart) 221 return; 222 223 if (event == QUICKSTART_EVENT_WAKE) 224 quickstart_data.pressed = quickstart->btn; 225 else if (event == QUICKSTART_EVENT_RUNTIME) { 226 input_report_key(quickstart_input, quickstart->btn->id, 1); 227 input_sync(quickstart_input); 228 input_report_key(quickstart_input, quickstart->btn->id, 0); 229 input_sync(quickstart_input); 230 } 231 return; 232} 233 234static void quickstart_acpi_ghid(struct quickstart_acpi *quickstart) 235{ 236 acpi_status status; 237 struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; 238 uint32_t usageid = 0; 239 240 if (!quickstart) 241 return; 242 243 /* This returns a buffer telling the button usage ID, 244 * and triggers pending notify events (The ones before booting). */ 245 status = acpi_evaluate_object(quickstart->device->handle, 246 "GHID", NULL, &buffer); 247 if (ACPI_FAILURE(status) || !buffer.pointer) { 248 printk(KERN_ERR "quickstart: %s GHID method failed.\n", 249 quickstart->btn->name); 250 return; 251 } 252 253 if (buffer.length < 8) 254 return; 255 256 /* <<The GHID method can return a BYTE, WORD, or DWORD. 257 * The value must be encoded in little-endian byte 258 * order (least significant byte first).>> */ 259 usageid = *((uint32_t *)(buffer.pointer + (buffer.length - 8))); 260 quickstart->btn->id = usageid; 261 262 kfree(buffer.pointer); 263} 264 265static int quickstart_acpi_config(struct quickstart_acpi *quickstart, char *bid) 266{ 267 int len = strlen(bid); 268 int ret; 269 270 /* Add button to list */ 271 ret = quickstart_btnlst_add(&quickstart->btn); 272 if (ret) 273 return ret; 274 275 quickstart->btn->name = kzalloc(len + 1, GFP_KERNEL); 276 if (!quickstart->btn->name) { 277 quickstart_btnlst_free(); 278 return -ENOMEM; 279 } 280 strcpy(quickstart->btn->name, bid); 281 282 return 0; 283} 284 285static int quickstart_acpi_add(struct acpi_device *device) 286{ 287 int ret = 0; 288 acpi_status status = AE_OK; 289 struct quickstart_acpi *quickstart = NULL; 290 291 if (!device) 292 return -EINVAL; 293 294 quickstart = kzalloc(sizeof(struct quickstart_acpi), GFP_KERNEL); 295 if (!quickstart) 296 return -ENOMEM; 297 298 quickstart->device = device; 299 strcpy(acpi_device_name(device), QUICKSTART_ACPI_DEVICE_NAME); 300 strcpy(acpi_device_class(device), QUICKSTART_ACPI_CLASS); 301 device->driver_data = quickstart; 302 303 /* Add button to list and initialize some stuff */ 304 ret = quickstart_acpi_config(quickstart, acpi_device_bid(device)); 305 if (ret) 306 goto fail_config; 307 308 status = acpi_install_notify_handler(device->handle, 309 ACPI_ALL_NOTIFY, 310 quickstart_acpi_notify, 311 quickstart); 312 if (ACPI_FAILURE(status)) { 313 printk(KERN_ERR "quickstart: Notify handler install error\n"); 314 ret = -ENODEV; 315 goto fail_installnotify; 316 } 317 318 quickstart_acpi_ghid(quickstart); 319 320 return 0; 321 322fail_installnotify: 323 quickstart_btnlst_del(quickstart->btn); 324 325fail_config: 326 327 kfree(quickstart); 328 329 return ret; 330} 331 332static int quickstart_acpi_remove(struct acpi_device *device, int type) 333{ 334 acpi_status status = 0; 335 struct quickstart_acpi *quickstart = NULL; 336 337 if (!device || !acpi_driver_data(device)) 338 return -EINVAL; 339 340 quickstart = acpi_driver_data(device); 341 342 status = acpi_remove_notify_handler(device->handle, 343 ACPI_ALL_NOTIFY, 344 quickstart_acpi_notify); 345 if (ACPI_FAILURE(status)) 346 printk(KERN_ERR "quickstart: Error removing notify handler\n"); 347 348 349 kfree(quickstart); 350 351 return 0; 352} 353 354/* Module functions */ 355 356static void quickstart_exit(void) 357{ 358 input_unregister_device(quickstart_input); 359 360 device_remove_file(&pf_device->dev, &dev_attr_pressed_button); 361 device_remove_file(&pf_device->dev, &dev_attr_buttons); 362 363 platform_device_unregister(pf_device); 364 365 platform_driver_unregister(&pf_driver); 366 367 acpi_bus_unregister_driver(&quickstart_acpi_driver); 368 369 quickstart_btnlst_free(); 370 371 return; 372} 373 374static int __init quickstart_init_input(void) 375{ 376 struct quickstart_btn **ptr = &quickstart_data.btn_lst; 377 int count; 378 int ret; 379 380 quickstart_input = input_allocate_device(); 381 382 if (!quickstart_input) 383 return -ENOMEM; 384 385 quickstart_input->name = "Quickstart ACPI Buttons"; 386 quickstart_input->id.bustype = BUS_HOST; 387 388 while (*ptr) { 389 count++; 390 set_bit(EV_KEY, quickstart_input->evbit); 391 set_bit((*ptr)->id, quickstart_input->keybit); 392 ptr = &((*ptr)->next); 393 } 394 395 ret = input_register_device(quickstart_input); 396 if (ret) { 397 input_free_device(quickstart_input); 398 return ret; 399 } 400 401 return 0; 402} 403 404static int __init quickstart_init(void) 405{ 406 int ret; 407 408 /* ACPI Check */ 409 if (acpi_disabled) 410 return -ENODEV; 411 412 /* ACPI driver register */ 413 ret = acpi_bus_register_driver(&quickstart_acpi_driver); 414 if (ret) 415 return ret; 416 417 /* If existing bus with no devices */ 418 if (!quickstart_data.btn_lst) { 419 ret = -ENODEV; 420 goto fail_pfdrv_reg; 421 } 422 423 /* Platform driver register */ 424 ret = platform_driver_register(&pf_driver); 425 if (ret) 426 goto fail_pfdrv_reg; 427 428 /* Platform device register */ 429 pf_device = platform_device_alloc(QUICKSTART_PF_DEVICE_NAME, -1); 430 if (!pf_device) { 431 ret = -ENOMEM; 432 goto fail_pfdev_alloc; 433 } 434 ret = platform_device_add(pf_device); 435 if (ret) 436 goto fail_pfdev_add; 437 438 /* Create device sysfs file */ 439 ret = device_create_file(&pf_device->dev, &dev_attr_pressed_button); 440 if (ret) 441 goto fail_dev_file; 442 443 ret = device_create_file(&pf_device->dev, &dev_attr_buttons); 444 if (ret) 445 goto fail_dev_file2; 446 447 448 /* Input device */ 449 ret = quickstart_init_input(); 450 if (ret) 451 goto fail_input; 452 453 printk(KERN_INFO "quickstart: ACPI Direct App Launch ver %s\n", 454 QUICKSTART_VERSION); 455 456 return 0; 457fail_input: 458 device_remove_file(&pf_device->dev, &dev_attr_buttons); 459 460fail_dev_file2: 461 device_remove_file(&pf_device->dev, &dev_attr_pressed_button); 462 463fail_dev_file: 464 platform_device_del(pf_device); 465 466fail_pfdev_add: 467 platform_device_put(pf_device); 468 469fail_pfdev_alloc: 470 platform_driver_unregister(&pf_driver); 471 472fail_pfdrv_reg: 473 acpi_bus_unregister_driver(&quickstart_acpi_driver); 474 475 return ret; 476} 477 478module_init(quickstart_init); 479module_exit(quickstart_exit);