linux/drivers/platform/x86/hp-wmi.c
<<
>>
Prefs
   1/*
   2 * HP WMI hotkeys
   3 *
   4 * Copyright (C) 2008 Red Hat <mjg@redhat.com>
   5 *
   6 * Portions based on wistron_btns.c:
   7 * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz>
   8 * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org>
   9 * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru>
  10 *
  11 *  This program is free software; you can redistribute it and/or modify
  12 *  it under the terms of the GNU General Public License as published by
  13 *  the Free Software Foundation; either version 2 of the License, or
  14 *  (at your option) any later version.
  15 *
  16 *  This program is distributed in the hope that it will be useful,
  17 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  18 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  19 *  GNU General Public License for more details.
  20 *
  21 *  You should have received a copy of the GNU General Public License
  22 *  along with this program; if not, write to the Free Software
  23 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  24 */
  25
  26#include <linux/kernel.h>
  27#include <linux/module.h>
  28#include <linux/init.h>
  29#include <linux/types.h>
  30#include <linux/input.h>
  31#include <acpi/acpi_drivers.h>
  32#include <linux/platform_device.h>
  33#include <linux/acpi.h>
  34#include <linux/rfkill.h>
  35#include <linux/string.h>
  36
  37MODULE_AUTHOR("Matthew Garrett <mjg59@srcf.ucam.org>");
  38MODULE_DESCRIPTION("HP laptop WMI hotkeys driver");
  39MODULE_LICENSE("GPL");
  40
  41MODULE_ALIAS("wmi:95F24279-4D7B-4334-9387-ACCDC67EF61C");
  42MODULE_ALIAS("wmi:5FB7F034-2C63-45e9-BE91-3D44E2C707E4");
  43
  44#define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C"
  45#define HPWMI_BIOS_GUID "5FB7F034-2C63-45e9-BE91-3D44E2C707E4"
  46
  47#define HPWMI_DISPLAY_QUERY 0x1
  48#define HPWMI_HDDTEMP_QUERY 0x2
  49#define HPWMI_ALS_QUERY 0x3
  50#define HPWMI_HARDWARE_QUERY 0x4
  51#define HPWMI_WIRELESS_QUERY 0x5
  52#define HPWMI_HOTKEY_QUERY 0xc
  53
  54static int __init hp_wmi_bios_setup(struct platform_device *device);
  55static int __exit hp_wmi_bios_remove(struct platform_device *device);
  56static int hp_wmi_resume_handler(struct device *device);
  57
  58struct bios_args {
  59        u32 signature;
  60        u32 command;
  61        u32 commandtype;
  62        u32 datasize;
  63        u32 data;
  64};
  65
  66struct bios_return {
  67        u32 sigpass;
  68        u32 return_code;
  69        u32 value;
  70};
  71
  72struct key_entry {
  73        char type;              /* See KE_* below */
  74        u16 code;
  75        u16 keycode;
  76};
  77
  78enum { KE_KEY, KE_END };
  79
  80static struct key_entry hp_wmi_keymap[] = {
  81        {KE_KEY, 0x02, KEY_BRIGHTNESSUP},
  82        {KE_KEY, 0x03, KEY_BRIGHTNESSDOWN},
  83        {KE_KEY, 0x20e6, KEY_PROG1},
  84        {KE_KEY, 0x2142, KEY_MEDIA},
  85        {KE_KEY, 0x213b, KEY_INFO},
  86        {KE_KEY, 0x231b, KEY_HELP},
  87        {KE_END, 0}
  88};
  89
  90static struct input_dev *hp_wmi_input_dev;
  91static struct platform_device *hp_wmi_platform_dev;
  92
  93static struct rfkill *wifi_rfkill;
  94static struct rfkill *bluetooth_rfkill;
  95static struct rfkill *wwan_rfkill;
  96
  97static struct dev_pm_ops hp_wmi_pm_ops = {
  98        .resume  = hp_wmi_resume_handler,
  99        .restore  = hp_wmi_resume_handler,
 100};
 101
 102static struct platform_driver hp_wmi_driver = {
 103        .driver = {
 104                .name = "hp-wmi",
 105                .owner = THIS_MODULE,
 106                .pm = &hp_wmi_pm_ops,
 107        },
 108        .probe = hp_wmi_bios_setup,
 109        .remove = hp_wmi_bios_remove,
 110};
 111
 112static int hp_wmi_perform_query(int query, int write, int value)
 113{
 114        struct bios_return bios_return;
 115        acpi_status status;
 116        union acpi_object *obj;
 117        struct bios_args args = {
 118                .signature = 0x55434553,
 119                .command = write ? 0x2 : 0x1,
 120                .commandtype = query,
 121                .datasize = write ? 0x4 : 0,
 122                .data = value,
 123        };
 124        struct acpi_buffer input = { sizeof(struct bios_args), &args };
 125        struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
 126
 127        status = wmi_evaluate_method(HPWMI_BIOS_GUID, 0, 0x3, &input, &output);
 128
 129        obj = output.pointer;
 130
 131        if (!obj || obj->type != ACPI_TYPE_BUFFER)
 132                return -EINVAL;
 133
 134        bios_return = *((struct bios_return *)obj->buffer.pointer);
 135        if (bios_return.return_code > 0)
 136                return bios_return.return_code * -1;
 137        else
 138                return bios_return.value;
 139}
 140
 141static int hp_wmi_display_state(void)
 142{
 143        return hp_wmi_perform_query(HPWMI_DISPLAY_QUERY, 0, 0);
 144}
 145
 146static int hp_wmi_hddtemp_state(void)
 147{
 148        return hp_wmi_perform_query(HPWMI_HDDTEMP_QUERY, 0, 0);
 149}
 150
 151static int hp_wmi_als_state(void)
 152{
 153        return hp_wmi_perform_query(HPWMI_ALS_QUERY, 0, 0);
 154}
 155
 156static int hp_wmi_dock_state(void)
 157{
 158        int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, 0);
 159
 160        if (ret < 0)
 161                return ret;
 162
 163        return ret & 0x1;
 164}
 165
 166static int hp_wmi_tablet_state(void)
 167{
 168        int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, 0);
 169
 170        if (ret < 0)
 171                return ret;
 172
 173        return (ret & 0x4) ? 1 : 0;
 174}
 175
 176static int hp_wmi_set_block(void *data, bool blocked)
 177{
 178        unsigned long b = (unsigned long) data;
 179        int query = BIT(b + 8) | ((!blocked) << b);
 180
 181        return hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, query);
 182}
 183
 184static const struct rfkill_ops hp_wmi_rfkill_ops = {
 185        .set_block = hp_wmi_set_block,
 186};
 187
 188static bool hp_wmi_wifi_state(void)
 189{
 190        int wireless = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, 0);
 191
 192        if (wireless & 0x100)
 193                return false;
 194        else
 195                return true;
 196}
 197
 198static bool hp_wmi_bluetooth_state(void)
 199{
 200        int wireless = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, 0);
 201
 202        if (wireless & 0x10000)
 203                return false;
 204        else
 205                return true;
 206}
 207
 208static bool hp_wmi_wwan_state(void)
 209{
 210        int wireless = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, 0);
 211
 212        if (wireless & 0x1000000)
 213                return false;
 214        else
 215                return true;
 216}
 217
 218static ssize_t show_display(struct device *dev, struct device_attribute *attr,
 219                            char *buf)
 220{
 221        int value = hp_wmi_display_state();
 222        if (value < 0)
 223                return -EINVAL;
 224        return sprintf(buf, "%d\n", value);
 225}
 226
 227static ssize_t show_hddtemp(struct device *dev, struct device_attribute *attr,
 228                            char *buf)
 229{
 230        int value = hp_wmi_hddtemp_state();
 231        if (value < 0)
 232                return -EINVAL;
 233        return sprintf(buf, "%d\n", value);
 234}
 235
 236static ssize_t show_als(struct device *dev, struct device_attribute *attr,
 237                        char *buf)
 238{
 239        int value = hp_wmi_als_state();
 240        if (value < 0)
 241                return -EINVAL;
 242        return sprintf(buf, "%d\n", value);
 243}
 244
 245static ssize_t show_dock(struct device *dev, struct device_attribute *attr,
 246                         char *buf)
 247{
 248        int value = hp_wmi_dock_state();
 249        if (value < 0)
 250                return -EINVAL;
 251        return sprintf(buf, "%d\n", value);
 252}
 253
 254static ssize_t show_tablet(struct device *dev, struct device_attribute *attr,
 255                         char *buf)
 256{
 257        int value = hp_wmi_tablet_state();
 258        if (value < 0)
 259                return -EINVAL;
 260        return sprintf(buf, "%d\n", value);
 261}
 262
 263static ssize_t set_als(struct device *dev, struct device_attribute *attr,
 264                       const char *buf, size_t count)
 265{
 266        u32 tmp = simple_strtoul(buf, NULL, 10);
 267        hp_wmi_perform_query(HPWMI_ALS_QUERY, 1, tmp);
 268        return count;
 269}
 270
 271static DEVICE_ATTR(display, S_IRUGO, show_display, NULL);
 272static DEVICE_ATTR(hddtemp, S_IRUGO, show_hddtemp, NULL);
 273static DEVICE_ATTR(als, S_IRUGO | S_IWUSR, show_als, set_als);
 274static DEVICE_ATTR(dock, S_IRUGO, show_dock, NULL);
 275static DEVICE_ATTR(tablet, S_IRUGO, show_tablet, NULL);
 276
 277static struct key_entry *hp_wmi_get_entry_by_scancode(int code)
 278{
 279        struct key_entry *key;
 280
 281        for (key = hp_wmi_keymap; key->type != KE_END; key++)
 282                if (code == key->code)
 283                        return key;
 284
 285        return NULL;
 286}
 287
 288static struct key_entry *hp_wmi_get_entry_by_keycode(int keycode)
 289{
 290        struct key_entry *key;
 291
 292        for (key = hp_wmi_keymap; key->type != KE_END; key++)
 293                if (key->type == KE_KEY && keycode == key->keycode)
 294                        return key;
 295
 296        return NULL;
 297}
 298
 299static int hp_wmi_getkeycode(struct input_dev *dev, int scancode, int *keycode)
 300{
 301        struct key_entry *key = hp_wmi_get_entry_by_scancode(scancode);
 302
 303        if (key && key->type == KE_KEY) {
 304                *keycode = key->keycode;
 305                return 0;
 306        }
 307
 308        return -EINVAL;
 309}
 310
 311static int hp_wmi_setkeycode(struct input_dev *dev, int scancode, int keycode)
 312{
 313        struct key_entry *key;
 314        int old_keycode;
 315
 316        if (keycode < 0 || keycode > KEY_MAX)
 317                return -EINVAL;
 318
 319        key = hp_wmi_get_entry_by_scancode(scancode);
 320        if (key && key->type == KE_KEY) {
 321                old_keycode = key->keycode;
 322                key->keycode = keycode;
 323                set_bit(keycode, dev->keybit);
 324                if (!hp_wmi_get_entry_by_keycode(old_keycode))
 325                        clear_bit(old_keycode, dev->keybit);
 326                return 0;
 327        }
 328
 329        return -EINVAL;
 330}
 331
 332static void hp_wmi_notify(u32 value, void *context)
 333{
 334        struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
 335        static struct key_entry *key;
 336        union acpi_object *obj;
 337
 338        wmi_get_event_data(value, &response);
 339
 340        obj = (union acpi_object *)response.pointer;
 341
 342        if (obj && obj->type == ACPI_TYPE_BUFFER && obj->buffer.length == 8) {
 343                int eventcode = *((u8 *) obj->buffer.pointer);
 344                if (eventcode == 0x4)
 345                        eventcode = hp_wmi_perform_query(HPWMI_HOTKEY_QUERY, 0,
 346                                                         0);
 347                key = hp_wmi_get_entry_by_scancode(eventcode);
 348                if (key) {
 349                        switch (key->type) {
 350                        case KE_KEY:
 351                                input_report_key(hp_wmi_input_dev,
 352                                                 key->keycode, 1);
 353                                input_sync(hp_wmi_input_dev);
 354                                input_report_key(hp_wmi_input_dev,
 355                                                 key->keycode, 0);
 356                                input_sync(hp_wmi_input_dev);
 357                                break;
 358                        }
 359                } else if (eventcode == 0x1) {
 360                        input_report_switch(hp_wmi_input_dev, SW_DOCK,
 361                                            hp_wmi_dock_state());
 362                        input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE,
 363                                            hp_wmi_tablet_state());
 364                        input_sync(hp_wmi_input_dev);
 365                } else if (eventcode == 0x5) {
 366                        if (wifi_rfkill)
 367                                rfkill_set_sw_state(wifi_rfkill,
 368                                                    hp_wmi_wifi_state());
 369                        if (bluetooth_rfkill)
 370                                rfkill_set_sw_state(bluetooth_rfkill,
 371                                                    hp_wmi_bluetooth_state());
 372                        if (wwan_rfkill)
 373                                rfkill_set_sw_state(wwan_rfkill,
 374                                                    hp_wmi_wwan_state());
 375                } else
 376                        printk(KERN_INFO "HP WMI: Unknown key pressed - %x\n",
 377                               eventcode);
 378        } else
 379                printk(KERN_INFO "HP WMI: Unknown response received\n");
 380}
 381
 382static int __init hp_wmi_input_setup(void)
 383{
 384        struct key_entry *key;
 385        int err;
 386
 387        hp_wmi_input_dev = input_allocate_device();
 388
 389        hp_wmi_input_dev->name = "HP WMI hotkeys";
 390        hp_wmi_input_dev->phys = "wmi/input0";
 391        hp_wmi_input_dev->id.bustype = BUS_HOST;
 392        hp_wmi_input_dev->getkeycode = hp_wmi_getkeycode;
 393        hp_wmi_input_dev->setkeycode = hp_wmi_setkeycode;
 394
 395        for (key = hp_wmi_keymap; key->type != KE_END; key++) {
 396                switch (key->type) {
 397                case KE_KEY:
 398                        set_bit(EV_KEY, hp_wmi_input_dev->evbit);
 399                        set_bit(key->keycode, hp_wmi_input_dev->keybit);
 400                        break;
 401                }
 402        }
 403
 404        set_bit(EV_SW, hp_wmi_input_dev->evbit);
 405        set_bit(SW_DOCK, hp_wmi_input_dev->swbit);
 406        set_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit);
 407
 408        /* Set initial hardware state */
 409        input_report_switch(hp_wmi_input_dev, SW_DOCK, hp_wmi_dock_state());
 410        input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE,
 411                            hp_wmi_tablet_state());
 412        input_sync(hp_wmi_input_dev);
 413
 414        err = input_register_device(hp_wmi_input_dev);
 415
 416        if (err) {
 417                input_free_device(hp_wmi_input_dev);
 418                return err;
 419        }
 420
 421        return 0;
 422}
 423
 424static void cleanup_sysfs(struct platform_device *device)
 425{
 426        device_remove_file(&device->dev, &dev_attr_display);
 427        device_remove_file(&device->dev, &dev_attr_hddtemp);
 428        device_remove_file(&device->dev, &dev_attr_als);
 429        device_remove_file(&device->dev, &dev_attr_dock);
 430        device_remove_file(&device->dev, &dev_attr_tablet);
 431}
 432
 433static int __init hp_wmi_bios_setup(struct platform_device *device)
 434{
 435        int err;
 436        int wireless = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, 0);
 437
 438        err = device_create_file(&device->dev, &dev_attr_display);
 439        if (err)
 440                goto add_sysfs_error;
 441        err = device_create_file(&device->dev, &dev_attr_hddtemp);
 442        if (err)
 443                goto add_sysfs_error;
 444        err = device_create_file(&device->dev, &dev_attr_als);
 445        if (err)
 446                goto add_sysfs_error;
 447        err = device_create_file(&device->dev, &dev_attr_dock);
 448        if (err)
 449                goto add_sysfs_error;
 450        err = device_create_file(&device->dev, &dev_attr_tablet);
 451        if (err)
 452                goto add_sysfs_error;
 453
 454        if (wireless & 0x1) {
 455                wifi_rfkill = rfkill_alloc("hp-wifi", &device->dev,
 456                                           RFKILL_TYPE_WLAN,
 457                                           &hp_wmi_rfkill_ops,
 458                                           (void *) 0);
 459                err = rfkill_register(wifi_rfkill);
 460                if (err)
 461                        goto register_wifi_error;
 462        }
 463
 464        if (wireless & 0x2) {
 465                bluetooth_rfkill = rfkill_alloc("hp-bluetooth", &device->dev,
 466                                                RFKILL_TYPE_BLUETOOTH,
 467                                                &hp_wmi_rfkill_ops,
 468                                                (void *) 1);
 469                err = rfkill_register(bluetooth_rfkill);
 470                if (err)
 471                        goto register_bluetooth_error;
 472        }
 473
 474        if (wireless & 0x4) {
 475                wwan_rfkill = rfkill_alloc("hp-wwan", &device->dev,
 476                                           RFKILL_TYPE_WWAN,
 477                                           &hp_wmi_rfkill_ops,
 478                                           (void *) 2);
 479                err = rfkill_register(wwan_rfkill);
 480                if (err)
 481                        goto register_wwan_err;
 482        }
 483
 484        return 0;
 485register_wwan_err:
 486        rfkill_destroy(wwan_rfkill);
 487        if (bluetooth_rfkill)
 488                rfkill_unregister(bluetooth_rfkill);
 489register_bluetooth_error:
 490        rfkill_destroy(bluetooth_rfkill);
 491        if (wifi_rfkill)
 492                rfkill_unregister(wifi_rfkill);
 493register_wifi_error:
 494        rfkill_destroy(wifi_rfkill);
 495add_sysfs_error:
 496        cleanup_sysfs(device);
 497        return err;
 498}
 499
 500static int __exit hp_wmi_bios_remove(struct platform_device *device)
 501{
 502        cleanup_sysfs(device);
 503
 504        if (wifi_rfkill) {
 505                rfkill_unregister(wifi_rfkill);
 506                rfkill_destroy(wifi_rfkill);
 507        }
 508        if (bluetooth_rfkill) {
 509                rfkill_unregister(bluetooth_rfkill);
 510                rfkill_destroy(bluetooth_rfkill);
 511        }
 512        if (wwan_rfkill) {
 513                rfkill_unregister(wwan_rfkill);
 514                rfkill_destroy(wwan_rfkill);
 515        }
 516
 517        return 0;
 518}
 519
 520static int hp_wmi_resume_handler(struct device *device)
 521{
 522        /*
 523         * Hardware state may have changed while suspended, so trigger
 524         * input events for the current state. As this is a switch,
 525         * the input layer will only actually pass it on if the state
 526         * changed.
 527         */
 528        if (hp_wmi_input_dev) {
 529                input_report_switch(hp_wmi_input_dev, SW_DOCK,
 530                                    hp_wmi_dock_state());
 531                input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE,
 532                                    hp_wmi_tablet_state());
 533                input_sync(hp_wmi_input_dev);
 534        }
 535
 536        return 0;
 537}
 538
 539static int __init hp_wmi_init(void)
 540{
 541        int err;
 542
 543        if (wmi_has_guid(HPWMI_EVENT_GUID)) {
 544                err = wmi_install_notify_handler(HPWMI_EVENT_GUID,
 545                                                 hp_wmi_notify, NULL);
 546                if (!err)
 547                        hp_wmi_input_setup();
 548        }
 549
 550        if (wmi_has_guid(HPWMI_BIOS_GUID)) {
 551                err = platform_driver_register(&hp_wmi_driver);
 552                if (err)
 553                        return 0;
 554                hp_wmi_platform_dev = platform_device_alloc("hp-wmi", -1);
 555                if (!hp_wmi_platform_dev) {
 556                        platform_driver_unregister(&hp_wmi_driver);
 557                        return 0;
 558                }
 559                platform_device_add(hp_wmi_platform_dev);
 560        }
 561
 562        return 0;
 563}
 564
 565static void __exit hp_wmi_exit(void)
 566{
 567        if (wmi_has_guid(HPWMI_EVENT_GUID)) {
 568                wmi_remove_notify_handler(HPWMI_EVENT_GUID);
 569                input_unregister_device(hp_wmi_input_dev);
 570        }
 571        if (hp_wmi_platform_dev) {
 572                platform_device_del(hp_wmi_platform_dev);
 573                platform_driver_unregister(&hp_wmi_driver);
 574        }
 575}
 576
 577module_init(hp_wmi_init);
 578module_exit(hp_wmi_exit);
 579