linux/drivers/platform/x86/ideapad-laptop.c
<<
>>
Prefs
   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#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  24
  25#include <linux/kernel.h>
  26#include <linux/module.h>
  27#include <linux/init.h>
  28#include <linux/types.h>
  29#include <acpi/acpi_bus.h>
  30#include <acpi/acpi_drivers.h>
  31#include <linux/rfkill.h>
  32#include <linux/platform_device.h>
  33#include <linux/input.h>
  34#include <linux/input/sparse-keymap.h>
  35#include <linux/backlight.h>
  36#include <linux/fb.h>
  37#include <linux/debugfs.h>
  38#include <linux/seq_file.h>
  39#include <linux/i8042.h>
  40
  41#define IDEAPAD_RFKILL_DEV_NUM  (3)
  42
  43#define CFG_BT_BIT      (16)
  44#define CFG_3G_BIT      (17)
  45#define CFG_WIFI_BIT    (18)
  46#define CFG_CAMERA_BIT  (19)
  47
  48enum {
  49        VPCCMD_R_VPC1 = 0x10,
  50        VPCCMD_R_BL_MAX,
  51        VPCCMD_R_BL,
  52        VPCCMD_W_BL,
  53        VPCCMD_R_WIFI,
  54        VPCCMD_W_WIFI,
  55        VPCCMD_R_BT,
  56        VPCCMD_W_BT,
  57        VPCCMD_R_BL_POWER,
  58        VPCCMD_R_NOVO,
  59        VPCCMD_R_VPC2,
  60        VPCCMD_R_TOUCHPAD,
  61        VPCCMD_W_TOUCHPAD,
  62        VPCCMD_R_CAMERA,
  63        VPCCMD_W_CAMERA,
  64        VPCCMD_R_3G,
  65        VPCCMD_W_3G,
  66        VPCCMD_R_ODD, /* 0x21 */
  67        VPCCMD_W_FAN,
  68        VPCCMD_R_RF,
  69        VPCCMD_W_RF,
  70        VPCCMD_R_FAN = 0x2B,
  71        VPCCMD_R_SPECIAL_BUTTONS = 0x31,
  72        VPCCMD_W_BL_POWER = 0x33,
  73};
  74
  75struct ideapad_private {
  76        struct rfkill *rfk[IDEAPAD_RFKILL_DEV_NUM];
  77        struct platform_device *platform_device;
  78        struct input_dev *inputdev;
  79        struct backlight_device *blightdev;
  80        struct dentry *debug;
  81        unsigned long cfg;
  82};
  83
  84static acpi_handle ideapad_handle;
  85static struct ideapad_private *ideapad_priv;
  86static bool no_bt_rfkill;
  87module_param(no_bt_rfkill, bool, 0444);
  88MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth.");
  89
  90/*
  91 * ACPI Helpers
  92 */
  93#define IDEAPAD_EC_TIMEOUT (100) /* in ms */
  94
  95static int read_method_int(acpi_handle handle, const char *method, int *val)
  96{
  97        acpi_status status;
  98        unsigned long long result;
  99
 100        status = acpi_evaluate_integer(handle, (char *)method, NULL, &result);
 101        if (ACPI_FAILURE(status)) {
 102                *val = -1;
 103                return -1;
 104        } else {
 105                *val = result;
 106                return 0;
 107        }
 108}
 109
 110static int method_vpcr(acpi_handle handle, int cmd, int *ret)
 111{
 112        acpi_status status;
 113        unsigned long long result;
 114        struct acpi_object_list params;
 115        union acpi_object in_obj;
 116
 117        params.count = 1;
 118        params.pointer = &in_obj;
 119        in_obj.type = ACPI_TYPE_INTEGER;
 120        in_obj.integer.value = cmd;
 121
 122        status = acpi_evaluate_integer(handle, "VPCR", &params, &result);
 123
 124        if (ACPI_FAILURE(status)) {
 125                *ret = -1;
 126                return -1;
 127        } else {
 128                *ret = result;
 129                return 0;
 130        }
 131}
 132
 133static int method_vpcw(acpi_handle handle, int cmd, int data)
 134{
 135        struct acpi_object_list params;
 136        union acpi_object in_obj[2];
 137        acpi_status status;
 138
 139        params.count = 2;
 140        params.pointer = in_obj;
 141        in_obj[0].type = ACPI_TYPE_INTEGER;
 142        in_obj[0].integer.value = cmd;
 143        in_obj[1].type = ACPI_TYPE_INTEGER;
 144        in_obj[1].integer.value = data;
 145
 146        status = acpi_evaluate_object(handle, "VPCW", &params, NULL);
 147        if (status != AE_OK)
 148                return -1;
 149        return 0;
 150}
 151
 152static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data)
 153{
 154        int val;
 155        unsigned long int end_jiffies;
 156
 157        if (method_vpcw(handle, 1, cmd))
 158                return -1;
 159
 160        for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1;
 161             time_before(jiffies, end_jiffies);) {
 162                schedule();
 163                if (method_vpcr(handle, 1, &val))
 164                        return -1;
 165                if (val == 0) {
 166                        if (method_vpcr(handle, 0, &val))
 167                                return -1;
 168                        *data = val;
 169                        return 0;
 170                }
 171        }
 172        pr_err("timeout in read_ec_cmd\n");
 173        return -1;
 174}
 175
 176static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data)
 177{
 178        int val;
 179        unsigned long int end_jiffies;
 180
 181        if (method_vpcw(handle, 0, data))
 182                return -1;
 183        if (method_vpcw(handle, 1, cmd))
 184                return -1;
 185
 186        for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1;
 187             time_before(jiffies, end_jiffies);) {
 188                schedule();
 189                if (method_vpcr(handle, 1, &val))
 190                        return -1;
 191                if (val == 0)
 192                        return 0;
 193        }
 194        pr_err("timeout in write_ec_cmd\n");
 195        return -1;
 196}
 197
 198/*
 199 * debugfs
 200 */
 201static int debugfs_status_show(struct seq_file *s, void *data)
 202{
 203        unsigned long value;
 204
 205        if (!read_ec_data(ideapad_handle, VPCCMD_R_BL_MAX, &value))
 206                seq_printf(s, "Backlight max:\t%lu\n", value);
 207        if (!read_ec_data(ideapad_handle, VPCCMD_R_BL, &value))
 208                seq_printf(s, "Backlight now:\t%lu\n", value);
 209        if (!read_ec_data(ideapad_handle, VPCCMD_R_BL_POWER, &value))
 210                seq_printf(s, "BL power value:\t%s\n", value ? "On" : "Off");
 211        seq_printf(s, "=====================\n");
 212
 213        if (!read_ec_data(ideapad_handle, VPCCMD_R_RF, &value))
 214                seq_printf(s, "Radio status:\t%s(%lu)\n",
 215                           value ? "On" : "Off", value);
 216        if (!read_ec_data(ideapad_handle, VPCCMD_R_WIFI, &value))
 217                seq_printf(s, "Wifi status:\t%s(%lu)\n",
 218                           value ? "On" : "Off", value);
 219        if (!read_ec_data(ideapad_handle, VPCCMD_R_BT, &value))
 220                seq_printf(s, "BT status:\t%s(%lu)\n",
 221                           value ? "On" : "Off", value);
 222        if (!read_ec_data(ideapad_handle, VPCCMD_R_3G, &value))
 223                seq_printf(s, "3G status:\t%s(%lu)\n",
 224                           value ? "On" : "Off", value);
 225        seq_printf(s, "=====================\n");
 226
 227        if (!read_ec_data(ideapad_handle, VPCCMD_R_TOUCHPAD, &value))
 228                seq_printf(s, "Touchpad status:%s(%lu)\n",
 229                           value ? "On" : "Off", value);
 230        if (!read_ec_data(ideapad_handle, VPCCMD_R_CAMERA, &value))
 231                seq_printf(s, "Camera status:\t%s(%lu)\n",
 232                           value ? "On" : "Off", value);
 233
 234        return 0;
 235}
 236
 237static int debugfs_status_open(struct inode *inode, struct file *file)
 238{
 239        return single_open(file, debugfs_status_show, NULL);
 240}
 241
 242static const struct file_operations debugfs_status_fops = {
 243        .owner = THIS_MODULE,
 244        .open = debugfs_status_open,
 245        .read = seq_read,
 246        .llseek = seq_lseek,
 247        .release = single_release,
 248};
 249
 250static int debugfs_cfg_show(struct seq_file *s, void *data)
 251{
 252        if (!ideapad_priv) {
 253                seq_printf(s, "cfg: N/A\n");
 254        } else {
 255                seq_printf(s, "cfg: 0x%.8lX\n\nCapability: ",
 256                           ideapad_priv->cfg);
 257                if (test_bit(CFG_BT_BIT, &ideapad_priv->cfg))
 258                        seq_printf(s, "Bluetooth ");
 259                if (test_bit(CFG_3G_BIT, &ideapad_priv->cfg))
 260                        seq_printf(s, "3G ");
 261                if (test_bit(CFG_WIFI_BIT, &ideapad_priv->cfg))
 262                        seq_printf(s, "Wireless ");
 263                if (test_bit(CFG_CAMERA_BIT, &ideapad_priv->cfg))
 264                        seq_printf(s, "Camera ");
 265                seq_printf(s, "\nGraphic: ");
 266                switch ((ideapad_priv->cfg)&0x700) {
 267                case 0x100:
 268                        seq_printf(s, "Intel");
 269                        break;
 270                case 0x200:
 271                        seq_printf(s, "ATI");
 272                        break;
 273                case 0x300:
 274                        seq_printf(s, "Nvidia");
 275                        break;
 276                case 0x400:
 277                        seq_printf(s, "Intel and ATI");
 278                        break;
 279                case 0x500:
 280                        seq_printf(s, "Intel and Nvidia");
 281                        break;
 282                }
 283                seq_printf(s, "\n");
 284        }
 285        return 0;
 286}
 287
 288static int debugfs_cfg_open(struct inode *inode, struct file *file)
 289{
 290        return single_open(file, debugfs_cfg_show, NULL);
 291}
 292
 293static const struct file_operations debugfs_cfg_fops = {
 294        .owner = THIS_MODULE,
 295        .open = debugfs_cfg_open,
 296        .read = seq_read,
 297        .llseek = seq_lseek,
 298        .release = single_release,
 299};
 300
 301static int ideapad_debugfs_init(struct ideapad_private *priv)
 302{
 303        struct dentry *node;
 304
 305        priv->debug = debugfs_create_dir("ideapad", NULL);
 306        if (priv->debug == NULL) {
 307                pr_err("failed to create debugfs directory");
 308                goto errout;
 309        }
 310
 311        node = debugfs_create_file("cfg", S_IRUGO, priv->debug, NULL,
 312                                   &debugfs_cfg_fops);
 313        if (!node) {
 314                pr_err("failed to create cfg in debugfs");
 315                goto errout;
 316        }
 317
 318        node = debugfs_create_file("status", S_IRUGO, priv->debug, NULL,
 319                                   &debugfs_status_fops);
 320        if (!node) {
 321                pr_err("failed to create status in debugfs");
 322                goto errout;
 323        }
 324
 325        return 0;
 326
 327errout:
 328        return -ENOMEM;
 329}
 330
 331static void ideapad_debugfs_exit(struct ideapad_private *priv)
 332{
 333        debugfs_remove_recursive(priv->debug);
 334        priv->debug = NULL;
 335}
 336
 337/*
 338 * sysfs
 339 */
 340static ssize_t show_ideapad_cam(struct device *dev,
 341                                struct device_attribute *attr,
 342                                char *buf)
 343{
 344        unsigned long result;
 345
 346        if (read_ec_data(ideapad_handle, VPCCMD_R_CAMERA, &result))
 347                return sprintf(buf, "-1\n");
 348        return sprintf(buf, "%lu\n", result);
 349}
 350
 351static ssize_t store_ideapad_cam(struct device *dev,
 352                                 struct device_attribute *attr,
 353                                 const char *buf, size_t count)
 354{
 355        int ret, state;
 356
 357        if (!count)
 358                return 0;
 359        if (sscanf(buf, "%i", &state) != 1)
 360                return -EINVAL;
 361        ret = write_ec_cmd(ideapad_handle, VPCCMD_W_CAMERA, state);
 362        if (ret < 0)
 363                return -EIO;
 364        return count;
 365}
 366
 367static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam);
 368
 369static ssize_t show_ideapad_fan(struct device *dev,
 370                                struct device_attribute *attr,
 371                                char *buf)
 372{
 373        unsigned long result;
 374
 375        if (read_ec_data(ideapad_handle, VPCCMD_R_FAN, &result))
 376                return sprintf(buf, "-1\n");
 377        return sprintf(buf, "%lu\n", result);
 378}
 379
 380static ssize_t store_ideapad_fan(struct device *dev,
 381                                 struct device_attribute *attr,
 382                                 const char *buf, size_t count)
 383{
 384        int ret, state;
 385
 386        if (!count)
 387                return 0;
 388        if (sscanf(buf, "%i", &state) != 1)
 389                return -EINVAL;
 390        if (state < 0 || state > 4 || state == 3)
 391                return -EINVAL;
 392        ret = write_ec_cmd(ideapad_handle, VPCCMD_W_FAN, state);
 393        if (ret < 0)
 394                return -EIO;
 395        return count;
 396}
 397
 398static DEVICE_ATTR(fan_mode, 0644, show_ideapad_fan, store_ideapad_fan);
 399
 400static struct attribute *ideapad_attributes[] = {
 401        &dev_attr_camera_power.attr,
 402        &dev_attr_fan_mode.attr,
 403        NULL
 404};
 405
 406static umode_t ideapad_is_visible(struct kobject *kobj,
 407                                 struct attribute *attr,
 408                                 int idx)
 409{
 410        struct device *dev = container_of(kobj, struct device, kobj);
 411        struct ideapad_private *priv = dev_get_drvdata(dev);
 412        bool supported;
 413
 414        if (attr == &dev_attr_camera_power.attr)
 415                supported = test_bit(CFG_CAMERA_BIT, &(priv->cfg));
 416        else if (attr == &dev_attr_fan_mode.attr) {
 417                unsigned long value;
 418                supported = !read_ec_data(ideapad_handle, VPCCMD_R_FAN, &value);
 419        } else
 420                supported = true;
 421
 422        return supported ? attr->mode : 0;
 423}
 424
 425static struct attribute_group ideapad_attribute_group = {
 426        .is_visible = ideapad_is_visible,
 427        .attrs = ideapad_attributes
 428};
 429
 430/*
 431 * Rfkill
 432 */
 433struct ideapad_rfk_data {
 434        char *name;
 435        int cfgbit;
 436        int opcode;
 437        int type;
 438};
 439
 440const struct ideapad_rfk_data ideapad_rfk_data[] = {
 441        { "ideapad_wlan",    CFG_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN },
 442        { "ideapad_bluetooth", CFG_BT_BIT, VPCCMD_W_BT, RFKILL_TYPE_BLUETOOTH },
 443        { "ideapad_3g",        CFG_3G_BIT, VPCCMD_W_3G, RFKILL_TYPE_WWAN },
 444};
 445
 446static int ideapad_rfk_set(void *data, bool blocked)
 447{
 448        unsigned long opcode = (unsigned long)data;
 449
 450        return write_ec_cmd(ideapad_handle, opcode, !blocked);
 451}
 452
 453static struct rfkill_ops ideapad_rfk_ops = {
 454        .set_block = ideapad_rfk_set,
 455};
 456
 457static void ideapad_sync_rfk_state(struct ideapad_private *priv)
 458{
 459        unsigned long hw_blocked;
 460        int i;
 461
 462        if (read_ec_data(ideapad_handle, VPCCMD_R_RF, &hw_blocked))
 463                return;
 464        hw_blocked = !hw_blocked;
 465
 466        for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
 467                if (priv->rfk[i])
 468                        rfkill_set_hw_state(priv->rfk[i], hw_blocked);
 469}
 470
 471static int ideapad_register_rfkill(struct acpi_device *adevice, int dev)
 472{
 473        struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
 474        int ret;
 475        unsigned long sw_blocked;
 476
 477        if (no_bt_rfkill &&
 478            (ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH)) {
 479                /* Force to enable bluetooth when no_bt_rfkill=1 */
 480                write_ec_cmd(ideapad_handle,
 481                             ideapad_rfk_data[dev].opcode, 1);
 482                return 0;
 483        }
 484
 485        priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name, &adevice->dev,
 486                                      ideapad_rfk_data[dev].type, &ideapad_rfk_ops,
 487                                      (void *)(long)dev);
 488        if (!priv->rfk[dev])
 489                return -ENOMEM;
 490
 491        if (read_ec_data(ideapad_handle, ideapad_rfk_data[dev].opcode-1,
 492                         &sw_blocked)) {
 493                rfkill_init_sw_state(priv->rfk[dev], 0);
 494        } else {
 495                sw_blocked = !sw_blocked;
 496                rfkill_init_sw_state(priv->rfk[dev], sw_blocked);
 497        }
 498
 499        ret = rfkill_register(priv->rfk[dev]);
 500        if (ret) {
 501                rfkill_destroy(priv->rfk[dev]);
 502                return ret;
 503        }
 504        return 0;
 505}
 506
 507static void ideapad_unregister_rfkill(struct acpi_device *adevice, int dev)
 508{
 509        struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
 510
 511        if (!priv->rfk[dev])
 512                return;
 513
 514        rfkill_unregister(priv->rfk[dev]);
 515        rfkill_destroy(priv->rfk[dev]);
 516}
 517
 518/*
 519 * Platform device
 520 */
 521static int ideapad_platform_init(struct ideapad_private *priv)
 522{
 523        int result;
 524
 525        priv->platform_device = platform_device_alloc("ideapad", -1);
 526        if (!priv->platform_device)
 527                return -ENOMEM;
 528        platform_set_drvdata(priv->platform_device, priv);
 529
 530        result = platform_device_add(priv->platform_device);
 531        if (result)
 532                goto fail_platform_device;
 533
 534        result = sysfs_create_group(&priv->platform_device->dev.kobj,
 535                                    &ideapad_attribute_group);
 536        if (result)
 537                goto fail_sysfs;
 538        return 0;
 539
 540fail_sysfs:
 541        platform_device_del(priv->platform_device);
 542fail_platform_device:
 543        platform_device_put(priv->platform_device);
 544        return result;
 545}
 546
 547static void ideapad_platform_exit(struct ideapad_private *priv)
 548{
 549        sysfs_remove_group(&priv->platform_device->dev.kobj,
 550                           &ideapad_attribute_group);
 551        platform_device_unregister(priv->platform_device);
 552}
 553
 554/*
 555 * input device
 556 */
 557static const struct key_entry ideapad_keymap[] = {
 558        { KE_KEY, 6,  { KEY_SWITCHVIDEOMODE } },
 559        { KE_KEY, 7,  { KEY_CAMERA } },
 560        { KE_KEY, 11, { KEY_F16 } },
 561        { KE_KEY, 13, { KEY_WLAN } },
 562        { KE_KEY, 16, { KEY_PROG1 } },
 563        { KE_KEY, 17, { KEY_PROG2 } },
 564        { KE_KEY, 64, { KEY_PROG3 } },
 565        { KE_KEY, 65, { KEY_PROG4 } },
 566        { KE_KEY, 66, { KEY_TOUCHPAD_OFF } },
 567        { KE_KEY, 67, { KEY_TOUCHPAD_ON } },
 568        { KE_END, 0 },
 569};
 570
 571static int ideapad_input_init(struct ideapad_private *priv)
 572{
 573        struct input_dev *inputdev;
 574        int error;
 575
 576        inputdev = input_allocate_device();
 577        if (!inputdev) {
 578                pr_info("Unable to allocate input device\n");
 579                return -ENOMEM;
 580        }
 581
 582        inputdev->name = "Ideapad extra buttons";
 583        inputdev->phys = "ideapad/input0";
 584        inputdev->id.bustype = BUS_HOST;
 585        inputdev->dev.parent = &priv->platform_device->dev;
 586
 587        error = sparse_keymap_setup(inputdev, ideapad_keymap, NULL);
 588        if (error) {
 589                pr_err("Unable to setup input device keymap\n");
 590                goto err_free_dev;
 591        }
 592
 593        error = input_register_device(inputdev);
 594        if (error) {
 595                pr_err("Unable to register input device\n");
 596                goto err_free_keymap;
 597        }
 598
 599        priv->inputdev = inputdev;
 600        return 0;
 601
 602err_free_keymap:
 603        sparse_keymap_free(inputdev);
 604err_free_dev:
 605        input_free_device(inputdev);
 606        return error;
 607}
 608
 609static void ideapad_input_exit(struct ideapad_private *priv)
 610{
 611        sparse_keymap_free(priv->inputdev);
 612        input_unregister_device(priv->inputdev);
 613        priv->inputdev = NULL;
 614}
 615
 616static void ideapad_input_report(struct ideapad_private *priv,
 617                                 unsigned long scancode)
 618{
 619        sparse_keymap_report_event(priv->inputdev, scancode, 1, true);
 620}
 621
 622static void ideapad_input_novokey(struct ideapad_private *priv)
 623{
 624        unsigned long long_pressed;
 625
 626        if (read_ec_data(ideapad_handle, VPCCMD_R_NOVO, &long_pressed))
 627                return;
 628        if (long_pressed)
 629                ideapad_input_report(priv, 17);
 630        else
 631                ideapad_input_report(priv, 16);
 632}
 633
 634static void ideapad_check_special_buttons(struct ideapad_private *priv)
 635{
 636        unsigned long bit, value;
 637
 638        read_ec_data(ideapad_handle, VPCCMD_R_SPECIAL_BUTTONS, &value);
 639
 640        for (bit = 0; bit < 16; bit++) {
 641                if (test_bit(bit, &value)) {
 642                        switch (bit) {
 643                        case 0: /* Z580 */
 644                        case 6: /* Z570 */
 645                                /* Thermal Management button */
 646                                ideapad_input_report(priv, 65);
 647                                break;
 648                        case 1:
 649                                /* OneKey Theater button */
 650                                ideapad_input_report(priv, 64);
 651                                break;
 652                        default:
 653                                pr_info("Unknown special button: %lu\n", bit);
 654                                break;
 655                        }
 656                }
 657        }
 658}
 659
 660/*
 661 * backlight
 662 */
 663static int ideapad_backlight_get_brightness(struct backlight_device *blightdev)
 664{
 665        unsigned long now;
 666
 667        if (read_ec_data(ideapad_handle, VPCCMD_R_BL, &now))
 668                return -EIO;
 669        return now;
 670}
 671
 672static int ideapad_backlight_update_status(struct backlight_device *blightdev)
 673{
 674        if (write_ec_cmd(ideapad_handle, VPCCMD_W_BL,
 675                         blightdev->props.brightness))
 676                return -EIO;
 677        if (write_ec_cmd(ideapad_handle, VPCCMD_W_BL_POWER,
 678                         blightdev->props.power == FB_BLANK_POWERDOWN ? 0 : 1))
 679                return -EIO;
 680
 681        return 0;
 682}
 683
 684static const struct backlight_ops ideapad_backlight_ops = {
 685        .get_brightness = ideapad_backlight_get_brightness,
 686        .update_status = ideapad_backlight_update_status,
 687};
 688
 689static int ideapad_backlight_init(struct ideapad_private *priv)
 690{
 691        struct backlight_device *blightdev;
 692        struct backlight_properties props;
 693        unsigned long max, now, power;
 694
 695        if (read_ec_data(ideapad_handle, VPCCMD_R_BL_MAX, &max))
 696                return -EIO;
 697        if (read_ec_data(ideapad_handle, VPCCMD_R_BL, &now))
 698                return -EIO;
 699        if (read_ec_data(ideapad_handle, VPCCMD_R_BL_POWER, &power))
 700                return -EIO;
 701
 702        memset(&props, 0, sizeof(struct backlight_properties));
 703        props.max_brightness = max;
 704        props.type = BACKLIGHT_PLATFORM;
 705        blightdev = backlight_device_register("ideapad",
 706                                              &priv->platform_device->dev,
 707                                              priv,
 708                                              &ideapad_backlight_ops,
 709                                              &props);
 710        if (IS_ERR(blightdev)) {
 711                pr_err("Could not register backlight device\n");
 712                return PTR_ERR(blightdev);
 713        }
 714
 715        priv->blightdev = blightdev;
 716        blightdev->props.brightness = now;
 717        blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
 718        backlight_update_status(blightdev);
 719
 720        return 0;
 721}
 722
 723static void ideapad_backlight_exit(struct ideapad_private *priv)
 724{
 725        if (priv->blightdev)
 726                backlight_device_unregister(priv->blightdev);
 727        priv->blightdev = NULL;
 728}
 729
 730static void ideapad_backlight_notify_power(struct ideapad_private *priv)
 731{
 732        unsigned long power;
 733        struct backlight_device *blightdev = priv->blightdev;
 734
 735        if (!blightdev)
 736                return;
 737        if (read_ec_data(ideapad_handle, VPCCMD_R_BL_POWER, &power))
 738                return;
 739        blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
 740}
 741
 742static void ideapad_backlight_notify_brightness(struct ideapad_private *priv)
 743{
 744        unsigned long now;
 745
 746        /* if we control brightness via acpi video driver */
 747        if (priv->blightdev == NULL) {
 748                read_ec_data(ideapad_handle, VPCCMD_R_BL, &now);
 749                return;
 750        }
 751
 752        backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY);
 753}
 754
 755/*
 756 * module init/exit
 757 */
 758static const struct acpi_device_id ideapad_device_ids[] = {
 759        { "VPC2004", 0},
 760        { "", 0},
 761};
 762MODULE_DEVICE_TABLE(acpi, ideapad_device_ids);
 763
 764static void ideapad_sync_touchpad_state(struct acpi_device *adevice)
 765{
 766        struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
 767        unsigned long value;
 768
 769        /* Without reading from EC touchpad LED doesn't switch state */
 770        if (!read_ec_data(adevice->handle, VPCCMD_R_TOUCHPAD, &value)) {
 771                /* Some IdeaPads don't really turn off touchpad - they only
 772                 * switch the LED state. We (de)activate KBC AUX port to turn
 773                 * touchpad off and on. We send KEY_TOUCHPAD_OFF and
 774                 * KEY_TOUCHPAD_ON to not to get out of sync with LED */
 775                unsigned char param;
 776                i8042_command(&param, value ? I8042_CMD_AUX_ENABLE :
 777                              I8042_CMD_AUX_DISABLE);
 778                ideapad_input_report(priv, value ? 67 : 66);
 779        }
 780}
 781
 782static int ideapad_acpi_add(struct acpi_device *adevice)
 783{
 784        int ret, i;
 785        int cfg;
 786        struct ideapad_private *priv;
 787
 788        if (read_method_int(adevice->handle, "_CFG", &cfg))
 789                return -ENODEV;
 790
 791        priv = kzalloc(sizeof(*priv), GFP_KERNEL);
 792        if (!priv)
 793                return -ENOMEM;
 794        dev_set_drvdata(&adevice->dev, priv);
 795        ideapad_priv = priv;
 796        ideapad_handle = adevice->handle;
 797        priv->cfg = cfg;
 798
 799        ret = ideapad_platform_init(priv);
 800        if (ret)
 801                goto platform_failed;
 802
 803        ret = ideapad_debugfs_init(priv);
 804        if (ret)
 805                goto debugfs_failed;
 806
 807        ret = ideapad_input_init(priv);
 808        if (ret)
 809                goto input_failed;
 810
 811        for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) {
 812                if (test_bit(ideapad_rfk_data[i].cfgbit, &priv->cfg))
 813                        ideapad_register_rfkill(adevice, i);
 814                else
 815                        priv->rfk[i] = NULL;
 816        }
 817        ideapad_sync_rfk_state(priv);
 818        ideapad_sync_touchpad_state(adevice);
 819
 820        if (!acpi_video_backlight_support()) {
 821                ret = ideapad_backlight_init(priv);
 822                if (ret && ret != -ENODEV)
 823                        goto backlight_failed;
 824        }
 825
 826        return 0;
 827
 828backlight_failed:
 829        for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
 830                ideapad_unregister_rfkill(adevice, i);
 831        ideapad_input_exit(priv);
 832input_failed:
 833        ideapad_debugfs_exit(priv);
 834debugfs_failed:
 835        ideapad_platform_exit(priv);
 836platform_failed:
 837        kfree(priv);
 838        return ret;
 839}
 840
 841static int ideapad_acpi_remove(struct acpi_device *adevice)
 842{
 843        struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
 844        int i;
 845
 846        ideapad_backlight_exit(priv);
 847        for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
 848                ideapad_unregister_rfkill(adevice, i);
 849        ideapad_input_exit(priv);
 850        ideapad_debugfs_exit(priv);
 851        ideapad_platform_exit(priv);
 852        dev_set_drvdata(&adevice->dev, NULL);
 853        kfree(priv);
 854
 855        return 0;
 856}
 857
 858static void ideapad_acpi_notify(struct acpi_device *adevice, u32 event)
 859{
 860        struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
 861        acpi_handle handle = adevice->handle;
 862        unsigned long vpc1, vpc2, vpc_bit;
 863
 864        if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1))
 865                return;
 866        if (read_ec_data(handle, VPCCMD_R_VPC2, &vpc2))
 867                return;
 868
 869        vpc1 = (vpc2 << 8) | vpc1;
 870        for (vpc_bit = 0; vpc_bit < 16; vpc_bit++) {
 871                if (test_bit(vpc_bit, &vpc1)) {
 872                        switch (vpc_bit) {
 873                        case 9:
 874                                ideapad_sync_rfk_state(priv);
 875                                break;
 876                        case 13:
 877                        case 11:
 878                        case 7:
 879                        case 6:
 880                                ideapad_input_report(priv, vpc_bit);
 881                                break;
 882                        case 5:
 883                                ideapad_sync_touchpad_state(adevice);
 884                                break;
 885                        case 4:
 886                                ideapad_backlight_notify_brightness(priv);
 887                                break;
 888                        case 3:
 889                                ideapad_input_novokey(priv);
 890                                break;
 891                        case 2:
 892                                ideapad_backlight_notify_power(priv);
 893                                break;
 894                        case 0:
 895                                ideapad_check_special_buttons(priv);
 896                                break;
 897                        default:
 898                                pr_info("Unknown event: %lu\n", vpc_bit);
 899                        }
 900                }
 901        }
 902}
 903
 904static int ideapad_acpi_resume(struct device *device)
 905{
 906        ideapad_sync_rfk_state(ideapad_priv);
 907        ideapad_sync_touchpad_state(to_acpi_device(device));
 908        return 0;
 909}
 910
 911static SIMPLE_DEV_PM_OPS(ideapad_pm, NULL, ideapad_acpi_resume);
 912
 913static struct acpi_driver ideapad_acpi_driver = {
 914        .name = "ideapad_acpi",
 915        .class = "IdeaPad",
 916        .ids = ideapad_device_ids,
 917        .ops.add = ideapad_acpi_add,
 918        .ops.remove = ideapad_acpi_remove,
 919        .ops.notify = ideapad_acpi_notify,
 920        .drv.pm = &ideapad_pm,
 921        .owner = THIS_MODULE,
 922};
 923module_acpi_driver(ideapad_acpi_driver);
 924
 925MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
 926MODULE_DESCRIPTION("IdeaPad ACPI Extras");
 927MODULE_LICENSE("GPL");
 928