116216333SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 257ac3b05SIke Panhc /* 3a4b5a279SIke Panhc * ideapad-laptop.c - Lenovo IdeaPad ACPI Extras 457ac3b05SIke Panhc * 557ac3b05SIke Panhc * Copyright © 2010 Intel Corporation 657ac3b05SIke Panhc * Copyright © 2010 David Woodhouse <dwmw2@infradead.org> 757ac3b05SIke Panhc */ 857ac3b05SIke Panhc 99ab23989SJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 109ab23989SJoe Perches 1157ac3b05SIke Panhc #include <linux/kernel.h> 1257ac3b05SIke Panhc #include <linux/module.h> 1357ac3b05SIke Panhc #include <linux/init.h> 1457ac3b05SIke Panhc #include <linux/types.h> 158b48463fSLv Zheng #include <linux/acpi.h> 1657ac3b05SIke Panhc #include <linux/rfkill.h> 1798ee6919SIke Panhc #include <linux/platform_device.h> 18eabe5339SJiaxun Yang #include <linux/platform_profile.h> 19f63409aeSIke Panhc #include <linux/input.h> 20f63409aeSIke Panhc #include <linux/input/sparse-keymap.h> 21a4ecbb8aSIke Panhc #include <linux/backlight.h> 22a4ecbb8aSIke Panhc #include <linux/fb.h> 23773e3206SIke Panhc #include <linux/debugfs.h> 24773e3206SIke Panhc #include <linux/seq_file.h> 2507a4a4fcSMaxim Mikityanskiy #include <linux/i8042.h> 2685093f79SHans de Goede #include <linux/dmi.h> 27b3facd7bSHimangi Saraogi #include <linux/device.h> 2826bff5f0SHans de Goede #include <acpi/video.h> 2957ac3b05SIke Panhc 30c1f73658SIke Panhc #define IDEAPAD_RFKILL_DEV_NUM (3) 3157ac3b05SIke Panhc 32ade50296SHao Wei Tee #define BM_CONSERVATION_BIT (5) 3340760717SOleg Keri #define HA_FNLOCK_BIT (10) 34ade50296SHao Wei Tee 353371f481SIke Panhc #define CFG_BT_BIT (16) 363371f481SIke Panhc #define CFG_3G_BIT (17) 373371f481SIke Panhc #define CFG_WIFI_BIT (18) 38a84511f7SIke Panhc #define CFG_CAMERA_BIT (19) 393371f481SIke Panhc 4074caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 412d98e0b9SArnd Bergmann static const char *const ideapad_wmi_fnesc_events[] = { 422d98e0b9SArnd Bergmann "26CAB2E5-5CF1-46AE-AAC3-4A12B6BA50E6", /* Yoga 3 */ 432d98e0b9SArnd Bergmann "56322276-8493-4CE8-A783-98C991274F5E", /* Yoga 700 */ 442d98e0b9SArnd Bergmann }; 4574caab99SArnd Bergmann #endif 4674caab99SArnd Bergmann 472be1dc21SIke Panhc enum { 48ade50296SHao Wei Tee BMCMD_CONSERVATION_ON = 3, 49ade50296SHao Wei Tee BMCMD_CONSERVATION_OFF = 5, 5040760717SOleg Keri HACMD_FNLOCK_ON = 0xe, 5140760717SOleg Keri HACMD_FNLOCK_OFF = 0xf, 52ade50296SHao Wei Tee }; 53ade50296SHao Wei Tee 54ade50296SHao Wei Tee enum { 552be1dc21SIke Panhc VPCCMD_R_VPC1 = 0x10, 562be1dc21SIke Panhc VPCCMD_R_BL_MAX, 572be1dc21SIke Panhc VPCCMD_R_BL, 582be1dc21SIke Panhc VPCCMD_W_BL, 592be1dc21SIke Panhc VPCCMD_R_WIFI, 602be1dc21SIke Panhc VPCCMD_W_WIFI, 612be1dc21SIke Panhc VPCCMD_R_BT, 622be1dc21SIke Panhc VPCCMD_W_BT, 632be1dc21SIke Panhc VPCCMD_R_BL_POWER, 642be1dc21SIke Panhc VPCCMD_R_NOVO, 652be1dc21SIke Panhc VPCCMD_R_VPC2, 662be1dc21SIke Panhc VPCCMD_R_TOUCHPAD, 672be1dc21SIke Panhc VPCCMD_W_TOUCHPAD, 682be1dc21SIke Panhc VPCCMD_R_CAMERA, 692be1dc21SIke Panhc VPCCMD_W_CAMERA, 702be1dc21SIke Panhc VPCCMD_R_3G, 712be1dc21SIke Panhc VPCCMD_W_3G, 722be1dc21SIke Panhc VPCCMD_R_ODD, /* 0x21 */ 730c7bbeb9SMaxim Mikityanskiy VPCCMD_W_FAN, 740c7bbeb9SMaxim Mikityanskiy VPCCMD_R_RF, 752be1dc21SIke Panhc VPCCMD_W_RF, 760c7bbeb9SMaxim Mikityanskiy VPCCMD_R_FAN = 0x2B, 77296f9fe0SMaxim Mikityanskiy VPCCMD_R_SPECIAL_BUTTONS = 0x31, 782be1dc21SIke Panhc VPCCMD_W_BL_POWER = 0x33, 792be1dc21SIke Panhc }; 802be1dc21SIke Panhc 81eabe5339SJiaxun Yang struct ideapad_dytc_priv { 82eabe5339SJiaxun Yang enum platform_profile_option current_profile; 83eabe5339SJiaxun Yang struct platform_profile_handler pprof; 84eabe5339SJiaxun Yang struct mutex mutex; 85eabe5339SJiaxun Yang struct ideapad_private *priv; 86eabe5339SJiaxun Yang }; 87eabe5339SJiaxun Yang 88331e0ea2SZhang Rui struct ideapad_rfk_priv { 89331e0ea2SZhang Rui int dev; 90331e0ea2SZhang Rui struct ideapad_private *priv; 91331e0ea2SZhang Rui }; 92331e0ea2SZhang Rui 9357ac3b05SIke Panhc struct ideapad_private { 94469f6434SZhang Rui struct acpi_device *adev; 95c1f73658SIke Panhc struct rfkill *rfk[IDEAPAD_RFKILL_DEV_NUM]; 96331e0ea2SZhang Rui struct ideapad_rfk_priv rfk_priv[IDEAPAD_RFKILL_DEV_NUM]; 9798ee6919SIke Panhc struct platform_device *platform_device; 98f63409aeSIke Panhc struct input_dev *inputdev; 99a4ecbb8aSIke Panhc struct backlight_device *blightdev; 100eabe5339SJiaxun Yang struct ideapad_dytc_priv *dytc; 101773e3206SIke Panhc struct dentry *debug; 1023371f481SIke Panhc unsigned long cfg; 103ce363c2bSHans de Goede bool has_hw_rfkill_switch; 104d69cd7eeSJiaxun Yang bool has_touchpad_switch; 1052d98e0b9SArnd Bergmann const char *fnesc_guid; 10657ac3b05SIke Panhc }; 10757ac3b05SIke Panhc 108bfa97b7dSIke Panhc static bool no_bt_rfkill; 109bfa97b7dSIke Panhc module_param(no_bt_rfkill, bool, 0444); 110bfa97b7dSIke Panhc MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth."); 111bfa97b7dSIke Panhc 11257ac3b05SIke Panhc /* 11357ac3b05SIke Panhc * ACPI Helpers 11457ac3b05SIke Panhc */ 115ed5b9ba7SAaron Ma #define IDEAPAD_EC_TIMEOUT (200) /* in ms */ 11657ac3b05SIke Panhc 11757ac3b05SIke Panhc static int read_method_int(acpi_handle handle, const char *method, int *val) 11857ac3b05SIke Panhc { 11957ac3b05SIke Panhc acpi_status status; 12057ac3b05SIke Panhc unsigned long long result; 12157ac3b05SIke Panhc 12257ac3b05SIke Panhc status = acpi_evaluate_integer(handle, (char *)method, NULL, &result); 12357ac3b05SIke Panhc if (ACPI_FAILURE(status)) { 12457ac3b05SIke Panhc *val = -1; 12557ac3b05SIke Panhc return -1; 126ba3a3387SJiaxun Yang } 12757ac3b05SIke Panhc *val = result; 12857ac3b05SIke Panhc return 0; 129ba3a3387SJiaxun Yang 13057ac3b05SIke Panhc } 13157ac3b05SIke Panhc 132ade50296SHao Wei Tee static int method_gbmd(acpi_handle handle, unsigned long *ret) 133ade50296SHao Wei Tee { 134ade50296SHao Wei Tee int result, val; 135ade50296SHao Wei Tee 136ade50296SHao Wei Tee result = read_method_int(handle, "GBMD", &val); 137ade50296SHao Wei Tee *ret = val; 138ade50296SHao Wei Tee return result; 139ade50296SHao Wei Tee } 140ade50296SHao Wei Tee 14140760717SOleg Keri static int method_int1(acpi_handle handle, char *method, int cmd) 142ade50296SHao Wei Tee { 143ade50296SHao Wei Tee acpi_status status; 144ade50296SHao Wei Tee 14540760717SOleg Keri status = acpi_execute_simple_method(handle, method, cmd); 146ade50296SHao Wei Tee return ACPI_FAILURE(status) ? -1 : 0; 147ade50296SHao Wei Tee } 148ade50296SHao Wei Tee 149eabe5339SJiaxun Yang static int method_dytc(acpi_handle handle, int cmd, int *ret) 150eabe5339SJiaxun Yang { 151eabe5339SJiaxun Yang acpi_status status; 152eabe5339SJiaxun Yang unsigned long long result; 153eabe5339SJiaxun Yang struct acpi_object_list params; 154eabe5339SJiaxun Yang union acpi_object in_obj; 155eabe5339SJiaxun Yang 156eabe5339SJiaxun Yang params.count = 1; 157eabe5339SJiaxun Yang params.pointer = &in_obj; 158eabe5339SJiaxun Yang in_obj.type = ACPI_TYPE_INTEGER; 159eabe5339SJiaxun Yang in_obj.integer.value = cmd; 160eabe5339SJiaxun Yang 161eabe5339SJiaxun Yang status = acpi_evaluate_integer(handle, "DYTC", ¶ms, &result); 162eabe5339SJiaxun Yang 163eabe5339SJiaxun Yang if (ACPI_FAILURE(status)) { 164eabe5339SJiaxun Yang *ret = -1; 165eabe5339SJiaxun Yang return -1; 166eabe5339SJiaxun Yang } 167eabe5339SJiaxun Yang *ret = result; 168eabe5339SJiaxun Yang return 0; 169eabe5339SJiaxun Yang } 170eabe5339SJiaxun Yang 17157ac3b05SIke Panhc static int method_vpcr(acpi_handle handle, int cmd, int *ret) 17257ac3b05SIke Panhc { 17357ac3b05SIke Panhc acpi_status status; 17457ac3b05SIke Panhc unsigned long long result; 17557ac3b05SIke Panhc struct acpi_object_list params; 17657ac3b05SIke Panhc union acpi_object in_obj; 17757ac3b05SIke Panhc 17857ac3b05SIke Panhc params.count = 1; 17957ac3b05SIke Panhc params.pointer = &in_obj; 18057ac3b05SIke Panhc in_obj.type = ACPI_TYPE_INTEGER; 18157ac3b05SIke Panhc in_obj.integer.value = cmd; 18257ac3b05SIke Panhc 18357ac3b05SIke Panhc status = acpi_evaluate_integer(handle, "VPCR", ¶ms, &result); 18457ac3b05SIke Panhc 18557ac3b05SIke Panhc if (ACPI_FAILURE(status)) { 18657ac3b05SIke Panhc *ret = -1; 18757ac3b05SIke Panhc return -1; 188ba3a3387SJiaxun Yang } 18957ac3b05SIke Panhc *ret = result; 19057ac3b05SIke Panhc return 0; 191ba3a3387SJiaxun Yang 19257ac3b05SIke Panhc } 19357ac3b05SIke Panhc 19457ac3b05SIke Panhc static int method_vpcw(acpi_handle handle, int cmd, int data) 19557ac3b05SIke Panhc { 19657ac3b05SIke Panhc struct acpi_object_list params; 19757ac3b05SIke Panhc union acpi_object in_obj[2]; 19857ac3b05SIke Panhc acpi_status status; 19957ac3b05SIke Panhc 20057ac3b05SIke Panhc params.count = 2; 20157ac3b05SIke Panhc params.pointer = in_obj; 20257ac3b05SIke Panhc in_obj[0].type = ACPI_TYPE_INTEGER; 20357ac3b05SIke Panhc in_obj[0].integer.value = cmd; 20457ac3b05SIke Panhc in_obj[1].type = ACPI_TYPE_INTEGER; 20557ac3b05SIke Panhc in_obj[1].integer.value = data; 20657ac3b05SIke Panhc 20757ac3b05SIke Panhc status = acpi_evaluate_object(handle, "VPCW", ¶ms, NULL); 20857ac3b05SIke Panhc if (status != AE_OK) 20957ac3b05SIke Panhc return -1; 21057ac3b05SIke Panhc return 0; 21157ac3b05SIke Panhc } 21257ac3b05SIke Panhc 21357ac3b05SIke Panhc static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data) 21457ac3b05SIke Panhc { 21557ac3b05SIke Panhc int val; 21657ac3b05SIke Panhc unsigned long int end_jiffies; 21757ac3b05SIke Panhc 21857ac3b05SIke Panhc if (method_vpcw(handle, 1, cmd)) 21957ac3b05SIke Panhc return -1; 22057ac3b05SIke Panhc 22157ac3b05SIke Panhc for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1; 22257ac3b05SIke Panhc time_before(jiffies, end_jiffies);) { 22357ac3b05SIke Panhc schedule(); 22457ac3b05SIke Panhc if (method_vpcr(handle, 1, &val)) 22557ac3b05SIke Panhc return -1; 22657ac3b05SIke Panhc if (val == 0) { 22757ac3b05SIke Panhc if (method_vpcr(handle, 0, &val)) 22857ac3b05SIke Panhc return -1; 22957ac3b05SIke Panhc *data = val; 23057ac3b05SIke Panhc return 0; 23157ac3b05SIke Panhc } 23257ac3b05SIke Panhc } 23331e56f23SZhang Xianwei pr_err("timeout in %s\n", __func__); 23457ac3b05SIke Panhc return -1; 23557ac3b05SIke Panhc } 23657ac3b05SIke Panhc 23757ac3b05SIke Panhc static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data) 23857ac3b05SIke Panhc { 23957ac3b05SIke Panhc int val; 24057ac3b05SIke Panhc unsigned long int end_jiffies; 24157ac3b05SIke Panhc 24257ac3b05SIke Panhc if (method_vpcw(handle, 0, data)) 24357ac3b05SIke Panhc return -1; 24457ac3b05SIke Panhc if (method_vpcw(handle, 1, cmd)) 24557ac3b05SIke Panhc return -1; 24657ac3b05SIke Panhc 24757ac3b05SIke Panhc for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1; 24857ac3b05SIke Panhc time_before(jiffies, end_jiffies);) { 24957ac3b05SIke Panhc schedule(); 25057ac3b05SIke Panhc if (method_vpcr(handle, 1, &val)) 25157ac3b05SIke Panhc return -1; 25257ac3b05SIke Panhc if (val == 0) 25357ac3b05SIke Panhc return 0; 25457ac3b05SIke Panhc } 255f1395edbSJiaxun Yang pr_err("timeout in %s\n", __func__); 25657ac3b05SIke Panhc return -1; 25757ac3b05SIke Panhc } 25857ac3b05SIke Panhc 259a4b5a279SIke Panhc /* 260773e3206SIke Panhc * debugfs 261773e3206SIke Panhc */ 262773e3206SIke Panhc static int debugfs_status_show(struct seq_file *s, void *data) 263773e3206SIke Panhc { 264331e0ea2SZhang Rui struct ideapad_private *priv = s->private; 265773e3206SIke Panhc unsigned long value; 266773e3206SIke Panhc 267331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &value)) 268773e3206SIke Panhc seq_printf(s, "Backlight max:\t%lu\n", value); 269331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL, &value)) 270773e3206SIke Panhc seq_printf(s, "Backlight now:\t%lu\n", value); 271331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &value)) 272773e3206SIke Panhc seq_printf(s, "BL power value:\t%s\n", value ? "On" : "Off"); 273773e3206SIke Panhc seq_printf(s, "=====================\n"); 274773e3206SIke Panhc 275331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_RF, &value)) 276773e3206SIke Panhc seq_printf(s, "Radio status:\t%s(%lu)\n", 277773e3206SIke Panhc value ? "On" : "Off", value); 278331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_WIFI, &value)) 279773e3206SIke Panhc seq_printf(s, "Wifi status:\t%s(%lu)\n", 280773e3206SIke Panhc value ? "On" : "Off", value); 281331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_BT, &value)) 282773e3206SIke Panhc seq_printf(s, "BT status:\t%s(%lu)\n", 283773e3206SIke Panhc value ? "On" : "Off", value); 284331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_3G, &value)) 285773e3206SIke Panhc seq_printf(s, "3G status:\t%s(%lu)\n", 286773e3206SIke Panhc value ? "On" : "Off", value); 287773e3206SIke Panhc seq_printf(s, "=====================\n"); 288773e3206SIke Panhc 289331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) 290773e3206SIke Panhc seq_printf(s, "Touchpad status:%s(%lu)\n", 291773e3206SIke Panhc value ? "On" : "Off", value); 292331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &value)) 293773e3206SIke Panhc seq_printf(s, "Camera status:\t%s(%lu)\n", 294773e3206SIke Panhc value ? "On" : "Off", value); 295ade50296SHao Wei Tee seq_puts(s, "=====================\n"); 296ade50296SHao Wei Tee 297ade50296SHao Wei Tee if (!method_gbmd(priv->adev->handle, &value)) { 298ade50296SHao Wei Tee seq_printf(s, "Conservation mode:\t%s(%lu)\n", 299ade50296SHao Wei Tee test_bit(BM_CONSERVATION_BIT, &value) ? "On" : "Off", 300ade50296SHao Wei Tee value); 301ade50296SHao Wei Tee } 302773e3206SIke Panhc 303773e3206SIke Panhc return 0; 304773e3206SIke Panhc } 305334c4efdSAndy Shevchenko DEFINE_SHOW_ATTRIBUTE(debugfs_status); 306773e3206SIke Panhc 307773e3206SIke Panhc static int debugfs_cfg_show(struct seq_file *s, void *data) 308773e3206SIke Panhc { 309331e0ea2SZhang Rui struct ideapad_private *priv = s->private; 310331e0ea2SZhang Rui 311773e3206SIke Panhc seq_printf(s, "cfg: 0x%.8lX\n\nCapability: ", 312331e0ea2SZhang Rui priv->cfg); 313331e0ea2SZhang Rui if (test_bit(CFG_BT_BIT, &priv->cfg)) 314773e3206SIke Panhc seq_printf(s, "Bluetooth "); 315331e0ea2SZhang Rui if (test_bit(CFG_3G_BIT, &priv->cfg)) 316773e3206SIke Panhc seq_printf(s, "3G "); 317331e0ea2SZhang Rui if (test_bit(CFG_WIFI_BIT, &priv->cfg)) 318773e3206SIke Panhc seq_printf(s, "Wireless "); 319331e0ea2SZhang Rui if (test_bit(CFG_CAMERA_BIT, &priv->cfg)) 320773e3206SIke Panhc seq_printf(s, "Camera "); 321773e3206SIke Panhc seq_printf(s, "\nGraphic: "); 322331e0ea2SZhang Rui switch ((priv->cfg)&0x700) { 323773e3206SIke Panhc case 0x100: 324773e3206SIke Panhc seq_printf(s, "Intel"); 325773e3206SIke Panhc break; 326773e3206SIke Panhc case 0x200: 327773e3206SIke Panhc seq_printf(s, "ATI"); 328773e3206SIke Panhc break; 329773e3206SIke Panhc case 0x300: 330773e3206SIke Panhc seq_printf(s, "Nvidia"); 331773e3206SIke Panhc break; 332773e3206SIke Panhc case 0x400: 333773e3206SIke Panhc seq_printf(s, "Intel and ATI"); 334773e3206SIke Panhc break; 335773e3206SIke Panhc case 0x500: 336773e3206SIke Panhc seq_printf(s, "Intel and Nvidia"); 337773e3206SIke Panhc break; 338773e3206SIke Panhc } 339773e3206SIke Panhc seq_printf(s, "\n"); 340e1a39a44SBarnabás Pőcze 341773e3206SIke Panhc return 0; 342773e3206SIke Panhc } 343334c4efdSAndy Shevchenko DEFINE_SHOW_ATTRIBUTE(debugfs_cfg); 344773e3206SIke Panhc 34517f1bf38SGreg Kroah-Hartman static void ideapad_debugfs_init(struct ideapad_private *priv) 346773e3206SIke Panhc { 34717f1bf38SGreg Kroah-Hartman struct dentry *dir; 348773e3206SIke Panhc 34917f1bf38SGreg Kroah-Hartman dir = debugfs_create_dir("ideapad", NULL); 35017f1bf38SGreg Kroah-Hartman priv->debug = dir; 351773e3206SIke Panhc 35217f1bf38SGreg Kroah-Hartman debugfs_create_file("cfg", S_IRUGO, dir, priv, &debugfs_cfg_fops); 35317f1bf38SGreg Kroah-Hartman debugfs_create_file("status", S_IRUGO, dir, priv, &debugfs_status_fops); 354773e3206SIke Panhc } 355773e3206SIke Panhc 356773e3206SIke Panhc static void ideapad_debugfs_exit(struct ideapad_private *priv) 357773e3206SIke Panhc { 358773e3206SIke Panhc debugfs_remove_recursive(priv->debug); 359773e3206SIke Panhc priv->debug = NULL; 360773e3206SIke Panhc } 361773e3206SIke Panhc 362773e3206SIke Panhc /* 3633371f481SIke Panhc * sysfs 364a4b5a279SIke Panhc */ 36557ac3b05SIke Panhc static ssize_t show_ideapad_cam(struct device *dev, 36657ac3b05SIke Panhc struct device_attribute *attr, 36757ac3b05SIke Panhc char *buf) 36857ac3b05SIke Panhc { 36957ac3b05SIke Panhc unsigned long result; 370331e0ea2SZhang Rui struct ideapad_private *priv = dev_get_drvdata(dev); 37157ac3b05SIke Panhc 372331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &result)) 37357ac3b05SIke Panhc return sprintf(buf, "-1\n"); 37457ac3b05SIke Panhc return sprintf(buf, "%lu\n", result); 37557ac3b05SIke Panhc } 37657ac3b05SIke Panhc 37757ac3b05SIke Panhc static ssize_t store_ideapad_cam(struct device *dev, 37857ac3b05SIke Panhc struct device_attribute *attr, 37957ac3b05SIke Panhc const char *buf, size_t count) 38057ac3b05SIke Panhc { 38157ac3b05SIke Panhc int ret, state; 382331e0ea2SZhang Rui struct ideapad_private *priv = dev_get_drvdata(dev); 38357ac3b05SIke Panhc 38457ac3b05SIke Panhc if (!count) 38557ac3b05SIke Panhc return 0; 38657ac3b05SIke Panhc if (sscanf(buf, "%i", &state) != 1) 38757ac3b05SIke Panhc return -EINVAL; 388331e0ea2SZhang Rui ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_CAMERA, state); 38957ac3b05SIke Panhc if (ret < 0) 3900c7bbeb9SMaxim Mikityanskiy return -EIO; 39157ac3b05SIke Panhc return count; 39257ac3b05SIke Panhc } 39357ac3b05SIke Panhc 39457ac3b05SIke Panhc static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam); 39557ac3b05SIke Panhc 3960c7bbeb9SMaxim Mikityanskiy static ssize_t show_ideapad_fan(struct device *dev, 3970c7bbeb9SMaxim Mikityanskiy struct device_attribute *attr, 3980c7bbeb9SMaxim Mikityanskiy char *buf) 3990c7bbeb9SMaxim Mikityanskiy { 4000c7bbeb9SMaxim Mikityanskiy unsigned long result; 401331e0ea2SZhang Rui struct ideapad_private *priv = dev_get_drvdata(dev); 4020c7bbeb9SMaxim Mikityanskiy 403331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result)) 4040c7bbeb9SMaxim Mikityanskiy return sprintf(buf, "-1\n"); 4050c7bbeb9SMaxim Mikityanskiy return sprintf(buf, "%lu\n", result); 4060c7bbeb9SMaxim Mikityanskiy } 4070c7bbeb9SMaxim Mikityanskiy 4080c7bbeb9SMaxim Mikityanskiy static ssize_t store_ideapad_fan(struct device *dev, 4090c7bbeb9SMaxim Mikityanskiy struct device_attribute *attr, 4100c7bbeb9SMaxim Mikityanskiy const char *buf, size_t count) 4110c7bbeb9SMaxim Mikityanskiy { 4120c7bbeb9SMaxim Mikityanskiy int ret, state; 413331e0ea2SZhang Rui struct ideapad_private *priv = dev_get_drvdata(dev); 4140c7bbeb9SMaxim Mikityanskiy 4150c7bbeb9SMaxim Mikityanskiy if (!count) 4160c7bbeb9SMaxim Mikityanskiy return 0; 4170c7bbeb9SMaxim Mikityanskiy if (sscanf(buf, "%i", &state) != 1) 4180c7bbeb9SMaxim Mikityanskiy return -EINVAL; 4190c7bbeb9SMaxim Mikityanskiy if (state < 0 || state > 4 || state == 3) 4200c7bbeb9SMaxim Mikityanskiy return -EINVAL; 421331e0ea2SZhang Rui ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_FAN, state); 4220c7bbeb9SMaxim Mikityanskiy if (ret < 0) 4230c7bbeb9SMaxim Mikityanskiy return -EIO; 4240c7bbeb9SMaxim Mikityanskiy return count; 4250c7bbeb9SMaxim Mikityanskiy } 4260c7bbeb9SMaxim Mikityanskiy 4270c7bbeb9SMaxim Mikityanskiy static DEVICE_ATTR(fan_mode, 0644, show_ideapad_fan, store_ideapad_fan); 4280c7bbeb9SMaxim Mikityanskiy 42936ac0d43SRitesh Raj Sarraf static ssize_t touchpad_show(struct device *dev, 43036ac0d43SRitesh Raj Sarraf struct device_attribute *attr, 43136ac0d43SRitesh Raj Sarraf char *buf) 43236ac0d43SRitesh Raj Sarraf { 43336ac0d43SRitesh Raj Sarraf struct ideapad_private *priv = dev_get_drvdata(dev); 43436ac0d43SRitesh Raj Sarraf unsigned long result; 43536ac0d43SRitesh Raj Sarraf 43636ac0d43SRitesh Raj Sarraf if (read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &result)) 43736ac0d43SRitesh Raj Sarraf return sprintf(buf, "-1\n"); 43836ac0d43SRitesh Raj Sarraf return sprintf(buf, "%lu\n", result); 43936ac0d43SRitesh Raj Sarraf } 44036ac0d43SRitesh Raj Sarraf 44146936fd6SArnd Bergmann /* Switch to RO for now: It might be revisited in the future */ 44246936fd6SArnd Bergmann static ssize_t __maybe_unused touchpad_store(struct device *dev, 44336ac0d43SRitesh Raj Sarraf struct device_attribute *attr, 44436ac0d43SRitesh Raj Sarraf const char *buf, size_t count) 44536ac0d43SRitesh Raj Sarraf { 44636ac0d43SRitesh Raj Sarraf struct ideapad_private *priv = dev_get_drvdata(dev); 44736ac0d43SRitesh Raj Sarraf bool state; 44836ac0d43SRitesh Raj Sarraf int ret; 44936ac0d43SRitesh Raj Sarraf 45036ac0d43SRitesh Raj Sarraf ret = kstrtobool(buf, &state); 45136ac0d43SRitesh Raj Sarraf if (ret) 45236ac0d43SRitesh Raj Sarraf return ret; 45336ac0d43SRitesh Raj Sarraf 45436ac0d43SRitesh Raj Sarraf ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, state); 45536ac0d43SRitesh Raj Sarraf if (ret < 0) 45636ac0d43SRitesh Raj Sarraf return -EIO; 45736ac0d43SRitesh Raj Sarraf return count; 45836ac0d43SRitesh Raj Sarraf } 45936ac0d43SRitesh Raj Sarraf 4607f363145SAndy Shevchenko static DEVICE_ATTR_RO(touchpad); 46136ac0d43SRitesh Raj Sarraf 462ade50296SHao Wei Tee static ssize_t conservation_mode_show(struct device *dev, 463ade50296SHao Wei Tee struct device_attribute *attr, 464ade50296SHao Wei Tee char *buf) 465ade50296SHao Wei Tee { 466ade50296SHao Wei Tee struct ideapad_private *priv = dev_get_drvdata(dev); 467ade50296SHao Wei Tee unsigned long result; 468ade50296SHao Wei Tee 469ade50296SHao Wei Tee if (method_gbmd(priv->adev->handle, &result)) 470ade50296SHao Wei Tee return sprintf(buf, "-1\n"); 471ade50296SHao Wei Tee return sprintf(buf, "%u\n", test_bit(BM_CONSERVATION_BIT, &result)); 472ade50296SHao Wei Tee } 473ade50296SHao Wei Tee 474ade50296SHao Wei Tee static ssize_t conservation_mode_store(struct device *dev, 475ade50296SHao Wei Tee struct device_attribute *attr, 476ade50296SHao Wei Tee const char *buf, size_t count) 477ade50296SHao Wei Tee { 478ade50296SHao Wei Tee struct ideapad_private *priv = dev_get_drvdata(dev); 479ade50296SHao Wei Tee bool state; 480ade50296SHao Wei Tee int ret; 481ade50296SHao Wei Tee 482ade50296SHao Wei Tee ret = kstrtobool(buf, &state); 483ade50296SHao Wei Tee if (ret) 484ade50296SHao Wei Tee return ret; 485ade50296SHao Wei Tee 48640760717SOleg Keri ret = method_int1(priv->adev->handle, "SBMC", state ? 487ade50296SHao Wei Tee BMCMD_CONSERVATION_ON : 488ade50296SHao Wei Tee BMCMD_CONSERVATION_OFF); 489ade50296SHao Wei Tee if (ret < 0) 490ade50296SHao Wei Tee return -EIO; 491ade50296SHao Wei Tee return count; 492ade50296SHao Wei Tee } 493ade50296SHao Wei Tee 494ade50296SHao Wei Tee static DEVICE_ATTR_RW(conservation_mode); 495ade50296SHao Wei Tee 49640760717SOleg Keri static ssize_t fn_lock_show(struct device *dev, 49740760717SOleg Keri struct device_attribute *attr, 49840760717SOleg Keri char *buf) 49940760717SOleg Keri { 50040760717SOleg Keri struct ideapad_private *priv = dev_get_drvdata(dev); 50140760717SOleg Keri unsigned long result; 50240760717SOleg Keri int hals; 50340760717SOleg Keri int fail = read_method_int(priv->adev->handle, "HALS", &hals); 50440760717SOleg Keri 50540760717SOleg Keri if (fail) 50640760717SOleg Keri return sprintf(buf, "-1\n"); 50740760717SOleg Keri 50840760717SOleg Keri result = hals; 50940760717SOleg Keri return sprintf(buf, "%u\n", test_bit(HA_FNLOCK_BIT, &result)); 51040760717SOleg Keri } 51140760717SOleg Keri 51240760717SOleg Keri static ssize_t fn_lock_store(struct device *dev, 51340760717SOleg Keri struct device_attribute *attr, 51440760717SOleg Keri const char *buf, size_t count) 51540760717SOleg Keri { 51640760717SOleg Keri struct ideapad_private *priv = dev_get_drvdata(dev); 51740760717SOleg Keri bool state; 51840760717SOleg Keri int ret; 51940760717SOleg Keri 52040760717SOleg Keri ret = kstrtobool(buf, &state); 52140760717SOleg Keri if (ret) 52240760717SOleg Keri return ret; 52340760717SOleg Keri 52440760717SOleg Keri ret = method_int1(priv->adev->handle, "SALS", state ? 52540760717SOleg Keri HACMD_FNLOCK_ON : 52640760717SOleg Keri HACMD_FNLOCK_OFF); 52740760717SOleg Keri if (ret < 0) 52840760717SOleg Keri return -EIO; 52940760717SOleg Keri return count; 53040760717SOleg Keri } 53140760717SOleg Keri 53240760717SOleg Keri static DEVICE_ATTR_RW(fn_lock); 53340760717SOleg Keri 53440760717SOleg Keri 5353371f481SIke Panhc static struct attribute *ideapad_attributes[] = { 5363371f481SIke Panhc &dev_attr_camera_power.attr, 5370c7bbeb9SMaxim Mikityanskiy &dev_attr_fan_mode.attr, 53836ac0d43SRitesh Raj Sarraf &dev_attr_touchpad.attr, 539ade50296SHao Wei Tee &dev_attr_conservation_mode.attr, 54040760717SOleg Keri &dev_attr_fn_lock.attr, 5413371f481SIke Panhc NULL 5423371f481SIke Panhc }; 5433371f481SIke Panhc 544587a1f16SAl Viro static umode_t ideapad_is_visible(struct kobject *kobj, 545a84511f7SIke Panhc struct attribute *attr, 546a84511f7SIke Panhc int idx) 547a84511f7SIke Panhc { 548a84511f7SIke Panhc struct device *dev = container_of(kobj, struct device, kobj); 549a84511f7SIke Panhc struct ideapad_private *priv = dev_get_drvdata(dev); 550a84511f7SIke Panhc bool supported; 551a84511f7SIke Panhc 552a84511f7SIke Panhc if (attr == &dev_attr_camera_power.attr) 553a84511f7SIke Panhc supported = test_bit(CFG_CAMERA_BIT, &(priv->cfg)); 5540c7bbeb9SMaxim Mikityanskiy else if (attr == &dev_attr_fan_mode.attr) { 5550c7bbeb9SMaxim Mikityanskiy unsigned long value; 556331e0ea2SZhang Rui supported = !read_ec_data(priv->adev->handle, VPCCMD_R_FAN, 557331e0ea2SZhang Rui &value); 558ade50296SHao Wei Tee } else if (attr == &dev_attr_conservation_mode.attr) { 559ade50296SHao Wei Tee supported = acpi_has_method(priv->adev->handle, "GBMD") && 560ade50296SHao Wei Tee acpi_has_method(priv->adev->handle, "SBMC"); 56140760717SOleg Keri } else if (attr == &dev_attr_fn_lock.attr) { 56240760717SOleg Keri supported = acpi_has_method(priv->adev->handle, "HALS") && 56340760717SOleg Keri acpi_has_method(priv->adev->handle, "SALS"); 564d69cd7eeSJiaxun Yang } else if (attr == &dev_attr_touchpad.attr) 565d69cd7eeSJiaxun Yang supported = priv->has_touchpad_switch; 566d69cd7eeSJiaxun Yang else 567a84511f7SIke Panhc supported = true; 568a84511f7SIke Panhc 569a84511f7SIke Panhc return supported ? attr->mode : 0; 570a84511f7SIke Panhc } 571a84511f7SIke Panhc 57249458e83SMathias Krause static const struct attribute_group ideapad_attribute_group = { 573a84511f7SIke Panhc .is_visible = ideapad_is_visible, 5743371f481SIke Panhc .attrs = ideapad_attributes 5753371f481SIke Panhc }; 5763371f481SIke Panhc 577a4b5a279SIke Panhc /* 578eabe5339SJiaxun Yang * DYTC Platform profile 579eabe5339SJiaxun Yang */ 580eabe5339SJiaxun Yang #define DYTC_CMD_QUERY 0 /* To get DYTC status - enable/revision */ 581eabe5339SJiaxun Yang #define DYTC_CMD_SET 1 /* To enable/disable IC function mode */ 582eabe5339SJiaxun Yang #define DYTC_CMD_GET 2 /* To get current IC function and mode */ 583eabe5339SJiaxun Yang #define DYTC_CMD_RESET 0x1ff /* To reset back to default */ 584eabe5339SJiaxun Yang 585eabe5339SJiaxun Yang #define DYTC_QUERY_ENABLE_BIT 8 /* Bit 8 - 0 = disabled, 1 = enabled */ 586eabe5339SJiaxun Yang #define DYTC_QUERY_SUBREV_BIT 16 /* Bits 16 - 27 - sub revision */ 587eabe5339SJiaxun Yang #define DYTC_QUERY_REV_BIT 28 /* Bits 28 - 31 - revision */ 588eabe5339SJiaxun Yang 589eabe5339SJiaxun Yang #define DYTC_GET_FUNCTION_BIT 8 /* Bits 8-11 - function setting */ 590eabe5339SJiaxun Yang #define DYTC_GET_MODE_BIT 12 /* Bits 12-15 - mode setting */ 591eabe5339SJiaxun Yang 592eabe5339SJiaxun Yang #define DYTC_SET_FUNCTION_BIT 12 /* Bits 12-15 - function setting */ 593eabe5339SJiaxun Yang #define DYTC_SET_MODE_BIT 16 /* Bits 16-19 - mode setting */ 594eabe5339SJiaxun Yang #define DYTC_SET_VALID_BIT 20 /* Bit 20 - 1 = on, 0 = off */ 595eabe5339SJiaxun Yang 596eabe5339SJiaxun Yang #define DYTC_FUNCTION_STD 0 /* Function = 0, standard mode */ 597eabe5339SJiaxun Yang #define DYTC_FUNCTION_CQL 1 /* Function = 1, lap mode */ 598eabe5339SJiaxun Yang #define DYTC_FUNCTION_MMC 11 /* Function = 11, desk mode */ 599eabe5339SJiaxun Yang 600eabe5339SJiaxun Yang #define DYTC_MODE_PERFORM 2 /* High power mode aka performance */ 601eabe5339SJiaxun Yang #define DYTC_MODE_LOW_POWER 3 /* Low power mode aka quiet */ 602eabe5339SJiaxun Yang #define DYTC_MODE_BALANCE 0xF /* Default mode aka balanced */ 603eabe5339SJiaxun Yang 604eabe5339SJiaxun Yang #define DYTC_SET_COMMAND(function, mode, on) \ 605eabe5339SJiaxun Yang (DYTC_CMD_SET | (function) << DYTC_SET_FUNCTION_BIT | \ 606eabe5339SJiaxun Yang (mode) << DYTC_SET_MODE_BIT | \ 607eabe5339SJiaxun Yang (on) << DYTC_SET_VALID_BIT) 608eabe5339SJiaxun Yang 609eabe5339SJiaxun Yang #define DYTC_DISABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 0) 610eabe5339SJiaxun Yang 611eabe5339SJiaxun Yang #define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 1) 612eabe5339SJiaxun Yang 613eabe5339SJiaxun Yang static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *profile) 614eabe5339SJiaxun Yang { 615eabe5339SJiaxun Yang switch (dytcmode) { 616eabe5339SJiaxun Yang case DYTC_MODE_LOW_POWER: 617eabe5339SJiaxun Yang *profile = PLATFORM_PROFILE_LOW_POWER; 618eabe5339SJiaxun Yang break; 619eabe5339SJiaxun Yang case DYTC_MODE_BALANCE: 620eabe5339SJiaxun Yang *profile = PLATFORM_PROFILE_BALANCED; 621eabe5339SJiaxun Yang break; 622eabe5339SJiaxun Yang case DYTC_MODE_PERFORM: 623eabe5339SJiaxun Yang *profile = PLATFORM_PROFILE_PERFORMANCE; 624eabe5339SJiaxun Yang break; 625eabe5339SJiaxun Yang default: /* Unknown mode */ 626eabe5339SJiaxun Yang return -EINVAL; 627eabe5339SJiaxun Yang } 628eabe5339SJiaxun Yang return 0; 629eabe5339SJiaxun Yang } 630eabe5339SJiaxun Yang 631eabe5339SJiaxun Yang static int convert_profile_to_dytc(enum platform_profile_option profile, int *perfmode) 632eabe5339SJiaxun Yang { 633eabe5339SJiaxun Yang switch (profile) { 634eabe5339SJiaxun Yang case PLATFORM_PROFILE_LOW_POWER: 635eabe5339SJiaxun Yang *perfmode = DYTC_MODE_LOW_POWER; 636eabe5339SJiaxun Yang break; 637eabe5339SJiaxun Yang case PLATFORM_PROFILE_BALANCED: 638eabe5339SJiaxun Yang *perfmode = DYTC_MODE_BALANCE; 639eabe5339SJiaxun Yang break; 640eabe5339SJiaxun Yang case PLATFORM_PROFILE_PERFORMANCE: 641eabe5339SJiaxun Yang *perfmode = DYTC_MODE_PERFORM; 642eabe5339SJiaxun Yang break; 643eabe5339SJiaxun Yang default: /* Unknown profile */ 644eabe5339SJiaxun Yang return -EOPNOTSUPP; 645eabe5339SJiaxun Yang } 646eabe5339SJiaxun Yang return 0; 647eabe5339SJiaxun Yang } 648eabe5339SJiaxun Yang 649eabe5339SJiaxun Yang /* 650eabe5339SJiaxun Yang * dytc_profile_get: Function to register with platform_profile 651eabe5339SJiaxun Yang * handler. Returns current platform profile. 652eabe5339SJiaxun Yang */ 653eabe5339SJiaxun Yang int dytc_profile_get(struct platform_profile_handler *pprof, 654eabe5339SJiaxun Yang enum platform_profile_option *profile) 655eabe5339SJiaxun Yang { 656eabe5339SJiaxun Yang struct ideapad_dytc_priv *dytc; 657eabe5339SJiaxun Yang 658eabe5339SJiaxun Yang dytc = container_of(pprof, struct ideapad_dytc_priv, pprof); 659eabe5339SJiaxun Yang *profile = dytc->current_profile; 660eabe5339SJiaxun Yang return 0; 661eabe5339SJiaxun Yang } 662eabe5339SJiaxun Yang 663eabe5339SJiaxun Yang /* 664eabe5339SJiaxun Yang * Helper function - check if we are in CQL mode and if we are 665eabe5339SJiaxun Yang * - disable CQL, 666eabe5339SJiaxun Yang * - run the command 667eabe5339SJiaxun Yang * - enable CQL 668eabe5339SJiaxun Yang * If not in CQL mode, just run the command 669eabe5339SJiaxun Yang */ 670eabe5339SJiaxun Yang int dytc_cql_command(struct ideapad_private *priv, int command, int *output) 671eabe5339SJiaxun Yang { 672eabe5339SJiaxun Yang int err, cmd_err, dummy; 673eabe5339SJiaxun Yang int cur_funcmode; 674eabe5339SJiaxun Yang 675eabe5339SJiaxun Yang /* Determine if we are in CQL mode. This alters the commands we do */ 676eabe5339SJiaxun Yang err = method_dytc(priv->adev->handle, DYTC_CMD_GET, output); 677eabe5339SJiaxun Yang if (err) 678eabe5339SJiaxun Yang return err; 679eabe5339SJiaxun Yang 680eabe5339SJiaxun Yang cur_funcmode = (*output >> DYTC_GET_FUNCTION_BIT) & 0xF; 681eabe5339SJiaxun Yang /* Check if we're OK to return immediately */ 682eabe5339SJiaxun Yang if ((command == DYTC_CMD_GET) && (cur_funcmode != DYTC_FUNCTION_CQL)) 683eabe5339SJiaxun Yang return 0; 684eabe5339SJiaxun Yang 685eabe5339SJiaxun Yang if (cur_funcmode == DYTC_FUNCTION_CQL) { 686eabe5339SJiaxun Yang err = method_dytc(priv->adev->handle, DYTC_DISABLE_CQL, &dummy); 687eabe5339SJiaxun Yang if (err) 688eabe5339SJiaxun Yang return err; 689eabe5339SJiaxun Yang } 690eabe5339SJiaxun Yang 691eabe5339SJiaxun Yang cmd_err = method_dytc(priv->adev->handle, command, output); 692eabe5339SJiaxun Yang /* Check return condition after we've restored CQL state */ 693eabe5339SJiaxun Yang 694eabe5339SJiaxun Yang if (cur_funcmode == DYTC_FUNCTION_CQL) { 695eabe5339SJiaxun Yang err = method_dytc(priv->adev->handle, DYTC_ENABLE_CQL, &dummy); 696eabe5339SJiaxun Yang if (err) 697eabe5339SJiaxun Yang return err; 698eabe5339SJiaxun Yang } 699eabe5339SJiaxun Yang 700eabe5339SJiaxun Yang return cmd_err; 701eabe5339SJiaxun Yang } 702eabe5339SJiaxun Yang 703eabe5339SJiaxun Yang /* 704eabe5339SJiaxun Yang * dytc_profile_set: Function to register with platform_profile 705eabe5339SJiaxun Yang * handler. Sets current platform profile. 706eabe5339SJiaxun Yang */ 707eabe5339SJiaxun Yang int dytc_profile_set(struct platform_profile_handler *pprof, 708eabe5339SJiaxun Yang enum platform_profile_option profile) 709eabe5339SJiaxun Yang { 710eabe5339SJiaxun Yang struct ideapad_dytc_priv *dytc; 711eabe5339SJiaxun Yang struct ideapad_private *priv; 712eabe5339SJiaxun Yang int output; 713eabe5339SJiaxun Yang int err; 714eabe5339SJiaxun Yang 715eabe5339SJiaxun Yang dytc = container_of(pprof, struct ideapad_dytc_priv, pprof); 716eabe5339SJiaxun Yang priv = dytc->priv; 717eabe5339SJiaxun Yang 718eabe5339SJiaxun Yang err = mutex_lock_interruptible(&dytc->mutex); 719eabe5339SJiaxun Yang if (err) 720eabe5339SJiaxun Yang return err; 721eabe5339SJiaxun Yang 722eabe5339SJiaxun Yang if (profile == PLATFORM_PROFILE_BALANCED) { 723eabe5339SJiaxun Yang /* To get back to balanced mode we just issue a reset command */ 724eabe5339SJiaxun Yang err = method_dytc(priv->adev->handle, DYTC_CMD_RESET, &output); 725eabe5339SJiaxun Yang if (err) 726eabe5339SJiaxun Yang goto unlock; 727eabe5339SJiaxun Yang } else { 728eabe5339SJiaxun Yang int perfmode; 729eabe5339SJiaxun Yang 730eabe5339SJiaxun Yang err = convert_profile_to_dytc(profile, &perfmode); 731eabe5339SJiaxun Yang if (err) 732eabe5339SJiaxun Yang goto unlock; 733eabe5339SJiaxun Yang 734eabe5339SJiaxun Yang /* Determine if we are in CQL mode. This alters the commands we do */ 735eabe5339SJiaxun Yang err = dytc_cql_command(priv, 736eabe5339SJiaxun Yang DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), 737eabe5339SJiaxun Yang &output); 738eabe5339SJiaxun Yang if (err) 739eabe5339SJiaxun Yang goto unlock; 740eabe5339SJiaxun Yang } 741eabe5339SJiaxun Yang /* Success - update current profile */ 742eabe5339SJiaxun Yang dytc->current_profile = profile; 743eabe5339SJiaxun Yang unlock: 744eabe5339SJiaxun Yang mutex_unlock(&dytc->mutex); 745eabe5339SJiaxun Yang return err; 746eabe5339SJiaxun Yang } 747eabe5339SJiaxun Yang 748eabe5339SJiaxun Yang static void dytc_profile_refresh(struct ideapad_private *priv) 749eabe5339SJiaxun Yang { 750eabe5339SJiaxun Yang enum platform_profile_option profile; 751eabe5339SJiaxun Yang int output, err; 752eabe5339SJiaxun Yang int perfmode; 753eabe5339SJiaxun Yang 754eabe5339SJiaxun Yang mutex_lock(&priv->dytc->mutex); 755eabe5339SJiaxun Yang err = dytc_cql_command(priv, DYTC_CMD_GET, &output); 756eabe5339SJiaxun Yang mutex_unlock(&priv->dytc->mutex); 757eabe5339SJiaxun Yang if (err) 758eabe5339SJiaxun Yang return; 759eabe5339SJiaxun Yang 760eabe5339SJiaxun Yang perfmode = (output >> DYTC_GET_MODE_BIT) & 0xF; 761eabe5339SJiaxun Yang convert_dytc_to_profile(perfmode, &profile); 762eabe5339SJiaxun Yang if (profile != priv->dytc->current_profile) { 763eabe5339SJiaxun Yang priv->dytc->current_profile = profile; 764eabe5339SJiaxun Yang platform_profile_notify(); 765eabe5339SJiaxun Yang } 766eabe5339SJiaxun Yang } 767eabe5339SJiaxun Yang 768eabe5339SJiaxun Yang static int ideapad_dytc_profile_init(struct ideapad_private *priv) 769eabe5339SJiaxun Yang { 770eabe5339SJiaxun Yang int err, output, dytc_version; 771eabe5339SJiaxun Yang 772eabe5339SJiaxun Yang err = method_dytc(priv->adev->handle, DYTC_CMD_QUERY, &output); 773eabe5339SJiaxun Yang /* For all other errors we can flag the failure */ 774eabe5339SJiaxun Yang if (err) 775eabe5339SJiaxun Yang return err; 776eabe5339SJiaxun Yang 777eabe5339SJiaxun Yang /* Check DYTC is enabled and supports mode setting */ 778eabe5339SJiaxun Yang if (!(output & BIT(DYTC_QUERY_ENABLE_BIT))) 779eabe5339SJiaxun Yang return -ENODEV; 780eabe5339SJiaxun Yang 781eabe5339SJiaxun Yang dytc_version = (output >> DYTC_QUERY_REV_BIT) & 0xF; 782eabe5339SJiaxun Yang if (dytc_version < 5) 783eabe5339SJiaxun Yang return -ENODEV; 784eabe5339SJiaxun Yang 785eabe5339SJiaxun Yang priv->dytc = kzalloc(sizeof(struct ideapad_dytc_priv), GFP_KERNEL); 786eabe5339SJiaxun Yang if (!priv->dytc) 787eabe5339SJiaxun Yang return -ENOMEM; 788eabe5339SJiaxun Yang 789eabe5339SJiaxun Yang mutex_init(&priv->dytc->mutex); 790eabe5339SJiaxun Yang 791eabe5339SJiaxun Yang priv->dytc->priv = priv; 792eabe5339SJiaxun Yang priv->dytc->pprof.profile_get = dytc_profile_get; 793eabe5339SJiaxun Yang priv->dytc->pprof.profile_set = dytc_profile_set; 794eabe5339SJiaxun Yang 795eabe5339SJiaxun Yang /* Setup supported modes */ 796eabe5339SJiaxun Yang set_bit(PLATFORM_PROFILE_LOW_POWER, priv->dytc->pprof.choices); 797eabe5339SJiaxun Yang set_bit(PLATFORM_PROFILE_BALANCED, priv->dytc->pprof.choices); 798eabe5339SJiaxun Yang set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->dytc->pprof.choices); 799eabe5339SJiaxun Yang 800eabe5339SJiaxun Yang /* Create platform_profile structure and register */ 801eabe5339SJiaxun Yang err = platform_profile_register(&priv->dytc->pprof); 802eabe5339SJiaxun Yang if (err) 803eabe5339SJiaxun Yang goto mutex_destroy; 804eabe5339SJiaxun Yang 805eabe5339SJiaxun Yang /* Ensure initial values are correct */ 806eabe5339SJiaxun Yang dytc_profile_refresh(priv); 807eabe5339SJiaxun Yang 808eabe5339SJiaxun Yang return 0; 809eabe5339SJiaxun Yang 810eabe5339SJiaxun Yang mutex_destroy: 811eabe5339SJiaxun Yang mutex_destroy(&priv->dytc->mutex); 812eabe5339SJiaxun Yang kfree(priv->dytc); 813eabe5339SJiaxun Yang priv->dytc = NULL; 814eabe5339SJiaxun Yang return err; 815eabe5339SJiaxun Yang } 816eabe5339SJiaxun Yang 817eabe5339SJiaxun Yang static void ideapad_dytc_profile_exit(struct ideapad_private *priv) 818eabe5339SJiaxun Yang { 819eabe5339SJiaxun Yang if (!priv->dytc) 820eabe5339SJiaxun Yang return; 821eabe5339SJiaxun Yang 822eabe5339SJiaxun Yang platform_profile_remove(); 823eabe5339SJiaxun Yang mutex_destroy(&priv->dytc->mutex); 824eabe5339SJiaxun Yang kfree(priv->dytc); 825eabe5339SJiaxun Yang priv->dytc = NULL; 826eabe5339SJiaxun Yang } 827eabe5339SJiaxun Yang 828eabe5339SJiaxun Yang /* 829a4b5a279SIke Panhc * Rfkill 830a4b5a279SIke Panhc */ 831c1f73658SIke Panhc struct ideapad_rfk_data { 832c1f73658SIke Panhc char *name; 833c1f73658SIke Panhc int cfgbit; 834c1f73658SIke Panhc int opcode; 835c1f73658SIke Panhc int type; 836c1f73658SIke Panhc }; 837c1f73658SIke Panhc 838b3d94d70SMathias Krause static const struct ideapad_rfk_data ideapad_rfk_data[] = { 8392be1dc21SIke Panhc { "ideapad_wlan", CFG_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN }, 8402be1dc21SIke Panhc { "ideapad_bluetooth", CFG_BT_BIT, VPCCMD_W_BT, RFKILL_TYPE_BLUETOOTH }, 8412be1dc21SIke Panhc { "ideapad_3g", CFG_3G_BIT, VPCCMD_W_3G, RFKILL_TYPE_WWAN }, 842c1f73658SIke Panhc }; 843c1f73658SIke Panhc 84457ac3b05SIke Panhc static int ideapad_rfk_set(void *data, bool blocked) 84557ac3b05SIke Panhc { 846331e0ea2SZhang Rui struct ideapad_rfk_priv *priv = data; 8474b200b46SArnd Bergmann int opcode = ideapad_rfk_data[priv->dev].opcode; 84857ac3b05SIke Panhc 8494b200b46SArnd Bergmann return write_ec_cmd(priv->priv->adev->handle, opcode, !blocked); 85057ac3b05SIke Panhc } 85157ac3b05SIke Panhc 8523d59dfcdSBhumika Goyal static const struct rfkill_ops ideapad_rfk_ops = { 85357ac3b05SIke Panhc .set_block = ideapad_rfk_set, 85457ac3b05SIke Panhc }; 85557ac3b05SIke Panhc 856923de84aSIke Panhc static void ideapad_sync_rfk_state(struct ideapad_private *priv) 85757ac3b05SIke Panhc { 858ce363c2bSHans de Goede unsigned long hw_blocked = 0; 85957ac3b05SIke Panhc int i; 86057ac3b05SIke Panhc 861ce363c2bSHans de Goede if (priv->has_hw_rfkill_switch) { 862331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_RF, &hw_blocked)) 86357ac3b05SIke Panhc return; 86457ac3b05SIke Panhc hw_blocked = !hw_blocked; 865ce363c2bSHans de Goede } 86657ac3b05SIke Panhc 867c1f73658SIke Panhc for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 86857ac3b05SIke Panhc if (priv->rfk[i]) 86957ac3b05SIke Panhc rfkill_set_hw_state(priv->rfk[i], hw_blocked); 87057ac3b05SIke Panhc } 87157ac3b05SIke Panhc 87275a11f11SZhang Rui static int ideapad_register_rfkill(struct ideapad_private *priv, int dev) 87357ac3b05SIke Panhc { 87457ac3b05SIke Panhc int ret; 87557ac3b05SIke Panhc unsigned long sw_blocked; 87657ac3b05SIke Panhc 877bfa97b7dSIke Panhc if (no_bt_rfkill && 878bfa97b7dSIke Panhc (ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH)) { 879bfa97b7dSIke Panhc /* Force to enable bluetooth when no_bt_rfkill=1 */ 880331e0ea2SZhang Rui write_ec_cmd(priv->adev->handle, 881bfa97b7dSIke Panhc ideapad_rfk_data[dev].opcode, 1); 882bfa97b7dSIke Panhc return 0; 883bfa97b7dSIke Panhc } 884331e0ea2SZhang Rui priv->rfk_priv[dev].dev = dev; 885331e0ea2SZhang Rui priv->rfk_priv[dev].priv = priv; 886bfa97b7dSIke Panhc 88775a11f11SZhang Rui priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name, 888b5c37b79SZhang Rui &priv->platform_device->dev, 88975a11f11SZhang Rui ideapad_rfk_data[dev].type, 89075a11f11SZhang Rui &ideapad_rfk_ops, 891331e0ea2SZhang Rui &priv->rfk_priv[dev]); 89257ac3b05SIke Panhc if (!priv->rfk[dev]) 89357ac3b05SIke Panhc return -ENOMEM; 89457ac3b05SIke Panhc 895331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, ideapad_rfk_data[dev].opcode-1, 89657ac3b05SIke Panhc &sw_blocked)) { 89757ac3b05SIke Panhc rfkill_init_sw_state(priv->rfk[dev], 0); 89857ac3b05SIke Panhc } else { 89957ac3b05SIke Panhc sw_blocked = !sw_blocked; 90057ac3b05SIke Panhc rfkill_init_sw_state(priv->rfk[dev], sw_blocked); 90157ac3b05SIke Panhc } 90257ac3b05SIke Panhc 90357ac3b05SIke Panhc ret = rfkill_register(priv->rfk[dev]); 90457ac3b05SIke Panhc if (ret) { 90557ac3b05SIke Panhc rfkill_destroy(priv->rfk[dev]); 90657ac3b05SIke Panhc return ret; 90757ac3b05SIke Panhc } 90857ac3b05SIke Panhc return 0; 90957ac3b05SIke Panhc } 91057ac3b05SIke Panhc 91175a11f11SZhang Rui static void ideapad_unregister_rfkill(struct ideapad_private *priv, int dev) 91257ac3b05SIke Panhc { 91357ac3b05SIke Panhc if (!priv->rfk[dev]) 91457ac3b05SIke Panhc return; 91557ac3b05SIke Panhc 91657ac3b05SIke Panhc rfkill_unregister(priv->rfk[dev]); 91757ac3b05SIke Panhc rfkill_destroy(priv->rfk[dev]); 91857ac3b05SIke Panhc } 91957ac3b05SIke Panhc 92098ee6919SIke Panhc /* 92198ee6919SIke Panhc * Platform device 92298ee6919SIke Panhc */ 923b5c37b79SZhang Rui static int ideapad_sysfs_init(struct ideapad_private *priv) 92498ee6919SIke Panhc { 925b5c37b79SZhang Rui return sysfs_create_group(&priv->platform_device->dev.kobj, 926c9f718d0SIke Panhc &ideapad_attribute_group); 92798ee6919SIke Panhc } 92898ee6919SIke Panhc 929b5c37b79SZhang Rui static void ideapad_sysfs_exit(struct ideapad_private *priv) 93098ee6919SIke Panhc { 9318693ae84SIke Panhc sysfs_remove_group(&priv->platform_device->dev.kobj, 932c9f718d0SIke Panhc &ideapad_attribute_group); 93398ee6919SIke Panhc } 93498ee6919SIke Panhc 935f63409aeSIke Panhc /* 936f63409aeSIke Panhc * input device 937f63409aeSIke Panhc */ 938f63409aeSIke Panhc static const struct key_entry ideapad_keymap[] = { 939f43d9ec0SIke Panhc { KE_KEY, 6, { KEY_SWITCHVIDEOMODE } }, 940296f9fe0SMaxim Mikityanskiy { KE_KEY, 7, { KEY_CAMERA } }, 94148f67d62SAlex Hung { KE_KEY, 8, { KEY_MICMUTE } }, 942296f9fe0SMaxim Mikityanskiy { KE_KEY, 11, { KEY_F16 } }, 943f43d9ec0SIke Panhc { KE_KEY, 13, { KEY_WLAN } }, 944f43d9ec0SIke Panhc { KE_KEY, 16, { KEY_PROG1 } }, 945f43d9ec0SIke Panhc { KE_KEY, 17, { KEY_PROG2 } }, 946296f9fe0SMaxim Mikityanskiy { KE_KEY, 64, { KEY_PROG3 } }, 947296f9fe0SMaxim Mikityanskiy { KE_KEY, 65, { KEY_PROG4 } }, 94807a4a4fcSMaxim Mikityanskiy { KE_KEY, 66, { KEY_TOUCHPAD_OFF } }, 94907a4a4fcSMaxim Mikityanskiy { KE_KEY, 67, { KEY_TOUCHPAD_ON } }, 95074caab99SArnd Bergmann { KE_KEY, 128, { KEY_ESC } }, 95174caab99SArnd Bergmann 952f63409aeSIke Panhc { KE_END, 0 }, 953f63409aeSIke Panhc }; 954f63409aeSIke Panhc 955b859f159SGreg Kroah-Hartman static int ideapad_input_init(struct ideapad_private *priv) 956f63409aeSIke Panhc { 957f63409aeSIke Panhc struct input_dev *inputdev; 958f63409aeSIke Panhc int error; 959f63409aeSIke Panhc 960f63409aeSIke Panhc inputdev = input_allocate_device(); 961b222cca6SJoe Perches if (!inputdev) 962f63409aeSIke Panhc return -ENOMEM; 963f63409aeSIke Panhc 964f63409aeSIke Panhc inputdev->name = "Ideapad extra buttons"; 965f63409aeSIke Panhc inputdev->phys = "ideapad/input0"; 966f63409aeSIke Panhc inputdev->id.bustype = BUS_HOST; 9678693ae84SIke Panhc inputdev->dev.parent = &priv->platform_device->dev; 968f63409aeSIke Panhc 969f63409aeSIke Panhc error = sparse_keymap_setup(inputdev, ideapad_keymap, NULL); 970f63409aeSIke Panhc if (error) { 971f63409aeSIke Panhc pr_err("Unable to setup input device keymap\n"); 972f63409aeSIke Panhc goto err_free_dev; 973f63409aeSIke Panhc } 974f63409aeSIke Panhc 975f63409aeSIke Panhc error = input_register_device(inputdev); 976f63409aeSIke Panhc if (error) { 977f63409aeSIke Panhc pr_err("Unable to register input device\n"); 978c973d4b5SMichał Kępień goto err_free_dev; 979f63409aeSIke Panhc } 980f63409aeSIke Panhc 9818693ae84SIke Panhc priv->inputdev = inputdev; 982f63409aeSIke Panhc return 0; 983f63409aeSIke Panhc 984f63409aeSIke Panhc err_free_dev: 985f63409aeSIke Panhc input_free_device(inputdev); 986f63409aeSIke Panhc return error; 987f63409aeSIke Panhc } 988f63409aeSIke Panhc 9897451a55aSAxel Lin static void ideapad_input_exit(struct ideapad_private *priv) 990f63409aeSIke Panhc { 9918693ae84SIke Panhc input_unregister_device(priv->inputdev); 9928693ae84SIke Panhc priv->inputdev = NULL; 993f63409aeSIke Panhc } 994f63409aeSIke Panhc 9958693ae84SIke Panhc static void ideapad_input_report(struct ideapad_private *priv, 9968693ae84SIke Panhc unsigned long scancode) 997f63409aeSIke Panhc { 9988693ae84SIke Panhc sparse_keymap_report_event(priv->inputdev, scancode, 1, true); 999f63409aeSIke Panhc } 1000f63409aeSIke Panhc 1001f43d9ec0SIke Panhc static void ideapad_input_novokey(struct ideapad_private *priv) 1002f43d9ec0SIke Panhc { 1003f43d9ec0SIke Panhc unsigned long long_pressed; 1004f43d9ec0SIke Panhc 1005331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_NOVO, &long_pressed)) 1006f43d9ec0SIke Panhc return; 1007f43d9ec0SIke Panhc if (long_pressed) 1008f43d9ec0SIke Panhc ideapad_input_report(priv, 17); 1009f43d9ec0SIke Panhc else 1010f43d9ec0SIke Panhc ideapad_input_report(priv, 16); 1011f43d9ec0SIke Panhc } 1012f43d9ec0SIke Panhc 1013296f9fe0SMaxim Mikityanskiy static void ideapad_check_special_buttons(struct ideapad_private *priv) 1014296f9fe0SMaxim Mikityanskiy { 1015296f9fe0SMaxim Mikityanskiy unsigned long bit, value; 1016296f9fe0SMaxim Mikityanskiy 1017331e0ea2SZhang Rui read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value); 1018296f9fe0SMaxim Mikityanskiy 1019296f9fe0SMaxim Mikityanskiy for (bit = 0; bit < 16; bit++) { 1020296f9fe0SMaxim Mikityanskiy if (test_bit(bit, &value)) { 1021296f9fe0SMaxim Mikityanskiy switch (bit) { 1022a1ec56edSMaxim Mikityanskiy case 0: /* Z580 */ 1023a1ec56edSMaxim Mikityanskiy case 6: /* Z570 */ 1024296f9fe0SMaxim Mikityanskiy /* Thermal Management button */ 1025296f9fe0SMaxim Mikityanskiy ideapad_input_report(priv, 65); 1026296f9fe0SMaxim Mikityanskiy break; 1027296f9fe0SMaxim Mikityanskiy case 1: 1028296f9fe0SMaxim Mikityanskiy /* OneKey Theater button */ 1029296f9fe0SMaxim Mikityanskiy ideapad_input_report(priv, 64); 1030296f9fe0SMaxim Mikityanskiy break; 1031a1ec56edSMaxim Mikityanskiy default: 1032a1ec56edSMaxim Mikityanskiy pr_info("Unknown special button: %lu\n", bit); 1033a1ec56edSMaxim Mikityanskiy break; 1034296f9fe0SMaxim Mikityanskiy } 1035296f9fe0SMaxim Mikityanskiy } 1036296f9fe0SMaxim Mikityanskiy } 1037296f9fe0SMaxim Mikityanskiy } 1038296f9fe0SMaxim Mikityanskiy 1039a4b5a279SIke Panhc /* 1040a4ecbb8aSIke Panhc * backlight 1041a4ecbb8aSIke Panhc */ 1042a4ecbb8aSIke Panhc static int ideapad_backlight_get_brightness(struct backlight_device *blightdev) 1043a4ecbb8aSIke Panhc { 1044331e0ea2SZhang Rui struct ideapad_private *priv = bl_get_data(blightdev); 1045a4ecbb8aSIke Panhc unsigned long now; 1046a4ecbb8aSIke Panhc 1047331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now)) 1048a4ecbb8aSIke Panhc return -EIO; 1049a4ecbb8aSIke Panhc return now; 1050a4ecbb8aSIke Panhc } 1051a4ecbb8aSIke Panhc 1052a4ecbb8aSIke Panhc static int ideapad_backlight_update_status(struct backlight_device *blightdev) 1053a4ecbb8aSIke Panhc { 1054331e0ea2SZhang Rui struct ideapad_private *priv = bl_get_data(blightdev); 1055331e0ea2SZhang Rui 1056331e0ea2SZhang Rui if (write_ec_cmd(priv->adev->handle, VPCCMD_W_BL, 10572be1dc21SIke Panhc blightdev->props.brightness)) 1058a4ecbb8aSIke Panhc return -EIO; 1059331e0ea2SZhang Rui if (write_ec_cmd(priv->adev->handle, VPCCMD_W_BL_POWER, 1060a4ecbb8aSIke Panhc blightdev->props.power == FB_BLANK_POWERDOWN ? 0 : 1)) 1061a4ecbb8aSIke Panhc return -EIO; 1062a4ecbb8aSIke Panhc 1063a4ecbb8aSIke Panhc return 0; 1064a4ecbb8aSIke Panhc } 1065a4ecbb8aSIke Panhc 1066a4ecbb8aSIke Panhc static const struct backlight_ops ideapad_backlight_ops = { 1067a4ecbb8aSIke Panhc .get_brightness = ideapad_backlight_get_brightness, 1068a4ecbb8aSIke Panhc .update_status = ideapad_backlight_update_status, 1069a4ecbb8aSIke Panhc }; 1070a4ecbb8aSIke Panhc 1071a4ecbb8aSIke Panhc static int ideapad_backlight_init(struct ideapad_private *priv) 1072a4ecbb8aSIke Panhc { 1073a4ecbb8aSIke Panhc struct backlight_device *blightdev; 1074a4ecbb8aSIke Panhc struct backlight_properties props; 1075a4ecbb8aSIke Panhc unsigned long max, now, power; 1076a4ecbb8aSIke Panhc 1077331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &max)) 1078a4ecbb8aSIke Panhc return -EIO; 1079331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now)) 1080a4ecbb8aSIke Panhc return -EIO; 1081331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power)) 1082a4ecbb8aSIke Panhc return -EIO; 1083a4ecbb8aSIke Panhc 1084a4ecbb8aSIke Panhc memset(&props, 0, sizeof(struct backlight_properties)); 1085a4ecbb8aSIke Panhc props.max_brightness = max; 1086a4ecbb8aSIke Panhc props.type = BACKLIGHT_PLATFORM; 1087a4ecbb8aSIke Panhc blightdev = backlight_device_register("ideapad", 1088a4ecbb8aSIke Panhc &priv->platform_device->dev, 1089a4ecbb8aSIke Panhc priv, 1090a4ecbb8aSIke Panhc &ideapad_backlight_ops, 1091a4ecbb8aSIke Panhc &props); 1092a4ecbb8aSIke Panhc if (IS_ERR(blightdev)) { 1093a4ecbb8aSIke Panhc pr_err("Could not register backlight device\n"); 1094a4ecbb8aSIke Panhc return PTR_ERR(blightdev); 1095a4ecbb8aSIke Panhc } 1096a4ecbb8aSIke Panhc 1097a4ecbb8aSIke Panhc priv->blightdev = blightdev; 1098a4ecbb8aSIke Panhc blightdev->props.brightness = now; 1099a4ecbb8aSIke Panhc blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; 1100a4ecbb8aSIke Panhc backlight_update_status(blightdev); 1101a4ecbb8aSIke Panhc 1102a4ecbb8aSIke Panhc return 0; 1103a4ecbb8aSIke Panhc } 1104a4ecbb8aSIke Panhc 1105a4ecbb8aSIke Panhc static void ideapad_backlight_exit(struct ideapad_private *priv) 1106a4ecbb8aSIke Panhc { 1107a4ecbb8aSIke Panhc backlight_device_unregister(priv->blightdev); 1108a4ecbb8aSIke Panhc priv->blightdev = NULL; 1109a4ecbb8aSIke Panhc } 1110a4ecbb8aSIke Panhc 1111a4ecbb8aSIke Panhc static void ideapad_backlight_notify_power(struct ideapad_private *priv) 1112a4ecbb8aSIke Panhc { 1113a4ecbb8aSIke Panhc unsigned long power; 1114a4ecbb8aSIke Panhc struct backlight_device *blightdev = priv->blightdev; 1115a4ecbb8aSIke Panhc 1116d4afc775SRene Bollford if (!blightdev) 1117d4afc775SRene Bollford return; 1118331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power)) 1119a4ecbb8aSIke Panhc return; 1120a4ecbb8aSIke Panhc blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; 1121a4ecbb8aSIke Panhc } 1122a4ecbb8aSIke Panhc 1123a4ecbb8aSIke Panhc static void ideapad_backlight_notify_brightness(struct ideapad_private *priv) 1124a4ecbb8aSIke Panhc { 1125a4ecbb8aSIke Panhc unsigned long now; 1126a4ecbb8aSIke Panhc 1127a4ecbb8aSIke Panhc /* if we control brightness via acpi video driver */ 1128a4ecbb8aSIke Panhc if (priv->blightdev == NULL) { 1129331e0ea2SZhang Rui read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); 1130a4ecbb8aSIke Panhc return; 1131a4ecbb8aSIke Panhc } 1132a4ecbb8aSIke Panhc 1133a4ecbb8aSIke Panhc backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY); 1134a4ecbb8aSIke Panhc } 1135a4ecbb8aSIke Panhc 1136a4ecbb8aSIke Panhc /* 1137a4b5a279SIke Panhc * module init/exit 1138a4b5a279SIke Panhc */ 113975a11f11SZhang Rui static void ideapad_sync_touchpad_state(struct ideapad_private *priv) 114007a4a4fcSMaxim Mikityanskiy { 114107a4a4fcSMaxim Mikityanskiy unsigned long value; 114207a4a4fcSMaxim Mikityanskiy 1143d69cd7eeSJiaxun Yang if (!priv->has_touchpad_switch) 1144d69cd7eeSJiaxun Yang return; 1145d69cd7eeSJiaxun Yang 114607a4a4fcSMaxim Mikityanskiy /* Without reading from EC touchpad LED doesn't switch state */ 114775a11f11SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) { 114807a4a4fcSMaxim Mikityanskiy /* Some IdeaPads don't really turn off touchpad - they only 114907a4a4fcSMaxim Mikityanskiy * switch the LED state. We (de)activate KBC AUX port to turn 115007a4a4fcSMaxim Mikityanskiy * touchpad off and on. We send KEY_TOUCHPAD_OFF and 115107a4a4fcSMaxim Mikityanskiy * KEY_TOUCHPAD_ON to not to get out of sync with LED */ 115207a4a4fcSMaxim Mikityanskiy unsigned char param; 115307a4a4fcSMaxim Mikityanskiy i8042_command(¶m, value ? I8042_CMD_AUX_ENABLE : 115407a4a4fcSMaxim Mikityanskiy I8042_CMD_AUX_DISABLE); 115507a4a4fcSMaxim Mikityanskiy ideapad_input_report(priv, value ? 67 : 66); 115607a4a4fcSMaxim Mikityanskiy } 115707a4a4fcSMaxim Mikityanskiy } 115807a4a4fcSMaxim Mikityanskiy 1159b5c37b79SZhang Rui static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) 116057ac3b05SIke Panhc { 1161b5c37b79SZhang Rui struct ideapad_private *priv = data; 116257ac3b05SIke Panhc unsigned long vpc1, vpc2, vpc_bit; 116357ac3b05SIke Panhc 11642be1dc21SIke Panhc if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1)) 116557ac3b05SIke Panhc return; 11662be1dc21SIke Panhc if (read_ec_data(handle, VPCCMD_R_VPC2, &vpc2)) 116757ac3b05SIke Panhc return; 116857ac3b05SIke Panhc 116957ac3b05SIke Panhc vpc1 = (vpc2 << 8) | vpc1; 117057ac3b05SIke Panhc for (vpc_bit = 0; vpc_bit < 16; vpc_bit++) { 117157ac3b05SIke Panhc if (test_bit(vpc_bit, &vpc1)) { 1172a4ecbb8aSIke Panhc switch (vpc_bit) { 1173a4ecbb8aSIke Panhc case 9: 1174923de84aSIke Panhc ideapad_sync_rfk_state(priv); 1175a4ecbb8aSIke Panhc break; 117620a769c1SIke Panhc case 13: 1177296f9fe0SMaxim Mikityanskiy case 11: 117848f67d62SAlex Hung case 8: 1179296f9fe0SMaxim Mikityanskiy case 7: 118020a769c1SIke Panhc case 6: 118120a769c1SIke Panhc ideapad_input_report(priv, vpc_bit); 118220a769c1SIke Panhc break; 118307a4a4fcSMaxim Mikityanskiy case 5: 118475a11f11SZhang Rui ideapad_sync_touchpad_state(priv); 118507a4a4fcSMaxim Mikityanskiy break; 1186a4ecbb8aSIke Panhc case 4: 1187a4ecbb8aSIke Panhc ideapad_backlight_notify_brightness(priv); 1188a4ecbb8aSIke Panhc break; 1189f43d9ec0SIke Panhc case 3: 1190f43d9ec0SIke Panhc ideapad_input_novokey(priv); 1191f43d9ec0SIke Panhc break; 1192a4ecbb8aSIke Panhc case 2: 1193a4ecbb8aSIke Panhc ideapad_backlight_notify_power(priv); 1194a4ecbb8aSIke Panhc break; 1195296f9fe0SMaxim Mikityanskiy case 0: 1196296f9fe0SMaxim Mikityanskiy ideapad_check_special_buttons(priv); 1197296f9fe0SMaxim Mikityanskiy break; 11983cfd956bSHao Wei Tee case 1: 11993cfd956bSHao Wei Tee /* Some IdeaPads report event 1 every ~20 12003cfd956bSHao Wei Tee * seconds while on battery power; some 12013cfd956bSHao Wei Tee * report this when changing to/from tablet 12023cfd956bSHao Wei Tee * mode. Squelch this event. 12033cfd956bSHao Wei Tee */ 12043cfd956bSHao Wei Tee break; 1205a4ecbb8aSIke Panhc default: 120620a769c1SIke Panhc pr_info("Unknown event: %lu\n", vpc_bit); 120757ac3b05SIke Panhc } 120857ac3b05SIke Panhc } 120957ac3b05SIke Panhc } 1210a4ecbb8aSIke Panhc } 121157ac3b05SIke Panhc 121274caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 121374caab99SArnd Bergmann static void ideapad_wmi_notify(u32 value, void *context) 121474caab99SArnd Bergmann { 121574caab99SArnd Bergmann switch (value) { 121674caab99SArnd Bergmann case 128: 121774caab99SArnd Bergmann ideapad_input_report(context, value); 121874caab99SArnd Bergmann break; 121974caab99SArnd Bergmann default: 122074caab99SArnd Bergmann pr_info("Unknown WMI event %u\n", value); 122174caab99SArnd Bergmann } 122274caab99SArnd Bergmann } 122374caab99SArnd Bergmann #endif 122474caab99SArnd Bergmann 1225ce363c2bSHans de Goede /* 12265105e78eSHans de Goede * Some ideapads have a hardware rfkill switch, but most do not have one. 12275105e78eSHans de Goede * Reading VPCCMD_R_RF always results in 0 on models without a hardware rfkill, 12285105e78eSHans de Goede * switch causing ideapad_laptop to wrongly report all radios as hw-blocked. 12295105e78eSHans de Goede * There used to be a long list of DMI ids for models without a hw rfkill 12305105e78eSHans de Goede * switch here, but that resulted in playing whack a mole. 12315105e78eSHans de Goede * More importantly wrongly reporting the wifi radio as hw-blocked, results in 12325105e78eSHans de Goede * non working wifi. Whereas not reporting it hw-blocked, when it actually is 12335105e78eSHans de Goede * hw-blocked results in an empty SSID list, which is a much more benign 12345105e78eSHans de Goede * failure mode. 12355105e78eSHans de Goede * So the default now is the much safer option of assuming there is no 12365105e78eSHans de Goede * hardware rfkill switch. This default also actually matches most hardware, 12375105e78eSHans de Goede * since having a hw rfkill switch is quite rare on modern hardware, so this 12385105e78eSHans de Goede * also leads to a much shorter list. 1239ce363c2bSHans de Goede */ 12405105e78eSHans de Goede static const struct dmi_system_id hw_rfkill_list[] = { 124185093f79SHans de Goede {} 124285093f79SHans de Goede }; 124385093f79SHans de Goede 1244b5c37b79SZhang Rui static int ideapad_acpi_add(struct platform_device *pdev) 1245b5c37b79SZhang Rui { 1246b5c37b79SZhang Rui int ret, i; 1247b5c37b79SZhang Rui int cfg; 1248b5c37b79SZhang Rui struct ideapad_private *priv; 1249b5c37b79SZhang Rui struct acpi_device *adev; 1250*803be832SBarnabás Pőcze acpi_status status; 1251b5c37b79SZhang Rui 1252b5c37b79SZhang Rui ret = acpi_bus_get_device(ACPI_HANDLE(&pdev->dev), &adev); 1253b5c37b79SZhang Rui if (ret) 1254b5c37b79SZhang Rui return -ENODEV; 1255b5c37b79SZhang Rui 1256b5c37b79SZhang Rui if (read_method_int(adev->handle, "_CFG", &cfg)) 1257b5c37b79SZhang Rui return -ENODEV; 1258b5c37b79SZhang Rui 1259b3facd7bSHimangi Saraogi priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 1260b5c37b79SZhang Rui if (!priv) 1261b5c37b79SZhang Rui return -ENOMEM; 1262b5c37b79SZhang Rui 1263b5c37b79SZhang Rui dev_set_drvdata(&pdev->dev, priv); 1264b5c37b79SZhang Rui priv->cfg = cfg; 1265b5c37b79SZhang Rui priv->adev = adev; 1266b5c37b79SZhang Rui priv->platform_device = pdev; 12675105e78eSHans de Goede priv->has_hw_rfkill_switch = dmi_check_system(hw_rfkill_list); 1268b5c37b79SZhang Rui 1269d69cd7eeSJiaxun Yang /* Most ideapads with ELAN0634 touchpad don't use EC touchpad switch */ 1270d69cd7eeSJiaxun Yang priv->has_touchpad_switch = !acpi_dev_present("ELAN0634", NULL, -1); 1271d69cd7eeSJiaxun Yang 1272b5c37b79SZhang Rui ret = ideapad_sysfs_init(priv); 1273b5c37b79SZhang Rui if (ret) 1274b3facd7bSHimangi Saraogi return ret; 1275b5c37b79SZhang Rui 127617f1bf38SGreg Kroah-Hartman ideapad_debugfs_init(priv); 1277b5c37b79SZhang Rui 1278b5c37b79SZhang Rui ret = ideapad_input_init(priv); 1279b5c37b79SZhang Rui if (ret) 1280b5c37b79SZhang Rui goto input_failed; 1281b5c37b79SZhang Rui 1282ce363c2bSHans de Goede /* 1283ce363c2bSHans de Goede * On some models without a hw-switch (the yoga 2 13 at least) 1284ce363c2bSHans de Goede * VPCCMD_W_RF must be explicitly set to 1 for the wifi to work. 1285ce363c2bSHans de Goede */ 1286ce363c2bSHans de Goede if (!priv->has_hw_rfkill_switch) 1287ce363c2bSHans de Goede write_ec_cmd(priv->adev->handle, VPCCMD_W_RF, 1); 1288ce363c2bSHans de Goede 1289d69cd7eeSJiaxun Yang /* The same for Touchpad */ 1290d69cd7eeSJiaxun Yang if (!priv->has_touchpad_switch) 1291d69cd7eeSJiaxun Yang write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, 1); 1292d69cd7eeSJiaxun Yang 129385093f79SHans de Goede for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 1294b5c37b79SZhang Rui if (test_bit(ideapad_rfk_data[i].cfgbit, &priv->cfg)) 1295b5c37b79SZhang Rui ideapad_register_rfkill(priv, i); 1296ce363c2bSHans de Goede 1297b5c37b79SZhang Rui ideapad_sync_rfk_state(priv); 1298b5c37b79SZhang Rui ideapad_sync_touchpad_state(priv); 1299b5c37b79SZhang Rui 1300eabe5339SJiaxun Yang ideapad_dytc_profile_init(priv); 1301eabe5339SJiaxun Yang 130226bff5f0SHans de Goede if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { 1303b5c37b79SZhang Rui ret = ideapad_backlight_init(priv); 1304b5c37b79SZhang Rui if (ret && ret != -ENODEV) 1305b5c37b79SZhang Rui goto backlight_failed; 1306b5c37b79SZhang Rui } 1307*803be832SBarnabás Pőcze status = acpi_install_notify_handler(adev->handle, 1308*803be832SBarnabás Pőcze ACPI_DEVICE_NOTIFY, 1309*803be832SBarnabás Pőcze ideapad_acpi_notify, priv); 1310*803be832SBarnabás Pőcze if (ACPI_FAILURE(status)) { 1311*803be832SBarnabás Pőcze ret = -EIO; 1312b5c37b79SZhang Rui goto notification_failed; 1313*803be832SBarnabás Pőcze } 13142d98e0b9SArnd Bergmann 131574caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 13162d98e0b9SArnd Bergmann for (i = 0; i < ARRAY_SIZE(ideapad_wmi_fnesc_events); i++) { 1317*803be832SBarnabás Pőcze status = wmi_install_notify_handler(ideapad_wmi_fnesc_events[i], 13182d98e0b9SArnd Bergmann ideapad_wmi_notify, priv); 1319*803be832SBarnabás Pőcze if (ACPI_SUCCESS(status)) { 13202d98e0b9SArnd Bergmann priv->fnesc_guid = ideapad_wmi_fnesc_events[i]; 13212d98e0b9SArnd Bergmann break; 13222d98e0b9SArnd Bergmann } 13232d98e0b9SArnd Bergmann } 1324*803be832SBarnabás Pőcze if (ACPI_FAILURE(status) && status != AE_NOT_EXIST) { 1325*803be832SBarnabás Pőcze ret = -EIO; 132674caab99SArnd Bergmann goto notification_failed_wmi; 1327*803be832SBarnabás Pőcze } 132874caab99SArnd Bergmann #endif 1329b5c37b79SZhang Rui 1330b5c37b79SZhang Rui return 0; 133174caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 133274caab99SArnd Bergmann notification_failed_wmi: 133374caab99SArnd Bergmann acpi_remove_notify_handler(priv->adev->handle, 133474caab99SArnd Bergmann ACPI_DEVICE_NOTIFY, ideapad_acpi_notify); 133574caab99SArnd Bergmann #endif 1336b5c37b79SZhang Rui notification_failed: 1337b5c37b79SZhang Rui ideapad_backlight_exit(priv); 1338b5c37b79SZhang Rui backlight_failed: 1339b5c37b79SZhang Rui for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 1340b5c37b79SZhang Rui ideapad_unregister_rfkill(priv, i); 1341b5c37b79SZhang Rui ideapad_input_exit(priv); 1342b5c37b79SZhang Rui input_failed: 1343b5c37b79SZhang Rui ideapad_debugfs_exit(priv); 1344b5c37b79SZhang Rui ideapad_sysfs_exit(priv); 1345b5c37b79SZhang Rui return ret; 1346b5c37b79SZhang Rui } 1347b5c37b79SZhang Rui 1348b5c37b79SZhang Rui static int ideapad_acpi_remove(struct platform_device *pdev) 1349b5c37b79SZhang Rui { 1350b5c37b79SZhang Rui struct ideapad_private *priv = dev_get_drvdata(&pdev->dev); 1351b5c37b79SZhang Rui int i; 1352b5c37b79SZhang Rui 135374caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 13542d98e0b9SArnd Bergmann if (priv->fnesc_guid) 13552d98e0b9SArnd Bergmann wmi_remove_notify_handler(priv->fnesc_guid); 135674caab99SArnd Bergmann #endif 1357b5c37b79SZhang Rui acpi_remove_notify_handler(priv->adev->handle, 1358b5c37b79SZhang Rui ACPI_DEVICE_NOTIFY, ideapad_acpi_notify); 1359b5c37b79SZhang Rui ideapad_backlight_exit(priv); 1360eabe5339SJiaxun Yang ideapad_dytc_profile_exit(priv); 1361b5c37b79SZhang Rui for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 1362b5c37b79SZhang Rui ideapad_unregister_rfkill(priv, i); 1363b5c37b79SZhang Rui ideapad_input_exit(priv); 1364b5c37b79SZhang Rui ideapad_debugfs_exit(priv); 1365b5c37b79SZhang Rui ideapad_sysfs_exit(priv); 1366b5c37b79SZhang Rui 1367b5c37b79SZhang Rui return 0; 1368b5c37b79SZhang Rui } 1369b5c37b79SZhang Rui 137011fa8da5SZhang Rui #ifdef CONFIG_PM_SLEEP 1371e1a39a44SBarnabás Pőcze static int ideapad_acpi_resume(struct device *dev) 137207a4a4fcSMaxim Mikityanskiy { 1373e1a39a44SBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 137475a11f11SZhang Rui 137575a11f11SZhang Rui ideapad_sync_rfk_state(priv); 137675a11f11SZhang Rui ideapad_sync_touchpad_state(priv); 1377eabe5339SJiaxun Yang 1378eabe5339SJiaxun Yang if (priv->dytc) 1379eabe5339SJiaxun Yang dytc_profile_refresh(priv); 1380eabe5339SJiaxun Yang 138107a4a4fcSMaxim Mikityanskiy return 0; 138207a4a4fcSMaxim Mikityanskiy } 1383b5c37b79SZhang Rui #endif 138407a4a4fcSMaxim Mikityanskiy static SIMPLE_DEV_PM_OPS(ideapad_pm, NULL, ideapad_acpi_resume); 138507a4a4fcSMaxim Mikityanskiy 1386b5c37b79SZhang Rui static const struct acpi_device_id ideapad_device_ids[] = { 1387b5c37b79SZhang Rui { "VPC2004", 0}, 1388b5c37b79SZhang Rui { "", 0}, 138957ac3b05SIke Panhc }; 1390b5c37b79SZhang Rui MODULE_DEVICE_TABLE(acpi, ideapad_device_ids); 1391b5c37b79SZhang Rui 1392b5c37b79SZhang Rui static struct platform_driver ideapad_acpi_driver = { 1393b5c37b79SZhang Rui .probe = ideapad_acpi_add, 1394b5c37b79SZhang Rui .remove = ideapad_acpi_remove, 1395b5c37b79SZhang Rui .driver = { 1396b5c37b79SZhang Rui .name = "ideapad_acpi", 1397b5c37b79SZhang Rui .pm = &ideapad_pm, 1398b5c37b79SZhang Rui .acpi_match_table = ACPI_PTR(ideapad_device_ids), 1399b5c37b79SZhang Rui }, 1400b5c37b79SZhang Rui }; 1401b5c37b79SZhang Rui 1402b5c37b79SZhang Rui module_platform_driver(ideapad_acpi_driver); 140357ac3b05SIke Panhc 140457ac3b05SIke Panhc MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); 140557ac3b05SIke Panhc MODULE_DESCRIPTION("IdeaPad ACPI Extras"); 140657ac3b05SIke Panhc MODULE_LICENSE("GPL"); 1407