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 118b48463fSLv Zheng #include <linux/acpi.h> 127d38f034SBarnabás Pőcze #include <linux/backlight.h> 130c4915b6SBarnabás Pőcze #include <linux/bitops.h> 147d38f034SBarnabás Pőcze #include <linux/debugfs.h> 157d38f034SBarnabás Pőcze #include <linux/device.h> 167d38f034SBarnabás Pőcze #include <linux/dmi.h> 177d38f034SBarnabás Pőcze #include <linux/fb.h> 187d38f034SBarnabás Pőcze #include <linux/i8042.h> 197d38f034SBarnabás Pőcze #include <linux/init.h> 20f63409aeSIke Panhc #include <linux/input.h> 21f63409aeSIke Panhc #include <linux/input/sparse-keymap.h> 2240e0447dSBarnabás Pőcze #include <linux/jiffies.h> 237d38f034SBarnabás Pőcze #include <linux/kernel.h> 247d38f034SBarnabás Pőcze #include <linux/module.h> 257d38f034SBarnabás Pőcze #include <linux/platform_device.h> 267d38f034SBarnabás Pőcze #include <linux/platform_profile.h> 277d38f034SBarnabás Pőcze #include <linux/rfkill.h> 28773e3206SIke Panhc #include <linux/seq_file.h> 29d6b50889SBarnabás Pőcze #include <linux/sysfs.h> 307d38f034SBarnabás Pőcze #include <linux/types.h> 317d38f034SBarnabás Pőcze 3226bff5f0SHans de Goede #include <acpi/video.h> 3357ac3b05SIke Panhc 34c1f73658SIke Panhc #define IDEAPAD_RFKILL_DEV_NUM (3) 3557ac3b05SIke Panhc 3674caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 372d98e0b9SArnd Bergmann static const char *const ideapad_wmi_fnesc_events[] = { 382d98e0b9SArnd Bergmann "26CAB2E5-5CF1-46AE-AAC3-4A12B6BA50E6", /* Yoga 3 */ 392d98e0b9SArnd Bergmann "56322276-8493-4CE8-A783-98C991274F5E", /* Yoga 700 */ 402d98e0b9SArnd Bergmann }; 4174caab99SArnd Bergmann #endif 4274caab99SArnd Bergmann 432be1dc21SIke Panhc enum { 440b765671SBarnabás Pőcze CFG_CAP_BT_BIT = 16, 450b765671SBarnabás Pőcze CFG_CAP_3G_BIT = 17, 460b765671SBarnabás Pőcze CFG_CAP_WIFI_BIT = 18, 470b765671SBarnabás Pőcze CFG_CAP_CAM_BIT = 19, 48b3ed1b7fSBarnabás Pőcze CFG_CAP_TOUCHPAD_BIT = 30, 490b765671SBarnabás Pőcze }; 500b765671SBarnabás Pőcze 510b765671SBarnabás Pőcze enum { 520b765671SBarnabás Pőcze GBMD_CONSERVATION_STATE_BIT = 5, 530b765671SBarnabás Pőcze }; 540b765671SBarnabás Pőcze 550b765671SBarnabás Pőcze enum { 560b765671SBarnabás Pőcze SMBC_CONSERVATION_ON = 3, 570b765671SBarnabás Pőcze SMBC_CONSERVATION_OFF = 5, 580b765671SBarnabás Pőcze }; 590b765671SBarnabás Pőcze 600b765671SBarnabás Pőcze enum { 61392cbf0aSBarnabás Pőcze HALS_FNLOCK_SUPPORT_BIT = 9, 620b765671SBarnabás Pőcze HALS_FNLOCK_STATE_BIT = 10, 63392cbf0aSBarnabás Pőcze HALS_HOTKEYS_PRIMARY_BIT = 11, 640b765671SBarnabás Pőcze }; 650b765671SBarnabás Pőcze 660b765671SBarnabás Pőcze enum { 670b765671SBarnabás Pőcze SALS_FNLOCK_ON = 0xe, 680b765671SBarnabás Pőcze SALS_FNLOCK_OFF = 0xf, 69ade50296SHao Wei Tee }; 70ade50296SHao Wei Tee 71ade50296SHao Wei Tee enum { 722be1dc21SIke Panhc VPCCMD_R_VPC1 = 0x10, 732be1dc21SIke Panhc VPCCMD_R_BL_MAX, 742be1dc21SIke Panhc VPCCMD_R_BL, 752be1dc21SIke Panhc VPCCMD_W_BL, 762be1dc21SIke Panhc VPCCMD_R_WIFI, 772be1dc21SIke Panhc VPCCMD_W_WIFI, 782be1dc21SIke Panhc VPCCMD_R_BT, 792be1dc21SIke Panhc VPCCMD_W_BT, 802be1dc21SIke Panhc VPCCMD_R_BL_POWER, 812be1dc21SIke Panhc VPCCMD_R_NOVO, 822be1dc21SIke Panhc VPCCMD_R_VPC2, 832be1dc21SIke Panhc VPCCMD_R_TOUCHPAD, 842be1dc21SIke Panhc VPCCMD_W_TOUCHPAD, 852be1dc21SIke Panhc VPCCMD_R_CAMERA, 862be1dc21SIke Panhc VPCCMD_W_CAMERA, 872be1dc21SIke Panhc VPCCMD_R_3G, 882be1dc21SIke Panhc VPCCMD_W_3G, 892be1dc21SIke Panhc VPCCMD_R_ODD, /* 0x21 */ 900c7bbeb9SMaxim Mikityanskiy VPCCMD_W_FAN, 910c7bbeb9SMaxim Mikityanskiy VPCCMD_R_RF, 922be1dc21SIke Panhc VPCCMD_W_RF, 930c7bbeb9SMaxim Mikityanskiy VPCCMD_R_FAN = 0x2B, 94296f9fe0SMaxim Mikityanskiy VPCCMD_R_SPECIAL_BUTTONS = 0x31, 952be1dc21SIke Panhc VPCCMD_W_BL_POWER = 0x33, 962be1dc21SIke Panhc }; 972be1dc21SIke Panhc 98eabe5339SJiaxun Yang struct ideapad_dytc_priv { 99eabe5339SJiaxun Yang enum platform_profile_option current_profile; 100eabe5339SJiaxun Yang struct platform_profile_handler pprof; 101eabe5339SJiaxun Yang struct mutex mutex; 102eabe5339SJiaxun Yang struct ideapad_private *priv; 103eabe5339SJiaxun Yang }; 104eabe5339SJiaxun Yang 105331e0ea2SZhang Rui struct ideapad_rfk_priv { 106331e0ea2SZhang Rui int dev; 107331e0ea2SZhang Rui struct ideapad_private *priv; 108331e0ea2SZhang Rui }; 109331e0ea2SZhang Rui 11057ac3b05SIke Panhc struct ideapad_private { 111469f6434SZhang Rui struct acpi_device *adev; 112c1f73658SIke Panhc struct rfkill *rfk[IDEAPAD_RFKILL_DEV_NUM]; 113331e0ea2SZhang Rui struct ideapad_rfk_priv rfk_priv[IDEAPAD_RFKILL_DEV_NUM]; 11498ee6919SIke Panhc struct platform_device *platform_device; 115f63409aeSIke Panhc struct input_dev *inputdev; 116a4ecbb8aSIke Panhc struct backlight_device *blightdev; 117eabe5339SJiaxun Yang struct ideapad_dytc_priv *dytc; 118773e3206SIke Panhc struct dentry *debug; 1193371f481SIke Panhc unsigned long cfg; 1202d98e0b9SArnd Bergmann const char *fnesc_guid; 1211c59de4aSBarnabás Pőcze struct { 1221c59de4aSBarnabás Pőcze bool conservation_mode : 1; 1231c59de4aSBarnabás Pőcze bool dytc : 1; 1241c59de4aSBarnabás Pőcze bool fan_mode : 1; 1251c59de4aSBarnabás Pőcze bool fn_lock : 1; 1261c59de4aSBarnabás Pőcze bool hw_rfkill_switch : 1; 1271c59de4aSBarnabás Pőcze bool touchpad_ctrl_via_ec : 1; 1281c59de4aSBarnabás Pőcze } features; 12957ac3b05SIke Panhc }; 13057ac3b05SIke Panhc 131bfa97b7dSIke Panhc static bool no_bt_rfkill; 132bfa97b7dSIke Panhc module_param(no_bt_rfkill, bool, 0444); 133bfa97b7dSIke Panhc MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth."); 134bfa97b7dSIke Panhc 13557ac3b05SIke Panhc /* 13657ac3b05SIke Panhc * ACPI Helpers 13757ac3b05SIke Panhc */ 138ed5b9ba7SAaron Ma #define IDEAPAD_EC_TIMEOUT (200) /* in ms */ 13957ac3b05SIke Panhc 140ff36b0d9SBarnabás Pőcze static int eval_int(acpi_handle handle, const char *name, unsigned long *res) 14157ac3b05SIke Panhc { 14257ac3b05SIke Panhc unsigned long long result; 143ade50296SHao Wei Tee acpi_status status; 144ade50296SHao Wei Tee 145ff36b0d9SBarnabás Pőcze status = acpi_evaluate_integer(handle, (char *)name, NULL, &result); 146ff36b0d9SBarnabás Pőcze if (ACPI_FAILURE(status)) 147ff36b0d9SBarnabás Pőcze return -EIO; 148ff36b0d9SBarnabás Pőcze *res = result; 149ff36b0d9SBarnabás Pőcze return 0; 150ff36b0d9SBarnabás Pőcze } 151ff36b0d9SBarnabás Pőcze 152ff36b0d9SBarnabás Pőcze static int exec_simple_method(acpi_handle handle, const char *name, unsigned long arg) 153ff36b0d9SBarnabás Pőcze { 154ff36b0d9SBarnabás Pőcze acpi_status status = acpi_execute_simple_method(handle, (char *)name, arg); 155ff36b0d9SBarnabás Pőcze 1567be193e3SBarnabás Pőcze return ACPI_FAILURE(status) ? -EIO : 0; 157ade50296SHao Wei Tee } 158ade50296SHao Wei Tee 159ff36b0d9SBarnabás Pőcze static int eval_gbmd(acpi_handle handle, unsigned long *res) 160eabe5339SJiaxun Yang { 161ff36b0d9SBarnabás Pőcze return eval_int(handle, "GBMD", res); 162ff36b0d9SBarnabás Pőcze } 163ff36b0d9SBarnabás Pőcze 164ff36b0d9SBarnabás Pőcze static int exec_smbc(acpi_handle handle, unsigned long arg) 165ff36b0d9SBarnabás Pőcze { 166ff36b0d9SBarnabás Pőcze return exec_simple_method(handle, "SMBC", arg); 167ff36b0d9SBarnabás Pőcze } 168ff36b0d9SBarnabás Pőcze 169ff36b0d9SBarnabás Pőcze static int eval_hals(acpi_handle handle, unsigned long *res) 170ff36b0d9SBarnabás Pőcze { 171ff36b0d9SBarnabás Pőcze return eval_int(handle, "HALS", res); 172ff36b0d9SBarnabás Pőcze } 173ff36b0d9SBarnabás Pőcze 174ff36b0d9SBarnabás Pőcze static int exec_sals(acpi_handle handle, unsigned long arg) 175ff36b0d9SBarnabás Pőcze { 176ff36b0d9SBarnabás Pőcze return exec_simple_method(handle, "SALS", arg); 177ff36b0d9SBarnabás Pőcze } 178ff36b0d9SBarnabás Pőcze 179ff36b0d9SBarnabás Pőcze static int eval_int_with_arg(acpi_handle handle, const char *name, unsigned long arg, unsigned long *res) 180ff36b0d9SBarnabás Pőcze { 181eabe5339SJiaxun Yang struct acpi_object_list params; 182ff36b0d9SBarnabás Pőcze unsigned long long result; 183eabe5339SJiaxun Yang union acpi_object in_obj; 184ff36b0d9SBarnabás Pőcze acpi_status status; 185eabe5339SJiaxun Yang 186eabe5339SJiaxun Yang params.count = 1; 187eabe5339SJiaxun Yang params.pointer = &in_obj; 188eabe5339SJiaxun Yang in_obj.type = ACPI_TYPE_INTEGER; 189ff36b0d9SBarnabás Pőcze in_obj.integer.value = arg; 190eabe5339SJiaxun Yang 191ff36b0d9SBarnabás Pőcze status = acpi_evaluate_integer(handle, (char *)name, ¶ms, &result); 192ff36b0d9SBarnabás Pőcze if (ACPI_FAILURE(status)) 1937be193e3SBarnabás Pőcze return -EIO; 194ff36b0d9SBarnabás Pőcze 195ff36b0d9SBarnabás Pőcze if (res) 196ff36b0d9SBarnabás Pőcze *res = result; 197ff36b0d9SBarnabás Pőcze 198eabe5339SJiaxun Yang return 0; 199eabe5339SJiaxun Yang } 200eabe5339SJiaxun Yang 201ff36b0d9SBarnabás Pőcze static int eval_dytc(acpi_handle handle, unsigned long cmd, unsigned long *res) 20257ac3b05SIke Panhc { 203ff36b0d9SBarnabás Pőcze return eval_int_with_arg(handle, "DYTC", cmd, res); 20457ac3b05SIke Panhc } 20557ac3b05SIke Panhc 206ff36b0d9SBarnabás Pőcze static int eval_vpcr(acpi_handle handle, unsigned long cmd, unsigned long *res) 207ff36b0d9SBarnabás Pőcze { 208ff36b0d9SBarnabás Pőcze return eval_int_with_arg(handle, "VPCR", cmd, res); 209ff36b0d9SBarnabás Pőcze } 210ff36b0d9SBarnabás Pőcze 211ff36b0d9SBarnabás Pőcze static int eval_vpcw(acpi_handle handle, unsigned long cmd, unsigned long data) 21257ac3b05SIke Panhc { 21357ac3b05SIke Panhc struct acpi_object_list params; 21457ac3b05SIke Panhc union acpi_object in_obj[2]; 21557ac3b05SIke Panhc acpi_status status; 21657ac3b05SIke Panhc 21757ac3b05SIke Panhc params.count = 2; 21857ac3b05SIke Panhc params.pointer = in_obj; 21957ac3b05SIke Panhc in_obj[0].type = ACPI_TYPE_INTEGER; 22057ac3b05SIke Panhc in_obj[0].integer.value = cmd; 22157ac3b05SIke Panhc in_obj[1].type = ACPI_TYPE_INTEGER; 22257ac3b05SIke Panhc in_obj[1].integer.value = data; 22357ac3b05SIke Panhc 22457ac3b05SIke Panhc status = acpi_evaluate_object(handle, "VPCW", ¶ms, NULL); 225ff36b0d9SBarnabás Pőcze if (ACPI_FAILURE(status)) 2267be193e3SBarnabás Pőcze return -EIO; 22757ac3b05SIke Panhc return 0; 22857ac3b05SIke Panhc } 22957ac3b05SIke Panhc 230ff36b0d9SBarnabás Pőcze static int read_ec_data(acpi_handle handle, unsigned long cmd, unsigned long *data) 23157ac3b05SIke Panhc { 232ff36b0d9SBarnabás Pőcze unsigned long end_jiffies, val; 233ff36b0d9SBarnabás Pőcze int err; 23457ac3b05SIke Panhc 235ff36b0d9SBarnabás Pőcze err = eval_vpcw(handle, 1, cmd); 2367be193e3SBarnabás Pőcze if (err) 2377be193e3SBarnabás Pőcze return err; 23857ac3b05SIke Panhc 23940e0447dSBarnabás Pőcze end_jiffies = jiffies + msecs_to_jiffies(IDEAPAD_EC_TIMEOUT) + 1; 24040e0447dSBarnabás Pőcze 24140e0447dSBarnabás Pőcze while (time_before(jiffies, end_jiffies)) { 24257ac3b05SIke Panhc schedule(); 243ff36b0d9SBarnabás Pőcze err = eval_vpcr(handle, 1, &val); 2447be193e3SBarnabás Pőcze if (err) 2457be193e3SBarnabás Pőcze return err; 246ff36b0d9SBarnabás Pőcze if (val == 0) 247ff36b0d9SBarnabás Pőcze return eval_vpcr(handle, 0, data); 24857ac3b05SIke Panhc } 249654324c4SBarnabás Pőcze acpi_handle_err(handle, "timeout in %s\n", __func__); 2507be193e3SBarnabás Pőcze return -ETIMEDOUT; 25157ac3b05SIke Panhc } 25257ac3b05SIke Panhc 253ff36b0d9SBarnabás Pőcze static int write_ec_cmd(acpi_handle handle, unsigned long cmd, unsigned long data) 25457ac3b05SIke Panhc { 255ff36b0d9SBarnabás Pőcze unsigned long end_jiffies, val; 256ff36b0d9SBarnabás Pőcze int err; 25757ac3b05SIke Panhc 258ff36b0d9SBarnabás Pőcze err = eval_vpcw(handle, 0, data); 2597be193e3SBarnabás Pőcze if (err) 2607be193e3SBarnabás Pőcze return err; 261ff36b0d9SBarnabás Pőcze err = eval_vpcw(handle, 1, cmd); 2627be193e3SBarnabás Pőcze if (err) 2637be193e3SBarnabás Pőcze return err; 26457ac3b05SIke Panhc 26540e0447dSBarnabás Pőcze end_jiffies = jiffies + msecs_to_jiffies(IDEAPAD_EC_TIMEOUT) + 1; 26640e0447dSBarnabás Pőcze 26740e0447dSBarnabás Pőcze while (time_before(jiffies, end_jiffies)) { 26857ac3b05SIke Panhc schedule(); 269ff36b0d9SBarnabás Pőcze err = eval_vpcr(handle, 1, &val); 2707be193e3SBarnabás Pőcze if (err) 2717be193e3SBarnabás Pőcze return err; 27257ac3b05SIke Panhc if (val == 0) 27357ac3b05SIke Panhc return 0; 27457ac3b05SIke Panhc } 275654324c4SBarnabás Pőcze acpi_handle_err(handle, "timeout in %s\n", __func__); 2767be193e3SBarnabás Pőcze return -ETIMEDOUT; 27757ac3b05SIke Panhc } 27857ac3b05SIke Panhc 279a4b5a279SIke Panhc /* 280773e3206SIke Panhc * debugfs 281773e3206SIke Panhc */ 282773e3206SIke Panhc static int debugfs_status_show(struct seq_file *s, void *data) 283773e3206SIke Panhc { 284331e0ea2SZhang Rui struct ideapad_private *priv = s->private; 285773e3206SIke Panhc unsigned long value; 286773e3206SIke Panhc 287331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &value)) 288*7553390dSBarnabás Pőcze seq_printf(s, "Backlight max: %lu\n", value); 289331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL, &value)) 290*7553390dSBarnabás Pőcze seq_printf(s, "Backlight now: %lu\n", value); 291331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &value)) 292*7553390dSBarnabás Pőcze seq_printf(s, "BL power value: %s (%lu)\n", value ? "on" : "off", value); 293ade50296SHao Wei Tee seq_puts(s, "=====================\n"); 294ade50296SHao Wei Tee 295*7553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_RF, &value)) 296*7553390dSBarnabás Pőcze seq_printf(s, "Radio status: %s (%lu)\n", value ? "on" : "off", value); 297*7553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_WIFI, &value)) 298*7553390dSBarnabás Pőcze seq_printf(s, "Wifi status: %s (%lu)\n", value ? "on" : "off", value); 299*7553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_BT, &value)) 300*7553390dSBarnabás Pőcze seq_printf(s, "BT status: %s (%lu)\n", value ? "on" : "off", value); 301*7553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_3G, &value)) 302*7553390dSBarnabás Pőcze seq_printf(s, "3G status: %s (%lu)\n", value ? "on" : "off", value); 303*7553390dSBarnabás Pőcze seq_puts(s, "=====================\n"); 304*7553390dSBarnabás Pőcze 305*7553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) 306*7553390dSBarnabás Pőcze seq_printf(s, "Touchpad status: %s (%lu)\n", value ? "on" : "off", value); 307*7553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &value)) 308*7553390dSBarnabás Pőcze seq_printf(s, "Camera status: %s (%lu)\n", value ? "on" : "off", value); 309*7553390dSBarnabás Pőcze seq_puts(s, "=====================\n"); 310*7553390dSBarnabás Pőcze 311*7553390dSBarnabás Pőcze if (!eval_gbmd(priv->adev->handle, &value)) 312*7553390dSBarnabás Pőcze seq_printf(s, "GBMD: %#010lx\n", value); 313*7553390dSBarnabás Pőcze if (!eval_hals(priv->adev->handle, &value)) 314*7553390dSBarnabás Pőcze seq_printf(s, "HALS: %#010lx\n", value); 315773e3206SIke Panhc 316773e3206SIke Panhc return 0; 317773e3206SIke Panhc } 318334c4efdSAndy Shevchenko DEFINE_SHOW_ATTRIBUTE(debugfs_status); 319773e3206SIke Panhc 320773e3206SIke Panhc static int debugfs_cfg_show(struct seq_file *s, void *data) 321773e3206SIke Panhc { 322331e0ea2SZhang Rui struct ideapad_private *priv = s->private; 323331e0ea2SZhang Rui 324773e3206SIke Panhc seq_printf(s, "cfg: 0x%.8lX\n\nCapability: ", 325331e0ea2SZhang Rui priv->cfg); 3260b765671SBarnabás Pőcze if (test_bit(CFG_CAP_BT_BIT, &priv->cfg)) 327773e3206SIke Panhc seq_printf(s, "Bluetooth "); 3280b765671SBarnabás Pőcze if (test_bit(CFG_CAP_3G_BIT, &priv->cfg)) 329773e3206SIke Panhc seq_printf(s, "3G "); 3300b765671SBarnabás Pőcze if (test_bit(CFG_CAP_WIFI_BIT, &priv->cfg)) 331773e3206SIke Panhc seq_printf(s, "Wireless "); 3320b765671SBarnabás Pőcze if (test_bit(CFG_CAP_CAM_BIT, &priv->cfg)) 333773e3206SIke Panhc seq_printf(s, "Camera "); 334b3ed1b7fSBarnabás Pőcze if (test_bit(CFG_CAP_TOUCHPAD_BIT, &priv->cfg)) 335b3ed1b7fSBarnabás Pőcze seq_printf(s, "Touchpad "); 336773e3206SIke Panhc seq_printf(s, "\nGraphic: "); 337331e0ea2SZhang Rui switch ((priv->cfg)&0x700) { 338773e3206SIke Panhc case 0x100: 339773e3206SIke Panhc seq_printf(s, "Intel"); 340773e3206SIke Panhc break; 341773e3206SIke Panhc case 0x200: 342773e3206SIke Panhc seq_printf(s, "ATI"); 343773e3206SIke Panhc break; 344773e3206SIke Panhc case 0x300: 345773e3206SIke Panhc seq_printf(s, "Nvidia"); 346773e3206SIke Panhc break; 347773e3206SIke Panhc case 0x400: 348773e3206SIke Panhc seq_printf(s, "Intel and ATI"); 349773e3206SIke Panhc break; 350773e3206SIke Panhc case 0x500: 351773e3206SIke Panhc seq_printf(s, "Intel and Nvidia"); 352773e3206SIke Panhc break; 353773e3206SIke Panhc } 354773e3206SIke Panhc seq_printf(s, "\n"); 355e1a39a44SBarnabás Pőcze 356773e3206SIke Panhc return 0; 357773e3206SIke Panhc } 358334c4efdSAndy Shevchenko DEFINE_SHOW_ATTRIBUTE(debugfs_cfg); 359773e3206SIke Panhc 36017f1bf38SGreg Kroah-Hartman static void ideapad_debugfs_init(struct ideapad_private *priv) 361773e3206SIke Panhc { 36217f1bf38SGreg Kroah-Hartman struct dentry *dir; 363773e3206SIke Panhc 36417f1bf38SGreg Kroah-Hartman dir = debugfs_create_dir("ideapad", NULL); 36517f1bf38SGreg Kroah-Hartman priv->debug = dir; 366773e3206SIke Panhc 36717f1bf38SGreg Kroah-Hartman debugfs_create_file("cfg", S_IRUGO, dir, priv, &debugfs_cfg_fops); 36817f1bf38SGreg Kroah-Hartman debugfs_create_file("status", S_IRUGO, dir, priv, &debugfs_status_fops); 369773e3206SIke Panhc } 370773e3206SIke Panhc 371773e3206SIke Panhc static void ideapad_debugfs_exit(struct ideapad_private *priv) 372773e3206SIke Panhc { 373773e3206SIke Panhc debugfs_remove_recursive(priv->debug); 374773e3206SIke Panhc priv->debug = NULL; 375773e3206SIke Panhc } 376773e3206SIke Panhc 377773e3206SIke Panhc /* 3783371f481SIke Panhc * sysfs 379a4b5a279SIke Panhc */ 38057ac3b05SIke Panhc static ssize_t show_ideapad_cam(struct device *dev, 38157ac3b05SIke Panhc struct device_attribute *attr, 38257ac3b05SIke Panhc char *buf) 38357ac3b05SIke Panhc { 38457ac3b05SIke Panhc unsigned long result; 385331e0ea2SZhang Rui struct ideapad_private *priv = dev_get_drvdata(dev); 386c81f2410SBarnabás Pőcze int err; 38757ac3b05SIke Panhc 388c81f2410SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &result); 389c81f2410SBarnabás Pőcze if (err) 390c81f2410SBarnabás Pőcze return err; 39100641c08SBarnabás Pőcze return sysfs_emit(buf, "%d\n", !!result); 39257ac3b05SIke Panhc } 39357ac3b05SIke Panhc 39457ac3b05SIke Panhc static ssize_t store_ideapad_cam(struct device *dev, 39557ac3b05SIke Panhc struct device_attribute *attr, 39657ac3b05SIke Panhc const char *buf, size_t count) 39757ac3b05SIke Panhc { 398331e0ea2SZhang Rui struct ideapad_private *priv = dev_get_drvdata(dev); 39900641c08SBarnabás Pőcze bool state; 40000641c08SBarnabás Pőcze int ret; 40157ac3b05SIke Panhc 40200641c08SBarnabás Pőcze ret = kstrtobool(buf, &state); 40300641c08SBarnabás Pőcze if (ret) 40400641c08SBarnabás Pőcze return ret; 405331e0ea2SZhang Rui ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_CAMERA, state); 4067be193e3SBarnabás Pőcze if (ret) 4077be193e3SBarnabás Pőcze return ret; 40857ac3b05SIke Panhc return count; 40957ac3b05SIke Panhc } 41057ac3b05SIke Panhc 41157ac3b05SIke Panhc static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam); 41257ac3b05SIke Panhc 4130c7bbeb9SMaxim Mikityanskiy static ssize_t show_ideapad_fan(struct device *dev, 4140c7bbeb9SMaxim Mikityanskiy struct device_attribute *attr, 4150c7bbeb9SMaxim Mikityanskiy char *buf) 4160c7bbeb9SMaxim Mikityanskiy { 4170c7bbeb9SMaxim Mikityanskiy unsigned long result; 418331e0ea2SZhang Rui struct ideapad_private *priv = dev_get_drvdata(dev); 419c81f2410SBarnabás Pőcze int err; 4200c7bbeb9SMaxim Mikityanskiy 421c81f2410SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result); 422c81f2410SBarnabás Pőcze if (err) 423c81f2410SBarnabás Pőcze return err; 424d6b50889SBarnabás Pőcze return sysfs_emit(buf, "%lu\n", result); 4250c7bbeb9SMaxim Mikityanskiy } 4260c7bbeb9SMaxim Mikityanskiy 4270c7bbeb9SMaxim Mikityanskiy static ssize_t store_ideapad_fan(struct device *dev, 4280c7bbeb9SMaxim Mikityanskiy struct device_attribute *attr, 4290c7bbeb9SMaxim Mikityanskiy const char *buf, size_t count) 4300c7bbeb9SMaxim Mikityanskiy { 431331e0ea2SZhang Rui struct ideapad_private *priv = dev_get_drvdata(dev); 43200641c08SBarnabás Pőcze unsigned int state; 43300641c08SBarnabás Pőcze int ret; 4340c7bbeb9SMaxim Mikityanskiy 43500641c08SBarnabás Pőcze ret = kstrtouint(buf, 0, &state); 43600641c08SBarnabás Pőcze if (ret) 43700641c08SBarnabás Pőcze return ret; 43800641c08SBarnabás Pőcze if (state > 4 || state == 3) 4390c7bbeb9SMaxim Mikityanskiy return -EINVAL; 440331e0ea2SZhang Rui ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_FAN, state); 4417be193e3SBarnabás Pőcze if (ret) 4427be193e3SBarnabás Pőcze return ret; 4430c7bbeb9SMaxim Mikityanskiy return count; 4440c7bbeb9SMaxim Mikityanskiy } 4450c7bbeb9SMaxim Mikityanskiy 4460c7bbeb9SMaxim Mikityanskiy static DEVICE_ATTR(fan_mode, 0644, show_ideapad_fan, store_ideapad_fan); 4470c7bbeb9SMaxim Mikityanskiy 44836ac0d43SRitesh Raj Sarraf static ssize_t touchpad_show(struct device *dev, 44936ac0d43SRitesh Raj Sarraf struct device_attribute *attr, 45036ac0d43SRitesh Raj Sarraf char *buf) 45136ac0d43SRitesh Raj Sarraf { 45236ac0d43SRitesh Raj Sarraf struct ideapad_private *priv = dev_get_drvdata(dev); 45336ac0d43SRitesh Raj Sarraf unsigned long result; 454c81f2410SBarnabás Pőcze int err; 45536ac0d43SRitesh Raj Sarraf 456c81f2410SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &result); 457c81f2410SBarnabás Pőcze if (err) 458c81f2410SBarnabás Pőcze return err; 45900641c08SBarnabás Pőcze return sysfs_emit(buf, "%d\n", !!result); 46036ac0d43SRitesh Raj Sarraf } 46136ac0d43SRitesh Raj Sarraf 46246936fd6SArnd Bergmann /* Switch to RO for now: It might be revisited in the future */ 46346936fd6SArnd Bergmann static ssize_t __maybe_unused touchpad_store(struct device *dev, 46436ac0d43SRitesh Raj Sarraf struct device_attribute *attr, 46536ac0d43SRitesh Raj Sarraf const char *buf, size_t count) 46636ac0d43SRitesh Raj Sarraf { 46736ac0d43SRitesh Raj Sarraf struct ideapad_private *priv = dev_get_drvdata(dev); 46836ac0d43SRitesh Raj Sarraf bool state; 46936ac0d43SRitesh Raj Sarraf int ret; 47036ac0d43SRitesh Raj Sarraf 47136ac0d43SRitesh Raj Sarraf ret = kstrtobool(buf, &state); 47236ac0d43SRitesh Raj Sarraf if (ret) 47336ac0d43SRitesh Raj Sarraf return ret; 47436ac0d43SRitesh Raj Sarraf 47536ac0d43SRitesh Raj Sarraf ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, state); 4767be193e3SBarnabás Pőcze if (ret) 4777be193e3SBarnabás Pőcze return ret; 47836ac0d43SRitesh Raj Sarraf return count; 47936ac0d43SRitesh Raj Sarraf } 48036ac0d43SRitesh Raj Sarraf 4817f363145SAndy Shevchenko static DEVICE_ATTR_RO(touchpad); 48236ac0d43SRitesh Raj Sarraf 483ade50296SHao Wei Tee static ssize_t conservation_mode_show(struct device *dev, 484ade50296SHao Wei Tee struct device_attribute *attr, 485ade50296SHao Wei Tee char *buf) 486ade50296SHao Wei Tee { 487ade50296SHao Wei Tee struct ideapad_private *priv = dev_get_drvdata(dev); 488ade50296SHao Wei Tee unsigned long result; 489c81f2410SBarnabás Pőcze int err; 490ade50296SHao Wei Tee 491ff36b0d9SBarnabás Pőcze err = eval_gbmd(priv->adev->handle, &result); 492c81f2410SBarnabás Pőcze if (err) 493c81f2410SBarnabás Pőcze return err; 4940b765671SBarnabás Pőcze return sysfs_emit(buf, "%d\n", !!test_bit(GBMD_CONSERVATION_STATE_BIT, &result)); 495ade50296SHao Wei Tee } 496ade50296SHao Wei Tee 497ade50296SHao Wei Tee static ssize_t conservation_mode_store(struct device *dev, 498ade50296SHao Wei Tee struct device_attribute *attr, 499ade50296SHao Wei Tee const char *buf, size_t count) 500ade50296SHao Wei Tee { 501ade50296SHao Wei Tee struct ideapad_private *priv = dev_get_drvdata(dev); 502ade50296SHao Wei Tee bool state; 503ade50296SHao Wei Tee int ret; 504ade50296SHao Wei Tee 505ade50296SHao Wei Tee ret = kstrtobool(buf, &state); 506ade50296SHao Wei Tee if (ret) 507ade50296SHao Wei Tee return ret; 508ade50296SHao Wei Tee 509ff36b0d9SBarnabás Pőcze ret = exec_smbc(priv->adev->handle, state ? SMBC_CONSERVATION_ON : SMBC_CONSERVATION_OFF); 5107be193e3SBarnabás Pőcze if (ret) 5117be193e3SBarnabás Pőcze return ret; 512ade50296SHao Wei Tee return count; 513ade50296SHao Wei Tee } 514ade50296SHao Wei Tee 515ade50296SHao Wei Tee static DEVICE_ATTR_RW(conservation_mode); 516ade50296SHao Wei Tee 51740760717SOleg Keri static ssize_t fn_lock_show(struct device *dev, 51840760717SOleg Keri struct device_attribute *attr, 51940760717SOleg Keri char *buf) 52040760717SOleg Keri { 52140760717SOleg Keri struct ideapad_private *priv = dev_get_drvdata(dev); 522ff36b0d9SBarnabás Pőcze unsigned long hals; 523ff36b0d9SBarnabás Pőcze int fail = eval_hals(priv->adev->handle, &hals); 52440760717SOleg Keri 52540760717SOleg Keri if (fail) 526c81f2410SBarnabás Pőcze return fail; 52740760717SOleg Keri 528ff36b0d9SBarnabás Pőcze return sysfs_emit(buf, "%d\n", !!test_bit(HALS_FNLOCK_STATE_BIT, &hals)); 52940760717SOleg Keri } 53040760717SOleg Keri 53140760717SOleg Keri static ssize_t fn_lock_store(struct device *dev, 53240760717SOleg Keri struct device_attribute *attr, 53340760717SOleg Keri const char *buf, size_t count) 53440760717SOleg Keri { 53540760717SOleg Keri struct ideapad_private *priv = dev_get_drvdata(dev); 53640760717SOleg Keri bool state; 53740760717SOleg Keri int ret; 53840760717SOleg Keri 53940760717SOleg Keri ret = kstrtobool(buf, &state); 54040760717SOleg Keri if (ret) 54140760717SOleg Keri return ret; 54240760717SOleg Keri 543ff36b0d9SBarnabás Pőcze ret = exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF); 5447be193e3SBarnabás Pőcze if (ret) 5457be193e3SBarnabás Pőcze return ret; 54640760717SOleg Keri return count; 54740760717SOleg Keri } 54840760717SOleg Keri 54940760717SOleg Keri static DEVICE_ATTR_RW(fn_lock); 55040760717SOleg Keri 55140760717SOleg Keri 5523371f481SIke Panhc static struct attribute *ideapad_attributes[] = { 5533371f481SIke Panhc &dev_attr_camera_power.attr, 5540c7bbeb9SMaxim Mikityanskiy &dev_attr_fan_mode.attr, 55536ac0d43SRitesh Raj Sarraf &dev_attr_touchpad.attr, 556ade50296SHao Wei Tee &dev_attr_conservation_mode.attr, 55740760717SOleg Keri &dev_attr_fn_lock.attr, 5583371f481SIke Panhc NULL 5593371f481SIke Panhc }; 5603371f481SIke Panhc 561587a1f16SAl Viro static umode_t ideapad_is_visible(struct kobject *kobj, 562a84511f7SIke Panhc struct attribute *attr, 563a84511f7SIke Panhc int idx) 564a84511f7SIke Panhc { 565708086b2SBarnabás Pőcze struct device *dev = kobj_to_dev(kobj); 566a84511f7SIke Panhc struct ideapad_private *priv = dev_get_drvdata(dev); 5671c59de4aSBarnabás Pőcze bool supported = true; 568a84511f7SIke Panhc 569a84511f7SIke Panhc if (attr == &dev_attr_camera_power.attr) 5700b765671SBarnabás Pőcze supported = test_bit(CFG_CAP_CAM_BIT, &priv->cfg); 5711c59de4aSBarnabás Pőcze else if (attr == &dev_attr_conservation_mode.attr) 5721c59de4aSBarnabás Pőcze supported = priv->features.conservation_mode; 5731c59de4aSBarnabás Pőcze else if (attr == &dev_attr_fan_mode.attr) 5741c59de4aSBarnabás Pőcze supported = priv->features.fan_mode; 5751c59de4aSBarnabás Pőcze else if (attr == &dev_attr_fn_lock.attr) 5761c59de4aSBarnabás Pőcze supported = priv->features.fn_lock; 5771c59de4aSBarnabás Pőcze else if (attr == &dev_attr_touchpad.attr) 578b3ed1b7fSBarnabás Pőcze supported = priv->features.touchpad_ctrl_via_ec && 579b3ed1b7fSBarnabás Pőcze test_bit(CFG_CAP_TOUCHPAD_BIT, &priv->cfg); 580a84511f7SIke Panhc 581a84511f7SIke Panhc return supported ? attr->mode : 0; 582a84511f7SIke Panhc } 583a84511f7SIke Panhc 58449458e83SMathias Krause static const struct attribute_group ideapad_attribute_group = { 585a84511f7SIke Panhc .is_visible = ideapad_is_visible, 5863371f481SIke Panhc .attrs = ideapad_attributes 5873371f481SIke Panhc }; 5883371f481SIke Panhc 589a4b5a279SIke Panhc /* 590eabe5339SJiaxun Yang * DYTC Platform profile 591eabe5339SJiaxun Yang */ 592eabe5339SJiaxun Yang #define DYTC_CMD_QUERY 0 /* To get DYTC status - enable/revision */ 593eabe5339SJiaxun Yang #define DYTC_CMD_SET 1 /* To enable/disable IC function mode */ 594eabe5339SJiaxun Yang #define DYTC_CMD_GET 2 /* To get current IC function and mode */ 595eabe5339SJiaxun Yang #define DYTC_CMD_RESET 0x1ff /* To reset back to default */ 596eabe5339SJiaxun Yang 597eabe5339SJiaxun Yang #define DYTC_QUERY_ENABLE_BIT 8 /* Bit 8 - 0 = disabled, 1 = enabled */ 598eabe5339SJiaxun Yang #define DYTC_QUERY_SUBREV_BIT 16 /* Bits 16 - 27 - sub revision */ 599eabe5339SJiaxun Yang #define DYTC_QUERY_REV_BIT 28 /* Bits 28 - 31 - revision */ 600eabe5339SJiaxun Yang 601eabe5339SJiaxun Yang #define DYTC_GET_FUNCTION_BIT 8 /* Bits 8-11 - function setting */ 602eabe5339SJiaxun Yang #define DYTC_GET_MODE_BIT 12 /* Bits 12-15 - mode setting */ 603eabe5339SJiaxun Yang 604eabe5339SJiaxun Yang #define DYTC_SET_FUNCTION_BIT 12 /* Bits 12-15 - function setting */ 605eabe5339SJiaxun Yang #define DYTC_SET_MODE_BIT 16 /* Bits 16-19 - mode setting */ 606eabe5339SJiaxun Yang #define DYTC_SET_VALID_BIT 20 /* Bit 20 - 1 = on, 0 = off */ 607eabe5339SJiaxun Yang 608eabe5339SJiaxun Yang #define DYTC_FUNCTION_STD 0 /* Function = 0, standard mode */ 609eabe5339SJiaxun Yang #define DYTC_FUNCTION_CQL 1 /* Function = 1, lap mode */ 610eabe5339SJiaxun Yang #define DYTC_FUNCTION_MMC 11 /* Function = 11, desk mode */ 611eabe5339SJiaxun Yang 612eabe5339SJiaxun Yang #define DYTC_MODE_PERFORM 2 /* High power mode aka performance */ 613eabe5339SJiaxun Yang #define DYTC_MODE_LOW_POWER 3 /* Low power mode aka quiet */ 614eabe5339SJiaxun Yang #define DYTC_MODE_BALANCE 0xF /* Default mode aka balanced */ 615eabe5339SJiaxun Yang 616eabe5339SJiaxun Yang #define DYTC_SET_COMMAND(function, mode, on) \ 617eabe5339SJiaxun Yang (DYTC_CMD_SET | (function) << DYTC_SET_FUNCTION_BIT | \ 618eabe5339SJiaxun Yang (mode) << DYTC_SET_MODE_BIT | \ 619eabe5339SJiaxun Yang (on) << DYTC_SET_VALID_BIT) 620eabe5339SJiaxun Yang 621eabe5339SJiaxun Yang #define DYTC_DISABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 0) 622eabe5339SJiaxun Yang 623eabe5339SJiaxun Yang #define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 1) 624eabe5339SJiaxun Yang 625eabe5339SJiaxun Yang static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *profile) 626eabe5339SJiaxun Yang { 627eabe5339SJiaxun Yang switch (dytcmode) { 628eabe5339SJiaxun Yang case DYTC_MODE_LOW_POWER: 629eabe5339SJiaxun Yang *profile = PLATFORM_PROFILE_LOW_POWER; 630eabe5339SJiaxun Yang break; 631eabe5339SJiaxun Yang case DYTC_MODE_BALANCE: 632eabe5339SJiaxun Yang *profile = PLATFORM_PROFILE_BALANCED; 633eabe5339SJiaxun Yang break; 634eabe5339SJiaxun Yang case DYTC_MODE_PERFORM: 635eabe5339SJiaxun Yang *profile = PLATFORM_PROFILE_PERFORMANCE; 636eabe5339SJiaxun Yang break; 637eabe5339SJiaxun Yang default: /* Unknown mode */ 638eabe5339SJiaxun Yang return -EINVAL; 639eabe5339SJiaxun Yang } 640eabe5339SJiaxun Yang return 0; 641eabe5339SJiaxun Yang } 642eabe5339SJiaxun Yang 643eabe5339SJiaxun Yang static int convert_profile_to_dytc(enum platform_profile_option profile, int *perfmode) 644eabe5339SJiaxun Yang { 645eabe5339SJiaxun Yang switch (profile) { 646eabe5339SJiaxun Yang case PLATFORM_PROFILE_LOW_POWER: 647eabe5339SJiaxun Yang *perfmode = DYTC_MODE_LOW_POWER; 648eabe5339SJiaxun Yang break; 649eabe5339SJiaxun Yang case PLATFORM_PROFILE_BALANCED: 650eabe5339SJiaxun Yang *perfmode = DYTC_MODE_BALANCE; 651eabe5339SJiaxun Yang break; 652eabe5339SJiaxun Yang case PLATFORM_PROFILE_PERFORMANCE: 653eabe5339SJiaxun Yang *perfmode = DYTC_MODE_PERFORM; 654eabe5339SJiaxun Yang break; 655eabe5339SJiaxun Yang default: /* Unknown profile */ 656eabe5339SJiaxun Yang return -EOPNOTSUPP; 657eabe5339SJiaxun Yang } 658eabe5339SJiaxun Yang return 0; 659eabe5339SJiaxun Yang } 660eabe5339SJiaxun Yang 661eabe5339SJiaxun Yang /* 662eabe5339SJiaxun Yang * dytc_profile_get: Function to register with platform_profile 663eabe5339SJiaxun Yang * handler. Returns current platform profile. 664eabe5339SJiaxun Yang */ 665eabe5339SJiaxun Yang int dytc_profile_get(struct platform_profile_handler *pprof, 666eabe5339SJiaxun Yang enum platform_profile_option *profile) 667eabe5339SJiaxun Yang { 668eabe5339SJiaxun Yang struct ideapad_dytc_priv *dytc; 669eabe5339SJiaxun Yang 670eabe5339SJiaxun Yang dytc = container_of(pprof, struct ideapad_dytc_priv, pprof); 671eabe5339SJiaxun Yang *profile = dytc->current_profile; 672eabe5339SJiaxun Yang return 0; 673eabe5339SJiaxun Yang } 674eabe5339SJiaxun Yang 675eabe5339SJiaxun Yang /* 676eabe5339SJiaxun Yang * Helper function - check if we are in CQL mode and if we are 677eabe5339SJiaxun Yang * - disable CQL, 678eabe5339SJiaxun Yang * - run the command 679eabe5339SJiaxun Yang * - enable CQL 680eabe5339SJiaxun Yang * If not in CQL mode, just run the command 681eabe5339SJiaxun Yang */ 682ff36b0d9SBarnabás Pőcze int dytc_cql_command(struct ideapad_private *priv, unsigned long cmd, unsigned long *output) 683eabe5339SJiaxun Yang { 684ff36b0d9SBarnabás Pőcze int err, cmd_err, cur_funcmode; 685eabe5339SJiaxun Yang 686eabe5339SJiaxun Yang /* Determine if we are in CQL mode. This alters the commands we do */ 687ff36b0d9SBarnabás Pőcze err = eval_dytc(priv->adev->handle, DYTC_CMD_GET, output); 688eabe5339SJiaxun Yang if (err) 689eabe5339SJiaxun Yang return err; 690eabe5339SJiaxun Yang 691eabe5339SJiaxun Yang cur_funcmode = (*output >> DYTC_GET_FUNCTION_BIT) & 0xF; 692eabe5339SJiaxun Yang /* Check if we're OK to return immediately */ 693ff36b0d9SBarnabás Pőcze if (cmd == DYTC_CMD_GET && cur_funcmode != DYTC_FUNCTION_CQL) 694eabe5339SJiaxun Yang return 0; 695eabe5339SJiaxun Yang 696eabe5339SJiaxun Yang if (cur_funcmode == DYTC_FUNCTION_CQL) { 697ff36b0d9SBarnabás Pőcze err = eval_dytc(priv->adev->handle, DYTC_DISABLE_CQL, NULL); 698eabe5339SJiaxun Yang if (err) 699eabe5339SJiaxun Yang return err; 700eabe5339SJiaxun Yang } 701eabe5339SJiaxun Yang 702ff36b0d9SBarnabás Pőcze cmd_err = eval_dytc(priv->adev->handle, cmd, output); 703eabe5339SJiaxun Yang /* Check return condition after we've restored CQL state */ 704eabe5339SJiaxun Yang 705eabe5339SJiaxun Yang if (cur_funcmode == DYTC_FUNCTION_CQL) { 706ff36b0d9SBarnabás Pőcze err = eval_dytc(priv->adev->handle, DYTC_ENABLE_CQL, NULL); 707eabe5339SJiaxun Yang if (err) 708eabe5339SJiaxun Yang return err; 709eabe5339SJiaxun Yang } 710eabe5339SJiaxun Yang 711eabe5339SJiaxun Yang return cmd_err; 712eabe5339SJiaxun Yang } 713eabe5339SJiaxun Yang 714eabe5339SJiaxun Yang /* 715eabe5339SJiaxun Yang * dytc_profile_set: Function to register with platform_profile 716eabe5339SJiaxun Yang * handler. Sets current platform profile. 717eabe5339SJiaxun Yang */ 718eabe5339SJiaxun Yang int dytc_profile_set(struct platform_profile_handler *pprof, 719eabe5339SJiaxun Yang enum platform_profile_option profile) 720eabe5339SJiaxun Yang { 721eabe5339SJiaxun Yang struct ideapad_dytc_priv *dytc; 722eabe5339SJiaxun Yang struct ideapad_private *priv; 723eabe5339SJiaxun Yang int err; 724eabe5339SJiaxun Yang 725eabe5339SJiaxun Yang dytc = container_of(pprof, struct ideapad_dytc_priv, pprof); 726eabe5339SJiaxun Yang priv = dytc->priv; 727eabe5339SJiaxun Yang 728eabe5339SJiaxun Yang err = mutex_lock_interruptible(&dytc->mutex); 729eabe5339SJiaxun Yang if (err) 730eabe5339SJiaxun Yang return err; 731eabe5339SJiaxun Yang 732eabe5339SJiaxun Yang if (profile == PLATFORM_PROFILE_BALANCED) { 733eabe5339SJiaxun Yang /* To get back to balanced mode we just issue a reset command */ 734ff36b0d9SBarnabás Pőcze err = eval_dytc(priv->adev->handle, DYTC_CMD_RESET, NULL); 735eabe5339SJiaxun Yang if (err) 736eabe5339SJiaxun Yang goto unlock; 737eabe5339SJiaxun Yang } else { 738eabe5339SJiaxun Yang int perfmode; 739eabe5339SJiaxun Yang 740eabe5339SJiaxun Yang err = convert_profile_to_dytc(profile, &perfmode); 741eabe5339SJiaxun Yang if (err) 742eabe5339SJiaxun Yang goto unlock; 743eabe5339SJiaxun Yang 744eabe5339SJiaxun Yang /* Determine if we are in CQL mode. This alters the commands we do */ 745eabe5339SJiaxun Yang err = dytc_cql_command(priv, 746eabe5339SJiaxun Yang DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), 747ff36b0d9SBarnabás Pőcze NULL); 748eabe5339SJiaxun Yang if (err) 749eabe5339SJiaxun Yang goto unlock; 750eabe5339SJiaxun Yang } 751eabe5339SJiaxun Yang /* Success - update current profile */ 752eabe5339SJiaxun Yang dytc->current_profile = profile; 753eabe5339SJiaxun Yang unlock: 754eabe5339SJiaxun Yang mutex_unlock(&dytc->mutex); 755eabe5339SJiaxun Yang return err; 756eabe5339SJiaxun Yang } 757eabe5339SJiaxun Yang 758eabe5339SJiaxun Yang static void dytc_profile_refresh(struct ideapad_private *priv) 759eabe5339SJiaxun Yang { 760eabe5339SJiaxun Yang enum platform_profile_option profile; 761ff36b0d9SBarnabás Pőcze unsigned long output; 762ff36b0d9SBarnabás Pőcze int err, perfmode; 763eabe5339SJiaxun Yang 764eabe5339SJiaxun Yang mutex_lock(&priv->dytc->mutex); 765eabe5339SJiaxun Yang err = dytc_cql_command(priv, DYTC_CMD_GET, &output); 766eabe5339SJiaxun Yang mutex_unlock(&priv->dytc->mutex); 767eabe5339SJiaxun Yang if (err) 768eabe5339SJiaxun Yang return; 769eabe5339SJiaxun Yang 770eabe5339SJiaxun Yang perfmode = (output >> DYTC_GET_MODE_BIT) & 0xF; 771eabe5339SJiaxun Yang convert_dytc_to_profile(perfmode, &profile); 772eabe5339SJiaxun Yang if (profile != priv->dytc->current_profile) { 773eabe5339SJiaxun Yang priv->dytc->current_profile = profile; 774eabe5339SJiaxun Yang platform_profile_notify(); 775eabe5339SJiaxun Yang } 776eabe5339SJiaxun Yang } 777eabe5339SJiaxun Yang 778eabe5339SJiaxun Yang static int ideapad_dytc_profile_init(struct ideapad_private *priv) 779eabe5339SJiaxun Yang { 780ff36b0d9SBarnabás Pőcze int err, dytc_version; 781ff36b0d9SBarnabás Pőcze unsigned long output; 782eabe5339SJiaxun Yang 7831c59de4aSBarnabás Pőcze if (!priv->features.dytc) 7841c59de4aSBarnabás Pőcze return -ENODEV; 7851c59de4aSBarnabás Pőcze 786ff36b0d9SBarnabás Pőcze err = eval_dytc(priv->adev->handle, DYTC_CMD_QUERY, &output); 787eabe5339SJiaxun Yang /* For all other errors we can flag the failure */ 788eabe5339SJiaxun Yang if (err) 789eabe5339SJiaxun Yang return err; 790eabe5339SJiaxun Yang 791eabe5339SJiaxun Yang /* Check DYTC is enabled and supports mode setting */ 792eabe5339SJiaxun Yang if (!(output & BIT(DYTC_QUERY_ENABLE_BIT))) 793eabe5339SJiaxun Yang return -ENODEV; 794eabe5339SJiaxun Yang 795eabe5339SJiaxun Yang dytc_version = (output >> DYTC_QUERY_REV_BIT) & 0xF; 796eabe5339SJiaxun Yang if (dytc_version < 5) 797eabe5339SJiaxun Yang return -ENODEV; 798eabe5339SJiaxun Yang 799eabe5339SJiaxun Yang priv->dytc = kzalloc(sizeof(struct ideapad_dytc_priv), GFP_KERNEL); 800eabe5339SJiaxun Yang if (!priv->dytc) 801eabe5339SJiaxun Yang return -ENOMEM; 802eabe5339SJiaxun Yang 803eabe5339SJiaxun Yang mutex_init(&priv->dytc->mutex); 804eabe5339SJiaxun Yang 805eabe5339SJiaxun Yang priv->dytc->priv = priv; 806eabe5339SJiaxun Yang priv->dytc->pprof.profile_get = dytc_profile_get; 807eabe5339SJiaxun Yang priv->dytc->pprof.profile_set = dytc_profile_set; 808eabe5339SJiaxun Yang 809eabe5339SJiaxun Yang /* Setup supported modes */ 810eabe5339SJiaxun Yang set_bit(PLATFORM_PROFILE_LOW_POWER, priv->dytc->pprof.choices); 811eabe5339SJiaxun Yang set_bit(PLATFORM_PROFILE_BALANCED, priv->dytc->pprof.choices); 812eabe5339SJiaxun Yang set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->dytc->pprof.choices); 813eabe5339SJiaxun Yang 814eabe5339SJiaxun Yang /* Create platform_profile structure and register */ 815eabe5339SJiaxun Yang err = platform_profile_register(&priv->dytc->pprof); 816eabe5339SJiaxun Yang if (err) 817eabe5339SJiaxun Yang goto mutex_destroy; 818eabe5339SJiaxun Yang 819eabe5339SJiaxun Yang /* Ensure initial values are correct */ 820eabe5339SJiaxun Yang dytc_profile_refresh(priv); 821eabe5339SJiaxun Yang 822eabe5339SJiaxun Yang return 0; 823eabe5339SJiaxun Yang 824eabe5339SJiaxun Yang mutex_destroy: 825eabe5339SJiaxun Yang mutex_destroy(&priv->dytc->mutex); 826eabe5339SJiaxun Yang kfree(priv->dytc); 827eabe5339SJiaxun Yang priv->dytc = NULL; 828eabe5339SJiaxun Yang return err; 829eabe5339SJiaxun Yang } 830eabe5339SJiaxun Yang 831eabe5339SJiaxun Yang static void ideapad_dytc_profile_exit(struct ideapad_private *priv) 832eabe5339SJiaxun Yang { 833eabe5339SJiaxun Yang if (!priv->dytc) 834eabe5339SJiaxun Yang return; 835eabe5339SJiaxun Yang 836eabe5339SJiaxun Yang platform_profile_remove(); 837eabe5339SJiaxun Yang mutex_destroy(&priv->dytc->mutex); 838eabe5339SJiaxun Yang kfree(priv->dytc); 839eabe5339SJiaxun Yang priv->dytc = NULL; 840eabe5339SJiaxun Yang } 841eabe5339SJiaxun Yang 842eabe5339SJiaxun Yang /* 843a4b5a279SIke Panhc * Rfkill 844a4b5a279SIke Panhc */ 845c1f73658SIke Panhc struct ideapad_rfk_data { 846c1f73658SIke Panhc char *name; 847c1f73658SIke Panhc int cfgbit; 848c1f73658SIke Panhc int opcode; 849c1f73658SIke Panhc int type; 850c1f73658SIke Panhc }; 851c1f73658SIke Panhc 852b3d94d70SMathias Krause static const struct ideapad_rfk_data ideapad_rfk_data[] = { 8530b765671SBarnabás Pőcze { "ideapad_wlan", CFG_CAP_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN }, 8540b765671SBarnabás Pőcze { "ideapad_bluetooth", CFG_CAP_BT_BIT, VPCCMD_W_BT, RFKILL_TYPE_BLUETOOTH }, 8550b765671SBarnabás Pőcze { "ideapad_3g", CFG_CAP_3G_BIT, VPCCMD_W_3G, RFKILL_TYPE_WWAN }, 856c1f73658SIke Panhc }; 857c1f73658SIke Panhc 85857ac3b05SIke Panhc static int ideapad_rfk_set(void *data, bool blocked) 85957ac3b05SIke Panhc { 860331e0ea2SZhang Rui struct ideapad_rfk_priv *priv = data; 8614b200b46SArnd Bergmann int opcode = ideapad_rfk_data[priv->dev].opcode; 86257ac3b05SIke Panhc 8634b200b46SArnd Bergmann return write_ec_cmd(priv->priv->adev->handle, opcode, !blocked); 86457ac3b05SIke Panhc } 86557ac3b05SIke Panhc 8663d59dfcdSBhumika Goyal static const struct rfkill_ops ideapad_rfk_ops = { 86757ac3b05SIke Panhc .set_block = ideapad_rfk_set, 86857ac3b05SIke Panhc }; 86957ac3b05SIke Panhc 870923de84aSIke Panhc static void ideapad_sync_rfk_state(struct ideapad_private *priv) 87157ac3b05SIke Panhc { 872ce363c2bSHans de Goede unsigned long hw_blocked = 0; 87357ac3b05SIke Panhc int i; 87457ac3b05SIke Panhc 8751c59de4aSBarnabás Pőcze if (priv->features.hw_rfkill_switch) { 876331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_RF, &hw_blocked)) 87757ac3b05SIke Panhc return; 87857ac3b05SIke Panhc hw_blocked = !hw_blocked; 879ce363c2bSHans de Goede } 88057ac3b05SIke Panhc 881c1f73658SIke Panhc for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 88257ac3b05SIke Panhc if (priv->rfk[i]) 88357ac3b05SIke Panhc rfkill_set_hw_state(priv->rfk[i], hw_blocked); 88457ac3b05SIke Panhc } 88557ac3b05SIke Panhc 88675a11f11SZhang Rui static int ideapad_register_rfkill(struct ideapad_private *priv, int dev) 88757ac3b05SIke Panhc { 88857ac3b05SIke Panhc int ret; 88957ac3b05SIke Panhc unsigned long sw_blocked; 89057ac3b05SIke Panhc 891bfa97b7dSIke Panhc if (no_bt_rfkill && 892bfa97b7dSIke Panhc (ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH)) { 893bfa97b7dSIke Panhc /* Force to enable bluetooth when no_bt_rfkill=1 */ 894331e0ea2SZhang Rui write_ec_cmd(priv->adev->handle, 895bfa97b7dSIke Panhc ideapad_rfk_data[dev].opcode, 1); 896bfa97b7dSIke Panhc return 0; 897bfa97b7dSIke Panhc } 898331e0ea2SZhang Rui priv->rfk_priv[dev].dev = dev; 899331e0ea2SZhang Rui priv->rfk_priv[dev].priv = priv; 900bfa97b7dSIke Panhc 90175a11f11SZhang Rui priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name, 902b5c37b79SZhang Rui &priv->platform_device->dev, 90375a11f11SZhang Rui ideapad_rfk_data[dev].type, 90475a11f11SZhang Rui &ideapad_rfk_ops, 905331e0ea2SZhang Rui &priv->rfk_priv[dev]); 90657ac3b05SIke Panhc if (!priv->rfk[dev]) 90757ac3b05SIke Panhc return -ENOMEM; 90857ac3b05SIke Panhc 909331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, ideapad_rfk_data[dev].opcode-1, 91057ac3b05SIke Panhc &sw_blocked)) { 91157ac3b05SIke Panhc rfkill_init_sw_state(priv->rfk[dev], 0); 91257ac3b05SIke Panhc } else { 91357ac3b05SIke Panhc sw_blocked = !sw_blocked; 91457ac3b05SIke Panhc rfkill_init_sw_state(priv->rfk[dev], sw_blocked); 91557ac3b05SIke Panhc } 91657ac3b05SIke Panhc 91757ac3b05SIke Panhc ret = rfkill_register(priv->rfk[dev]); 91857ac3b05SIke Panhc if (ret) { 91957ac3b05SIke Panhc rfkill_destroy(priv->rfk[dev]); 92057ac3b05SIke Panhc return ret; 92157ac3b05SIke Panhc } 92257ac3b05SIke Panhc return 0; 92357ac3b05SIke Panhc } 92457ac3b05SIke Panhc 92575a11f11SZhang Rui static void ideapad_unregister_rfkill(struct ideapad_private *priv, int dev) 92657ac3b05SIke Panhc { 92757ac3b05SIke Panhc if (!priv->rfk[dev]) 92857ac3b05SIke Panhc return; 92957ac3b05SIke Panhc 93057ac3b05SIke Panhc rfkill_unregister(priv->rfk[dev]); 93157ac3b05SIke Panhc rfkill_destroy(priv->rfk[dev]); 93257ac3b05SIke Panhc } 93357ac3b05SIke Panhc 93498ee6919SIke Panhc /* 93598ee6919SIke Panhc * Platform device 93698ee6919SIke Panhc */ 937b5c37b79SZhang Rui static int ideapad_sysfs_init(struct ideapad_private *priv) 93898ee6919SIke Panhc { 9398782d8d7SBarnabás Pőcze return device_add_group(&priv->platform_device->dev, 940c9f718d0SIke Panhc &ideapad_attribute_group); 94198ee6919SIke Panhc } 94298ee6919SIke Panhc 943b5c37b79SZhang Rui static void ideapad_sysfs_exit(struct ideapad_private *priv) 94498ee6919SIke Panhc { 9458782d8d7SBarnabás Pőcze device_remove_group(&priv->platform_device->dev, 946c9f718d0SIke Panhc &ideapad_attribute_group); 94798ee6919SIke Panhc } 94898ee6919SIke Panhc 949f63409aeSIke Panhc /* 950f63409aeSIke Panhc * input device 951f63409aeSIke Panhc */ 952f63409aeSIke Panhc static const struct key_entry ideapad_keymap[] = { 953f43d9ec0SIke Panhc { KE_KEY, 6, { KEY_SWITCHVIDEOMODE } }, 954296f9fe0SMaxim Mikityanskiy { KE_KEY, 7, { KEY_CAMERA } }, 95548f67d62SAlex Hung { KE_KEY, 8, { KEY_MICMUTE } }, 956296f9fe0SMaxim Mikityanskiy { KE_KEY, 11, { KEY_F16 } }, 957f43d9ec0SIke Panhc { KE_KEY, 13, { KEY_WLAN } }, 958f43d9ec0SIke Panhc { KE_KEY, 16, { KEY_PROG1 } }, 959f43d9ec0SIke Panhc { KE_KEY, 17, { KEY_PROG2 } }, 960296f9fe0SMaxim Mikityanskiy { KE_KEY, 64, { KEY_PROG3 } }, 961296f9fe0SMaxim Mikityanskiy { KE_KEY, 65, { KEY_PROG4 } }, 96207a4a4fcSMaxim Mikityanskiy { KE_KEY, 66, { KEY_TOUCHPAD_OFF } }, 96307a4a4fcSMaxim Mikityanskiy { KE_KEY, 67, { KEY_TOUCHPAD_ON } }, 96474caab99SArnd Bergmann { KE_KEY, 128, { KEY_ESC } }, 96574caab99SArnd Bergmann 966f63409aeSIke Panhc { KE_END, 0 }, 967f63409aeSIke Panhc }; 968f63409aeSIke Panhc 969b859f159SGreg Kroah-Hartman static int ideapad_input_init(struct ideapad_private *priv) 970f63409aeSIke Panhc { 971f63409aeSIke Panhc struct input_dev *inputdev; 972f63409aeSIke Panhc int error; 973f63409aeSIke Panhc 974f63409aeSIke Panhc inputdev = input_allocate_device(); 975b222cca6SJoe Perches if (!inputdev) 976f63409aeSIke Panhc return -ENOMEM; 977f63409aeSIke Panhc 978f63409aeSIke Panhc inputdev->name = "Ideapad extra buttons"; 979f63409aeSIke Panhc inputdev->phys = "ideapad/input0"; 980f63409aeSIke Panhc inputdev->id.bustype = BUS_HOST; 9818693ae84SIke Panhc inputdev->dev.parent = &priv->platform_device->dev; 982f63409aeSIke Panhc 983f63409aeSIke Panhc error = sparse_keymap_setup(inputdev, ideapad_keymap, NULL); 984f63409aeSIke Panhc if (error) { 985654324c4SBarnabás Pőcze dev_err(&priv->platform_device->dev, 986654324c4SBarnabás Pőcze "Unable to setup input device keymap\n"); 987f63409aeSIke Panhc goto err_free_dev; 988f63409aeSIke Panhc } 989f63409aeSIke Panhc 990f63409aeSIke Panhc error = input_register_device(inputdev); 991f63409aeSIke Panhc if (error) { 992654324c4SBarnabás Pőcze dev_err(&priv->platform_device->dev, 993654324c4SBarnabás Pőcze "Unable to register input device\n"); 994c973d4b5SMichał Kępień goto err_free_dev; 995f63409aeSIke Panhc } 996f63409aeSIke Panhc 9978693ae84SIke Panhc priv->inputdev = inputdev; 998f63409aeSIke Panhc return 0; 999f63409aeSIke Panhc 1000f63409aeSIke Panhc err_free_dev: 1001f63409aeSIke Panhc input_free_device(inputdev); 1002f63409aeSIke Panhc return error; 1003f63409aeSIke Panhc } 1004f63409aeSIke Panhc 10057451a55aSAxel Lin static void ideapad_input_exit(struct ideapad_private *priv) 1006f63409aeSIke Panhc { 10078693ae84SIke Panhc input_unregister_device(priv->inputdev); 10088693ae84SIke Panhc priv->inputdev = NULL; 1009f63409aeSIke Panhc } 1010f63409aeSIke Panhc 10118693ae84SIke Panhc static void ideapad_input_report(struct ideapad_private *priv, 10128693ae84SIke Panhc unsigned long scancode) 1013f63409aeSIke Panhc { 10148693ae84SIke Panhc sparse_keymap_report_event(priv->inputdev, scancode, 1, true); 1015f63409aeSIke Panhc } 1016f63409aeSIke Panhc 1017f43d9ec0SIke Panhc static void ideapad_input_novokey(struct ideapad_private *priv) 1018f43d9ec0SIke Panhc { 1019f43d9ec0SIke Panhc unsigned long long_pressed; 1020f43d9ec0SIke Panhc 1021331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_NOVO, &long_pressed)) 1022f43d9ec0SIke Panhc return; 1023f43d9ec0SIke Panhc if (long_pressed) 1024f43d9ec0SIke Panhc ideapad_input_report(priv, 17); 1025f43d9ec0SIke Panhc else 1026f43d9ec0SIke Panhc ideapad_input_report(priv, 16); 1027f43d9ec0SIke Panhc } 1028f43d9ec0SIke Panhc 1029296f9fe0SMaxim Mikityanskiy static void ideapad_check_special_buttons(struct ideapad_private *priv) 1030296f9fe0SMaxim Mikityanskiy { 1031296f9fe0SMaxim Mikityanskiy unsigned long bit, value; 1032296f9fe0SMaxim Mikityanskiy 10337be193e3SBarnabás Pőcze if (read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value)) 10347be193e3SBarnabás Pőcze return; 1035296f9fe0SMaxim Mikityanskiy 10360c4915b6SBarnabás Pőcze for_each_set_bit (bit, &value, 16) { 1037296f9fe0SMaxim Mikityanskiy switch (bit) { 1038a1ec56edSMaxim Mikityanskiy case 0: /* Z580 */ 1039a1ec56edSMaxim Mikityanskiy case 6: /* Z570 */ 1040296f9fe0SMaxim Mikityanskiy /* Thermal Management button */ 1041296f9fe0SMaxim Mikityanskiy ideapad_input_report(priv, 65); 1042296f9fe0SMaxim Mikityanskiy break; 1043296f9fe0SMaxim Mikityanskiy case 1: 1044296f9fe0SMaxim Mikityanskiy /* OneKey Theater button */ 1045296f9fe0SMaxim Mikityanskiy ideapad_input_report(priv, 64); 1046296f9fe0SMaxim Mikityanskiy break; 1047a1ec56edSMaxim Mikityanskiy default: 1048654324c4SBarnabás Pőcze dev_info(&priv->platform_device->dev, 1049654324c4SBarnabás Pőcze "Unknown special button: %lu\n", bit); 1050a1ec56edSMaxim Mikityanskiy break; 1051296f9fe0SMaxim Mikityanskiy } 1052296f9fe0SMaxim Mikityanskiy } 1053296f9fe0SMaxim Mikityanskiy } 1054296f9fe0SMaxim Mikityanskiy 1055a4b5a279SIke Panhc /* 1056a4ecbb8aSIke Panhc * backlight 1057a4ecbb8aSIke Panhc */ 1058a4ecbb8aSIke Panhc static int ideapad_backlight_get_brightness(struct backlight_device *blightdev) 1059a4ecbb8aSIke Panhc { 1060331e0ea2SZhang Rui struct ideapad_private *priv = bl_get_data(blightdev); 1061a4ecbb8aSIke Panhc unsigned long now; 10627be193e3SBarnabás Pőcze int err; 1063a4ecbb8aSIke Panhc 10647be193e3SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); 10657be193e3SBarnabás Pőcze if (err) 10667be193e3SBarnabás Pőcze return err; 1067a4ecbb8aSIke Panhc return now; 1068a4ecbb8aSIke Panhc } 1069a4ecbb8aSIke Panhc 1070a4ecbb8aSIke Panhc static int ideapad_backlight_update_status(struct backlight_device *blightdev) 1071a4ecbb8aSIke Panhc { 1072331e0ea2SZhang Rui struct ideapad_private *priv = bl_get_data(blightdev); 10737be193e3SBarnabás Pőcze int err; 1074331e0ea2SZhang Rui 10757be193e3SBarnabás Pőcze err = write_ec_cmd(priv->adev->handle, VPCCMD_W_BL, 10767be193e3SBarnabás Pőcze blightdev->props.brightness); 10777be193e3SBarnabás Pőcze if (err) 10787be193e3SBarnabás Pőcze return err; 10797be193e3SBarnabás Pőcze err = write_ec_cmd(priv->adev->handle, VPCCMD_W_BL_POWER, 10807be193e3SBarnabás Pőcze blightdev->props.power != FB_BLANK_POWERDOWN); 10817be193e3SBarnabás Pőcze if (err) 10827be193e3SBarnabás Pőcze return err; 1083a4ecbb8aSIke Panhc 1084a4ecbb8aSIke Panhc return 0; 1085a4ecbb8aSIke Panhc } 1086a4ecbb8aSIke Panhc 1087a4ecbb8aSIke Panhc static const struct backlight_ops ideapad_backlight_ops = { 1088a4ecbb8aSIke Panhc .get_brightness = ideapad_backlight_get_brightness, 1089a4ecbb8aSIke Panhc .update_status = ideapad_backlight_update_status, 1090a4ecbb8aSIke Panhc }; 1091a4ecbb8aSIke Panhc 1092a4ecbb8aSIke Panhc static int ideapad_backlight_init(struct ideapad_private *priv) 1093a4ecbb8aSIke Panhc { 1094a4ecbb8aSIke Panhc struct backlight_device *blightdev; 1095a4ecbb8aSIke Panhc struct backlight_properties props; 1096a4ecbb8aSIke Panhc unsigned long max, now, power; 10977be193e3SBarnabás Pőcze int err; 1098a4ecbb8aSIke Panhc 10997be193e3SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &max); 11007be193e3SBarnabás Pőcze if (err) 11017be193e3SBarnabás Pőcze return err; 11027be193e3SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); 11037be193e3SBarnabás Pőcze if (err) 11047be193e3SBarnabás Pőcze return err; 11057be193e3SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power); 11067be193e3SBarnabás Pőcze if (err) 11077be193e3SBarnabás Pőcze return err; 1108a4ecbb8aSIke Panhc 1109a4ecbb8aSIke Panhc memset(&props, 0, sizeof(struct backlight_properties)); 1110a4ecbb8aSIke Panhc props.max_brightness = max; 1111a4ecbb8aSIke Panhc props.type = BACKLIGHT_PLATFORM; 1112a4ecbb8aSIke Panhc blightdev = backlight_device_register("ideapad", 1113a4ecbb8aSIke Panhc &priv->platform_device->dev, 1114a4ecbb8aSIke Panhc priv, 1115a4ecbb8aSIke Panhc &ideapad_backlight_ops, 1116a4ecbb8aSIke Panhc &props); 1117a4ecbb8aSIke Panhc if (IS_ERR(blightdev)) { 1118654324c4SBarnabás Pőcze dev_err(&priv->platform_device->dev, 1119654324c4SBarnabás Pőcze "Could not register backlight device\n"); 1120a4ecbb8aSIke Panhc return PTR_ERR(blightdev); 1121a4ecbb8aSIke Panhc } 1122a4ecbb8aSIke Panhc 1123a4ecbb8aSIke Panhc priv->blightdev = blightdev; 1124a4ecbb8aSIke Panhc blightdev->props.brightness = now; 1125a4ecbb8aSIke Panhc blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; 1126a4ecbb8aSIke Panhc backlight_update_status(blightdev); 1127a4ecbb8aSIke Panhc 1128a4ecbb8aSIke Panhc return 0; 1129a4ecbb8aSIke Panhc } 1130a4ecbb8aSIke Panhc 1131a4ecbb8aSIke Panhc static void ideapad_backlight_exit(struct ideapad_private *priv) 1132a4ecbb8aSIke Panhc { 1133a4ecbb8aSIke Panhc backlight_device_unregister(priv->blightdev); 1134a4ecbb8aSIke Panhc priv->blightdev = NULL; 1135a4ecbb8aSIke Panhc } 1136a4ecbb8aSIke Panhc 1137a4ecbb8aSIke Panhc static void ideapad_backlight_notify_power(struct ideapad_private *priv) 1138a4ecbb8aSIke Panhc { 1139a4ecbb8aSIke Panhc unsigned long power; 1140a4ecbb8aSIke Panhc struct backlight_device *blightdev = priv->blightdev; 1141a4ecbb8aSIke Panhc 1142d4afc775SRene Bollford if (!blightdev) 1143d4afc775SRene Bollford return; 1144331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power)) 1145a4ecbb8aSIke Panhc return; 1146a4ecbb8aSIke Panhc blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; 1147a4ecbb8aSIke Panhc } 1148a4ecbb8aSIke Panhc 1149a4ecbb8aSIke Panhc static void ideapad_backlight_notify_brightness(struct ideapad_private *priv) 1150a4ecbb8aSIke Panhc { 1151a4ecbb8aSIke Panhc unsigned long now; 1152a4ecbb8aSIke Panhc 1153a4ecbb8aSIke Panhc /* if we control brightness via acpi video driver */ 1154a4ecbb8aSIke Panhc if (priv->blightdev == NULL) { 1155331e0ea2SZhang Rui read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); 1156a4ecbb8aSIke Panhc return; 1157a4ecbb8aSIke Panhc } 1158a4ecbb8aSIke Panhc 1159a4ecbb8aSIke Panhc backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY); 1160a4ecbb8aSIke Panhc } 1161a4ecbb8aSIke Panhc 1162a4ecbb8aSIke Panhc /* 1163a4b5a279SIke Panhc * module init/exit 1164a4b5a279SIke Panhc */ 116575a11f11SZhang Rui static void ideapad_sync_touchpad_state(struct ideapad_private *priv) 116607a4a4fcSMaxim Mikityanskiy { 116707a4a4fcSMaxim Mikityanskiy unsigned long value; 116807a4a4fcSMaxim Mikityanskiy 11691c59de4aSBarnabás Pőcze if (!priv->features.touchpad_ctrl_via_ec) 1170d69cd7eeSJiaxun Yang return; 1171d69cd7eeSJiaxun Yang 117207a4a4fcSMaxim Mikityanskiy /* Without reading from EC touchpad LED doesn't switch state */ 117375a11f11SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) { 117407a4a4fcSMaxim Mikityanskiy /* Some IdeaPads don't really turn off touchpad - they only 117507a4a4fcSMaxim Mikityanskiy * switch the LED state. We (de)activate KBC AUX port to turn 117607a4a4fcSMaxim Mikityanskiy * touchpad off and on. We send KEY_TOUCHPAD_OFF and 117707a4a4fcSMaxim Mikityanskiy * KEY_TOUCHPAD_ON to not to get out of sync with LED */ 117807a4a4fcSMaxim Mikityanskiy unsigned char param; 117907a4a4fcSMaxim Mikityanskiy i8042_command(¶m, value ? I8042_CMD_AUX_ENABLE : 118007a4a4fcSMaxim Mikityanskiy I8042_CMD_AUX_DISABLE); 118107a4a4fcSMaxim Mikityanskiy ideapad_input_report(priv, value ? 67 : 66); 118207a4a4fcSMaxim Mikityanskiy } 118307a4a4fcSMaxim Mikityanskiy } 118407a4a4fcSMaxim Mikityanskiy 1185b5c37b79SZhang Rui static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) 118657ac3b05SIke Panhc { 1187b5c37b79SZhang Rui struct ideapad_private *priv = data; 11880c4915b6SBarnabás Pőcze unsigned long vpc1, vpc2, bit; 118957ac3b05SIke Panhc 11902be1dc21SIke Panhc if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1)) 119157ac3b05SIke Panhc return; 11922be1dc21SIke Panhc if (read_ec_data(handle, VPCCMD_R_VPC2, &vpc2)) 119357ac3b05SIke Panhc return; 119457ac3b05SIke Panhc 119557ac3b05SIke Panhc vpc1 = (vpc2 << 8) | vpc1; 11960c4915b6SBarnabás Pőcze 11970c4915b6SBarnabás Pőcze for_each_set_bit (bit, &vpc1, 16) { 11980c4915b6SBarnabás Pőcze switch (bit) { 1199a4ecbb8aSIke Panhc case 9: 1200923de84aSIke Panhc ideapad_sync_rfk_state(priv); 1201a4ecbb8aSIke Panhc break; 120220a769c1SIke Panhc case 13: 1203296f9fe0SMaxim Mikityanskiy case 11: 120448f67d62SAlex Hung case 8: 1205296f9fe0SMaxim Mikityanskiy case 7: 120620a769c1SIke Panhc case 6: 12070c4915b6SBarnabás Pőcze ideapad_input_report(priv, bit); 120820a769c1SIke Panhc break; 120907a4a4fcSMaxim Mikityanskiy case 5: 121075a11f11SZhang Rui ideapad_sync_touchpad_state(priv); 121107a4a4fcSMaxim Mikityanskiy break; 1212a4ecbb8aSIke Panhc case 4: 1213a4ecbb8aSIke Panhc ideapad_backlight_notify_brightness(priv); 1214a4ecbb8aSIke Panhc break; 1215f43d9ec0SIke Panhc case 3: 1216f43d9ec0SIke Panhc ideapad_input_novokey(priv); 1217f43d9ec0SIke Panhc break; 1218a4ecbb8aSIke Panhc case 2: 1219a4ecbb8aSIke Panhc ideapad_backlight_notify_power(priv); 1220a4ecbb8aSIke Panhc break; 1221296f9fe0SMaxim Mikityanskiy case 0: 1222296f9fe0SMaxim Mikityanskiy ideapad_check_special_buttons(priv); 1223296f9fe0SMaxim Mikityanskiy break; 12243cfd956bSHao Wei Tee case 1: 12253cfd956bSHao Wei Tee /* Some IdeaPads report event 1 every ~20 12263cfd956bSHao Wei Tee * seconds while on battery power; some 12273cfd956bSHao Wei Tee * report this when changing to/from tablet 12283cfd956bSHao Wei Tee * mode. Squelch this event. 12293cfd956bSHao Wei Tee */ 12303cfd956bSHao Wei Tee break; 1231a4ecbb8aSIke Panhc default: 1232654324c4SBarnabás Pőcze dev_info(&priv->platform_device->dev, 1233654324c4SBarnabás Pőcze "Unknown event: %lu\n", bit); 123457ac3b05SIke Panhc } 123557ac3b05SIke Panhc } 1236a4ecbb8aSIke Panhc } 123757ac3b05SIke Panhc 123874caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 123974caab99SArnd Bergmann static void ideapad_wmi_notify(u32 value, void *context) 124074caab99SArnd Bergmann { 1241654324c4SBarnabás Pőcze struct ideapad_private *priv = context; 1242654324c4SBarnabás Pőcze 124374caab99SArnd Bergmann switch (value) { 124474caab99SArnd Bergmann case 128: 1245654324c4SBarnabás Pőcze ideapad_input_report(priv, value); 124674caab99SArnd Bergmann break; 124774caab99SArnd Bergmann default: 1248654324c4SBarnabás Pőcze dev_info(&priv->platform_device->dev, 1249654324c4SBarnabás Pőcze "Unknown WMI event: %u\n", value); 125074caab99SArnd Bergmann } 125174caab99SArnd Bergmann } 125274caab99SArnd Bergmann #endif 125374caab99SArnd Bergmann 1254ce363c2bSHans de Goede /* 12555105e78eSHans de Goede * Some ideapads have a hardware rfkill switch, but most do not have one. 12565105e78eSHans de Goede * Reading VPCCMD_R_RF always results in 0 on models without a hardware rfkill, 12575105e78eSHans de Goede * switch causing ideapad_laptop to wrongly report all radios as hw-blocked. 12585105e78eSHans de Goede * There used to be a long list of DMI ids for models without a hw rfkill 12595105e78eSHans de Goede * switch here, but that resulted in playing whack a mole. 12605105e78eSHans de Goede * More importantly wrongly reporting the wifi radio as hw-blocked, results in 12615105e78eSHans de Goede * non working wifi. Whereas not reporting it hw-blocked, when it actually is 12625105e78eSHans de Goede * hw-blocked results in an empty SSID list, which is a much more benign 12635105e78eSHans de Goede * failure mode. 12645105e78eSHans de Goede * So the default now is the much safer option of assuming there is no 12655105e78eSHans de Goede * hardware rfkill switch. This default also actually matches most hardware, 12665105e78eSHans de Goede * since having a hw rfkill switch is quite rare on modern hardware, so this 12675105e78eSHans de Goede * also leads to a much shorter list. 1268ce363c2bSHans de Goede */ 12695105e78eSHans de Goede static const struct dmi_system_id hw_rfkill_list[] = { 127085093f79SHans de Goede {} 127185093f79SHans de Goede }; 127285093f79SHans de Goede 12731c59de4aSBarnabás Pőcze static void ideapad_check_features(struct ideapad_private *priv) 12741c59de4aSBarnabás Pőcze { 12751c59de4aSBarnabás Pőcze acpi_handle handle = priv->adev->handle; 12761c59de4aSBarnabás Pőcze unsigned long val; 12771c59de4aSBarnabás Pőcze 12781c59de4aSBarnabás Pőcze priv->features.hw_rfkill_switch = dmi_check_system(hw_rfkill_list); 12791c59de4aSBarnabás Pőcze 12801c59de4aSBarnabás Pőcze /* Most ideapads with ELAN0634 touchpad don't use EC touchpad switch */ 12811c59de4aSBarnabás Pőcze priv->features.touchpad_ctrl_via_ec = !acpi_dev_present("ELAN0634", NULL, -1); 12821c59de4aSBarnabás Pőcze 12831c59de4aSBarnabás Pőcze if (!read_ec_data(handle, VPCCMD_R_FAN, &val)) 12841c59de4aSBarnabás Pőcze priv->features.fan_mode = true; 12851c59de4aSBarnabás Pőcze 12861c59de4aSBarnabás Pőcze if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) 12871c59de4aSBarnabás Pőcze priv->features.conservation_mode = true; 12881c59de4aSBarnabás Pőcze 12891c59de4aSBarnabás Pőcze if (acpi_has_method(handle, "DYTC")) 12901c59de4aSBarnabás Pőcze priv->features.dytc = true; 12911c59de4aSBarnabás Pőcze 1292392cbf0aSBarnabás Pőcze if (acpi_has_method(handle, "HALS") && acpi_has_method(handle, "SALS")) { 1293392cbf0aSBarnabás Pőcze if (!eval_hals(handle, &val)) { 1294392cbf0aSBarnabás Pőcze if (test_bit(HALS_FNLOCK_SUPPORT_BIT, &val)) 12951c59de4aSBarnabás Pőcze priv->features.fn_lock = true; 12961c59de4aSBarnabás Pőcze } 1297392cbf0aSBarnabás Pőcze } 1298392cbf0aSBarnabás Pőcze } 12991c59de4aSBarnabás Pőcze 1300b5c37b79SZhang Rui static int ideapad_acpi_add(struct platform_device *pdev) 1301b5c37b79SZhang Rui { 1302b5c37b79SZhang Rui int ret, i; 1303b5c37b79SZhang Rui struct ideapad_private *priv; 1304b5c37b79SZhang Rui struct acpi_device *adev; 1305803be832SBarnabás Pőcze acpi_status status; 1306ff36b0d9SBarnabás Pőcze unsigned long cfg; 1307b5c37b79SZhang Rui 1308b5c37b79SZhang Rui ret = acpi_bus_get_device(ACPI_HANDLE(&pdev->dev), &adev); 1309b5c37b79SZhang Rui if (ret) 1310b5c37b79SZhang Rui return -ENODEV; 1311b5c37b79SZhang Rui 1312ff36b0d9SBarnabás Pőcze if (eval_int(adev->handle, "_CFG", &cfg)) 1313b5c37b79SZhang Rui return -ENODEV; 1314b5c37b79SZhang Rui 1315b3facd7bSHimangi Saraogi priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 1316b5c37b79SZhang Rui if (!priv) 1317b5c37b79SZhang Rui return -ENOMEM; 1318b5c37b79SZhang Rui 1319b5c37b79SZhang Rui dev_set_drvdata(&pdev->dev, priv); 1320b5c37b79SZhang Rui priv->cfg = cfg; 1321b5c37b79SZhang Rui priv->adev = adev; 1322b5c37b79SZhang Rui priv->platform_device = pdev; 1323b5c37b79SZhang Rui 13241c59de4aSBarnabás Pőcze ideapad_check_features(priv); 1325d69cd7eeSJiaxun Yang 1326b5c37b79SZhang Rui ret = ideapad_sysfs_init(priv); 1327b5c37b79SZhang Rui if (ret) 1328b3facd7bSHimangi Saraogi return ret; 1329b5c37b79SZhang Rui 133017f1bf38SGreg Kroah-Hartman ideapad_debugfs_init(priv); 1331b5c37b79SZhang Rui 1332b5c37b79SZhang Rui ret = ideapad_input_init(priv); 1333b5c37b79SZhang Rui if (ret) 1334b5c37b79SZhang Rui goto input_failed; 1335b5c37b79SZhang Rui 1336ce363c2bSHans de Goede /* 1337ce363c2bSHans de Goede * On some models without a hw-switch (the yoga 2 13 at least) 1338ce363c2bSHans de Goede * VPCCMD_W_RF must be explicitly set to 1 for the wifi to work. 1339ce363c2bSHans de Goede */ 13401c59de4aSBarnabás Pőcze if (!priv->features.hw_rfkill_switch) 1341ce363c2bSHans de Goede write_ec_cmd(priv->adev->handle, VPCCMD_W_RF, 1); 1342ce363c2bSHans de Goede 1343d69cd7eeSJiaxun Yang /* The same for Touchpad */ 13441c59de4aSBarnabás Pőcze if (!priv->features.touchpad_ctrl_via_ec) 1345d69cd7eeSJiaxun Yang write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, 1); 1346d69cd7eeSJiaxun Yang 134785093f79SHans de Goede for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 1348b5c37b79SZhang Rui if (test_bit(ideapad_rfk_data[i].cfgbit, &priv->cfg)) 1349b5c37b79SZhang Rui ideapad_register_rfkill(priv, i); 1350ce363c2bSHans de Goede 1351b5c37b79SZhang Rui ideapad_sync_rfk_state(priv); 1352b5c37b79SZhang Rui ideapad_sync_touchpad_state(priv); 1353b5c37b79SZhang Rui 13541c59de4aSBarnabás Pőcze ret = ideapad_dytc_profile_init(priv); 13551c59de4aSBarnabás Pőcze if (ret) { 13561c59de4aSBarnabás Pőcze if (ret != -ENODEV) 13571c59de4aSBarnabás Pőcze dev_warn(&pdev->dev, "Could not set up DYTC interface: %d\n", ret); 13581c59de4aSBarnabás Pőcze else 13591c59de4aSBarnabás Pőcze dev_info(&pdev->dev, "DYTC interface is not available\n"); 13601c59de4aSBarnabás Pőcze } 1361eabe5339SJiaxun Yang 136226bff5f0SHans de Goede if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { 1363b5c37b79SZhang Rui ret = ideapad_backlight_init(priv); 1364b5c37b79SZhang Rui if (ret && ret != -ENODEV) 1365b5c37b79SZhang Rui goto backlight_failed; 1366b5c37b79SZhang Rui } 1367803be832SBarnabás Pőcze status = acpi_install_notify_handler(adev->handle, 1368803be832SBarnabás Pőcze ACPI_DEVICE_NOTIFY, 1369803be832SBarnabás Pőcze ideapad_acpi_notify, priv); 1370803be832SBarnabás Pőcze if (ACPI_FAILURE(status)) { 1371803be832SBarnabás Pőcze ret = -EIO; 1372b5c37b79SZhang Rui goto notification_failed; 1373803be832SBarnabás Pőcze } 13742d98e0b9SArnd Bergmann 137574caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 13762d98e0b9SArnd Bergmann for (i = 0; i < ARRAY_SIZE(ideapad_wmi_fnesc_events); i++) { 1377803be832SBarnabás Pőcze status = wmi_install_notify_handler(ideapad_wmi_fnesc_events[i], 13782d98e0b9SArnd Bergmann ideapad_wmi_notify, priv); 1379803be832SBarnabás Pőcze if (ACPI_SUCCESS(status)) { 13802d98e0b9SArnd Bergmann priv->fnesc_guid = ideapad_wmi_fnesc_events[i]; 13812d98e0b9SArnd Bergmann break; 13822d98e0b9SArnd Bergmann } 13832d98e0b9SArnd Bergmann } 1384803be832SBarnabás Pőcze if (ACPI_FAILURE(status) && status != AE_NOT_EXIST) { 1385803be832SBarnabás Pőcze ret = -EIO; 138674caab99SArnd Bergmann goto notification_failed_wmi; 1387803be832SBarnabás Pőcze } 138874caab99SArnd Bergmann #endif 1389b5c37b79SZhang Rui 1390b5c37b79SZhang Rui return 0; 139174caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 139274caab99SArnd Bergmann notification_failed_wmi: 139374caab99SArnd Bergmann acpi_remove_notify_handler(priv->adev->handle, 139474caab99SArnd Bergmann ACPI_DEVICE_NOTIFY, ideapad_acpi_notify); 139574caab99SArnd Bergmann #endif 1396b5c37b79SZhang Rui notification_failed: 1397b5c37b79SZhang Rui ideapad_backlight_exit(priv); 1398b5c37b79SZhang Rui backlight_failed: 1399caa315b8SBarnabás Pőcze ideapad_dytc_profile_exit(priv); 1400b5c37b79SZhang Rui for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 1401b5c37b79SZhang Rui ideapad_unregister_rfkill(priv, i); 1402b5c37b79SZhang Rui ideapad_input_exit(priv); 1403b5c37b79SZhang Rui input_failed: 1404b5c37b79SZhang Rui ideapad_debugfs_exit(priv); 1405b5c37b79SZhang Rui ideapad_sysfs_exit(priv); 1406b5c37b79SZhang Rui return ret; 1407b5c37b79SZhang Rui } 1408b5c37b79SZhang Rui 1409b5c37b79SZhang Rui static int ideapad_acpi_remove(struct platform_device *pdev) 1410b5c37b79SZhang Rui { 1411b5c37b79SZhang Rui struct ideapad_private *priv = dev_get_drvdata(&pdev->dev); 1412b5c37b79SZhang Rui int i; 1413b5c37b79SZhang Rui 141474caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 14152d98e0b9SArnd Bergmann if (priv->fnesc_guid) 14162d98e0b9SArnd Bergmann wmi_remove_notify_handler(priv->fnesc_guid); 141774caab99SArnd Bergmann #endif 1418b5c37b79SZhang Rui acpi_remove_notify_handler(priv->adev->handle, 1419b5c37b79SZhang Rui ACPI_DEVICE_NOTIFY, ideapad_acpi_notify); 1420b5c37b79SZhang Rui ideapad_backlight_exit(priv); 1421eabe5339SJiaxun Yang ideapad_dytc_profile_exit(priv); 1422b5c37b79SZhang Rui for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 1423b5c37b79SZhang Rui ideapad_unregister_rfkill(priv, i); 1424b5c37b79SZhang Rui ideapad_input_exit(priv); 1425b5c37b79SZhang Rui ideapad_debugfs_exit(priv); 1426b5c37b79SZhang Rui ideapad_sysfs_exit(priv); 1427b5c37b79SZhang Rui 1428b5c37b79SZhang Rui return 0; 1429b5c37b79SZhang Rui } 1430b5c37b79SZhang Rui 143111fa8da5SZhang Rui #ifdef CONFIG_PM_SLEEP 1432e1a39a44SBarnabás Pőcze static int ideapad_acpi_resume(struct device *dev) 143307a4a4fcSMaxim Mikityanskiy { 1434e1a39a44SBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 143575a11f11SZhang Rui 143675a11f11SZhang Rui ideapad_sync_rfk_state(priv); 143775a11f11SZhang Rui ideapad_sync_touchpad_state(priv); 1438eabe5339SJiaxun Yang 1439eabe5339SJiaxun Yang if (priv->dytc) 1440eabe5339SJiaxun Yang dytc_profile_refresh(priv); 1441eabe5339SJiaxun Yang 144207a4a4fcSMaxim Mikityanskiy return 0; 144307a4a4fcSMaxim Mikityanskiy } 1444b5c37b79SZhang Rui #endif 144507a4a4fcSMaxim Mikityanskiy static SIMPLE_DEV_PM_OPS(ideapad_pm, NULL, ideapad_acpi_resume); 144607a4a4fcSMaxim Mikityanskiy 1447b5c37b79SZhang Rui static const struct acpi_device_id ideapad_device_ids[] = { 1448b5c37b79SZhang Rui { "VPC2004", 0}, 1449b5c37b79SZhang Rui { "", 0}, 145057ac3b05SIke Panhc }; 1451b5c37b79SZhang Rui MODULE_DEVICE_TABLE(acpi, ideapad_device_ids); 1452b5c37b79SZhang Rui 1453b5c37b79SZhang Rui static struct platform_driver ideapad_acpi_driver = { 1454b5c37b79SZhang Rui .probe = ideapad_acpi_add, 1455b5c37b79SZhang Rui .remove = ideapad_acpi_remove, 1456b5c37b79SZhang Rui .driver = { 1457b5c37b79SZhang Rui .name = "ideapad_acpi", 1458b5c37b79SZhang Rui .pm = &ideapad_pm, 1459b5c37b79SZhang Rui .acpi_match_table = ACPI_PTR(ideapad_device_ids), 1460b5c37b79SZhang Rui }, 1461b5c37b79SZhang Rui }; 1462b5c37b79SZhang Rui 1463b5c37b79SZhang Rui module_platform_driver(ideapad_acpi_driver); 146457ac3b05SIke Panhc 146557ac3b05SIke Panhc MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); 146657ac3b05SIke Panhc MODULE_DESCRIPTION("IdeaPad ACPI Extras"); 146757ac3b05SIke Panhc MODULE_LICENSE("GPL"); 1468