157ac3b05SIke Panhc /* 257ac3b05SIke Panhc * ideapad_acpi.c - Lenovo IdeaPad ACPI Extras 357ac3b05SIke Panhc * 457ac3b05SIke Panhc * Copyright © 2010 Intel Corporation 557ac3b05SIke Panhc * Copyright © 2010 David Woodhouse <dwmw2@infradead.org> 657ac3b05SIke Panhc * 757ac3b05SIke Panhc * This program is free software; you can redistribute it and/or modify 857ac3b05SIke Panhc * it under the terms of the GNU General Public License as published by 957ac3b05SIke Panhc * the Free Software Foundation; either version 2 of the License, or 1057ac3b05SIke Panhc * (at your option) any later version. 1157ac3b05SIke Panhc * 1257ac3b05SIke Panhc * This program is distributed in the hope that it will be useful, 1357ac3b05SIke Panhc * but WITHOUT ANY WARRANTY; without even the implied warranty of 1457ac3b05SIke Panhc * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1557ac3b05SIke Panhc * GNU General Public License for more details. 1657ac3b05SIke Panhc * 1757ac3b05SIke Panhc * You should have received a copy of the GNU General Public License 1857ac3b05SIke Panhc * along with this program; if not, write to the Free Software 1957ac3b05SIke Panhc * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2057ac3b05SIke Panhc * 02110-1301, USA. 2157ac3b05SIke Panhc */ 2257ac3b05SIke Panhc 2357ac3b05SIke Panhc #include <linux/kernel.h> 2457ac3b05SIke Panhc #include <linux/module.h> 2557ac3b05SIke Panhc #include <linux/init.h> 2657ac3b05SIke Panhc #include <linux/types.h> 2757ac3b05SIke Panhc #include <acpi/acpi_bus.h> 2857ac3b05SIke Panhc #include <acpi/acpi_drivers.h> 2957ac3b05SIke Panhc #include <linux/rfkill.h> 3098ee6919SIke Panhc #include <linux/platform_device.h> 31f63409aeSIke Panhc #include <linux/input.h> 32f63409aeSIke Panhc #include <linux/input/sparse-keymap.h> 3357ac3b05SIke Panhc 3457ac3b05SIke Panhc #define IDEAPAD_DEV_CAMERA 0 3557ac3b05SIke Panhc #define IDEAPAD_DEV_WLAN 1 3657ac3b05SIke Panhc #define IDEAPAD_DEV_BLUETOOTH 2 3757ac3b05SIke Panhc #define IDEAPAD_DEV_3G 3 3857ac3b05SIke Panhc #define IDEAPAD_DEV_KILLSW 4 3957ac3b05SIke Panhc 4057ac3b05SIke Panhc struct ideapad_private { 4157ac3b05SIke Panhc acpi_handle handle; 4257ac3b05SIke Panhc struct rfkill *rfk[5]; 4398ee6919SIke Panhc struct platform_device *platform_device; 44f63409aeSIke Panhc struct input_dev *inputdev; 4557ac3b05SIke Panhc } *ideapad_priv; 4657ac3b05SIke Panhc 4757ac3b05SIke Panhc static struct { 4857ac3b05SIke Panhc char *name; 4957ac3b05SIke Panhc int cfgbit; 5057ac3b05SIke Panhc int opcode; 5157ac3b05SIke Panhc int type; 5257ac3b05SIke Panhc } ideapad_rfk_data[] = { 5357ac3b05SIke Panhc { "ideapad_camera", 19, 0x1E, NUM_RFKILL_TYPES }, 5457ac3b05SIke Panhc { "ideapad_wlan", 18, 0x15, RFKILL_TYPE_WLAN }, 5557ac3b05SIke Panhc { "ideapad_bluetooth", 16, 0x17, RFKILL_TYPE_BLUETOOTH }, 5657ac3b05SIke Panhc { "ideapad_3g", 17, 0x20, RFKILL_TYPE_WWAN }, 5757ac3b05SIke Panhc { "ideapad_killsw", 0, 0, RFKILL_TYPE_WLAN } 5857ac3b05SIke Panhc }; 5957ac3b05SIke Panhc 60bfa97b7dSIke Panhc static bool no_bt_rfkill; 61bfa97b7dSIke Panhc module_param(no_bt_rfkill, bool, 0444); 62bfa97b7dSIke Panhc MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth."); 63bfa97b7dSIke Panhc 6457ac3b05SIke Panhc /* 6557ac3b05SIke Panhc * ACPI Helpers 6657ac3b05SIke Panhc */ 6757ac3b05SIke Panhc #define IDEAPAD_EC_TIMEOUT (100) /* in ms */ 6857ac3b05SIke Panhc 6957ac3b05SIke Panhc static int read_method_int(acpi_handle handle, const char *method, int *val) 7057ac3b05SIke Panhc { 7157ac3b05SIke Panhc acpi_status status; 7257ac3b05SIke Panhc unsigned long long result; 7357ac3b05SIke Panhc 7457ac3b05SIke Panhc status = acpi_evaluate_integer(handle, (char *)method, NULL, &result); 7557ac3b05SIke Panhc if (ACPI_FAILURE(status)) { 7657ac3b05SIke Panhc *val = -1; 7757ac3b05SIke Panhc return -1; 7857ac3b05SIke Panhc } else { 7957ac3b05SIke Panhc *val = result; 8057ac3b05SIke Panhc return 0; 8157ac3b05SIke Panhc } 8257ac3b05SIke Panhc } 8357ac3b05SIke Panhc 8457ac3b05SIke Panhc static int method_vpcr(acpi_handle handle, int cmd, int *ret) 8557ac3b05SIke Panhc { 8657ac3b05SIke Panhc acpi_status status; 8757ac3b05SIke Panhc unsigned long long result; 8857ac3b05SIke Panhc struct acpi_object_list params; 8957ac3b05SIke Panhc union acpi_object in_obj; 9057ac3b05SIke Panhc 9157ac3b05SIke Panhc params.count = 1; 9257ac3b05SIke Panhc params.pointer = &in_obj; 9357ac3b05SIke Panhc in_obj.type = ACPI_TYPE_INTEGER; 9457ac3b05SIke Panhc in_obj.integer.value = cmd; 9557ac3b05SIke Panhc 9657ac3b05SIke Panhc status = acpi_evaluate_integer(handle, "VPCR", ¶ms, &result); 9757ac3b05SIke Panhc 9857ac3b05SIke Panhc if (ACPI_FAILURE(status)) { 9957ac3b05SIke Panhc *ret = -1; 10057ac3b05SIke Panhc return -1; 10157ac3b05SIke Panhc } else { 10257ac3b05SIke Panhc *ret = result; 10357ac3b05SIke Panhc return 0; 10457ac3b05SIke Panhc } 10557ac3b05SIke Panhc } 10657ac3b05SIke Panhc 10757ac3b05SIke Panhc static int method_vpcw(acpi_handle handle, int cmd, int data) 10857ac3b05SIke Panhc { 10957ac3b05SIke Panhc struct acpi_object_list params; 11057ac3b05SIke Panhc union acpi_object in_obj[2]; 11157ac3b05SIke Panhc acpi_status status; 11257ac3b05SIke Panhc 11357ac3b05SIke Panhc params.count = 2; 11457ac3b05SIke Panhc params.pointer = in_obj; 11557ac3b05SIke Panhc in_obj[0].type = ACPI_TYPE_INTEGER; 11657ac3b05SIke Panhc in_obj[0].integer.value = cmd; 11757ac3b05SIke Panhc in_obj[1].type = ACPI_TYPE_INTEGER; 11857ac3b05SIke Panhc in_obj[1].integer.value = data; 11957ac3b05SIke Panhc 12057ac3b05SIke Panhc status = acpi_evaluate_object(handle, "VPCW", ¶ms, NULL); 12157ac3b05SIke Panhc if (status != AE_OK) 12257ac3b05SIke Panhc return -1; 12357ac3b05SIke Panhc return 0; 12457ac3b05SIke Panhc } 12557ac3b05SIke Panhc 12657ac3b05SIke Panhc static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data) 12757ac3b05SIke Panhc { 12857ac3b05SIke Panhc int val; 12957ac3b05SIke Panhc unsigned long int end_jiffies; 13057ac3b05SIke Panhc 13157ac3b05SIke Panhc if (method_vpcw(handle, 1, cmd)) 13257ac3b05SIke Panhc return -1; 13357ac3b05SIke Panhc 13457ac3b05SIke Panhc for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1; 13557ac3b05SIke Panhc time_before(jiffies, end_jiffies);) { 13657ac3b05SIke Panhc schedule(); 13757ac3b05SIke Panhc if (method_vpcr(handle, 1, &val)) 13857ac3b05SIke Panhc return -1; 13957ac3b05SIke Panhc if (val == 0) { 14057ac3b05SIke Panhc if (method_vpcr(handle, 0, &val)) 14157ac3b05SIke Panhc return -1; 14257ac3b05SIke Panhc *data = val; 14357ac3b05SIke Panhc return 0; 14457ac3b05SIke Panhc } 14557ac3b05SIke Panhc } 14657ac3b05SIke Panhc pr_err("timeout in read_ec_cmd\n"); 14757ac3b05SIke Panhc return -1; 14857ac3b05SIke Panhc } 14957ac3b05SIke Panhc 15057ac3b05SIke Panhc static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data) 15157ac3b05SIke Panhc { 15257ac3b05SIke Panhc int val; 15357ac3b05SIke Panhc unsigned long int end_jiffies; 15457ac3b05SIke Panhc 15557ac3b05SIke Panhc if (method_vpcw(handle, 0, data)) 15657ac3b05SIke Panhc return -1; 15757ac3b05SIke Panhc if (method_vpcw(handle, 1, cmd)) 15857ac3b05SIke Panhc return -1; 15957ac3b05SIke Panhc 16057ac3b05SIke Panhc for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1; 16157ac3b05SIke Panhc time_before(jiffies, end_jiffies);) { 16257ac3b05SIke Panhc schedule(); 16357ac3b05SIke Panhc if (method_vpcr(handle, 1, &val)) 16457ac3b05SIke Panhc return -1; 16557ac3b05SIke Panhc if (val == 0) 16657ac3b05SIke Panhc return 0; 16757ac3b05SIke Panhc } 16857ac3b05SIke Panhc pr_err("timeout in write_ec_cmd\n"); 16957ac3b05SIke Panhc return -1; 17057ac3b05SIke Panhc } 17157ac3b05SIke Panhc /* the above is ACPI helpers */ 17257ac3b05SIke Panhc 17357ac3b05SIke Panhc static ssize_t show_ideapad_cam(struct device *dev, 17457ac3b05SIke Panhc struct device_attribute *attr, 17557ac3b05SIke Panhc char *buf) 17657ac3b05SIke Panhc { 17757ac3b05SIke Panhc struct ideapad_private *priv = dev_get_drvdata(dev); 17857ac3b05SIke Panhc acpi_handle handle = priv->handle; 17957ac3b05SIke Panhc unsigned long result; 18057ac3b05SIke Panhc 18157ac3b05SIke Panhc if (read_ec_data(handle, 0x1D, &result)) 18257ac3b05SIke Panhc return sprintf(buf, "-1\n"); 18357ac3b05SIke Panhc return sprintf(buf, "%lu\n", result); 18457ac3b05SIke Panhc } 18557ac3b05SIke Panhc 18657ac3b05SIke Panhc static ssize_t store_ideapad_cam(struct device *dev, 18757ac3b05SIke Panhc struct device_attribute *attr, 18857ac3b05SIke Panhc const char *buf, size_t count) 18957ac3b05SIke Panhc { 19057ac3b05SIke Panhc struct ideapad_private *priv = dev_get_drvdata(dev); 19157ac3b05SIke Panhc acpi_handle handle = priv->handle; 19257ac3b05SIke Panhc int ret, state; 19357ac3b05SIke Panhc 19457ac3b05SIke Panhc if (!count) 19557ac3b05SIke Panhc return 0; 19657ac3b05SIke Panhc if (sscanf(buf, "%i", &state) != 1) 19757ac3b05SIke Panhc return -EINVAL; 19857ac3b05SIke Panhc ret = write_ec_cmd(handle, 0x1E, state); 19957ac3b05SIke Panhc if (ret < 0) 20057ac3b05SIke Panhc return ret; 20157ac3b05SIke Panhc return count; 20257ac3b05SIke Panhc } 20357ac3b05SIke Panhc 20457ac3b05SIke Panhc static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam); 20557ac3b05SIke Panhc 20657ac3b05SIke Panhc static int ideapad_rfk_set(void *data, bool blocked) 20757ac3b05SIke Panhc { 20857ac3b05SIke Panhc int device = (unsigned long)data; 20957ac3b05SIke Panhc 21057ac3b05SIke Panhc if (device == IDEAPAD_DEV_KILLSW) 21157ac3b05SIke Panhc return -EINVAL; 21257ac3b05SIke Panhc 21357ac3b05SIke Panhc return write_ec_cmd(ideapad_priv->handle, 21457ac3b05SIke Panhc ideapad_rfk_data[device].opcode, 21557ac3b05SIke Panhc !blocked); 21657ac3b05SIke Panhc } 21757ac3b05SIke Panhc 21857ac3b05SIke Panhc static struct rfkill_ops ideapad_rfk_ops = { 21957ac3b05SIke Panhc .set_block = ideapad_rfk_set, 22057ac3b05SIke Panhc }; 22157ac3b05SIke Panhc 22257ac3b05SIke Panhc static void ideapad_sync_rfk_state(struct acpi_device *adevice) 22357ac3b05SIke Panhc { 22457ac3b05SIke Panhc struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); 22557ac3b05SIke Panhc acpi_handle handle = priv->handle; 22657ac3b05SIke Panhc unsigned long hw_blocked; 22757ac3b05SIke Panhc int i; 22857ac3b05SIke Panhc 22957ac3b05SIke Panhc if (read_ec_data(handle, 0x23, &hw_blocked)) 23057ac3b05SIke Panhc return; 23157ac3b05SIke Panhc hw_blocked = !hw_blocked; 23257ac3b05SIke Panhc 23357ac3b05SIke Panhc for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++) 23457ac3b05SIke Panhc if (priv->rfk[i]) 23557ac3b05SIke Panhc rfkill_set_hw_state(priv->rfk[i], hw_blocked); 23657ac3b05SIke Panhc } 23757ac3b05SIke Panhc 23857ac3b05SIke Panhc static int ideapad_register_rfkill(struct acpi_device *adevice, int dev) 23957ac3b05SIke Panhc { 24057ac3b05SIke Panhc struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); 24157ac3b05SIke Panhc int ret; 24257ac3b05SIke Panhc unsigned long sw_blocked; 24357ac3b05SIke Panhc 244bfa97b7dSIke Panhc if (no_bt_rfkill && 245bfa97b7dSIke Panhc (ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH)) { 246bfa97b7dSIke Panhc /* Force to enable bluetooth when no_bt_rfkill=1 */ 247bfa97b7dSIke Panhc write_ec_cmd(ideapad_priv->handle, 248bfa97b7dSIke Panhc ideapad_rfk_data[dev].opcode, 1); 249bfa97b7dSIke Panhc return 0; 250bfa97b7dSIke Panhc } 251bfa97b7dSIke Panhc 25257ac3b05SIke Panhc priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name, &adevice->dev, 25357ac3b05SIke Panhc ideapad_rfk_data[dev].type, &ideapad_rfk_ops, 25457ac3b05SIke Panhc (void *)(long)dev); 25557ac3b05SIke Panhc if (!priv->rfk[dev]) 25657ac3b05SIke Panhc return -ENOMEM; 25757ac3b05SIke Panhc 25857ac3b05SIke Panhc if (read_ec_data(ideapad_priv->handle, ideapad_rfk_data[dev].opcode-1, 25957ac3b05SIke Panhc &sw_blocked)) { 26057ac3b05SIke Panhc rfkill_init_sw_state(priv->rfk[dev], 0); 26157ac3b05SIke Panhc } else { 26257ac3b05SIke Panhc sw_blocked = !sw_blocked; 26357ac3b05SIke Panhc rfkill_init_sw_state(priv->rfk[dev], sw_blocked); 26457ac3b05SIke Panhc } 26557ac3b05SIke Panhc 26657ac3b05SIke Panhc ret = rfkill_register(priv->rfk[dev]); 26757ac3b05SIke Panhc if (ret) { 26857ac3b05SIke Panhc rfkill_destroy(priv->rfk[dev]); 26957ac3b05SIke Panhc return ret; 27057ac3b05SIke Panhc } 27157ac3b05SIke Panhc return 0; 27257ac3b05SIke Panhc } 27357ac3b05SIke Panhc 27457ac3b05SIke Panhc static void ideapad_unregister_rfkill(struct acpi_device *adevice, int dev) 27557ac3b05SIke Panhc { 27657ac3b05SIke Panhc struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); 27757ac3b05SIke Panhc 27857ac3b05SIke Panhc if (!priv->rfk[dev]) 27957ac3b05SIke Panhc return; 28057ac3b05SIke Panhc 28157ac3b05SIke Panhc rfkill_unregister(priv->rfk[dev]); 28257ac3b05SIke Panhc rfkill_destroy(priv->rfk[dev]); 28357ac3b05SIke Panhc } 28457ac3b05SIke Panhc 28598ee6919SIke Panhc /* 28698ee6919SIke Panhc * Platform device 28798ee6919SIke Panhc */ 288c9f718d0SIke Panhc static struct attribute *ideapad_attributes[] = { 289c9f718d0SIke Panhc &dev_attr_camera_power.attr, 290c9f718d0SIke Panhc NULL 291c9f718d0SIke Panhc }; 292c9f718d0SIke Panhc 293c9f718d0SIke Panhc static struct attribute_group ideapad_attribute_group = { 294c9f718d0SIke Panhc .attrs = ideapad_attributes 295c9f718d0SIke Panhc }; 296c9f718d0SIke Panhc 29798ee6919SIke Panhc static int __devinit ideapad_platform_init(void) 29898ee6919SIke Panhc { 29998ee6919SIke Panhc int result; 30098ee6919SIke Panhc 30198ee6919SIke Panhc ideapad_priv->platform_device = platform_device_alloc("ideapad", -1); 30298ee6919SIke Panhc if (!ideapad_priv->platform_device) 30398ee6919SIke Panhc return -ENOMEM; 30498ee6919SIke Panhc platform_set_drvdata(ideapad_priv->platform_device, ideapad_priv); 30598ee6919SIke Panhc 30698ee6919SIke Panhc result = platform_device_add(ideapad_priv->platform_device); 30798ee6919SIke Panhc if (result) 30898ee6919SIke Panhc goto fail_platform_device; 30998ee6919SIke Panhc 310c9f718d0SIke Panhc result = sysfs_create_group(&ideapad_priv->platform_device->dev.kobj, 311c9f718d0SIke Panhc &ideapad_attribute_group); 312c9f718d0SIke Panhc if (result) 313c9f718d0SIke Panhc goto fail_sysfs; 31498ee6919SIke Panhc return 0; 31598ee6919SIke Panhc 316c9f718d0SIke Panhc fail_sysfs: 317c9f718d0SIke Panhc platform_device_del(ideapad_priv->platform_device); 31898ee6919SIke Panhc fail_platform_device: 31998ee6919SIke Panhc platform_device_put(ideapad_priv->platform_device); 32098ee6919SIke Panhc return result; 32198ee6919SIke Panhc } 32298ee6919SIke Panhc 32398ee6919SIke Panhc static void ideapad_platform_exit(void) 32498ee6919SIke Panhc { 325c9f718d0SIke Panhc sysfs_remove_group(&ideapad_priv->platform_device->dev.kobj, 326c9f718d0SIke Panhc &ideapad_attribute_group); 32798ee6919SIke Panhc platform_device_unregister(ideapad_priv->platform_device); 32898ee6919SIke Panhc } 32998ee6919SIke Panhc /* the above is platform device */ 33098ee6919SIke Panhc 331f63409aeSIke Panhc /* 332f63409aeSIke Panhc * input device 333f63409aeSIke Panhc */ 334f63409aeSIke Panhc static const struct key_entry ideapad_keymap[] = { 335f63409aeSIke Panhc { KE_KEY, 0x06, { KEY_SWITCHVIDEOMODE } }, 336f63409aeSIke Panhc { KE_KEY, 0x0D, { KEY_WLAN } }, 337f63409aeSIke Panhc { KE_END, 0 }, 338f63409aeSIke Panhc }; 339f63409aeSIke Panhc 340f63409aeSIke Panhc static int __devinit ideapad_input_init(void) 341f63409aeSIke Panhc { 342f63409aeSIke Panhc struct input_dev *inputdev; 343f63409aeSIke Panhc int error; 344f63409aeSIke Panhc 345f63409aeSIke Panhc inputdev = input_allocate_device(); 346f63409aeSIke Panhc if (!inputdev) { 347f63409aeSIke Panhc pr_info("Unable to allocate input device\n"); 348f63409aeSIke Panhc return -ENOMEM; 349f63409aeSIke Panhc } 350f63409aeSIke Panhc 351f63409aeSIke Panhc inputdev->name = "Ideapad extra buttons"; 352f63409aeSIke Panhc inputdev->phys = "ideapad/input0"; 353f63409aeSIke Panhc inputdev->id.bustype = BUS_HOST; 354f63409aeSIke Panhc inputdev->dev.parent = &ideapad_priv->platform_device->dev; 355f63409aeSIke Panhc 356f63409aeSIke Panhc error = sparse_keymap_setup(inputdev, ideapad_keymap, NULL); 357f63409aeSIke Panhc if (error) { 358f63409aeSIke Panhc pr_err("Unable to setup input device keymap\n"); 359f63409aeSIke Panhc goto err_free_dev; 360f63409aeSIke Panhc } 361f63409aeSIke Panhc 362f63409aeSIke Panhc error = input_register_device(inputdev); 363f63409aeSIke Panhc if (error) { 364f63409aeSIke Panhc pr_err("Unable to register input device\n"); 365f63409aeSIke Panhc goto err_free_keymap; 366f63409aeSIke Panhc } 367f63409aeSIke Panhc 368f63409aeSIke Panhc ideapad_priv->inputdev = inputdev; 369f63409aeSIke Panhc return 0; 370f63409aeSIke Panhc 371f63409aeSIke Panhc err_free_keymap: 372f63409aeSIke Panhc sparse_keymap_free(inputdev); 373f63409aeSIke Panhc err_free_dev: 374f63409aeSIke Panhc input_free_device(inputdev); 375f63409aeSIke Panhc return error; 376f63409aeSIke Panhc } 377f63409aeSIke Panhc 378f63409aeSIke Panhc static void __devexit ideapad_input_exit(void) 379f63409aeSIke Panhc { 380f63409aeSIke Panhc sparse_keymap_free(ideapad_priv->inputdev); 381f63409aeSIke Panhc input_unregister_device(ideapad_priv->inputdev); 382f63409aeSIke Panhc ideapad_priv->inputdev = NULL; 383f63409aeSIke Panhc } 384f63409aeSIke Panhc 385f63409aeSIke Panhc static void ideapad_input_report(unsigned long scancode) 386f63409aeSIke Panhc { 387f63409aeSIke Panhc sparse_keymap_report_event(ideapad_priv->inputdev, scancode, 1, true); 388f63409aeSIke Panhc } 389f63409aeSIke Panhc /* the above is input device */ 390f63409aeSIke Panhc 39157ac3b05SIke Panhc static const struct acpi_device_id ideapad_device_ids[] = { 39257ac3b05SIke Panhc { "VPC2004", 0}, 39357ac3b05SIke Panhc { "", 0}, 39457ac3b05SIke Panhc }; 39557ac3b05SIke Panhc MODULE_DEVICE_TABLE(acpi, ideapad_device_ids); 39657ac3b05SIke Panhc 39757ac3b05SIke Panhc static int ideapad_acpi_add(struct acpi_device *adevice) 39857ac3b05SIke Panhc { 39998ee6919SIke Panhc int ret, i, cfg; 40057ac3b05SIke Panhc struct ideapad_private *priv; 40157ac3b05SIke Panhc 40257ac3b05SIke Panhc if (read_method_int(adevice->handle, "_CFG", &cfg)) 40357ac3b05SIke Panhc return -ENODEV; 40457ac3b05SIke Panhc 40557ac3b05SIke Panhc priv = kzalloc(sizeof(*priv), GFP_KERNEL); 40657ac3b05SIke Panhc if (!priv) 40757ac3b05SIke Panhc return -ENOMEM; 40898ee6919SIke Panhc ideapad_priv = priv; 409c9f718d0SIke Panhc priv->handle = adevice->handle; 410c9f718d0SIke Panhc dev_set_drvdata(&adevice->dev, priv); 41198ee6919SIke Panhc 41298ee6919SIke Panhc ret = ideapad_platform_init(); 41398ee6919SIke Panhc if (ret) 41498ee6919SIke Panhc goto platform_failed; 41557ac3b05SIke Panhc 416f63409aeSIke Panhc ret = ideapad_input_init(); 417f63409aeSIke Panhc if (ret) 418f63409aeSIke Panhc goto input_failed; 419f63409aeSIke Panhc 420c9f718d0SIke Panhc for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++) { 421c9f718d0SIke Panhc if (test_bit(ideapad_rfk_data[i].cfgbit, (unsigned long *)&cfg)) 42257ac3b05SIke Panhc ideapad_register_rfkill(adevice, i); 42357ac3b05SIke Panhc } 42457ac3b05SIke Panhc ideapad_sync_rfk_state(adevice); 425c9f718d0SIke Panhc 42657ac3b05SIke Panhc return 0; 42798ee6919SIke Panhc 428f63409aeSIke Panhc input_failed: 429f63409aeSIke Panhc ideapad_platform_exit(); 43098ee6919SIke Panhc platform_failed: 43198ee6919SIke Panhc kfree(priv); 43298ee6919SIke Panhc return ret; 43357ac3b05SIke Panhc } 43457ac3b05SIke Panhc 43557ac3b05SIke Panhc static int ideapad_acpi_remove(struct acpi_device *adevice, int type) 43657ac3b05SIke Panhc { 43757ac3b05SIke Panhc struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); 43857ac3b05SIke Panhc int i; 43957ac3b05SIke Panhc 440c9f718d0SIke Panhc for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++) 44157ac3b05SIke Panhc ideapad_unregister_rfkill(adevice, i); 442f63409aeSIke Panhc ideapad_input_exit(); 44398ee6919SIke Panhc ideapad_platform_exit(); 44457ac3b05SIke Panhc dev_set_drvdata(&adevice->dev, NULL); 44557ac3b05SIke Panhc kfree(priv); 446c9f718d0SIke Panhc 44757ac3b05SIke Panhc return 0; 44857ac3b05SIke Panhc } 44957ac3b05SIke Panhc 45057ac3b05SIke Panhc static void ideapad_acpi_notify(struct acpi_device *adevice, u32 event) 45157ac3b05SIke Panhc { 45257ac3b05SIke Panhc acpi_handle handle = adevice->handle; 45357ac3b05SIke Panhc unsigned long vpc1, vpc2, vpc_bit; 45457ac3b05SIke Panhc 45557ac3b05SIke Panhc if (read_ec_data(handle, 0x10, &vpc1)) 45657ac3b05SIke Panhc return; 45757ac3b05SIke Panhc if (read_ec_data(handle, 0x1A, &vpc2)) 45857ac3b05SIke Panhc return; 45957ac3b05SIke Panhc 46057ac3b05SIke Panhc vpc1 = (vpc2 << 8) | vpc1; 46157ac3b05SIke Panhc for (vpc_bit = 0; vpc_bit < 16; vpc_bit++) { 46257ac3b05SIke Panhc if (test_bit(vpc_bit, &vpc1)) { 46357ac3b05SIke Panhc if (vpc_bit == 9) 46457ac3b05SIke Panhc ideapad_sync_rfk_state(adevice); 465f63409aeSIke Panhc else 466f63409aeSIke Panhc ideapad_input_report(vpc_bit); 46757ac3b05SIke Panhc } 46857ac3b05SIke Panhc } 46957ac3b05SIke Panhc } 47057ac3b05SIke Panhc 47157ac3b05SIke Panhc static struct acpi_driver ideapad_acpi_driver = { 47257ac3b05SIke Panhc .name = "ideapad_acpi", 47357ac3b05SIke Panhc .class = "IdeaPad", 47457ac3b05SIke Panhc .ids = ideapad_device_ids, 47557ac3b05SIke Panhc .ops.add = ideapad_acpi_add, 47657ac3b05SIke Panhc .ops.remove = ideapad_acpi_remove, 47757ac3b05SIke Panhc .ops.notify = ideapad_acpi_notify, 47857ac3b05SIke Panhc .owner = THIS_MODULE, 47957ac3b05SIke Panhc }; 48057ac3b05SIke Panhc 48157ac3b05SIke Panhc 48257ac3b05SIke Panhc static int __init ideapad_acpi_module_init(void) 48357ac3b05SIke Panhc { 48457ac3b05SIke Panhc acpi_bus_register_driver(&ideapad_acpi_driver); 48557ac3b05SIke Panhc 48657ac3b05SIke Panhc return 0; 48757ac3b05SIke Panhc } 48857ac3b05SIke Panhc 48957ac3b05SIke Panhc 49057ac3b05SIke Panhc static void __exit ideapad_acpi_module_exit(void) 49157ac3b05SIke Panhc { 49257ac3b05SIke Panhc acpi_bus_unregister_driver(&ideapad_acpi_driver); 49357ac3b05SIke Panhc 49457ac3b05SIke Panhc } 49557ac3b05SIke Panhc 49657ac3b05SIke Panhc MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); 49757ac3b05SIke Panhc MODULE_DESCRIPTION("IdeaPad ACPI Extras"); 49857ac3b05SIke Panhc MODULE_LICENSE("GPL"); 49957ac3b05SIke Panhc 50057ac3b05SIke Panhc module_init(ideapad_acpi_module_init); 50157ac3b05SIke Panhc module_exit(ideapad_acpi_module_exit); 502