linux/drivers/staging/quickstart/quickstart.c
<<
>>
Prefs
   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 disassembled 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.04"
  27
  28#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  29
  30#include <linux/kernel.h>
  31#include <linux/module.h>
  32#include <linux/init.h>
  33#include <linux/types.h>
  34#include <acpi/acpi_drivers.h>
  35#include <linux/platform_device.h>
  36#include <linux/input.h>
  37
  38MODULE_AUTHOR("Angelo Arrifano");
  39MODULE_DESCRIPTION("ACPI Direct App Launch driver");
  40MODULE_LICENSE("GPL");
  41
  42#define QUICKSTART_ACPI_DEVICE_NAME     "quickstart"
  43#define QUICKSTART_ACPI_CLASS           "quickstart"
  44#define QUICKSTART_ACPI_HID             "PNP0C32"
  45
  46#define QUICKSTART_PF_DRIVER_NAME       "quickstart"
  47#define QUICKSTART_PF_DEVICE_NAME       "quickstart"
  48
  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 */
  54#define QUICKSTART_EVENT_WAKE           0x02
  55#define QUICKSTART_EVENT_RUNTIME        0x80
  56
  57struct quickstart_button {
  58        char *name;
  59        unsigned int id;
  60        struct list_head list;
  61};
  62
  63struct quickstart_acpi {
  64        struct acpi_device *device;
  65        struct quickstart_button *button;
  66};
  67
  68static LIST_HEAD(buttons);
  69static struct quickstart_button *pressed;
  70
  71static struct input_dev *quickstart_input;
  72
  73/* Platform driver functions */
  74static ssize_t quickstart_buttons_show(struct device *dev,
  75                                        struct device_attribute *attr,
  76                                        char *buf)
  77{
  78        int count = 0;
  79        struct quickstart_button *b;
  80
  81        if (list_empty(&buttons))
  82                return snprintf(buf, PAGE_SIZE, "none");
  83
  84        list_for_each_entry(b, &buttons, list) {
  85                count += snprintf(buf + count, PAGE_SIZE - count, "%u\t%s\n",
  86                                                        b->id, b->name);
  87
  88                if (count >= PAGE_SIZE) {
  89                        count = PAGE_SIZE;
  90                        break;
  91                }
  92        }
  93
  94        return count;
  95}
  96
  97static ssize_t quickstart_pressed_button_show(struct device *dev,
  98                                                struct device_attribute *attr,
  99                                                char *buf)
 100{
 101        return scnprintf(buf, PAGE_SIZE, "%s\n",
 102                                        (pressed ? pressed->name : "none"));
 103}
 104
 105
 106static ssize_t quickstart_pressed_button_store(struct device *dev,
 107                                                struct device_attribute *attr,
 108                                                const char *buf, size_t count)
 109{
 110        if (count < 2)
 111                return -EINVAL;
 112
 113        if (strncasecmp(buf, "none", 4) != 0)
 114                return -EINVAL;
 115
 116        pressed = NULL;
 117        return count;
 118}
 119
 120/* Helper functions */
 121static struct quickstart_button *quickstart_buttons_add(void)
 122{
 123        struct quickstart_button *b;
 124
 125        b = kzalloc(sizeof(*b), GFP_KERNEL);
 126        if (!b)
 127                return NULL;
 128
 129        list_add_tail(&b->list, &buttons);
 130
 131        return b;
 132}
 133
 134static void quickstart_button_del(struct quickstart_button *data)
 135{
 136        if (!data)
 137                return;
 138
 139        list_del(&data->list);
 140        kfree(data->name);
 141        kfree(data);
 142}
 143
 144static void quickstart_buttons_free(void)
 145{
 146        struct quickstart_button *b, *n;
 147
 148        list_for_each_entry_safe(b, n, &buttons, list)
 149                quickstart_button_del(b);
 150}
 151
 152/* ACPI Driver functions */
 153static void quickstart_acpi_notify(acpi_handle handle, u32 event, void *data)
 154{
 155        struct quickstart_acpi *quickstart = data;
 156
 157        if (!quickstart)
 158                return;
 159
 160        switch (event) {
 161        case QUICKSTART_EVENT_WAKE:
 162                pressed = quickstart->button;
 163                break;
 164        case QUICKSTART_EVENT_RUNTIME:
 165                input_report_key(quickstart_input, quickstart->button->id, 1);
 166                input_sync(quickstart_input);
 167                input_report_key(quickstart_input, quickstart->button->id, 0);
 168                input_sync(quickstart_input);
 169                break;
 170        default:
 171                pr_err("Unexpected ACPI event notify (%u)\n", event);
 172                break;
 173        }
 174}
 175
 176static int quickstart_acpi_ghid(struct quickstart_acpi *quickstart)
 177{
 178        acpi_status status;
 179        struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
 180        int ret = 0;
 181
 182        /*
 183         * This returns a buffer telling the button usage ID,
 184         * and triggers pending notify events (The ones before booting).
 185         */
 186        status = acpi_evaluate_object(quickstart->device->handle, "GHID", NULL,
 187                                                                &buffer);
 188        if (ACPI_FAILURE(status)) {
 189                pr_err("%s GHID method failed\n", quickstart->button->name);
 190                return -EINVAL;
 191        }
 192
 193        /*
 194         * <<The GHID method can return a BYTE, WORD, or DWORD.
 195         * The value must be encoded in little-endian byte
 196         * order (least significant byte first).>>
 197         */
 198        switch (buffer.length) {
 199        case 1:
 200                quickstart->button->id = *(uint8_t *)buffer.pointer;
 201                break;
 202        case 2:
 203                quickstart->button->id = *(uint16_t *)buffer.pointer;
 204                break;
 205        case 4:
 206                quickstart->button->id = *(uint32_t *)buffer.pointer;
 207                break;
 208        case 8:
 209                quickstart->button->id = *(uint64_t *)buffer.pointer;
 210                break;
 211        default:
 212                pr_err("%s GHID method returned buffer of unexpected length %lu\n",
 213                                quickstart->button->name,
 214                                (unsigned long)buffer.length);
 215                ret = -EINVAL;
 216                break;
 217        }
 218
 219        kfree(buffer.pointer);
 220
 221        return ret;
 222}
 223
 224static int quickstart_acpi_config(struct quickstart_acpi *quickstart)
 225{
 226        char *bid = acpi_device_bid(quickstart->device);
 227        char *name;
 228
 229        name = kmalloc(strlen(bid) + 1, GFP_KERNEL);
 230        if (!name)
 231                return -ENOMEM;
 232
 233        /* Add new button to list */
 234        quickstart->button = quickstart_buttons_add();
 235        if (!quickstart->button) {
 236                kfree(name);
 237                return -ENOMEM;
 238        }
 239
 240        quickstart->button->name = name;
 241        strcpy(quickstart->button->name, bid);
 242
 243        return 0;
 244}
 245
 246static int quickstart_acpi_add(struct acpi_device *device)
 247{
 248        int ret;
 249        acpi_status status;
 250        struct quickstart_acpi *quickstart;
 251
 252        if (!device)
 253                return -EINVAL;
 254
 255        quickstart = kzalloc(sizeof(*quickstart), GFP_KERNEL);
 256        if (!quickstart)
 257                return -ENOMEM;
 258
 259        quickstart->device = device;
 260
 261        strcpy(acpi_device_name(device), QUICKSTART_ACPI_DEVICE_NAME);
 262        strcpy(acpi_device_class(device), QUICKSTART_ACPI_CLASS);
 263        device->driver_data = quickstart;
 264
 265        /* Add button to list and initialize some stuff */
 266        ret = quickstart_acpi_config(quickstart);
 267        if (ret < 0)
 268                goto fail_config;
 269
 270        status = acpi_install_notify_handler(device->handle, ACPI_ALL_NOTIFY,
 271                                                quickstart_acpi_notify,
 272                                                quickstart);
 273        if (ACPI_FAILURE(status)) {
 274                pr_err("Notify handler install error\n");
 275                ret = -ENODEV;
 276                goto fail_installnotify;
 277        }
 278
 279        ret = quickstart_acpi_ghid(quickstart);
 280        if (ret < 0)
 281                goto fail_ghid;
 282
 283        return 0;
 284
 285fail_ghid:
 286        acpi_remove_notify_handler(device->handle, ACPI_ALL_NOTIFY,
 287                                                quickstart_acpi_notify);
 288
 289fail_installnotify:
 290        quickstart_button_del(quickstart->button);
 291
 292fail_config:
 293
 294        kfree(quickstart);
 295
 296        return ret;
 297}
 298
 299static int quickstart_acpi_remove(struct acpi_device *device)
 300{
 301        acpi_status status;
 302        struct quickstart_acpi *quickstart;
 303
 304        if (!device)
 305                return -EINVAL;
 306
 307        quickstart = acpi_driver_data(device);
 308        if (!quickstart)
 309                return -EINVAL;
 310
 311        status = acpi_remove_notify_handler(device->handle, ACPI_ALL_NOTIFY,
 312                                                quickstart_acpi_notify);
 313        if (ACPI_FAILURE(status))
 314                pr_err("Error removing notify handler\n");
 315
 316        kfree(quickstart);
 317
 318        return 0;
 319}
 320
 321/* Platform driver structs */
 322static DEVICE_ATTR(pressed_button, 0666, quickstart_pressed_button_show,
 323                                         quickstart_pressed_button_store);
 324static DEVICE_ATTR(buttons, 0444, quickstart_buttons_show, NULL);
 325static struct platform_device *pf_device;
 326static struct platform_driver pf_driver = {
 327        .driver = {
 328                .name = QUICKSTART_PF_DRIVER_NAME,
 329                .owner = THIS_MODULE,
 330        }
 331};
 332
 333static const struct acpi_device_id quickstart_device_ids[] = {
 334        {QUICKSTART_ACPI_HID, 0},
 335        {"", 0},
 336};
 337
 338static struct acpi_driver quickstart_acpi_driver = {
 339        .name = "quickstart",
 340        .class = QUICKSTART_ACPI_CLASS,
 341        .ids = quickstart_device_ids,
 342        .ops = {
 343                        .add = quickstart_acpi_add,
 344                        .remove = quickstart_acpi_remove,
 345                },
 346};
 347
 348/* Module functions */
 349static void quickstart_exit(void)
 350{
 351        input_unregister_device(quickstart_input);
 352
 353        device_remove_file(&pf_device->dev, &dev_attr_pressed_button);
 354        device_remove_file(&pf_device->dev, &dev_attr_buttons);
 355
 356        platform_device_unregister(pf_device);
 357
 358        platform_driver_unregister(&pf_driver);
 359
 360        acpi_bus_unregister_driver(&quickstart_acpi_driver);
 361
 362        quickstart_buttons_free();
 363}
 364
 365static int __init quickstart_init_input(void)
 366{
 367        struct quickstart_button *b;
 368        int ret;
 369
 370        quickstart_input = input_allocate_device();
 371
 372        if (!quickstart_input)
 373                return -ENOMEM;
 374
 375        quickstart_input->name = "Quickstart ACPI Buttons";
 376        quickstart_input->id.bustype = BUS_HOST;
 377
 378        list_for_each_entry(b, &buttons, list) {
 379                set_bit(EV_KEY, quickstart_input->evbit);
 380                set_bit(b->id, quickstart_input->keybit);
 381        }
 382
 383        ret = input_register_device(quickstart_input);
 384        if (ret) {
 385                input_free_device(quickstart_input);
 386                return ret;
 387        }
 388
 389        return 0;
 390}
 391
 392static int __init quickstart_init(void)
 393{
 394        int ret;
 395
 396        /* ACPI Check */
 397        if (acpi_disabled)
 398                return -ENODEV;
 399
 400        /* ACPI driver register */
 401        ret = acpi_bus_register_driver(&quickstart_acpi_driver);
 402        if (ret)
 403                return ret;
 404
 405        /* If existing bus with no devices */
 406        if (list_empty(&buttons)) {
 407                ret = -ENODEV;
 408                goto fail_pfdrv_reg;
 409        }
 410
 411        /* Platform driver register */
 412        ret = platform_driver_register(&pf_driver);
 413        if (ret)
 414                goto fail_pfdrv_reg;
 415
 416        /* Platform device register */
 417        pf_device = platform_device_alloc(QUICKSTART_PF_DEVICE_NAME, -1);
 418        if (!pf_device) {
 419                ret = -ENOMEM;
 420                goto fail_pfdev_alloc;
 421        }
 422        ret = platform_device_add(pf_device);
 423        if (ret)
 424                goto fail_pfdev_add;
 425
 426        /* Create device sysfs file */
 427        ret = device_create_file(&pf_device->dev, &dev_attr_pressed_button);
 428        if (ret)
 429                goto fail_dev_file;
 430
 431        ret = device_create_file(&pf_device->dev, &dev_attr_buttons);
 432        if (ret)
 433                goto fail_dev_file2;
 434
 435        /* Input device */
 436        ret = quickstart_init_input();
 437        if (ret)
 438                goto fail_input;
 439
 440        pr_info("ACPI Direct App Launch ver %s\n", QUICKSTART_VERSION);
 441
 442        return 0;
 443fail_input:
 444        device_remove_file(&pf_device->dev, &dev_attr_buttons);
 445
 446fail_dev_file2:
 447        device_remove_file(&pf_device->dev, &dev_attr_pressed_button);
 448
 449fail_dev_file:
 450        platform_device_del(pf_device);
 451
 452fail_pfdev_add:
 453        platform_device_put(pf_device);
 454
 455fail_pfdev_alloc:
 456        platform_driver_unregister(&pf_driver);
 457
 458fail_pfdrv_reg:
 459        acpi_bus_unregister_driver(&quickstart_acpi_driver);
 460
 461        return ret;
 462}
 463
 464module_init(quickstart_init);
 465module_exit(quickstart_exit);
 466