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#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);
 494