157ac3b05SIke Panhc /* 2a4b5a279SIke Panhc * ideapad-laptop.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 239ab23989SJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 249ab23989SJoe Perches 2557ac3b05SIke Panhc #include <linux/kernel.h> 2657ac3b05SIke Panhc #include <linux/module.h> 2757ac3b05SIke Panhc #include <linux/init.h> 2857ac3b05SIke Panhc #include <linux/types.h> 2957ac3b05SIke Panhc #include <acpi/acpi_bus.h> 3057ac3b05SIke Panhc #include <acpi/acpi_drivers.h> 3157ac3b05SIke Panhc #include <linux/rfkill.h> 3298ee6919SIke Panhc #include <linux/platform_device.h> 33f63409aeSIke Panhc #include <linux/input.h> 34f63409aeSIke Panhc #include <linux/input/sparse-keymap.h> 35a4ecbb8aSIke Panhc #include <linux/backlight.h> 36a4ecbb8aSIke Panhc #include <linux/fb.h> 37773e3206SIke Panhc #include <linux/debugfs.h> 38773e3206SIke Panhc #include <linux/seq_file.h> 3907a4a4fcSMaxim Mikityanskiy #include <linux/i8042.h> 4057ac3b05SIke Panhc 41c1f73658SIke Panhc #define IDEAPAD_RFKILL_DEV_NUM (3) 4257ac3b05SIke Panhc 433371f481SIke Panhc #define CFG_BT_BIT (16) 443371f481SIke Panhc #define CFG_3G_BIT (17) 453371f481SIke Panhc #define CFG_WIFI_BIT (18) 46a84511f7SIke Panhc #define CFG_CAMERA_BIT (19) 473371f481SIke Panhc 482be1dc21SIke Panhc enum { 492be1dc21SIke Panhc VPCCMD_R_VPC1 = 0x10, 502be1dc21SIke Panhc VPCCMD_R_BL_MAX, 512be1dc21SIke Panhc VPCCMD_R_BL, 522be1dc21SIke Panhc VPCCMD_W_BL, 532be1dc21SIke Panhc VPCCMD_R_WIFI, 542be1dc21SIke Panhc VPCCMD_W_WIFI, 552be1dc21SIke Panhc VPCCMD_R_BT, 562be1dc21SIke Panhc VPCCMD_W_BT, 572be1dc21SIke Panhc VPCCMD_R_BL_POWER, 582be1dc21SIke Panhc VPCCMD_R_NOVO, 592be1dc21SIke Panhc VPCCMD_R_VPC2, 602be1dc21SIke Panhc VPCCMD_R_TOUCHPAD, 612be1dc21SIke Panhc VPCCMD_W_TOUCHPAD, 622be1dc21SIke Panhc VPCCMD_R_CAMERA, 632be1dc21SIke Panhc VPCCMD_W_CAMERA, 642be1dc21SIke Panhc VPCCMD_R_3G, 652be1dc21SIke Panhc VPCCMD_W_3G, 662be1dc21SIke Panhc VPCCMD_R_ODD, /* 0x21 */ 670c7bbeb9SMaxim Mikityanskiy VPCCMD_W_FAN, 680c7bbeb9SMaxim Mikityanskiy VPCCMD_R_RF, 692be1dc21SIke Panhc VPCCMD_W_RF, 700c7bbeb9SMaxim Mikityanskiy VPCCMD_R_FAN = 0x2B, 71296f9fe0SMaxim Mikityanskiy VPCCMD_R_SPECIAL_BUTTONS = 0x31, 722be1dc21SIke Panhc VPCCMD_W_BL_POWER = 0x33, 732be1dc21SIke Panhc }; 742be1dc21SIke Panhc 7557ac3b05SIke Panhc struct ideapad_private { 76c1f73658SIke Panhc struct rfkill *rfk[IDEAPAD_RFKILL_DEV_NUM]; 7798ee6919SIke Panhc struct platform_device *platform_device; 78f63409aeSIke Panhc struct input_dev *inputdev; 79a4ecbb8aSIke Panhc struct backlight_device *blightdev; 80773e3206SIke Panhc struct dentry *debug; 813371f481SIke Panhc unsigned long cfg; 8257ac3b05SIke Panhc }; 8357ac3b05SIke Panhc 84c1f73658SIke Panhc static acpi_handle ideapad_handle; 85773e3206SIke Panhc static struct ideapad_private *ideapad_priv; 86bfa97b7dSIke Panhc static bool no_bt_rfkill; 87bfa97b7dSIke Panhc module_param(no_bt_rfkill, bool, 0444); 88bfa97b7dSIke Panhc MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth."); 89bfa97b7dSIke Panhc 9057ac3b05SIke Panhc /* 9157ac3b05SIke Panhc * ACPI Helpers 9257ac3b05SIke Panhc */ 9357ac3b05SIke Panhc #define IDEAPAD_EC_TIMEOUT (100) /* in ms */ 9457ac3b05SIke Panhc 9557ac3b05SIke Panhc static int read_method_int(acpi_handle handle, const char *method, int *val) 9657ac3b05SIke Panhc { 9757ac3b05SIke Panhc acpi_status status; 9857ac3b05SIke Panhc unsigned long long result; 9957ac3b05SIke Panhc 10057ac3b05SIke Panhc status = acpi_evaluate_integer(handle, (char *)method, NULL, &result); 10157ac3b05SIke Panhc if (ACPI_FAILURE(status)) { 10257ac3b05SIke Panhc *val = -1; 10357ac3b05SIke Panhc return -1; 10457ac3b05SIke Panhc } else { 10557ac3b05SIke Panhc *val = result; 10657ac3b05SIke Panhc return 0; 10757ac3b05SIke Panhc } 10857ac3b05SIke Panhc } 10957ac3b05SIke Panhc 11057ac3b05SIke Panhc static int method_vpcr(acpi_handle handle, int cmd, int *ret) 11157ac3b05SIke Panhc { 11257ac3b05SIke Panhc acpi_status status; 11357ac3b05SIke Panhc unsigned long long result; 11457ac3b05SIke Panhc struct acpi_object_list params; 11557ac3b05SIke Panhc union acpi_object in_obj; 11657ac3b05SIke Panhc 11757ac3b05SIke Panhc params.count = 1; 11857ac3b05SIke Panhc params.pointer = &in_obj; 11957ac3b05SIke Panhc in_obj.type = ACPI_TYPE_INTEGER; 12057ac3b05SIke Panhc in_obj.integer.value = cmd; 12157ac3b05SIke Panhc 12257ac3b05SIke Panhc status = acpi_evaluate_integer(handle, "VPCR", ¶ms, &result); 12357ac3b05SIke Panhc 12457ac3b05SIke Panhc if (ACPI_FAILURE(status)) { 12557ac3b05SIke Panhc *ret = -1; 12657ac3b05SIke Panhc return -1; 12757ac3b05SIke Panhc } else { 12857ac3b05SIke Panhc *ret = result; 12957ac3b05SIke Panhc return 0; 13057ac3b05SIke Panhc } 13157ac3b05SIke Panhc } 13257ac3b05SIke Panhc 13357ac3b05SIke Panhc static int method_vpcw(acpi_handle handle, int cmd, int data) 13457ac3b05SIke Panhc { 13557ac3b05SIke Panhc struct acpi_object_list params; 13657ac3b05SIke Panhc union acpi_object in_obj[2]; 13757ac3b05SIke Panhc acpi_status status; 13857ac3b05SIke Panhc 13957ac3b05SIke Panhc params.count = 2; 14057ac3b05SIke Panhc params.pointer = in_obj; 14157ac3b05SIke Panhc in_obj[0].type = ACPI_TYPE_INTEGER; 14257ac3b05SIke Panhc in_obj[0].integer.value = cmd; 14357ac3b05SIke Panhc in_obj[1].type = ACPI_TYPE_INTEGER; 14457ac3b05SIke Panhc in_obj[1].integer.value = data; 14557ac3b05SIke Panhc 14657ac3b05SIke Panhc status = acpi_evaluate_object(handle, "VPCW", ¶ms, NULL); 14757ac3b05SIke Panhc if (status != AE_OK) 14857ac3b05SIke Panhc return -1; 14957ac3b05SIke Panhc return 0; 15057ac3b05SIke Panhc } 15157ac3b05SIke Panhc 15257ac3b05SIke Panhc static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data) 15357ac3b05SIke Panhc { 15457ac3b05SIke Panhc int val; 15557ac3b05SIke Panhc unsigned long int end_jiffies; 15657ac3b05SIke Panhc 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 if (method_vpcr(handle, 0, &val)) 16757ac3b05SIke Panhc return -1; 16857ac3b05SIke Panhc *data = val; 16957ac3b05SIke Panhc return 0; 17057ac3b05SIke Panhc } 17157ac3b05SIke Panhc } 17257ac3b05SIke Panhc pr_err("timeout in read_ec_cmd\n"); 17357ac3b05SIke Panhc return -1; 17457ac3b05SIke Panhc } 17557ac3b05SIke Panhc 17657ac3b05SIke Panhc static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data) 17757ac3b05SIke Panhc { 17857ac3b05SIke Panhc int val; 17957ac3b05SIke Panhc unsigned long int end_jiffies; 18057ac3b05SIke Panhc 18157ac3b05SIke Panhc if (method_vpcw(handle, 0, data)) 18257ac3b05SIke Panhc return -1; 18357ac3b05SIke Panhc if (method_vpcw(handle, 1, cmd)) 18457ac3b05SIke Panhc return -1; 18557ac3b05SIke Panhc 18657ac3b05SIke Panhc for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1; 18757ac3b05SIke Panhc time_before(jiffies, end_jiffies);) { 18857ac3b05SIke Panhc schedule(); 18957ac3b05SIke Panhc if (method_vpcr(handle, 1, &val)) 19057ac3b05SIke Panhc return -1; 19157ac3b05SIke Panhc if (val == 0) 19257ac3b05SIke Panhc return 0; 19357ac3b05SIke Panhc } 19457ac3b05SIke Panhc pr_err("timeout in write_ec_cmd\n"); 19557ac3b05SIke Panhc return -1; 19657ac3b05SIke Panhc } 19757ac3b05SIke Panhc 198a4b5a279SIke Panhc /* 199773e3206SIke Panhc * debugfs 200773e3206SIke Panhc */ 201773e3206SIke Panhc static int debugfs_status_show(struct seq_file *s, void *data) 202773e3206SIke Panhc { 203773e3206SIke Panhc unsigned long value; 204773e3206SIke Panhc 205773e3206SIke Panhc if (!read_ec_data(ideapad_handle, VPCCMD_R_BL_MAX, &value)) 206773e3206SIke Panhc seq_printf(s, "Backlight max:\t%lu\n", value); 207773e3206SIke Panhc if (!read_ec_data(ideapad_handle, VPCCMD_R_BL, &value)) 208773e3206SIke Panhc seq_printf(s, "Backlight now:\t%lu\n", value); 209773e3206SIke Panhc if (!read_ec_data(ideapad_handle, VPCCMD_R_BL_POWER, &value)) 210773e3206SIke Panhc seq_printf(s, "BL power value:\t%s\n", value ? "On" : "Off"); 211773e3206SIke Panhc seq_printf(s, "=====================\n"); 212773e3206SIke Panhc 213773e3206SIke Panhc if (!read_ec_data(ideapad_handle, VPCCMD_R_RF, &value)) 214773e3206SIke Panhc seq_printf(s, "Radio status:\t%s(%lu)\n", 215773e3206SIke Panhc value ? "On" : "Off", value); 216773e3206SIke Panhc if (!read_ec_data(ideapad_handle, VPCCMD_R_WIFI, &value)) 217773e3206SIke Panhc seq_printf(s, "Wifi status:\t%s(%lu)\n", 218773e3206SIke Panhc value ? "On" : "Off", value); 219773e3206SIke Panhc if (!read_ec_data(ideapad_handle, VPCCMD_R_BT, &value)) 220773e3206SIke Panhc seq_printf(s, "BT status:\t%s(%lu)\n", 221773e3206SIke Panhc value ? "On" : "Off", value); 222773e3206SIke Panhc if (!read_ec_data(ideapad_handle, VPCCMD_R_3G, &value)) 223773e3206SIke Panhc seq_printf(s, "3G status:\t%s(%lu)\n", 224773e3206SIke Panhc value ? "On" : "Off", value); 225773e3206SIke Panhc seq_printf(s, "=====================\n"); 226773e3206SIke Panhc 227773e3206SIke Panhc if (!read_ec_data(ideapad_handle, VPCCMD_R_TOUCHPAD, &value)) 228773e3206SIke Panhc seq_printf(s, "Touchpad status:%s(%lu)\n", 229773e3206SIke Panhc value ? "On" : "Off", value); 230773e3206SIke Panhc if (!read_ec_data(ideapad_handle, VPCCMD_R_CAMERA, &value)) 231773e3206SIke Panhc seq_printf(s, "Camera status:\t%s(%lu)\n", 232773e3206SIke Panhc value ? "On" : "Off", value); 233773e3206SIke Panhc 234773e3206SIke Panhc return 0; 235773e3206SIke Panhc } 236773e3206SIke Panhc 237773e3206SIke Panhc static int debugfs_status_open(struct inode *inode, struct file *file) 238773e3206SIke Panhc { 239773e3206SIke Panhc return single_open(file, debugfs_status_show, NULL); 240773e3206SIke Panhc } 241773e3206SIke Panhc 242773e3206SIke Panhc static const struct file_operations debugfs_status_fops = { 243773e3206SIke Panhc .owner = THIS_MODULE, 244773e3206SIke Panhc .open = debugfs_status_open, 245773e3206SIke Panhc .read = seq_read, 246773e3206SIke Panhc .llseek = seq_lseek, 247773e3206SIke Panhc .release = single_release, 248773e3206SIke Panhc }; 249773e3206SIke Panhc 250773e3206SIke Panhc static int debugfs_cfg_show(struct seq_file *s, void *data) 251773e3206SIke Panhc { 252773e3206SIke Panhc if (!ideapad_priv) { 253773e3206SIke Panhc seq_printf(s, "cfg: N/A\n"); 254773e3206SIke Panhc } else { 255773e3206SIke Panhc seq_printf(s, "cfg: 0x%.8lX\n\nCapability: ", 256773e3206SIke Panhc ideapad_priv->cfg); 257773e3206SIke Panhc if (test_bit(CFG_BT_BIT, &ideapad_priv->cfg)) 258773e3206SIke Panhc seq_printf(s, "Bluetooth "); 259773e3206SIke Panhc if (test_bit(CFG_3G_BIT, &ideapad_priv->cfg)) 260773e3206SIke Panhc seq_printf(s, "3G "); 261773e3206SIke Panhc if (test_bit(CFG_WIFI_BIT, &ideapad_priv->cfg)) 262773e3206SIke Panhc seq_printf(s, "Wireless "); 263773e3206SIke Panhc if (test_bit(CFG_CAMERA_BIT, &ideapad_priv->cfg)) 264773e3206SIke Panhc seq_printf(s, "Camera "); 265773e3206SIke Panhc seq_printf(s, "\nGraphic: "); 266773e3206SIke Panhc switch ((ideapad_priv->cfg)&0x700) { 267773e3206SIke Panhc case 0x100: 268773e3206SIke Panhc seq_printf(s, "Intel"); 269773e3206SIke Panhc break; 270773e3206SIke Panhc case 0x200: 271773e3206SIke Panhc seq_printf(s, "ATI"); 272773e3206SIke Panhc break; 273773e3206SIke Panhc case 0x300: 274773e3206SIke Panhc seq_printf(s, "Nvidia"); 275773e3206SIke Panhc break; 276773e3206SIke Panhc case 0x400: 277773e3206SIke Panhc seq_printf(s, "Intel and ATI"); 278773e3206SIke Panhc break; 279773e3206SIke Panhc case 0x500: 280773e3206SIke Panhc seq_printf(s, "Intel and Nvidia"); 281773e3206SIke Panhc break; 282773e3206SIke Panhc } 283773e3206SIke Panhc seq_printf(s, "\n"); 284773e3206SIke Panhc } 285773e3206SIke Panhc return 0; 286773e3206SIke Panhc } 287773e3206SIke Panhc 288773e3206SIke Panhc static int debugfs_cfg_open(struct inode *inode, struct file *file) 289773e3206SIke Panhc { 290773e3206SIke Panhc return single_open(file, debugfs_cfg_show, NULL); 291773e3206SIke Panhc } 292773e3206SIke Panhc 293773e3206SIke Panhc static const struct file_operations debugfs_cfg_fops = { 294773e3206SIke Panhc .owner = THIS_MODULE, 295773e3206SIke Panhc .open = debugfs_cfg_open, 296773e3206SIke Panhc .read = seq_read, 297773e3206SIke Panhc .llseek = seq_lseek, 298773e3206SIke Panhc .release = single_release, 299773e3206SIke Panhc }; 300773e3206SIke Panhc 301773e3206SIke Panhc static int __devinit ideapad_debugfs_init(struct ideapad_private *priv) 302773e3206SIke Panhc { 303773e3206SIke Panhc struct dentry *node; 304773e3206SIke Panhc 305773e3206SIke Panhc priv->debug = debugfs_create_dir("ideapad", NULL); 306773e3206SIke Panhc if (priv->debug == NULL) { 307773e3206SIke Panhc pr_err("failed to create debugfs directory"); 308773e3206SIke Panhc goto errout; 309773e3206SIke Panhc } 310773e3206SIke Panhc 311773e3206SIke Panhc node = debugfs_create_file("cfg", S_IRUGO, priv->debug, NULL, 312773e3206SIke Panhc &debugfs_cfg_fops); 313773e3206SIke Panhc if (!node) { 314773e3206SIke Panhc pr_err("failed to create cfg in debugfs"); 315773e3206SIke Panhc goto errout; 316773e3206SIke Panhc } 317773e3206SIke Panhc 318773e3206SIke Panhc node = debugfs_create_file("status", S_IRUGO, priv->debug, NULL, 319773e3206SIke Panhc &debugfs_status_fops); 320773e3206SIke Panhc if (!node) { 321a5c3892fSIke Panhc pr_err("failed to create status in debugfs"); 322773e3206SIke Panhc goto errout; 323773e3206SIke Panhc } 324773e3206SIke Panhc 325773e3206SIke Panhc return 0; 326773e3206SIke Panhc 327773e3206SIke Panhc errout: 328773e3206SIke Panhc return -ENOMEM; 329773e3206SIke Panhc } 330773e3206SIke Panhc 331773e3206SIke Panhc static void ideapad_debugfs_exit(struct ideapad_private *priv) 332773e3206SIke Panhc { 333773e3206SIke Panhc debugfs_remove_recursive(priv->debug); 334773e3206SIke Panhc priv->debug = NULL; 335773e3206SIke Panhc } 336773e3206SIke Panhc 337773e3206SIke Panhc /* 3383371f481SIke Panhc * sysfs 339a4b5a279SIke Panhc */ 34057ac3b05SIke Panhc static ssize_t show_ideapad_cam(struct device *dev, 34157ac3b05SIke Panhc struct device_attribute *attr, 34257ac3b05SIke Panhc char *buf) 34357ac3b05SIke Panhc { 34457ac3b05SIke Panhc unsigned long result; 34557ac3b05SIke Panhc 3462be1dc21SIke Panhc if (read_ec_data(ideapad_handle, VPCCMD_R_CAMERA, &result)) 34757ac3b05SIke Panhc return sprintf(buf, "-1\n"); 34857ac3b05SIke Panhc return sprintf(buf, "%lu\n", result); 34957ac3b05SIke Panhc } 35057ac3b05SIke Panhc 35157ac3b05SIke Panhc static ssize_t store_ideapad_cam(struct device *dev, 35257ac3b05SIke Panhc struct device_attribute *attr, 35357ac3b05SIke Panhc const char *buf, size_t count) 35457ac3b05SIke Panhc { 35557ac3b05SIke Panhc int ret, state; 35657ac3b05SIke Panhc 35757ac3b05SIke Panhc if (!count) 35857ac3b05SIke Panhc return 0; 35957ac3b05SIke Panhc if (sscanf(buf, "%i", &state) != 1) 36057ac3b05SIke Panhc return -EINVAL; 3612be1dc21SIke Panhc ret = write_ec_cmd(ideapad_handle, VPCCMD_W_CAMERA, state); 36257ac3b05SIke Panhc if (ret < 0) 3630c7bbeb9SMaxim Mikityanskiy return -EIO; 36457ac3b05SIke Panhc return count; 36557ac3b05SIke Panhc } 36657ac3b05SIke Panhc 36757ac3b05SIke Panhc static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam); 36857ac3b05SIke Panhc 3690c7bbeb9SMaxim Mikityanskiy static ssize_t show_ideapad_fan(struct device *dev, 3700c7bbeb9SMaxim Mikityanskiy struct device_attribute *attr, 3710c7bbeb9SMaxim Mikityanskiy char *buf) 3720c7bbeb9SMaxim Mikityanskiy { 3730c7bbeb9SMaxim Mikityanskiy unsigned long result; 3740c7bbeb9SMaxim Mikityanskiy 3750c7bbeb9SMaxim Mikityanskiy if (read_ec_data(ideapad_handle, VPCCMD_R_FAN, &result)) 3760c7bbeb9SMaxim Mikityanskiy return sprintf(buf, "-1\n"); 3770c7bbeb9SMaxim Mikityanskiy return sprintf(buf, "%lu\n", result); 3780c7bbeb9SMaxim Mikityanskiy } 3790c7bbeb9SMaxim Mikityanskiy 3800c7bbeb9SMaxim Mikityanskiy static ssize_t store_ideapad_fan(struct device *dev, 3810c7bbeb9SMaxim Mikityanskiy struct device_attribute *attr, 3820c7bbeb9SMaxim Mikityanskiy const char *buf, size_t count) 3830c7bbeb9SMaxim Mikityanskiy { 3840c7bbeb9SMaxim Mikityanskiy int ret, state; 3850c7bbeb9SMaxim Mikityanskiy 3860c7bbeb9SMaxim Mikityanskiy if (!count) 3870c7bbeb9SMaxim Mikityanskiy return 0; 3880c7bbeb9SMaxim Mikityanskiy if (sscanf(buf, "%i", &state) != 1) 3890c7bbeb9SMaxim Mikityanskiy return -EINVAL; 3900c7bbeb9SMaxim Mikityanskiy if (state < 0 || state > 4 || state == 3) 3910c7bbeb9SMaxim Mikityanskiy return -EINVAL; 3920c7bbeb9SMaxim Mikityanskiy ret = write_ec_cmd(ideapad_handle, VPCCMD_W_FAN, state); 3930c7bbeb9SMaxim Mikityanskiy if (ret < 0) 3940c7bbeb9SMaxim Mikityanskiy return -EIO; 3950c7bbeb9SMaxim Mikityanskiy return count; 3960c7bbeb9SMaxim Mikityanskiy } 3970c7bbeb9SMaxim Mikityanskiy 3980c7bbeb9SMaxim Mikityanskiy static DEVICE_ATTR(fan_mode, 0644, show_ideapad_fan, store_ideapad_fan); 3990c7bbeb9SMaxim Mikityanskiy 4003371f481SIke Panhc static struct attribute *ideapad_attributes[] = { 4013371f481SIke Panhc &dev_attr_camera_power.attr, 4020c7bbeb9SMaxim Mikityanskiy &dev_attr_fan_mode.attr, 4033371f481SIke Panhc NULL 4043371f481SIke Panhc }; 4053371f481SIke Panhc 406587a1f16SAl Viro static umode_t ideapad_is_visible(struct kobject *kobj, 407a84511f7SIke Panhc struct attribute *attr, 408a84511f7SIke Panhc int idx) 409a84511f7SIke Panhc { 410a84511f7SIke Panhc struct device *dev = container_of(kobj, struct device, kobj); 411a84511f7SIke Panhc struct ideapad_private *priv = dev_get_drvdata(dev); 412a84511f7SIke Panhc bool supported; 413a84511f7SIke Panhc 414a84511f7SIke Panhc if (attr == &dev_attr_camera_power.attr) 415a84511f7SIke Panhc supported = test_bit(CFG_CAMERA_BIT, &(priv->cfg)); 4160c7bbeb9SMaxim Mikityanskiy else if (attr == &dev_attr_fan_mode.attr) { 4170c7bbeb9SMaxim Mikityanskiy unsigned long value; 4180c7bbeb9SMaxim Mikityanskiy supported = !read_ec_data(ideapad_handle, VPCCMD_R_FAN, &value); 4190c7bbeb9SMaxim Mikityanskiy } else 420a84511f7SIke Panhc supported = true; 421a84511f7SIke Panhc 422a84511f7SIke Panhc return supported ? attr->mode : 0; 423a84511f7SIke Panhc } 424a84511f7SIke Panhc 4253371f481SIke Panhc static struct attribute_group ideapad_attribute_group = { 426a84511f7SIke Panhc .is_visible = ideapad_is_visible, 4273371f481SIke Panhc .attrs = ideapad_attributes 4283371f481SIke Panhc }; 4293371f481SIke Panhc 430a4b5a279SIke Panhc /* 431a4b5a279SIke Panhc * Rfkill 432a4b5a279SIke Panhc */ 433c1f73658SIke Panhc struct ideapad_rfk_data { 434c1f73658SIke Panhc char *name; 435c1f73658SIke Panhc int cfgbit; 436c1f73658SIke Panhc int opcode; 437c1f73658SIke Panhc int type; 438c1f73658SIke Panhc }; 439c1f73658SIke Panhc 440c1f73658SIke Panhc const struct ideapad_rfk_data ideapad_rfk_data[] = { 4412be1dc21SIke Panhc { "ideapad_wlan", CFG_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN }, 4422be1dc21SIke Panhc { "ideapad_bluetooth", CFG_BT_BIT, VPCCMD_W_BT, RFKILL_TYPE_BLUETOOTH }, 4432be1dc21SIke Panhc { "ideapad_3g", CFG_3G_BIT, VPCCMD_W_3G, RFKILL_TYPE_WWAN }, 444c1f73658SIke Panhc }; 445c1f73658SIke Panhc 44657ac3b05SIke Panhc static int ideapad_rfk_set(void *data, bool blocked) 44757ac3b05SIke Panhc { 448c1f73658SIke Panhc unsigned long opcode = (unsigned long)data; 44957ac3b05SIke Panhc 450c1f73658SIke Panhc return write_ec_cmd(ideapad_handle, opcode, !blocked); 45157ac3b05SIke Panhc } 45257ac3b05SIke Panhc 45357ac3b05SIke Panhc static struct rfkill_ops ideapad_rfk_ops = { 45457ac3b05SIke Panhc .set_block = ideapad_rfk_set, 45557ac3b05SIke Panhc }; 45657ac3b05SIke Panhc 457923de84aSIke Panhc static void ideapad_sync_rfk_state(struct ideapad_private *priv) 45857ac3b05SIke Panhc { 45957ac3b05SIke Panhc unsigned long hw_blocked; 46057ac3b05SIke Panhc int i; 46157ac3b05SIke Panhc 4622be1dc21SIke Panhc if (read_ec_data(ideapad_handle, VPCCMD_R_RF, &hw_blocked)) 46357ac3b05SIke Panhc return; 46457ac3b05SIke Panhc hw_blocked = !hw_blocked; 46557ac3b05SIke Panhc 466c1f73658SIke Panhc for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 46757ac3b05SIke Panhc if (priv->rfk[i]) 46857ac3b05SIke Panhc rfkill_set_hw_state(priv->rfk[i], hw_blocked); 46957ac3b05SIke Panhc } 47057ac3b05SIke Panhc 471a4b5a279SIke Panhc static int __devinit ideapad_register_rfkill(struct acpi_device *adevice, 472a4b5a279SIke Panhc int dev) 47357ac3b05SIke Panhc { 47457ac3b05SIke Panhc struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); 47557ac3b05SIke Panhc int ret; 47657ac3b05SIke Panhc unsigned long sw_blocked; 47757ac3b05SIke Panhc 478bfa97b7dSIke Panhc if (no_bt_rfkill && 479bfa97b7dSIke Panhc (ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH)) { 480bfa97b7dSIke Panhc /* Force to enable bluetooth when no_bt_rfkill=1 */ 481c1f73658SIke Panhc write_ec_cmd(ideapad_handle, 482bfa97b7dSIke Panhc ideapad_rfk_data[dev].opcode, 1); 483bfa97b7dSIke Panhc return 0; 484bfa97b7dSIke Panhc } 485bfa97b7dSIke Panhc 48657ac3b05SIke Panhc priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name, &adevice->dev, 48757ac3b05SIke Panhc ideapad_rfk_data[dev].type, &ideapad_rfk_ops, 48857ac3b05SIke Panhc (void *)(long)dev); 48957ac3b05SIke Panhc if (!priv->rfk[dev]) 49057ac3b05SIke Panhc return -ENOMEM; 49157ac3b05SIke Panhc 492c1f73658SIke Panhc if (read_ec_data(ideapad_handle, ideapad_rfk_data[dev].opcode-1, 49357ac3b05SIke Panhc &sw_blocked)) { 49457ac3b05SIke Panhc rfkill_init_sw_state(priv->rfk[dev], 0); 49557ac3b05SIke Panhc } else { 49657ac3b05SIke Panhc sw_blocked = !sw_blocked; 49757ac3b05SIke Panhc rfkill_init_sw_state(priv->rfk[dev], sw_blocked); 49857ac3b05SIke Panhc } 49957ac3b05SIke Panhc 50057ac3b05SIke Panhc ret = rfkill_register(priv->rfk[dev]); 50157ac3b05SIke Panhc if (ret) { 50257ac3b05SIke Panhc rfkill_destroy(priv->rfk[dev]); 50357ac3b05SIke Panhc return ret; 50457ac3b05SIke Panhc } 50557ac3b05SIke Panhc return 0; 50657ac3b05SIke Panhc } 50757ac3b05SIke Panhc 508a4ecbb8aSIke Panhc static void ideapad_unregister_rfkill(struct acpi_device *adevice, int dev) 50957ac3b05SIke Panhc { 51057ac3b05SIke Panhc struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); 51157ac3b05SIke Panhc 51257ac3b05SIke Panhc if (!priv->rfk[dev]) 51357ac3b05SIke Panhc return; 51457ac3b05SIke Panhc 51557ac3b05SIke Panhc rfkill_unregister(priv->rfk[dev]); 51657ac3b05SIke Panhc rfkill_destroy(priv->rfk[dev]); 51757ac3b05SIke Panhc } 51857ac3b05SIke Panhc 51998ee6919SIke Panhc /* 52098ee6919SIke Panhc * Platform device 52198ee6919SIke Panhc */ 5228693ae84SIke Panhc static int __devinit ideapad_platform_init(struct ideapad_private *priv) 52398ee6919SIke Panhc { 52498ee6919SIke Panhc int result; 52598ee6919SIke Panhc 5268693ae84SIke Panhc priv->platform_device = platform_device_alloc("ideapad", -1); 5278693ae84SIke Panhc if (!priv->platform_device) 52898ee6919SIke Panhc return -ENOMEM; 5298693ae84SIke Panhc platform_set_drvdata(priv->platform_device, priv); 53098ee6919SIke Panhc 5318693ae84SIke Panhc result = platform_device_add(priv->platform_device); 53298ee6919SIke Panhc if (result) 53398ee6919SIke Panhc goto fail_platform_device; 53498ee6919SIke Panhc 5358693ae84SIke Panhc result = sysfs_create_group(&priv->platform_device->dev.kobj, 536c9f718d0SIke Panhc &ideapad_attribute_group); 537c9f718d0SIke Panhc if (result) 538c9f718d0SIke Panhc goto fail_sysfs; 53998ee6919SIke Panhc return 0; 54098ee6919SIke Panhc 541c9f718d0SIke Panhc fail_sysfs: 5428693ae84SIke Panhc platform_device_del(priv->platform_device); 54398ee6919SIke Panhc fail_platform_device: 5448693ae84SIke Panhc platform_device_put(priv->platform_device); 54598ee6919SIke Panhc return result; 54698ee6919SIke Panhc } 54798ee6919SIke Panhc 5488693ae84SIke Panhc static void ideapad_platform_exit(struct ideapad_private *priv) 54998ee6919SIke Panhc { 5508693ae84SIke Panhc sysfs_remove_group(&priv->platform_device->dev.kobj, 551c9f718d0SIke Panhc &ideapad_attribute_group); 5528693ae84SIke Panhc platform_device_unregister(priv->platform_device); 55398ee6919SIke Panhc } 55498ee6919SIke Panhc 555f63409aeSIke Panhc /* 556f63409aeSIke Panhc * input device 557f63409aeSIke Panhc */ 558f63409aeSIke Panhc static const struct key_entry ideapad_keymap[] = { 559f43d9ec0SIke Panhc { KE_KEY, 6, { KEY_SWITCHVIDEOMODE } }, 560296f9fe0SMaxim Mikityanskiy { KE_KEY, 7, { KEY_CAMERA } }, 561296f9fe0SMaxim Mikityanskiy { KE_KEY, 11, { KEY_F16 } }, 562f43d9ec0SIke Panhc { KE_KEY, 13, { KEY_WLAN } }, 563f43d9ec0SIke Panhc { KE_KEY, 16, { KEY_PROG1 } }, 564f43d9ec0SIke Panhc { KE_KEY, 17, { KEY_PROG2 } }, 565296f9fe0SMaxim Mikityanskiy { KE_KEY, 64, { KEY_PROG3 } }, 566296f9fe0SMaxim Mikityanskiy { KE_KEY, 65, { KEY_PROG4 } }, 56707a4a4fcSMaxim Mikityanskiy { KE_KEY, 66, { KEY_TOUCHPAD_OFF } }, 56807a4a4fcSMaxim Mikityanskiy { KE_KEY, 67, { KEY_TOUCHPAD_ON } }, 569f63409aeSIke Panhc { KE_END, 0 }, 570f63409aeSIke Panhc }; 571f63409aeSIke Panhc 5728693ae84SIke Panhc static int __devinit ideapad_input_init(struct ideapad_private *priv) 573f63409aeSIke Panhc { 574f63409aeSIke Panhc struct input_dev *inputdev; 575f63409aeSIke Panhc int error; 576f63409aeSIke Panhc 577f63409aeSIke Panhc inputdev = input_allocate_device(); 578f63409aeSIke Panhc if (!inputdev) { 579f63409aeSIke Panhc pr_info("Unable to allocate input device\n"); 580f63409aeSIke Panhc return -ENOMEM; 581f63409aeSIke Panhc } 582f63409aeSIke Panhc 583f63409aeSIke Panhc inputdev->name = "Ideapad extra buttons"; 584f63409aeSIke Panhc inputdev->phys = "ideapad/input0"; 585f63409aeSIke Panhc inputdev->id.bustype = BUS_HOST; 5868693ae84SIke Panhc inputdev->dev.parent = &priv->platform_device->dev; 587f63409aeSIke Panhc 588f63409aeSIke Panhc error = sparse_keymap_setup(inputdev, ideapad_keymap, NULL); 589f63409aeSIke Panhc if (error) { 590f63409aeSIke Panhc pr_err("Unable to setup input device keymap\n"); 591f63409aeSIke Panhc goto err_free_dev; 592f63409aeSIke Panhc } 593f63409aeSIke Panhc 594f63409aeSIke Panhc error = input_register_device(inputdev); 595f63409aeSIke Panhc if (error) { 596f63409aeSIke Panhc pr_err("Unable to register input device\n"); 597f63409aeSIke Panhc goto err_free_keymap; 598f63409aeSIke Panhc } 599f63409aeSIke Panhc 6008693ae84SIke Panhc priv->inputdev = inputdev; 601f63409aeSIke Panhc return 0; 602f63409aeSIke Panhc 603f63409aeSIke Panhc err_free_keymap: 604f63409aeSIke Panhc sparse_keymap_free(inputdev); 605f63409aeSIke Panhc err_free_dev: 606f63409aeSIke Panhc input_free_device(inputdev); 607f63409aeSIke Panhc return error; 608f63409aeSIke Panhc } 609f63409aeSIke Panhc 6107451a55aSAxel Lin static void ideapad_input_exit(struct ideapad_private *priv) 611f63409aeSIke Panhc { 6128693ae84SIke Panhc sparse_keymap_free(priv->inputdev); 6138693ae84SIke Panhc input_unregister_device(priv->inputdev); 6148693ae84SIke Panhc priv->inputdev = NULL; 615f63409aeSIke Panhc } 616f63409aeSIke Panhc 6178693ae84SIke Panhc static void ideapad_input_report(struct ideapad_private *priv, 6188693ae84SIke Panhc unsigned long scancode) 619f63409aeSIke Panhc { 6208693ae84SIke Panhc sparse_keymap_report_event(priv->inputdev, scancode, 1, true); 621f63409aeSIke Panhc } 622f63409aeSIke Panhc 623f43d9ec0SIke Panhc static void ideapad_input_novokey(struct ideapad_private *priv) 624f43d9ec0SIke Panhc { 625f43d9ec0SIke Panhc unsigned long long_pressed; 626f43d9ec0SIke Panhc 627f43d9ec0SIke Panhc if (read_ec_data(ideapad_handle, VPCCMD_R_NOVO, &long_pressed)) 628f43d9ec0SIke Panhc return; 629f43d9ec0SIke Panhc if (long_pressed) 630f43d9ec0SIke Panhc ideapad_input_report(priv, 17); 631f43d9ec0SIke Panhc else 632f43d9ec0SIke Panhc ideapad_input_report(priv, 16); 633f43d9ec0SIke Panhc } 634f43d9ec0SIke Panhc 635296f9fe0SMaxim Mikityanskiy static void ideapad_check_special_buttons(struct ideapad_private *priv) 636296f9fe0SMaxim Mikityanskiy { 637296f9fe0SMaxim Mikityanskiy unsigned long bit, value; 638296f9fe0SMaxim Mikityanskiy 639296f9fe0SMaxim Mikityanskiy read_ec_data(ideapad_handle, VPCCMD_R_SPECIAL_BUTTONS, &value); 640296f9fe0SMaxim Mikityanskiy 641296f9fe0SMaxim Mikityanskiy for (bit = 0; bit < 16; bit++) { 642296f9fe0SMaxim Mikityanskiy if (test_bit(bit, &value)) { 643296f9fe0SMaxim Mikityanskiy switch (bit) { 644296f9fe0SMaxim Mikityanskiy case 6: 645296f9fe0SMaxim Mikityanskiy /* Thermal Management button */ 646296f9fe0SMaxim Mikityanskiy ideapad_input_report(priv, 65); 647296f9fe0SMaxim Mikityanskiy break; 648296f9fe0SMaxim Mikityanskiy case 1: 649296f9fe0SMaxim Mikityanskiy /* OneKey Theater button */ 650296f9fe0SMaxim Mikityanskiy ideapad_input_report(priv, 64); 651296f9fe0SMaxim Mikityanskiy break; 652296f9fe0SMaxim Mikityanskiy } 653296f9fe0SMaxim Mikityanskiy } 654296f9fe0SMaxim Mikityanskiy } 655296f9fe0SMaxim Mikityanskiy } 656296f9fe0SMaxim Mikityanskiy 657a4b5a279SIke Panhc /* 658a4ecbb8aSIke Panhc * backlight 659a4ecbb8aSIke Panhc */ 660a4ecbb8aSIke Panhc static int ideapad_backlight_get_brightness(struct backlight_device *blightdev) 661a4ecbb8aSIke Panhc { 662a4ecbb8aSIke Panhc unsigned long now; 663a4ecbb8aSIke Panhc 6642be1dc21SIke Panhc if (read_ec_data(ideapad_handle, VPCCMD_R_BL, &now)) 665a4ecbb8aSIke Panhc return -EIO; 666a4ecbb8aSIke Panhc return now; 667a4ecbb8aSIke Panhc } 668a4ecbb8aSIke Panhc 669a4ecbb8aSIke Panhc static int ideapad_backlight_update_status(struct backlight_device *blightdev) 670a4ecbb8aSIke Panhc { 6712be1dc21SIke Panhc if (write_ec_cmd(ideapad_handle, VPCCMD_W_BL, 6722be1dc21SIke Panhc blightdev->props.brightness)) 673a4ecbb8aSIke Panhc return -EIO; 6742be1dc21SIke Panhc if (write_ec_cmd(ideapad_handle, VPCCMD_W_BL_POWER, 675a4ecbb8aSIke Panhc blightdev->props.power == FB_BLANK_POWERDOWN ? 0 : 1)) 676a4ecbb8aSIke Panhc return -EIO; 677a4ecbb8aSIke Panhc 678a4ecbb8aSIke Panhc return 0; 679a4ecbb8aSIke Panhc } 680a4ecbb8aSIke Panhc 681a4ecbb8aSIke Panhc static const struct backlight_ops ideapad_backlight_ops = { 682a4ecbb8aSIke Panhc .get_brightness = ideapad_backlight_get_brightness, 683a4ecbb8aSIke Panhc .update_status = ideapad_backlight_update_status, 684a4ecbb8aSIke Panhc }; 685a4ecbb8aSIke Panhc 686a4ecbb8aSIke Panhc static int ideapad_backlight_init(struct ideapad_private *priv) 687a4ecbb8aSIke Panhc { 688a4ecbb8aSIke Panhc struct backlight_device *blightdev; 689a4ecbb8aSIke Panhc struct backlight_properties props; 690a4ecbb8aSIke Panhc unsigned long max, now, power; 691a4ecbb8aSIke Panhc 6922be1dc21SIke Panhc if (read_ec_data(ideapad_handle, VPCCMD_R_BL_MAX, &max)) 693a4ecbb8aSIke Panhc return -EIO; 6942be1dc21SIke Panhc if (read_ec_data(ideapad_handle, VPCCMD_R_BL, &now)) 695a4ecbb8aSIke Panhc return -EIO; 6962be1dc21SIke Panhc if (read_ec_data(ideapad_handle, VPCCMD_R_BL_POWER, &power)) 697a4ecbb8aSIke Panhc return -EIO; 698a4ecbb8aSIke Panhc 699a4ecbb8aSIke Panhc memset(&props, 0, sizeof(struct backlight_properties)); 700a4ecbb8aSIke Panhc props.max_brightness = max; 701a4ecbb8aSIke Panhc props.type = BACKLIGHT_PLATFORM; 702a4ecbb8aSIke Panhc blightdev = backlight_device_register("ideapad", 703a4ecbb8aSIke Panhc &priv->platform_device->dev, 704a4ecbb8aSIke Panhc priv, 705a4ecbb8aSIke Panhc &ideapad_backlight_ops, 706a4ecbb8aSIke Panhc &props); 707a4ecbb8aSIke Panhc if (IS_ERR(blightdev)) { 708a4ecbb8aSIke Panhc pr_err("Could not register backlight device\n"); 709a4ecbb8aSIke Panhc return PTR_ERR(blightdev); 710a4ecbb8aSIke Panhc } 711a4ecbb8aSIke Panhc 712a4ecbb8aSIke Panhc priv->blightdev = blightdev; 713a4ecbb8aSIke Panhc blightdev->props.brightness = now; 714a4ecbb8aSIke Panhc blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; 715a4ecbb8aSIke Panhc backlight_update_status(blightdev); 716a4ecbb8aSIke Panhc 717a4ecbb8aSIke Panhc return 0; 718a4ecbb8aSIke Panhc } 719a4ecbb8aSIke Panhc 720a4ecbb8aSIke Panhc static void ideapad_backlight_exit(struct ideapad_private *priv) 721a4ecbb8aSIke Panhc { 722a4ecbb8aSIke Panhc if (priv->blightdev) 723a4ecbb8aSIke Panhc backlight_device_unregister(priv->blightdev); 724a4ecbb8aSIke Panhc priv->blightdev = NULL; 725a4ecbb8aSIke Panhc } 726a4ecbb8aSIke Panhc 727a4ecbb8aSIke Panhc static void ideapad_backlight_notify_power(struct ideapad_private *priv) 728a4ecbb8aSIke Panhc { 729a4ecbb8aSIke Panhc unsigned long power; 730a4ecbb8aSIke Panhc struct backlight_device *blightdev = priv->blightdev; 731a4ecbb8aSIke Panhc 732d4afc775SRene Bollford if (!blightdev) 733d4afc775SRene Bollford return; 7342be1dc21SIke Panhc if (read_ec_data(ideapad_handle, VPCCMD_R_BL_POWER, &power)) 735a4ecbb8aSIke Panhc return; 736a4ecbb8aSIke Panhc blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; 737a4ecbb8aSIke Panhc } 738a4ecbb8aSIke Panhc 739a4ecbb8aSIke Panhc static void ideapad_backlight_notify_brightness(struct ideapad_private *priv) 740a4ecbb8aSIke Panhc { 741a4ecbb8aSIke Panhc unsigned long now; 742a4ecbb8aSIke Panhc 743a4ecbb8aSIke Panhc /* if we control brightness via acpi video driver */ 744a4ecbb8aSIke Panhc if (priv->blightdev == NULL) { 7452be1dc21SIke Panhc read_ec_data(ideapad_handle, VPCCMD_R_BL, &now); 746a4ecbb8aSIke Panhc return; 747a4ecbb8aSIke Panhc } 748a4ecbb8aSIke Panhc 749a4ecbb8aSIke Panhc backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY); 750a4ecbb8aSIke Panhc } 751a4ecbb8aSIke Panhc 752a4ecbb8aSIke Panhc /* 753a4b5a279SIke Panhc * module init/exit 754a4b5a279SIke Panhc */ 75557ac3b05SIke Panhc static const struct acpi_device_id ideapad_device_ids[] = { 75657ac3b05SIke Panhc { "VPC2004", 0}, 75757ac3b05SIke Panhc { "", 0}, 75857ac3b05SIke Panhc }; 75957ac3b05SIke Panhc MODULE_DEVICE_TABLE(acpi, ideapad_device_ids); 76057ac3b05SIke Panhc 76107a4a4fcSMaxim Mikityanskiy static void ideapad_sync_touchpad_state(struct acpi_device *adevice) 76207a4a4fcSMaxim Mikityanskiy { 76307a4a4fcSMaxim Mikityanskiy struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); 76407a4a4fcSMaxim Mikityanskiy unsigned long value; 76507a4a4fcSMaxim Mikityanskiy 76607a4a4fcSMaxim Mikityanskiy /* Without reading from EC touchpad LED doesn't switch state */ 76707a4a4fcSMaxim Mikityanskiy if (!read_ec_data(adevice->handle, VPCCMD_R_TOUCHPAD, &value)) { 76807a4a4fcSMaxim Mikityanskiy /* Some IdeaPads don't really turn off touchpad - they only 76907a4a4fcSMaxim Mikityanskiy * switch the LED state. We (de)activate KBC AUX port to turn 77007a4a4fcSMaxim Mikityanskiy * touchpad off and on. We send KEY_TOUCHPAD_OFF and 77107a4a4fcSMaxim Mikityanskiy * KEY_TOUCHPAD_ON to not to get out of sync with LED */ 77207a4a4fcSMaxim Mikityanskiy unsigned char param; 77307a4a4fcSMaxim Mikityanskiy i8042_command(¶m, value ? I8042_CMD_AUX_ENABLE : 77407a4a4fcSMaxim Mikityanskiy I8042_CMD_AUX_DISABLE); 77507a4a4fcSMaxim Mikityanskiy ideapad_input_report(priv, value ? 67 : 66); 77607a4a4fcSMaxim Mikityanskiy } 77707a4a4fcSMaxim Mikityanskiy } 77807a4a4fcSMaxim Mikityanskiy 779a4b5a279SIke Panhc static int __devinit ideapad_acpi_add(struct acpi_device *adevice) 78057ac3b05SIke Panhc { 7813371f481SIke Panhc int ret, i; 78257f9616bSDan Carpenter int cfg; 78357ac3b05SIke Panhc struct ideapad_private *priv; 78457ac3b05SIke Panhc 78557f9616bSDan Carpenter if (read_method_int(adevice->handle, "_CFG", &cfg)) 78657ac3b05SIke Panhc return -ENODEV; 78757ac3b05SIke Panhc 78857ac3b05SIke Panhc priv = kzalloc(sizeof(*priv), GFP_KERNEL); 78957ac3b05SIke Panhc if (!priv) 79057ac3b05SIke Panhc return -ENOMEM; 791c9f718d0SIke Panhc dev_set_drvdata(&adevice->dev, priv); 792773e3206SIke Panhc ideapad_priv = priv; 793c1f73658SIke Panhc ideapad_handle = adevice->handle; 7943371f481SIke Panhc priv->cfg = cfg; 79598ee6919SIke Panhc 7968693ae84SIke Panhc ret = ideapad_platform_init(priv); 79798ee6919SIke Panhc if (ret) 79898ee6919SIke Panhc goto platform_failed; 79957ac3b05SIke Panhc 800773e3206SIke Panhc ret = ideapad_debugfs_init(priv); 801773e3206SIke Panhc if (ret) 802773e3206SIke Panhc goto debugfs_failed; 803773e3206SIke Panhc 8048693ae84SIke Panhc ret = ideapad_input_init(priv); 805f63409aeSIke Panhc if (ret) 806f63409aeSIke Panhc goto input_failed; 807f63409aeSIke Panhc 808c1f73658SIke Panhc for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) { 80957f9616bSDan Carpenter if (test_bit(ideapad_rfk_data[i].cfgbit, &priv->cfg)) 81057ac3b05SIke Panhc ideapad_register_rfkill(adevice, i); 811c1f73658SIke Panhc else 812c1f73658SIke Panhc priv->rfk[i] = NULL; 81357ac3b05SIke Panhc } 814923de84aSIke Panhc ideapad_sync_rfk_state(priv); 81507a4a4fcSMaxim Mikityanskiy ideapad_sync_touchpad_state(adevice); 816c9f718d0SIke Panhc 817a4ecbb8aSIke Panhc if (!acpi_video_backlight_support()) { 818a4ecbb8aSIke Panhc ret = ideapad_backlight_init(priv); 819a4ecbb8aSIke Panhc if (ret && ret != -ENODEV) 820a4ecbb8aSIke Panhc goto backlight_failed; 821a4ecbb8aSIke Panhc } 822a4ecbb8aSIke Panhc 82357ac3b05SIke Panhc return 0; 82498ee6919SIke Panhc 825a4ecbb8aSIke Panhc backlight_failed: 826a4ecbb8aSIke Panhc for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 827a4ecbb8aSIke Panhc ideapad_unregister_rfkill(adevice, i); 8287451a55aSAxel Lin ideapad_input_exit(priv); 829f63409aeSIke Panhc input_failed: 830773e3206SIke Panhc ideapad_debugfs_exit(priv); 831773e3206SIke Panhc debugfs_failed: 8328693ae84SIke Panhc ideapad_platform_exit(priv); 83398ee6919SIke Panhc platform_failed: 83498ee6919SIke Panhc kfree(priv); 83598ee6919SIke Panhc return ret; 83657ac3b05SIke Panhc } 83757ac3b05SIke Panhc 838a4b5a279SIke Panhc static int __devexit ideapad_acpi_remove(struct acpi_device *adevice, int type) 83957ac3b05SIke Panhc { 84057ac3b05SIke Panhc struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); 84157ac3b05SIke Panhc int i; 84257ac3b05SIke Panhc 843a4ecbb8aSIke Panhc ideapad_backlight_exit(priv); 844c1f73658SIke Panhc for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 84557ac3b05SIke Panhc ideapad_unregister_rfkill(adevice, i); 8468693ae84SIke Panhc ideapad_input_exit(priv); 847773e3206SIke Panhc ideapad_debugfs_exit(priv); 8488693ae84SIke Panhc ideapad_platform_exit(priv); 84957ac3b05SIke Panhc dev_set_drvdata(&adevice->dev, NULL); 85057ac3b05SIke Panhc kfree(priv); 851c9f718d0SIke Panhc 85257ac3b05SIke Panhc return 0; 85357ac3b05SIke Panhc } 85457ac3b05SIke Panhc 85557ac3b05SIke Panhc static void ideapad_acpi_notify(struct acpi_device *adevice, u32 event) 85657ac3b05SIke Panhc { 8578693ae84SIke Panhc struct ideapad_private *priv = dev_get_drvdata(&adevice->dev); 85857ac3b05SIke Panhc acpi_handle handle = adevice->handle; 85957ac3b05SIke Panhc unsigned long vpc1, vpc2, vpc_bit; 86057ac3b05SIke Panhc 8612be1dc21SIke Panhc if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1)) 86257ac3b05SIke Panhc return; 8632be1dc21SIke Panhc if (read_ec_data(handle, VPCCMD_R_VPC2, &vpc2)) 86457ac3b05SIke Panhc return; 86557ac3b05SIke Panhc 86657ac3b05SIke Panhc vpc1 = (vpc2 << 8) | vpc1; 86757ac3b05SIke Panhc for (vpc_bit = 0; vpc_bit < 16; vpc_bit++) { 86857ac3b05SIke Panhc if (test_bit(vpc_bit, &vpc1)) { 869a4ecbb8aSIke Panhc switch (vpc_bit) { 870a4ecbb8aSIke Panhc case 9: 871923de84aSIke Panhc ideapad_sync_rfk_state(priv); 872a4ecbb8aSIke Panhc break; 87320a769c1SIke Panhc case 13: 874296f9fe0SMaxim Mikityanskiy case 11: 875296f9fe0SMaxim Mikityanskiy case 7: 87620a769c1SIke Panhc case 6: 87720a769c1SIke Panhc ideapad_input_report(priv, vpc_bit); 87820a769c1SIke Panhc break; 87907a4a4fcSMaxim Mikityanskiy case 5: 88007a4a4fcSMaxim Mikityanskiy ideapad_sync_touchpad_state(adevice); 88107a4a4fcSMaxim Mikityanskiy break; 882a4ecbb8aSIke Panhc case 4: 883a4ecbb8aSIke Panhc ideapad_backlight_notify_brightness(priv); 884a4ecbb8aSIke Panhc break; 885f43d9ec0SIke Panhc case 3: 886f43d9ec0SIke Panhc ideapad_input_novokey(priv); 887f43d9ec0SIke Panhc break; 888a4ecbb8aSIke Panhc case 2: 889a4ecbb8aSIke Panhc ideapad_backlight_notify_power(priv); 890a4ecbb8aSIke Panhc break; 891296f9fe0SMaxim Mikityanskiy case 0: 892296f9fe0SMaxim Mikityanskiy ideapad_check_special_buttons(priv); 893296f9fe0SMaxim Mikityanskiy break; 894a4ecbb8aSIke Panhc default: 89520a769c1SIke Panhc pr_info("Unknown event: %lu\n", vpc_bit); 89657ac3b05SIke Panhc } 89757ac3b05SIke Panhc } 89857ac3b05SIke Panhc } 899a4ecbb8aSIke Panhc } 90057ac3b05SIke Panhc 90107a4a4fcSMaxim Mikityanskiy static int ideapad_acpi_resume(struct device *device) 90207a4a4fcSMaxim Mikityanskiy { 90307a4a4fcSMaxim Mikityanskiy ideapad_sync_rfk_state(ideapad_priv); 90407a4a4fcSMaxim Mikityanskiy ideapad_sync_touchpad_state(to_acpi_device(device)); 90507a4a4fcSMaxim Mikityanskiy return 0; 90607a4a4fcSMaxim Mikityanskiy } 90707a4a4fcSMaxim Mikityanskiy 90807a4a4fcSMaxim Mikityanskiy static SIMPLE_DEV_PM_OPS(ideapad_pm, NULL, ideapad_acpi_resume); 90907a4a4fcSMaxim Mikityanskiy 91057ac3b05SIke Panhc static struct acpi_driver ideapad_acpi_driver = { 91157ac3b05SIke Panhc .name = "ideapad_acpi", 91257ac3b05SIke Panhc .class = "IdeaPad", 91357ac3b05SIke Panhc .ids = ideapad_device_ids, 91457ac3b05SIke Panhc .ops.add = ideapad_acpi_add, 91557ac3b05SIke Panhc .ops.remove = ideapad_acpi_remove, 91657ac3b05SIke Panhc .ops.notify = ideapad_acpi_notify, 91707a4a4fcSMaxim Mikityanskiy .drv.pm = &ideapad_pm, 91857ac3b05SIke Panhc .owner = THIS_MODULE, 91957ac3b05SIke Panhc }; 92057ac3b05SIke Panhc 92157ac3b05SIke Panhc static int __init ideapad_acpi_module_init(void) 92257ac3b05SIke Panhc { 923a4b5a279SIke Panhc return acpi_bus_register_driver(&ideapad_acpi_driver); 92457ac3b05SIke Panhc } 92557ac3b05SIke Panhc 92657ac3b05SIke Panhc static void __exit ideapad_acpi_module_exit(void) 92757ac3b05SIke Panhc { 92857ac3b05SIke Panhc acpi_bus_unregister_driver(&ideapad_acpi_driver); 92957ac3b05SIke Panhc } 93057ac3b05SIke Panhc 93157ac3b05SIke Panhc MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); 93257ac3b05SIke Panhc MODULE_DESCRIPTION("IdeaPad ACPI Extras"); 93357ac3b05SIke Panhc MODULE_LICENSE("GPL"); 93457ac3b05SIke Panhc 93557ac3b05SIke Panhc module_init(ideapad_acpi_module_init); 93657ac3b05SIke Panhc module_exit(ideapad_acpi_module_exit); 937