162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * ideapad-laptop.c - Lenovo IdeaPad ACPI Extras 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright © 2010 Intel Corporation 662306a36Sopenharmony_ci * Copyright © 2010 David Woodhouse <dwmw2@infradead.org> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/acpi.h> 1262306a36Sopenharmony_ci#include <linux/backlight.h> 1362306a36Sopenharmony_ci#include <linux/bitfield.h> 1462306a36Sopenharmony_ci#include <linux/bitops.h> 1562306a36Sopenharmony_ci#include <linux/bug.h> 1662306a36Sopenharmony_ci#include <linux/debugfs.h> 1762306a36Sopenharmony_ci#include <linux/device.h> 1862306a36Sopenharmony_ci#include <linux/dmi.h> 1962306a36Sopenharmony_ci#include <linux/fb.h> 2062306a36Sopenharmony_ci#include <linux/i8042.h> 2162306a36Sopenharmony_ci#include <linux/init.h> 2262306a36Sopenharmony_ci#include <linux/input.h> 2362306a36Sopenharmony_ci#include <linux/input/sparse-keymap.h> 2462306a36Sopenharmony_ci#include <linux/kernel.h> 2562306a36Sopenharmony_ci#include <linux/leds.h> 2662306a36Sopenharmony_ci#include <linux/module.h> 2762306a36Sopenharmony_ci#include <linux/platform_device.h> 2862306a36Sopenharmony_ci#include <linux/platform_profile.h> 2962306a36Sopenharmony_ci#include <linux/rfkill.h> 3062306a36Sopenharmony_ci#include <linux/seq_file.h> 3162306a36Sopenharmony_ci#include <linux/sysfs.h> 3262306a36Sopenharmony_ci#include <linux/types.h> 3362306a36Sopenharmony_ci#include <linux/wmi.h> 3462306a36Sopenharmony_ci#include "ideapad-laptop.h" 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci#include <acpi/video.h> 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci#include <dt-bindings/leds/common.h> 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci#define IDEAPAD_RFKILL_DEV_NUM 3 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_cienum { 4362306a36Sopenharmony_ci CFG_CAP_BT_BIT = 16, 4462306a36Sopenharmony_ci CFG_CAP_3G_BIT = 17, 4562306a36Sopenharmony_ci CFG_CAP_WIFI_BIT = 18, 4662306a36Sopenharmony_ci CFG_CAP_CAM_BIT = 19, 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci /* 4962306a36Sopenharmony_ci * These are OnScreenDisplay support bits that can be useful to determine 5062306a36Sopenharmony_ci * whether a hotkey exists/should show OSD. But they aren't particularly 5162306a36Sopenharmony_ci * meaningful since they were introduced later, i.e. 2010 IdeaPads 5262306a36Sopenharmony_ci * don't have these, but they still have had OSD for hotkeys. 5362306a36Sopenharmony_ci */ 5462306a36Sopenharmony_ci CFG_OSD_NUMLK_BIT = 27, 5562306a36Sopenharmony_ci CFG_OSD_CAPSLK_BIT = 28, 5662306a36Sopenharmony_ci CFG_OSD_MICMUTE_BIT = 29, 5762306a36Sopenharmony_ci CFG_OSD_TOUCHPAD_BIT = 30, 5862306a36Sopenharmony_ci CFG_OSD_CAM_BIT = 31, 5962306a36Sopenharmony_ci}; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cienum { 6262306a36Sopenharmony_ci GBMD_CONSERVATION_STATE_BIT = 5, 6362306a36Sopenharmony_ci}; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_cienum { 6662306a36Sopenharmony_ci SBMC_CONSERVATION_ON = 3, 6762306a36Sopenharmony_ci SBMC_CONSERVATION_OFF = 5, 6862306a36Sopenharmony_ci}; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cienum { 7162306a36Sopenharmony_ci HALS_KBD_BL_SUPPORT_BIT = 4, 7262306a36Sopenharmony_ci HALS_KBD_BL_STATE_BIT = 5, 7362306a36Sopenharmony_ci HALS_USB_CHARGING_SUPPORT_BIT = 6, 7462306a36Sopenharmony_ci HALS_USB_CHARGING_STATE_BIT = 7, 7562306a36Sopenharmony_ci HALS_FNLOCK_SUPPORT_BIT = 9, 7662306a36Sopenharmony_ci HALS_FNLOCK_STATE_BIT = 10, 7762306a36Sopenharmony_ci HALS_HOTKEYS_PRIMARY_BIT = 11, 7862306a36Sopenharmony_ci}; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_cienum { 8162306a36Sopenharmony_ci SALS_KBD_BL_ON = 0x8, 8262306a36Sopenharmony_ci SALS_KBD_BL_OFF = 0x9, 8362306a36Sopenharmony_ci SALS_USB_CHARGING_ON = 0xa, 8462306a36Sopenharmony_ci SALS_USB_CHARGING_OFF = 0xb, 8562306a36Sopenharmony_ci SALS_FNLOCK_ON = 0xe, 8662306a36Sopenharmony_ci SALS_FNLOCK_OFF = 0xf, 8762306a36Sopenharmony_ci}; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci/* 9062306a36Sopenharmony_ci * These correspond to the number of supported states - 1 9162306a36Sopenharmony_ci * Future keyboard types may need a new system, if there's a collision 9262306a36Sopenharmony_ci * KBD_BL_TRISTATE_AUTO has no way to report or set the auto state 9362306a36Sopenharmony_ci * so it effectively has 3 states, but needs to handle 4 9462306a36Sopenharmony_ci */ 9562306a36Sopenharmony_cienum { 9662306a36Sopenharmony_ci KBD_BL_STANDARD = 1, 9762306a36Sopenharmony_ci KBD_BL_TRISTATE = 2, 9862306a36Sopenharmony_ci KBD_BL_TRISTATE_AUTO = 3, 9962306a36Sopenharmony_ci}; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci#define KBD_BL_QUERY_TYPE 0x1 10262306a36Sopenharmony_ci#define KBD_BL_TRISTATE_TYPE 0x5 10362306a36Sopenharmony_ci#define KBD_BL_TRISTATE_AUTO_TYPE 0x7 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci#define KBD_BL_COMMAND_GET 0x2 10662306a36Sopenharmony_ci#define KBD_BL_COMMAND_SET 0x3 10762306a36Sopenharmony_ci#define KBD_BL_COMMAND_TYPE GENMASK(7, 4) 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci#define KBD_BL_GET_BRIGHTNESS GENMASK(15, 1) 11062306a36Sopenharmony_ci#define KBD_BL_SET_BRIGHTNESS GENMASK(19, 16) 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci#define KBD_BL_KBLC_CHANGED_EVENT 12 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_cistruct ideapad_dytc_priv { 11562306a36Sopenharmony_ci enum platform_profile_option current_profile; 11662306a36Sopenharmony_ci struct platform_profile_handler pprof; 11762306a36Sopenharmony_ci struct mutex mutex; /* protects the DYTC interface */ 11862306a36Sopenharmony_ci struct ideapad_private *priv; 11962306a36Sopenharmony_ci}; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_cistruct ideapad_rfk_priv { 12262306a36Sopenharmony_ci int dev; 12362306a36Sopenharmony_ci struct ideapad_private *priv; 12462306a36Sopenharmony_ci}; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_cistruct ideapad_private { 12762306a36Sopenharmony_ci struct acpi_device *adev; 12862306a36Sopenharmony_ci struct rfkill *rfk[IDEAPAD_RFKILL_DEV_NUM]; 12962306a36Sopenharmony_ci struct ideapad_rfk_priv rfk_priv[IDEAPAD_RFKILL_DEV_NUM]; 13062306a36Sopenharmony_ci struct platform_device *platform_device; 13162306a36Sopenharmony_ci struct input_dev *inputdev; 13262306a36Sopenharmony_ci struct backlight_device *blightdev; 13362306a36Sopenharmony_ci struct ideapad_dytc_priv *dytc; 13462306a36Sopenharmony_ci struct dentry *debug; 13562306a36Sopenharmony_ci unsigned long cfg; 13662306a36Sopenharmony_ci unsigned long r_touchpad_val; 13762306a36Sopenharmony_ci struct { 13862306a36Sopenharmony_ci bool conservation_mode : 1; 13962306a36Sopenharmony_ci bool dytc : 1; 14062306a36Sopenharmony_ci bool fan_mode : 1; 14162306a36Sopenharmony_ci bool fn_lock : 1; 14262306a36Sopenharmony_ci bool set_fn_lock_led : 1; 14362306a36Sopenharmony_ci bool hw_rfkill_switch : 1; 14462306a36Sopenharmony_ci bool kbd_bl : 1; 14562306a36Sopenharmony_ci bool touchpad_ctrl_via_ec : 1; 14662306a36Sopenharmony_ci bool ctrl_ps2_aux_port : 1; 14762306a36Sopenharmony_ci bool usb_charging : 1; 14862306a36Sopenharmony_ci } features; 14962306a36Sopenharmony_ci struct { 15062306a36Sopenharmony_ci bool initialized; 15162306a36Sopenharmony_ci int type; 15262306a36Sopenharmony_ci struct led_classdev led; 15362306a36Sopenharmony_ci unsigned int last_brightness; 15462306a36Sopenharmony_ci } kbd_bl; 15562306a36Sopenharmony_ci}; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_cistatic bool no_bt_rfkill; 15862306a36Sopenharmony_cimodule_param(no_bt_rfkill, bool, 0444); 15962306a36Sopenharmony_ciMODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth."); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_cistatic bool allow_v4_dytc; 16262306a36Sopenharmony_cimodule_param(allow_v4_dytc, bool, 0444); 16362306a36Sopenharmony_ciMODULE_PARM_DESC(allow_v4_dytc, 16462306a36Sopenharmony_ci "Enable DYTC version 4 platform-profile support. " 16562306a36Sopenharmony_ci "If you need this please report this to: platform-driver-x86@vger.kernel.org"); 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_cistatic bool hw_rfkill_switch; 16862306a36Sopenharmony_cimodule_param(hw_rfkill_switch, bool, 0444); 16962306a36Sopenharmony_ciMODULE_PARM_DESC(hw_rfkill_switch, 17062306a36Sopenharmony_ci "Enable rfkill support for laptops with a hw on/off wifi switch/slider. " 17162306a36Sopenharmony_ci "If you need this please report this to: platform-driver-x86@vger.kernel.org"); 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_cistatic bool set_fn_lock_led; 17462306a36Sopenharmony_cimodule_param(set_fn_lock_led, bool, 0444); 17562306a36Sopenharmony_ciMODULE_PARM_DESC(set_fn_lock_led, 17662306a36Sopenharmony_ci "Enable driver based updates of the fn-lock LED on fn-lock changes. " 17762306a36Sopenharmony_ci "If you need this please report this to: platform-driver-x86@vger.kernel.org"); 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_cistatic bool ctrl_ps2_aux_port; 18062306a36Sopenharmony_cimodule_param(ctrl_ps2_aux_port, bool, 0444); 18162306a36Sopenharmony_ciMODULE_PARM_DESC(ctrl_ps2_aux_port, 18262306a36Sopenharmony_ci "Enable driver based PS/2 aux port en-/dis-abling on touchpad on/off toggle. " 18362306a36Sopenharmony_ci "If you need this please report this to: platform-driver-x86@vger.kernel.org"); 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_cistatic bool touchpad_ctrl_via_ec; 18662306a36Sopenharmony_cimodule_param(touchpad_ctrl_via_ec, bool, 0444); 18762306a36Sopenharmony_ciMODULE_PARM_DESC(touchpad_ctrl_via_ec, 18862306a36Sopenharmony_ci "Enable registering a 'touchpad' sysfs-attribute which can be used to manually " 18962306a36Sopenharmony_ci "tell the EC to enable/disable the touchpad. This may not work on all models."); 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci/* 19262306a36Sopenharmony_ci * shared data 19362306a36Sopenharmony_ci */ 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_cistatic struct ideapad_private *ideapad_shared; 19662306a36Sopenharmony_cistatic DEFINE_MUTEX(ideapad_shared_mutex); 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_cistatic int ideapad_shared_init(struct ideapad_private *priv) 19962306a36Sopenharmony_ci{ 20062306a36Sopenharmony_ci int ret; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci mutex_lock(&ideapad_shared_mutex); 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci if (!ideapad_shared) { 20562306a36Sopenharmony_ci ideapad_shared = priv; 20662306a36Sopenharmony_ci ret = 0; 20762306a36Sopenharmony_ci } else { 20862306a36Sopenharmony_ci dev_warn(&priv->adev->dev, "found multiple platform devices\n"); 20962306a36Sopenharmony_ci ret = -EINVAL; 21062306a36Sopenharmony_ci } 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci mutex_unlock(&ideapad_shared_mutex); 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci return ret; 21562306a36Sopenharmony_ci} 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_cistatic void ideapad_shared_exit(struct ideapad_private *priv) 21862306a36Sopenharmony_ci{ 21962306a36Sopenharmony_ci mutex_lock(&ideapad_shared_mutex); 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci if (ideapad_shared == priv) 22262306a36Sopenharmony_ci ideapad_shared = NULL; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci mutex_unlock(&ideapad_shared_mutex); 22562306a36Sopenharmony_ci} 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci/* 22862306a36Sopenharmony_ci * ACPI Helpers 22962306a36Sopenharmony_ci */ 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_cistatic int eval_int(acpi_handle handle, const char *name, unsigned long *res) 23262306a36Sopenharmony_ci{ 23362306a36Sopenharmony_ci unsigned long long result; 23462306a36Sopenharmony_ci acpi_status status; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci status = acpi_evaluate_integer(handle, (char *)name, NULL, &result); 23762306a36Sopenharmony_ci if (ACPI_FAILURE(status)) 23862306a36Sopenharmony_ci return -EIO; 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci *res = result; 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci return 0; 24362306a36Sopenharmony_ci} 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_cistatic int exec_simple_method(acpi_handle handle, const char *name, unsigned long arg) 24662306a36Sopenharmony_ci{ 24762306a36Sopenharmony_ci acpi_status status = acpi_execute_simple_method(handle, (char *)name, arg); 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci return ACPI_FAILURE(status) ? -EIO : 0; 25062306a36Sopenharmony_ci} 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_cistatic int eval_gbmd(acpi_handle handle, unsigned long *res) 25362306a36Sopenharmony_ci{ 25462306a36Sopenharmony_ci return eval_int(handle, "GBMD", res); 25562306a36Sopenharmony_ci} 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_cistatic int exec_sbmc(acpi_handle handle, unsigned long arg) 25862306a36Sopenharmony_ci{ 25962306a36Sopenharmony_ci return exec_simple_method(handle, "SBMC", arg); 26062306a36Sopenharmony_ci} 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_cistatic int eval_hals(acpi_handle handle, unsigned long *res) 26362306a36Sopenharmony_ci{ 26462306a36Sopenharmony_ci return eval_int(handle, "HALS", res); 26562306a36Sopenharmony_ci} 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_cistatic int exec_sals(acpi_handle handle, unsigned long arg) 26862306a36Sopenharmony_ci{ 26962306a36Sopenharmony_ci return exec_simple_method(handle, "SALS", arg); 27062306a36Sopenharmony_ci} 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_cistatic int exec_kblc(acpi_handle handle, unsigned long arg) 27362306a36Sopenharmony_ci{ 27462306a36Sopenharmony_ci return exec_simple_method(handle, "KBLC", arg); 27562306a36Sopenharmony_ci} 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_cistatic int eval_kblc(acpi_handle handle, unsigned long cmd, unsigned long *res) 27862306a36Sopenharmony_ci{ 27962306a36Sopenharmony_ci return eval_int_with_arg(handle, "KBLC", cmd, res); 28062306a36Sopenharmony_ci} 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_cistatic int eval_dytc(acpi_handle handle, unsigned long cmd, unsigned long *res) 28362306a36Sopenharmony_ci{ 28462306a36Sopenharmony_ci return eval_int_with_arg(handle, "DYTC", cmd, res); 28562306a36Sopenharmony_ci} 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci/* 28862306a36Sopenharmony_ci * debugfs 28962306a36Sopenharmony_ci */ 29062306a36Sopenharmony_cistatic int debugfs_status_show(struct seq_file *s, void *data) 29162306a36Sopenharmony_ci{ 29262306a36Sopenharmony_ci struct ideapad_private *priv = s->private; 29362306a36Sopenharmony_ci unsigned long value; 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &value)) 29662306a36Sopenharmony_ci seq_printf(s, "Backlight max: %lu\n", value); 29762306a36Sopenharmony_ci if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL, &value)) 29862306a36Sopenharmony_ci seq_printf(s, "Backlight now: %lu\n", value); 29962306a36Sopenharmony_ci if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &value)) 30062306a36Sopenharmony_ci seq_printf(s, "BL power value: %s (%lu)\n", value ? "on" : "off", value); 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci seq_puts(s, "=====================\n"); 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci if (!read_ec_data(priv->adev->handle, VPCCMD_R_RF, &value)) 30562306a36Sopenharmony_ci seq_printf(s, "Radio status: %s (%lu)\n", value ? "on" : "off", value); 30662306a36Sopenharmony_ci if (!read_ec_data(priv->adev->handle, VPCCMD_R_WIFI, &value)) 30762306a36Sopenharmony_ci seq_printf(s, "Wifi status: %s (%lu)\n", value ? "on" : "off", value); 30862306a36Sopenharmony_ci if (!read_ec_data(priv->adev->handle, VPCCMD_R_BT, &value)) 30962306a36Sopenharmony_ci seq_printf(s, "BT status: %s (%lu)\n", value ? "on" : "off", value); 31062306a36Sopenharmony_ci if (!read_ec_data(priv->adev->handle, VPCCMD_R_3G, &value)) 31162306a36Sopenharmony_ci seq_printf(s, "3G status: %s (%lu)\n", value ? "on" : "off", value); 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci seq_puts(s, "=====================\n"); 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) 31662306a36Sopenharmony_ci seq_printf(s, "Touchpad status: %s (%lu)\n", value ? "on" : "off", value); 31762306a36Sopenharmony_ci if (!read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &value)) 31862306a36Sopenharmony_ci seq_printf(s, "Camera status: %s (%lu)\n", value ? "on" : "off", value); 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci seq_puts(s, "=====================\n"); 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci if (!eval_gbmd(priv->adev->handle, &value)) 32362306a36Sopenharmony_ci seq_printf(s, "GBMD: %#010lx\n", value); 32462306a36Sopenharmony_ci if (!eval_hals(priv->adev->handle, &value)) 32562306a36Sopenharmony_ci seq_printf(s, "HALS: %#010lx\n", value); 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci return 0; 32862306a36Sopenharmony_ci} 32962306a36Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(debugfs_status); 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_cistatic int debugfs_cfg_show(struct seq_file *s, void *data) 33262306a36Sopenharmony_ci{ 33362306a36Sopenharmony_ci struct ideapad_private *priv = s->private; 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_ci seq_printf(s, "_CFG: %#010lx\n\n", priv->cfg); 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci seq_puts(s, "Capabilities:"); 33862306a36Sopenharmony_ci if (test_bit(CFG_CAP_BT_BIT, &priv->cfg)) 33962306a36Sopenharmony_ci seq_puts(s, " bluetooth"); 34062306a36Sopenharmony_ci if (test_bit(CFG_CAP_3G_BIT, &priv->cfg)) 34162306a36Sopenharmony_ci seq_puts(s, " 3G"); 34262306a36Sopenharmony_ci if (test_bit(CFG_CAP_WIFI_BIT, &priv->cfg)) 34362306a36Sopenharmony_ci seq_puts(s, " wifi"); 34462306a36Sopenharmony_ci if (test_bit(CFG_CAP_CAM_BIT, &priv->cfg)) 34562306a36Sopenharmony_ci seq_puts(s, " camera"); 34662306a36Sopenharmony_ci seq_puts(s, "\n"); 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci seq_puts(s, "OSD support:"); 34962306a36Sopenharmony_ci if (test_bit(CFG_OSD_NUMLK_BIT, &priv->cfg)) 35062306a36Sopenharmony_ci seq_puts(s, " num-lock"); 35162306a36Sopenharmony_ci if (test_bit(CFG_OSD_CAPSLK_BIT, &priv->cfg)) 35262306a36Sopenharmony_ci seq_puts(s, " caps-lock"); 35362306a36Sopenharmony_ci if (test_bit(CFG_OSD_MICMUTE_BIT, &priv->cfg)) 35462306a36Sopenharmony_ci seq_puts(s, " mic-mute"); 35562306a36Sopenharmony_ci if (test_bit(CFG_OSD_TOUCHPAD_BIT, &priv->cfg)) 35662306a36Sopenharmony_ci seq_puts(s, " touchpad"); 35762306a36Sopenharmony_ci if (test_bit(CFG_OSD_CAM_BIT, &priv->cfg)) 35862306a36Sopenharmony_ci seq_puts(s, " camera"); 35962306a36Sopenharmony_ci seq_puts(s, "\n"); 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci seq_puts(s, "Graphics: "); 36262306a36Sopenharmony_ci switch (priv->cfg & 0x700) { 36362306a36Sopenharmony_ci case 0x100: 36462306a36Sopenharmony_ci seq_puts(s, "Intel"); 36562306a36Sopenharmony_ci break; 36662306a36Sopenharmony_ci case 0x200: 36762306a36Sopenharmony_ci seq_puts(s, "ATI"); 36862306a36Sopenharmony_ci break; 36962306a36Sopenharmony_ci case 0x300: 37062306a36Sopenharmony_ci seq_puts(s, "Nvidia"); 37162306a36Sopenharmony_ci break; 37262306a36Sopenharmony_ci case 0x400: 37362306a36Sopenharmony_ci seq_puts(s, "Intel and ATI"); 37462306a36Sopenharmony_ci break; 37562306a36Sopenharmony_ci case 0x500: 37662306a36Sopenharmony_ci seq_puts(s, "Intel and Nvidia"); 37762306a36Sopenharmony_ci break; 37862306a36Sopenharmony_ci } 37962306a36Sopenharmony_ci seq_puts(s, "\n"); 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci return 0; 38262306a36Sopenharmony_ci} 38362306a36Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(debugfs_cfg); 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_cistatic void ideapad_debugfs_init(struct ideapad_private *priv) 38662306a36Sopenharmony_ci{ 38762306a36Sopenharmony_ci struct dentry *dir; 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci dir = debugfs_create_dir("ideapad", NULL); 39062306a36Sopenharmony_ci priv->debug = dir; 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci debugfs_create_file("cfg", 0444, dir, priv, &debugfs_cfg_fops); 39362306a36Sopenharmony_ci debugfs_create_file("status", 0444, dir, priv, &debugfs_status_fops); 39462306a36Sopenharmony_ci} 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_cistatic void ideapad_debugfs_exit(struct ideapad_private *priv) 39762306a36Sopenharmony_ci{ 39862306a36Sopenharmony_ci debugfs_remove_recursive(priv->debug); 39962306a36Sopenharmony_ci priv->debug = NULL; 40062306a36Sopenharmony_ci} 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci/* 40362306a36Sopenharmony_ci * sysfs 40462306a36Sopenharmony_ci */ 40562306a36Sopenharmony_cistatic ssize_t camera_power_show(struct device *dev, 40662306a36Sopenharmony_ci struct device_attribute *attr, 40762306a36Sopenharmony_ci char *buf) 40862306a36Sopenharmony_ci{ 40962306a36Sopenharmony_ci struct ideapad_private *priv = dev_get_drvdata(dev); 41062306a36Sopenharmony_ci unsigned long result; 41162306a36Sopenharmony_ci int err; 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci err = read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &result); 41462306a36Sopenharmony_ci if (err) 41562306a36Sopenharmony_ci return err; 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci return sysfs_emit(buf, "%d\n", !!result); 41862306a36Sopenharmony_ci} 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_cistatic ssize_t camera_power_store(struct device *dev, 42162306a36Sopenharmony_ci struct device_attribute *attr, 42262306a36Sopenharmony_ci const char *buf, size_t count) 42362306a36Sopenharmony_ci{ 42462306a36Sopenharmony_ci struct ideapad_private *priv = dev_get_drvdata(dev); 42562306a36Sopenharmony_ci bool state; 42662306a36Sopenharmony_ci int err; 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci err = kstrtobool(buf, &state); 42962306a36Sopenharmony_ci if (err) 43062306a36Sopenharmony_ci return err; 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci err = write_ec_cmd(priv->adev->handle, VPCCMD_W_CAMERA, state); 43362306a36Sopenharmony_ci if (err) 43462306a36Sopenharmony_ci return err; 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_ci return count; 43762306a36Sopenharmony_ci} 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_cistatic DEVICE_ATTR_RW(camera_power); 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_cistatic ssize_t conservation_mode_show(struct device *dev, 44262306a36Sopenharmony_ci struct device_attribute *attr, 44362306a36Sopenharmony_ci char *buf) 44462306a36Sopenharmony_ci{ 44562306a36Sopenharmony_ci struct ideapad_private *priv = dev_get_drvdata(dev); 44662306a36Sopenharmony_ci unsigned long result; 44762306a36Sopenharmony_ci int err; 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci err = eval_gbmd(priv->adev->handle, &result); 45062306a36Sopenharmony_ci if (err) 45162306a36Sopenharmony_ci return err; 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci return sysfs_emit(buf, "%d\n", !!test_bit(GBMD_CONSERVATION_STATE_BIT, &result)); 45462306a36Sopenharmony_ci} 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_cistatic ssize_t conservation_mode_store(struct device *dev, 45762306a36Sopenharmony_ci struct device_attribute *attr, 45862306a36Sopenharmony_ci const char *buf, size_t count) 45962306a36Sopenharmony_ci{ 46062306a36Sopenharmony_ci struct ideapad_private *priv = dev_get_drvdata(dev); 46162306a36Sopenharmony_ci bool state; 46262306a36Sopenharmony_ci int err; 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_ci err = kstrtobool(buf, &state); 46562306a36Sopenharmony_ci if (err) 46662306a36Sopenharmony_ci return err; 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci err = exec_sbmc(priv->adev->handle, state ? SBMC_CONSERVATION_ON : SBMC_CONSERVATION_OFF); 46962306a36Sopenharmony_ci if (err) 47062306a36Sopenharmony_ci return err; 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_ci return count; 47362306a36Sopenharmony_ci} 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_cistatic DEVICE_ATTR_RW(conservation_mode); 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_cistatic ssize_t fan_mode_show(struct device *dev, 47862306a36Sopenharmony_ci struct device_attribute *attr, 47962306a36Sopenharmony_ci char *buf) 48062306a36Sopenharmony_ci{ 48162306a36Sopenharmony_ci struct ideapad_private *priv = dev_get_drvdata(dev); 48262306a36Sopenharmony_ci unsigned long result; 48362306a36Sopenharmony_ci int err; 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ci err = read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result); 48662306a36Sopenharmony_ci if (err) 48762306a36Sopenharmony_ci return err; 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ci return sysfs_emit(buf, "%lu\n", result); 49062306a36Sopenharmony_ci} 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_cistatic ssize_t fan_mode_store(struct device *dev, 49362306a36Sopenharmony_ci struct device_attribute *attr, 49462306a36Sopenharmony_ci const char *buf, size_t count) 49562306a36Sopenharmony_ci{ 49662306a36Sopenharmony_ci struct ideapad_private *priv = dev_get_drvdata(dev); 49762306a36Sopenharmony_ci unsigned int state; 49862306a36Sopenharmony_ci int err; 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_ci err = kstrtouint(buf, 0, &state); 50162306a36Sopenharmony_ci if (err) 50262306a36Sopenharmony_ci return err; 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ci if (state > 4 || state == 3) 50562306a36Sopenharmony_ci return -EINVAL; 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_ci err = write_ec_cmd(priv->adev->handle, VPCCMD_W_FAN, state); 50862306a36Sopenharmony_ci if (err) 50962306a36Sopenharmony_ci return err; 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_ci return count; 51262306a36Sopenharmony_ci} 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_cistatic DEVICE_ATTR_RW(fan_mode); 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_cistatic ssize_t fn_lock_show(struct device *dev, 51762306a36Sopenharmony_ci struct device_attribute *attr, 51862306a36Sopenharmony_ci char *buf) 51962306a36Sopenharmony_ci{ 52062306a36Sopenharmony_ci struct ideapad_private *priv = dev_get_drvdata(dev); 52162306a36Sopenharmony_ci unsigned long hals; 52262306a36Sopenharmony_ci int err; 52362306a36Sopenharmony_ci 52462306a36Sopenharmony_ci err = eval_hals(priv->adev->handle, &hals); 52562306a36Sopenharmony_ci if (err) 52662306a36Sopenharmony_ci return err; 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_ci return sysfs_emit(buf, "%d\n", !!test_bit(HALS_FNLOCK_STATE_BIT, &hals)); 52962306a36Sopenharmony_ci} 53062306a36Sopenharmony_ci 53162306a36Sopenharmony_cistatic ssize_t fn_lock_store(struct device *dev, 53262306a36Sopenharmony_ci struct device_attribute *attr, 53362306a36Sopenharmony_ci const char *buf, size_t count) 53462306a36Sopenharmony_ci{ 53562306a36Sopenharmony_ci struct ideapad_private *priv = dev_get_drvdata(dev); 53662306a36Sopenharmony_ci bool state; 53762306a36Sopenharmony_ci int err; 53862306a36Sopenharmony_ci 53962306a36Sopenharmony_ci err = kstrtobool(buf, &state); 54062306a36Sopenharmony_ci if (err) 54162306a36Sopenharmony_ci return err; 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci err = exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF); 54462306a36Sopenharmony_ci if (err) 54562306a36Sopenharmony_ci return err; 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci return count; 54862306a36Sopenharmony_ci} 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_cistatic DEVICE_ATTR_RW(fn_lock); 55162306a36Sopenharmony_ci 55262306a36Sopenharmony_cistatic ssize_t touchpad_show(struct device *dev, 55362306a36Sopenharmony_ci struct device_attribute *attr, 55462306a36Sopenharmony_ci char *buf) 55562306a36Sopenharmony_ci{ 55662306a36Sopenharmony_ci struct ideapad_private *priv = dev_get_drvdata(dev); 55762306a36Sopenharmony_ci unsigned long result; 55862306a36Sopenharmony_ci int err; 55962306a36Sopenharmony_ci 56062306a36Sopenharmony_ci err = read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &result); 56162306a36Sopenharmony_ci if (err) 56262306a36Sopenharmony_ci return err; 56362306a36Sopenharmony_ci 56462306a36Sopenharmony_ci priv->r_touchpad_val = result; 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci return sysfs_emit(buf, "%d\n", !!result); 56762306a36Sopenharmony_ci} 56862306a36Sopenharmony_ci 56962306a36Sopenharmony_cistatic ssize_t touchpad_store(struct device *dev, 57062306a36Sopenharmony_ci struct device_attribute *attr, 57162306a36Sopenharmony_ci const char *buf, size_t count) 57262306a36Sopenharmony_ci{ 57362306a36Sopenharmony_ci struct ideapad_private *priv = dev_get_drvdata(dev); 57462306a36Sopenharmony_ci bool state; 57562306a36Sopenharmony_ci int err; 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ci err = kstrtobool(buf, &state); 57862306a36Sopenharmony_ci if (err) 57962306a36Sopenharmony_ci return err; 58062306a36Sopenharmony_ci 58162306a36Sopenharmony_ci err = write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, state); 58262306a36Sopenharmony_ci if (err) 58362306a36Sopenharmony_ci return err; 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_ci priv->r_touchpad_val = state; 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci return count; 58862306a36Sopenharmony_ci} 58962306a36Sopenharmony_ci 59062306a36Sopenharmony_cistatic DEVICE_ATTR_RW(touchpad); 59162306a36Sopenharmony_ci 59262306a36Sopenharmony_cistatic ssize_t usb_charging_show(struct device *dev, 59362306a36Sopenharmony_ci struct device_attribute *attr, 59462306a36Sopenharmony_ci char *buf) 59562306a36Sopenharmony_ci{ 59662306a36Sopenharmony_ci struct ideapad_private *priv = dev_get_drvdata(dev); 59762306a36Sopenharmony_ci unsigned long hals; 59862306a36Sopenharmony_ci int err; 59962306a36Sopenharmony_ci 60062306a36Sopenharmony_ci err = eval_hals(priv->adev->handle, &hals); 60162306a36Sopenharmony_ci if (err) 60262306a36Sopenharmony_ci return err; 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_ci return sysfs_emit(buf, "%d\n", !!test_bit(HALS_USB_CHARGING_STATE_BIT, &hals)); 60562306a36Sopenharmony_ci} 60662306a36Sopenharmony_ci 60762306a36Sopenharmony_cistatic ssize_t usb_charging_store(struct device *dev, 60862306a36Sopenharmony_ci struct device_attribute *attr, 60962306a36Sopenharmony_ci const char *buf, size_t count) 61062306a36Sopenharmony_ci{ 61162306a36Sopenharmony_ci struct ideapad_private *priv = dev_get_drvdata(dev); 61262306a36Sopenharmony_ci bool state; 61362306a36Sopenharmony_ci int err; 61462306a36Sopenharmony_ci 61562306a36Sopenharmony_ci err = kstrtobool(buf, &state); 61662306a36Sopenharmony_ci if (err) 61762306a36Sopenharmony_ci return err; 61862306a36Sopenharmony_ci 61962306a36Sopenharmony_ci err = exec_sals(priv->adev->handle, state ? SALS_USB_CHARGING_ON : SALS_USB_CHARGING_OFF); 62062306a36Sopenharmony_ci if (err) 62162306a36Sopenharmony_ci return err; 62262306a36Sopenharmony_ci 62362306a36Sopenharmony_ci return count; 62462306a36Sopenharmony_ci} 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_cistatic DEVICE_ATTR_RW(usb_charging); 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_cistatic struct attribute *ideapad_attributes[] = { 62962306a36Sopenharmony_ci &dev_attr_camera_power.attr, 63062306a36Sopenharmony_ci &dev_attr_conservation_mode.attr, 63162306a36Sopenharmony_ci &dev_attr_fan_mode.attr, 63262306a36Sopenharmony_ci &dev_attr_fn_lock.attr, 63362306a36Sopenharmony_ci &dev_attr_touchpad.attr, 63462306a36Sopenharmony_ci &dev_attr_usb_charging.attr, 63562306a36Sopenharmony_ci NULL 63662306a36Sopenharmony_ci}; 63762306a36Sopenharmony_ci 63862306a36Sopenharmony_cistatic umode_t ideapad_is_visible(struct kobject *kobj, 63962306a36Sopenharmony_ci struct attribute *attr, 64062306a36Sopenharmony_ci int idx) 64162306a36Sopenharmony_ci{ 64262306a36Sopenharmony_ci struct device *dev = kobj_to_dev(kobj); 64362306a36Sopenharmony_ci struct ideapad_private *priv = dev_get_drvdata(dev); 64462306a36Sopenharmony_ci bool supported = true; 64562306a36Sopenharmony_ci 64662306a36Sopenharmony_ci if (attr == &dev_attr_camera_power.attr) 64762306a36Sopenharmony_ci supported = test_bit(CFG_CAP_CAM_BIT, &priv->cfg); 64862306a36Sopenharmony_ci else if (attr == &dev_attr_conservation_mode.attr) 64962306a36Sopenharmony_ci supported = priv->features.conservation_mode; 65062306a36Sopenharmony_ci else if (attr == &dev_attr_fan_mode.attr) 65162306a36Sopenharmony_ci supported = priv->features.fan_mode; 65262306a36Sopenharmony_ci else if (attr == &dev_attr_fn_lock.attr) 65362306a36Sopenharmony_ci supported = priv->features.fn_lock; 65462306a36Sopenharmony_ci else if (attr == &dev_attr_touchpad.attr) 65562306a36Sopenharmony_ci supported = priv->features.touchpad_ctrl_via_ec; 65662306a36Sopenharmony_ci else if (attr == &dev_attr_usb_charging.attr) 65762306a36Sopenharmony_ci supported = priv->features.usb_charging; 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_ci return supported ? attr->mode : 0; 66062306a36Sopenharmony_ci} 66162306a36Sopenharmony_ci 66262306a36Sopenharmony_cistatic const struct attribute_group ideapad_attribute_group = { 66362306a36Sopenharmony_ci .is_visible = ideapad_is_visible, 66462306a36Sopenharmony_ci .attrs = ideapad_attributes 66562306a36Sopenharmony_ci}; 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_ci/* 66862306a36Sopenharmony_ci * DYTC Platform profile 66962306a36Sopenharmony_ci */ 67062306a36Sopenharmony_ci#define DYTC_CMD_QUERY 0 /* To get DYTC status - enable/revision */ 67162306a36Sopenharmony_ci#define DYTC_CMD_SET 1 /* To enable/disable IC function mode */ 67262306a36Sopenharmony_ci#define DYTC_CMD_GET 2 /* To get current IC function and mode */ 67362306a36Sopenharmony_ci#define DYTC_CMD_RESET 0x1ff /* To reset back to default */ 67462306a36Sopenharmony_ci 67562306a36Sopenharmony_ci#define DYTC_QUERY_ENABLE_BIT 8 /* Bit 8 - 0 = disabled, 1 = enabled */ 67662306a36Sopenharmony_ci#define DYTC_QUERY_SUBREV_BIT 16 /* Bits 16 - 27 - sub revision */ 67762306a36Sopenharmony_ci#define DYTC_QUERY_REV_BIT 28 /* Bits 28 - 31 - revision */ 67862306a36Sopenharmony_ci 67962306a36Sopenharmony_ci#define DYTC_GET_FUNCTION_BIT 8 /* Bits 8-11 - function setting */ 68062306a36Sopenharmony_ci#define DYTC_GET_MODE_BIT 12 /* Bits 12-15 - mode setting */ 68162306a36Sopenharmony_ci 68262306a36Sopenharmony_ci#define DYTC_SET_FUNCTION_BIT 12 /* Bits 12-15 - function setting */ 68362306a36Sopenharmony_ci#define DYTC_SET_MODE_BIT 16 /* Bits 16-19 - mode setting */ 68462306a36Sopenharmony_ci#define DYTC_SET_VALID_BIT 20 /* Bit 20 - 1 = on, 0 = off */ 68562306a36Sopenharmony_ci 68662306a36Sopenharmony_ci#define DYTC_FUNCTION_STD 0 /* Function = 0, standard mode */ 68762306a36Sopenharmony_ci#define DYTC_FUNCTION_CQL 1 /* Function = 1, lap mode */ 68862306a36Sopenharmony_ci#define DYTC_FUNCTION_MMC 11 /* Function = 11, desk mode */ 68962306a36Sopenharmony_ci 69062306a36Sopenharmony_ci#define DYTC_MODE_PERFORM 2 /* High power mode aka performance */ 69162306a36Sopenharmony_ci#define DYTC_MODE_LOW_POWER 3 /* Low power mode aka quiet */ 69262306a36Sopenharmony_ci#define DYTC_MODE_BALANCE 0xF /* Default mode aka balanced */ 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_ci#define DYTC_SET_COMMAND(function, mode, on) \ 69562306a36Sopenharmony_ci (DYTC_CMD_SET | (function) << DYTC_SET_FUNCTION_BIT | \ 69662306a36Sopenharmony_ci (mode) << DYTC_SET_MODE_BIT | \ 69762306a36Sopenharmony_ci (on) << DYTC_SET_VALID_BIT) 69862306a36Sopenharmony_ci 69962306a36Sopenharmony_ci#define DYTC_DISABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 0) 70062306a36Sopenharmony_ci 70162306a36Sopenharmony_ci#define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 1) 70262306a36Sopenharmony_ci 70362306a36Sopenharmony_cistatic int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *profile) 70462306a36Sopenharmony_ci{ 70562306a36Sopenharmony_ci switch (dytcmode) { 70662306a36Sopenharmony_ci case DYTC_MODE_LOW_POWER: 70762306a36Sopenharmony_ci *profile = PLATFORM_PROFILE_LOW_POWER; 70862306a36Sopenharmony_ci break; 70962306a36Sopenharmony_ci case DYTC_MODE_BALANCE: 71062306a36Sopenharmony_ci *profile = PLATFORM_PROFILE_BALANCED; 71162306a36Sopenharmony_ci break; 71262306a36Sopenharmony_ci case DYTC_MODE_PERFORM: 71362306a36Sopenharmony_ci *profile = PLATFORM_PROFILE_PERFORMANCE; 71462306a36Sopenharmony_ci break; 71562306a36Sopenharmony_ci default: /* Unknown mode */ 71662306a36Sopenharmony_ci return -EINVAL; 71762306a36Sopenharmony_ci } 71862306a36Sopenharmony_ci 71962306a36Sopenharmony_ci return 0; 72062306a36Sopenharmony_ci} 72162306a36Sopenharmony_ci 72262306a36Sopenharmony_cistatic int convert_profile_to_dytc(enum platform_profile_option profile, int *perfmode) 72362306a36Sopenharmony_ci{ 72462306a36Sopenharmony_ci switch (profile) { 72562306a36Sopenharmony_ci case PLATFORM_PROFILE_LOW_POWER: 72662306a36Sopenharmony_ci *perfmode = DYTC_MODE_LOW_POWER; 72762306a36Sopenharmony_ci break; 72862306a36Sopenharmony_ci case PLATFORM_PROFILE_BALANCED: 72962306a36Sopenharmony_ci *perfmode = DYTC_MODE_BALANCE; 73062306a36Sopenharmony_ci break; 73162306a36Sopenharmony_ci case PLATFORM_PROFILE_PERFORMANCE: 73262306a36Sopenharmony_ci *perfmode = DYTC_MODE_PERFORM; 73362306a36Sopenharmony_ci break; 73462306a36Sopenharmony_ci default: /* Unknown profile */ 73562306a36Sopenharmony_ci return -EOPNOTSUPP; 73662306a36Sopenharmony_ci } 73762306a36Sopenharmony_ci 73862306a36Sopenharmony_ci return 0; 73962306a36Sopenharmony_ci} 74062306a36Sopenharmony_ci 74162306a36Sopenharmony_ci/* 74262306a36Sopenharmony_ci * dytc_profile_get: Function to register with platform_profile 74362306a36Sopenharmony_ci * handler. Returns current platform profile. 74462306a36Sopenharmony_ci */ 74562306a36Sopenharmony_cistatic int dytc_profile_get(struct platform_profile_handler *pprof, 74662306a36Sopenharmony_ci enum platform_profile_option *profile) 74762306a36Sopenharmony_ci{ 74862306a36Sopenharmony_ci struct ideapad_dytc_priv *dytc = container_of(pprof, struct ideapad_dytc_priv, pprof); 74962306a36Sopenharmony_ci 75062306a36Sopenharmony_ci *profile = dytc->current_profile; 75162306a36Sopenharmony_ci return 0; 75262306a36Sopenharmony_ci} 75362306a36Sopenharmony_ci 75462306a36Sopenharmony_ci/* 75562306a36Sopenharmony_ci * Helper function - check if we are in CQL mode and if we are 75662306a36Sopenharmony_ci * - disable CQL, 75762306a36Sopenharmony_ci * - run the command 75862306a36Sopenharmony_ci * - enable CQL 75962306a36Sopenharmony_ci * If not in CQL mode, just run the command 76062306a36Sopenharmony_ci */ 76162306a36Sopenharmony_cistatic int dytc_cql_command(struct ideapad_private *priv, unsigned long cmd, 76262306a36Sopenharmony_ci unsigned long *output) 76362306a36Sopenharmony_ci{ 76462306a36Sopenharmony_ci int err, cmd_err, cur_funcmode; 76562306a36Sopenharmony_ci 76662306a36Sopenharmony_ci /* Determine if we are in CQL mode. This alters the commands we do */ 76762306a36Sopenharmony_ci err = eval_dytc(priv->adev->handle, DYTC_CMD_GET, output); 76862306a36Sopenharmony_ci if (err) 76962306a36Sopenharmony_ci return err; 77062306a36Sopenharmony_ci 77162306a36Sopenharmony_ci cur_funcmode = (*output >> DYTC_GET_FUNCTION_BIT) & 0xF; 77262306a36Sopenharmony_ci /* Check if we're OK to return immediately */ 77362306a36Sopenharmony_ci if (cmd == DYTC_CMD_GET && cur_funcmode != DYTC_FUNCTION_CQL) 77462306a36Sopenharmony_ci return 0; 77562306a36Sopenharmony_ci 77662306a36Sopenharmony_ci if (cur_funcmode == DYTC_FUNCTION_CQL) { 77762306a36Sopenharmony_ci err = eval_dytc(priv->adev->handle, DYTC_DISABLE_CQL, NULL); 77862306a36Sopenharmony_ci if (err) 77962306a36Sopenharmony_ci return err; 78062306a36Sopenharmony_ci } 78162306a36Sopenharmony_ci 78262306a36Sopenharmony_ci cmd_err = eval_dytc(priv->adev->handle, cmd, output); 78362306a36Sopenharmony_ci /* Check return condition after we've restored CQL state */ 78462306a36Sopenharmony_ci 78562306a36Sopenharmony_ci if (cur_funcmode == DYTC_FUNCTION_CQL) { 78662306a36Sopenharmony_ci err = eval_dytc(priv->adev->handle, DYTC_ENABLE_CQL, NULL); 78762306a36Sopenharmony_ci if (err) 78862306a36Sopenharmony_ci return err; 78962306a36Sopenharmony_ci } 79062306a36Sopenharmony_ci 79162306a36Sopenharmony_ci return cmd_err; 79262306a36Sopenharmony_ci} 79362306a36Sopenharmony_ci 79462306a36Sopenharmony_ci/* 79562306a36Sopenharmony_ci * dytc_profile_set: Function to register with platform_profile 79662306a36Sopenharmony_ci * handler. Sets current platform profile. 79762306a36Sopenharmony_ci */ 79862306a36Sopenharmony_cistatic int dytc_profile_set(struct platform_profile_handler *pprof, 79962306a36Sopenharmony_ci enum platform_profile_option profile) 80062306a36Sopenharmony_ci{ 80162306a36Sopenharmony_ci struct ideapad_dytc_priv *dytc = container_of(pprof, struct ideapad_dytc_priv, pprof); 80262306a36Sopenharmony_ci struct ideapad_private *priv = dytc->priv; 80362306a36Sopenharmony_ci unsigned long output; 80462306a36Sopenharmony_ci int err; 80562306a36Sopenharmony_ci 80662306a36Sopenharmony_ci err = mutex_lock_interruptible(&dytc->mutex); 80762306a36Sopenharmony_ci if (err) 80862306a36Sopenharmony_ci return err; 80962306a36Sopenharmony_ci 81062306a36Sopenharmony_ci if (profile == PLATFORM_PROFILE_BALANCED) { 81162306a36Sopenharmony_ci /* To get back to balanced mode we just issue a reset command */ 81262306a36Sopenharmony_ci err = eval_dytc(priv->adev->handle, DYTC_CMD_RESET, NULL); 81362306a36Sopenharmony_ci if (err) 81462306a36Sopenharmony_ci goto unlock; 81562306a36Sopenharmony_ci } else { 81662306a36Sopenharmony_ci int perfmode; 81762306a36Sopenharmony_ci 81862306a36Sopenharmony_ci err = convert_profile_to_dytc(profile, &perfmode); 81962306a36Sopenharmony_ci if (err) 82062306a36Sopenharmony_ci goto unlock; 82162306a36Sopenharmony_ci 82262306a36Sopenharmony_ci /* Determine if we are in CQL mode. This alters the commands we do */ 82362306a36Sopenharmony_ci err = dytc_cql_command(priv, DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), 82462306a36Sopenharmony_ci &output); 82562306a36Sopenharmony_ci if (err) 82662306a36Sopenharmony_ci goto unlock; 82762306a36Sopenharmony_ci } 82862306a36Sopenharmony_ci 82962306a36Sopenharmony_ci /* Success - update current profile */ 83062306a36Sopenharmony_ci dytc->current_profile = profile; 83162306a36Sopenharmony_ci 83262306a36Sopenharmony_ciunlock: 83362306a36Sopenharmony_ci mutex_unlock(&dytc->mutex); 83462306a36Sopenharmony_ci 83562306a36Sopenharmony_ci return err; 83662306a36Sopenharmony_ci} 83762306a36Sopenharmony_ci 83862306a36Sopenharmony_cistatic void dytc_profile_refresh(struct ideapad_private *priv) 83962306a36Sopenharmony_ci{ 84062306a36Sopenharmony_ci enum platform_profile_option profile; 84162306a36Sopenharmony_ci unsigned long output; 84262306a36Sopenharmony_ci int err, perfmode; 84362306a36Sopenharmony_ci 84462306a36Sopenharmony_ci mutex_lock(&priv->dytc->mutex); 84562306a36Sopenharmony_ci err = dytc_cql_command(priv, DYTC_CMD_GET, &output); 84662306a36Sopenharmony_ci mutex_unlock(&priv->dytc->mutex); 84762306a36Sopenharmony_ci if (err) 84862306a36Sopenharmony_ci return; 84962306a36Sopenharmony_ci 85062306a36Sopenharmony_ci perfmode = (output >> DYTC_GET_MODE_BIT) & 0xF; 85162306a36Sopenharmony_ci 85262306a36Sopenharmony_ci if (convert_dytc_to_profile(perfmode, &profile)) 85362306a36Sopenharmony_ci return; 85462306a36Sopenharmony_ci 85562306a36Sopenharmony_ci if (profile != priv->dytc->current_profile) { 85662306a36Sopenharmony_ci priv->dytc->current_profile = profile; 85762306a36Sopenharmony_ci platform_profile_notify(); 85862306a36Sopenharmony_ci } 85962306a36Sopenharmony_ci} 86062306a36Sopenharmony_ci 86162306a36Sopenharmony_cistatic const struct dmi_system_id ideapad_dytc_v4_allow_table[] = { 86262306a36Sopenharmony_ci { 86362306a36Sopenharmony_ci /* Ideapad 5 Pro 16ACH6 */ 86462306a36Sopenharmony_ci .matches = { 86562306a36Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 86662306a36Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_NAME, "82L5") 86762306a36Sopenharmony_ci } 86862306a36Sopenharmony_ci }, 86962306a36Sopenharmony_ci { 87062306a36Sopenharmony_ci /* Ideapad 5 15ITL05 */ 87162306a36Sopenharmony_ci .matches = { 87262306a36Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 87362306a36Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_VERSION, "IdeaPad 5 15ITL05") 87462306a36Sopenharmony_ci } 87562306a36Sopenharmony_ci }, 87662306a36Sopenharmony_ci {} 87762306a36Sopenharmony_ci}; 87862306a36Sopenharmony_ci 87962306a36Sopenharmony_cistatic int ideapad_dytc_profile_init(struct ideapad_private *priv) 88062306a36Sopenharmony_ci{ 88162306a36Sopenharmony_ci int err, dytc_version; 88262306a36Sopenharmony_ci unsigned long output; 88362306a36Sopenharmony_ci 88462306a36Sopenharmony_ci if (!priv->features.dytc) 88562306a36Sopenharmony_ci return -ENODEV; 88662306a36Sopenharmony_ci 88762306a36Sopenharmony_ci err = eval_dytc(priv->adev->handle, DYTC_CMD_QUERY, &output); 88862306a36Sopenharmony_ci /* For all other errors we can flag the failure */ 88962306a36Sopenharmony_ci if (err) 89062306a36Sopenharmony_ci return err; 89162306a36Sopenharmony_ci 89262306a36Sopenharmony_ci /* Check DYTC is enabled and supports mode setting */ 89362306a36Sopenharmony_ci if (!test_bit(DYTC_QUERY_ENABLE_BIT, &output)) { 89462306a36Sopenharmony_ci dev_info(&priv->platform_device->dev, "DYTC_QUERY_ENABLE_BIT returned false\n"); 89562306a36Sopenharmony_ci return -ENODEV; 89662306a36Sopenharmony_ci } 89762306a36Sopenharmony_ci 89862306a36Sopenharmony_ci dytc_version = (output >> DYTC_QUERY_REV_BIT) & 0xF; 89962306a36Sopenharmony_ci 90062306a36Sopenharmony_ci if (dytc_version < 4) { 90162306a36Sopenharmony_ci dev_info(&priv->platform_device->dev, "DYTC_VERSION < 4 is not supported\n"); 90262306a36Sopenharmony_ci return -ENODEV; 90362306a36Sopenharmony_ci } 90462306a36Sopenharmony_ci 90562306a36Sopenharmony_ci if (dytc_version < 5 && 90662306a36Sopenharmony_ci !(allow_v4_dytc || dmi_check_system(ideapad_dytc_v4_allow_table))) { 90762306a36Sopenharmony_ci dev_info(&priv->platform_device->dev, 90862306a36Sopenharmony_ci "DYTC_VERSION 4 support may not work. Pass ideapad_laptop.allow_v4_dytc=Y on the kernel commandline to enable\n"); 90962306a36Sopenharmony_ci return -ENODEV; 91062306a36Sopenharmony_ci } 91162306a36Sopenharmony_ci 91262306a36Sopenharmony_ci priv->dytc = kzalloc(sizeof(*priv->dytc), GFP_KERNEL); 91362306a36Sopenharmony_ci if (!priv->dytc) 91462306a36Sopenharmony_ci return -ENOMEM; 91562306a36Sopenharmony_ci 91662306a36Sopenharmony_ci mutex_init(&priv->dytc->mutex); 91762306a36Sopenharmony_ci 91862306a36Sopenharmony_ci priv->dytc->priv = priv; 91962306a36Sopenharmony_ci priv->dytc->pprof.profile_get = dytc_profile_get; 92062306a36Sopenharmony_ci priv->dytc->pprof.profile_set = dytc_profile_set; 92162306a36Sopenharmony_ci 92262306a36Sopenharmony_ci /* Setup supported modes */ 92362306a36Sopenharmony_ci set_bit(PLATFORM_PROFILE_LOW_POWER, priv->dytc->pprof.choices); 92462306a36Sopenharmony_ci set_bit(PLATFORM_PROFILE_BALANCED, priv->dytc->pprof.choices); 92562306a36Sopenharmony_ci set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->dytc->pprof.choices); 92662306a36Sopenharmony_ci 92762306a36Sopenharmony_ci /* Create platform_profile structure and register */ 92862306a36Sopenharmony_ci err = platform_profile_register(&priv->dytc->pprof); 92962306a36Sopenharmony_ci if (err) 93062306a36Sopenharmony_ci goto pp_reg_failed; 93162306a36Sopenharmony_ci 93262306a36Sopenharmony_ci /* Ensure initial values are correct */ 93362306a36Sopenharmony_ci dytc_profile_refresh(priv); 93462306a36Sopenharmony_ci 93562306a36Sopenharmony_ci return 0; 93662306a36Sopenharmony_ci 93762306a36Sopenharmony_cipp_reg_failed: 93862306a36Sopenharmony_ci mutex_destroy(&priv->dytc->mutex); 93962306a36Sopenharmony_ci kfree(priv->dytc); 94062306a36Sopenharmony_ci priv->dytc = NULL; 94162306a36Sopenharmony_ci 94262306a36Sopenharmony_ci return err; 94362306a36Sopenharmony_ci} 94462306a36Sopenharmony_ci 94562306a36Sopenharmony_cistatic void ideapad_dytc_profile_exit(struct ideapad_private *priv) 94662306a36Sopenharmony_ci{ 94762306a36Sopenharmony_ci if (!priv->dytc) 94862306a36Sopenharmony_ci return; 94962306a36Sopenharmony_ci 95062306a36Sopenharmony_ci platform_profile_remove(); 95162306a36Sopenharmony_ci mutex_destroy(&priv->dytc->mutex); 95262306a36Sopenharmony_ci kfree(priv->dytc); 95362306a36Sopenharmony_ci 95462306a36Sopenharmony_ci priv->dytc = NULL; 95562306a36Sopenharmony_ci} 95662306a36Sopenharmony_ci 95762306a36Sopenharmony_ci/* 95862306a36Sopenharmony_ci * Rfkill 95962306a36Sopenharmony_ci */ 96062306a36Sopenharmony_cistruct ideapad_rfk_data { 96162306a36Sopenharmony_ci char *name; 96262306a36Sopenharmony_ci int cfgbit; 96362306a36Sopenharmony_ci int opcode; 96462306a36Sopenharmony_ci int type; 96562306a36Sopenharmony_ci}; 96662306a36Sopenharmony_ci 96762306a36Sopenharmony_cistatic const struct ideapad_rfk_data ideapad_rfk_data[] = { 96862306a36Sopenharmony_ci { "ideapad_wlan", CFG_CAP_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN }, 96962306a36Sopenharmony_ci { "ideapad_bluetooth", CFG_CAP_BT_BIT, VPCCMD_W_BT, RFKILL_TYPE_BLUETOOTH }, 97062306a36Sopenharmony_ci { "ideapad_3g", CFG_CAP_3G_BIT, VPCCMD_W_3G, RFKILL_TYPE_WWAN }, 97162306a36Sopenharmony_ci}; 97262306a36Sopenharmony_ci 97362306a36Sopenharmony_cistatic int ideapad_rfk_set(void *data, bool blocked) 97462306a36Sopenharmony_ci{ 97562306a36Sopenharmony_ci struct ideapad_rfk_priv *priv = data; 97662306a36Sopenharmony_ci int opcode = ideapad_rfk_data[priv->dev].opcode; 97762306a36Sopenharmony_ci 97862306a36Sopenharmony_ci return write_ec_cmd(priv->priv->adev->handle, opcode, !blocked); 97962306a36Sopenharmony_ci} 98062306a36Sopenharmony_ci 98162306a36Sopenharmony_cistatic const struct rfkill_ops ideapad_rfk_ops = { 98262306a36Sopenharmony_ci .set_block = ideapad_rfk_set, 98362306a36Sopenharmony_ci}; 98462306a36Sopenharmony_ci 98562306a36Sopenharmony_cistatic void ideapad_sync_rfk_state(struct ideapad_private *priv) 98662306a36Sopenharmony_ci{ 98762306a36Sopenharmony_ci unsigned long hw_blocked = 0; 98862306a36Sopenharmony_ci int i; 98962306a36Sopenharmony_ci 99062306a36Sopenharmony_ci if (priv->features.hw_rfkill_switch) { 99162306a36Sopenharmony_ci if (read_ec_data(priv->adev->handle, VPCCMD_R_RF, &hw_blocked)) 99262306a36Sopenharmony_ci return; 99362306a36Sopenharmony_ci hw_blocked = !hw_blocked; 99462306a36Sopenharmony_ci } 99562306a36Sopenharmony_ci 99662306a36Sopenharmony_ci for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 99762306a36Sopenharmony_ci if (priv->rfk[i]) 99862306a36Sopenharmony_ci rfkill_set_hw_state(priv->rfk[i], hw_blocked); 99962306a36Sopenharmony_ci} 100062306a36Sopenharmony_ci 100162306a36Sopenharmony_cistatic int ideapad_register_rfkill(struct ideapad_private *priv, int dev) 100262306a36Sopenharmony_ci{ 100362306a36Sopenharmony_ci unsigned long rf_enabled; 100462306a36Sopenharmony_ci int err; 100562306a36Sopenharmony_ci 100662306a36Sopenharmony_ci if (no_bt_rfkill && ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH) { 100762306a36Sopenharmony_ci /* Force to enable bluetooth when no_bt_rfkill=1 */ 100862306a36Sopenharmony_ci write_ec_cmd(priv->adev->handle, ideapad_rfk_data[dev].opcode, 1); 100962306a36Sopenharmony_ci return 0; 101062306a36Sopenharmony_ci } 101162306a36Sopenharmony_ci 101262306a36Sopenharmony_ci priv->rfk_priv[dev].dev = dev; 101362306a36Sopenharmony_ci priv->rfk_priv[dev].priv = priv; 101462306a36Sopenharmony_ci 101562306a36Sopenharmony_ci priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name, 101662306a36Sopenharmony_ci &priv->platform_device->dev, 101762306a36Sopenharmony_ci ideapad_rfk_data[dev].type, 101862306a36Sopenharmony_ci &ideapad_rfk_ops, 101962306a36Sopenharmony_ci &priv->rfk_priv[dev]); 102062306a36Sopenharmony_ci if (!priv->rfk[dev]) 102162306a36Sopenharmony_ci return -ENOMEM; 102262306a36Sopenharmony_ci 102362306a36Sopenharmony_ci err = read_ec_data(priv->adev->handle, ideapad_rfk_data[dev].opcode - 1, &rf_enabled); 102462306a36Sopenharmony_ci if (err) 102562306a36Sopenharmony_ci rf_enabled = 1; 102662306a36Sopenharmony_ci 102762306a36Sopenharmony_ci rfkill_init_sw_state(priv->rfk[dev], !rf_enabled); 102862306a36Sopenharmony_ci 102962306a36Sopenharmony_ci err = rfkill_register(priv->rfk[dev]); 103062306a36Sopenharmony_ci if (err) 103162306a36Sopenharmony_ci rfkill_destroy(priv->rfk[dev]); 103262306a36Sopenharmony_ci 103362306a36Sopenharmony_ci return err; 103462306a36Sopenharmony_ci} 103562306a36Sopenharmony_ci 103662306a36Sopenharmony_cistatic void ideapad_unregister_rfkill(struct ideapad_private *priv, int dev) 103762306a36Sopenharmony_ci{ 103862306a36Sopenharmony_ci if (!priv->rfk[dev]) 103962306a36Sopenharmony_ci return; 104062306a36Sopenharmony_ci 104162306a36Sopenharmony_ci rfkill_unregister(priv->rfk[dev]); 104262306a36Sopenharmony_ci rfkill_destroy(priv->rfk[dev]); 104362306a36Sopenharmony_ci} 104462306a36Sopenharmony_ci 104562306a36Sopenharmony_ci/* 104662306a36Sopenharmony_ci * Platform device 104762306a36Sopenharmony_ci */ 104862306a36Sopenharmony_cistatic int ideapad_sysfs_init(struct ideapad_private *priv) 104962306a36Sopenharmony_ci{ 105062306a36Sopenharmony_ci return device_add_group(&priv->platform_device->dev, 105162306a36Sopenharmony_ci &ideapad_attribute_group); 105262306a36Sopenharmony_ci} 105362306a36Sopenharmony_ci 105462306a36Sopenharmony_cistatic void ideapad_sysfs_exit(struct ideapad_private *priv) 105562306a36Sopenharmony_ci{ 105662306a36Sopenharmony_ci device_remove_group(&priv->platform_device->dev, 105762306a36Sopenharmony_ci &ideapad_attribute_group); 105862306a36Sopenharmony_ci} 105962306a36Sopenharmony_ci 106062306a36Sopenharmony_ci/* 106162306a36Sopenharmony_ci * input device 106262306a36Sopenharmony_ci */ 106362306a36Sopenharmony_ci#define IDEAPAD_WMI_KEY 0x100 106462306a36Sopenharmony_ci 106562306a36Sopenharmony_cistatic const struct key_entry ideapad_keymap[] = { 106662306a36Sopenharmony_ci { KE_KEY, 6, { KEY_SWITCHVIDEOMODE } }, 106762306a36Sopenharmony_ci { KE_KEY, 7, { KEY_CAMERA } }, 106862306a36Sopenharmony_ci { KE_KEY, 8, { KEY_MICMUTE } }, 106962306a36Sopenharmony_ci { KE_KEY, 11, { KEY_F16 } }, 107062306a36Sopenharmony_ci { KE_KEY, 13, { KEY_WLAN } }, 107162306a36Sopenharmony_ci { KE_KEY, 16, { KEY_PROG1 } }, 107262306a36Sopenharmony_ci { KE_KEY, 17, { KEY_PROG2 } }, 107362306a36Sopenharmony_ci { KE_KEY, 64, { KEY_PROG3 } }, 107462306a36Sopenharmony_ci { KE_KEY, 65, { KEY_PROG4 } }, 107562306a36Sopenharmony_ci { KE_KEY, 66, { KEY_TOUCHPAD_OFF } }, 107662306a36Sopenharmony_ci { KE_KEY, 67, { KEY_TOUCHPAD_ON } }, 107762306a36Sopenharmony_ci { KE_KEY, 128, { KEY_ESC } }, 107862306a36Sopenharmony_ci 107962306a36Sopenharmony_ci /* 108062306a36Sopenharmony_ci * WMI keys 108162306a36Sopenharmony_ci */ 108262306a36Sopenharmony_ci 108362306a36Sopenharmony_ci /* FnLock (handled by the firmware) */ 108462306a36Sopenharmony_ci { KE_IGNORE, 0x02 | IDEAPAD_WMI_KEY }, 108562306a36Sopenharmony_ci /* Esc (handled by the firmware) */ 108662306a36Sopenharmony_ci { KE_IGNORE, 0x03 | IDEAPAD_WMI_KEY }, 108762306a36Sopenharmony_ci /* Customizable Lenovo Hotkey ("star" with 'S' inside) */ 108862306a36Sopenharmony_ci { KE_KEY, 0x01 | IDEAPAD_WMI_KEY, { KEY_FAVORITES } }, 108962306a36Sopenharmony_ci { KE_KEY, 0x04 | IDEAPAD_WMI_KEY, { KEY_SELECTIVE_SCREENSHOT } }, 109062306a36Sopenharmony_ci /* Lenovo Support */ 109162306a36Sopenharmony_ci { KE_KEY, 0x07 | IDEAPAD_WMI_KEY, { KEY_HELP } }, 109262306a36Sopenharmony_ci { KE_KEY, 0x0e | IDEAPAD_WMI_KEY, { KEY_PICKUP_PHONE } }, 109362306a36Sopenharmony_ci { KE_KEY, 0x0f | IDEAPAD_WMI_KEY, { KEY_HANGUP_PHONE } }, 109462306a36Sopenharmony_ci /* Dark mode toggle */ 109562306a36Sopenharmony_ci { KE_KEY, 0x13 | IDEAPAD_WMI_KEY, { KEY_PROG1 } }, 109662306a36Sopenharmony_ci /* Sound profile switch */ 109762306a36Sopenharmony_ci { KE_KEY, 0x12 | IDEAPAD_WMI_KEY, { KEY_PROG2 } }, 109862306a36Sopenharmony_ci /* Lenovo Virtual Background application */ 109962306a36Sopenharmony_ci { KE_KEY, 0x28 | IDEAPAD_WMI_KEY, { KEY_PROG3 } }, 110062306a36Sopenharmony_ci /* Lenovo Support */ 110162306a36Sopenharmony_ci { KE_KEY, 0x27 | IDEAPAD_WMI_KEY, { KEY_HELP } }, 110262306a36Sopenharmony_ci /* Refresh Rate Toggle */ 110362306a36Sopenharmony_ci { KE_KEY, 0x0a | IDEAPAD_WMI_KEY, { KEY_DISPLAYTOGGLE } }, 110462306a36Sopenharmony_ci 110562306a36Sopenharmony_ci { KE_END }, 110662306a36Sopenharmony_ci}; 110762306a36Sopenharmony_ci 110862306a36Sopenharmony_cistatic int ideapad_input_init(struct ideapad_private *priv) 110962306a36Sopenharmony_ci{ 111062306a36Sopenharmony_ci struct input_dev *inputdev; 111162306a36Sopenharmony_ci int err; 111262306a36Sopenharmony_ci 111362306a36Sopenharmony_ci inputdev = input_allocate_device(); 111462306a36Sopenharmony_ci if (!inputdev) 111562306a36Sopenharmony_ci return -ENOMEM; 111662306a36Sopenharmony_ci 111762306a36Sopenharmony_ci inputdev->name = "Ideapad extra buttons"; 111862306a36Sopenharmony_ci inputdev->phys = "ideapad/input0"; 111962306a36Sopenharmony_ci inputdev->id.bustype = BUS_HOST; 112062306a36Sopenharmony_ci inputdev->dev.parent = &priv->platform_device->dev; 112162306a36Sopenharmony_ci 112262306a36Sopenharmony_ci err = sparse_keymap_setup(inputdev, ideapad_keymap, NULL); 112362306a36Sopenharmony_ci if (err) { 112462306a36Sopenharmony_ci dev_err(&priv->platform_device->dev, 112562306a36Sopenharmony_ci "Could not set up input device keymap: %d\n", err); 112662306a36Sopenharmony_ci goto err_free_dev; 112762306a36Sopenharmony_ci } 112862306a36Sopenharmony_ci 112962306a36Sopenharmony_ci err = input_register_device(inputdev); 113062306a36Sopenharmony_ci if (err) { 113162306a36Sopenharmony_ci dev_err(&priv->platform_device->dev, 113262306a36Sopenharmony_ci "Could not register input device: %d\n", err); 113362306a36Sopenharmony_ci goto err_free_dev; 113462306a36Sopenharmony_ci } 113562306a36Sopenharmony_ci 113662306a36Sopenharmony_ci priv->inputdev = inputdev; 113762306a36Sopenharmony_ci 113862306a36Sopenharmony_ci return 0; 113962306a36Sopenharmony_ci 114062306a36Sopenharmony_cierr_free_dev: 114162306a36Sopenharmony_ci input_free_device(inputdev); 114262306a36Sopenharmony_ci 114362306a36Sopenharmony_ci return err; 114462306a36Sopenharmony_ci} 114562306a36Sopenharmony_ci 114662306a36Sopenharmony_cistatic void ideapad_input_exit(struct ideapad_private *priv) 114762306a36Sopenharmony_ci{ 114862306a36Sopenharmony_ci input_unregister_device(priv->inputdev); 114962306a36Sopenharmony_ci priv->inputdev = NULL; 115062306a36Sopenharmony_ci} 115162306a36Sopenharmony_ci 115262306a36Sopenharmony_cistatic void ideapad_input_report(struct ideapad_private *priv, 115362306a36Sopenharmony_ci unsigned long scancode) 115462306a36Sopenharmony_ci{ 115562306a36Sopenharmony_ci sparse_keymap_report_event(priv->inputdev, scancode, 1, true); 115662306a36Sopenharmony_ci} 115762306a36Sopenharmony_ci 115862306a36Sopenharmony_cistatic void ideapad_input_novokey(struct ideapad_private *priv) 115962306a36Sopenharmony_ci{ 116062306a36Sopenharmony_ci unsigned long long_pressed; 116162306a36Sopenharmony_ci 116262306a36Sopenharmony_ci if (read_ec_data(priv->adev->handle, VPCCMD_R_NOVO, &long_pressed)) 116362306a36Sopenharmony_ci return; 116462306a36Sopenharmony_ci 116562306a36Sopenharmony_ci if (long_pressed) 116662306a36Sopenharmony_ci ideapad_input_report(priv, 17); 116762306a36Sopenharmony_ci else 116862306a36Sopenharmony_ci ideapad_input_report(priv, 16); 116962306a36Sopenharmony_ci} 117062306a36Sopenharmony_ci 117162306a36Sopenharmony_cistatic void ideapad_check_special_buttons(struct ideapad_private *priv) 117262306a36Sopenharmony_ci{ 117362306a36Sopenharmony_ci unsigned long bit, value; 117462306a36Sopenharmony_ci 117562306a36Sopenharmony_ci if (read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value)) 117662306a36Sopenharmony_ci return; 117762306a36Sopenharmony_ci 117862306a36Sopenharmony_ci for_each_set_bit (bit, &value, 16) { 117962306a36Sopenharmony_ci switch (bit) { 118062306a36Sopenharmony_ci case 6: /* Z570 */ 118162306a36Sopenharmony_ci case 0: /* Z580 */ 118262306a36Sopenharmony_ci /* Thermal Management button */ 118362306a36Sopenharmony_ci ideapad_input_report(priv, 65); 118462306a36Sopenharmony_ci break; 118562306a36Sopenharmony_ci case 1: 118662306a36Sopenharmony_ci /* OneKey Theater button */ 118762306a36Sopenharmony_ci ideapad_input_report(priv, 64); 118862306a36Sopenharmony_ci break; 118962306a36Sopenharmony_ci default: 119062306a36Sopenharmony_ci dev_info(&priv->platform_device->dev, 119162306a36Sopenharmony_ci "Unknown special button: %lu\n", bit); 119262306a36Sopenharmony_ci break; 119362306a36Sopenharmony_ci } 119462306a36Sopenharmony_ci } 119562306a36Sopenharmony_ci} 119662306a36Sopenharmony_ci 119762306a36Sopenharmony_ci/* 119862306a36Sopenharmony_ci * backlight 119962306a36Sopenharmony_ci */ 120062306a36Sopenharmony_cistatic int ideapad_backlight_get_brightness(struct backlight_device *blightdev) 120162306a36Sopenharmony_ci{ 120262306a36Sopenharmony_ci struct ideapad_private *priv = bl_get_data(blightdev); 120362306a36Sopenharmony_ci unsigned long now; 120462306a36Sopenharmony_ci int err; 120562306a36Sopenharmony_ci 120662306a36Sopenharmony_ci err = read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); 120762306a36Sopenharmony_ci if (err) 120862306a36Sopenharmony_ci return err; 120962306a36Sopenharmony_ci 121062306a36Sopenharmony_ci return now; 121162306a36Sopenharmony_ci} 121262306a36Sopenharmony_ci 121362306a36Sopenharmony_cistatic int ideapad_backlight_update_status(struct backlight_device *blightdev) 121462306a36Sopenharmony_ci{ 121562306a36Sopenharmony_ci struct ideapad_private *priv = bl_get_data(blightdev); 121662306a36Sopenharmony_ci int err; 121762306a36Sopenharmony_ci 121862306a36Sopenharmony_ci err = write_ec_cmd(priv->adev->handle, VPCCMD_W_BL, 121962306a36Sopenharmony_ci blightdev->props.brightness); 122062306a36Sopenharmony_ci if (err) 122162306a36Sopenharmony_ci return err; 122262306a36Sopenharmony_ci 122362306a36Sopenharmony_ci err = write_ec_cmd(priv->adev->handle, VPCCMD_W_BL_POWER, 122462306a36Sopenharmony_ci blightdev->props.power != FB_BLANK_POWERDOWN); 122562306a36Sopenharmony_ci if (err) 122662306a36Sopenharmony_ci return err; 122762306a36Sopenharmony_ci 122862306a36Sopenharmony_ci return 0; 122962306a36Sopenharmony_ci} 123062306a36Sopenharmony_ci 123162306a36Sopenharmony_cistatic const struct backlight_ops ideapad_backlight_ops = { 123262306a36Sopenharmony_ci .get_brightness = ideapad_backlight_get_brightness, 123362306a36Sopenharmony_ci .update_status = ideapad_backlight_update_status, 123462306a36Sopenharmony_ci}; 123562306a36Sopenharmony_ci 123662306a36Sopenharmony_cistatic int ideapad_backlight_init(struct ideapad_private *priv) 123762306a36Sopenharmony_ci{ 123862306a36Sopenharmony_ci struct backlight_device *blightdev; 123962306a36Sopenharmony_ci struct backlight_properties props; 124062306a36Sopenharmony_ci unsigned long max, now, power; 124162306a36Sopenharmony_ci int err; 124262306a36Sopenharmony_ci 124362306a36Sopenharmony_ci err = read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &max); 124462306a36Sopenharmony_ci if (err) 124562306a36Sopenharmony_ci return err; 124662306a36Sopenharmony_ci 124762306a36Sopenharmony_ci err = read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); 124862306a36Sopenharmony_ci if (err) 124962306a36Sopenharmony_ci return err; 125062306a36Sopenharmony_ci 125162306a36Sopenharmony_ci err = read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power); 125262306a36Sopenharmony_ci if (err) 125362306a36Sopenharmony_ci return err; 125462306a36Sopenharmony_ci 125562306a36Sopenharmony_ci memset(&props, 0, sizeof(props)); 125662306a36Sopenharmony_ci 125762306a36Sopenharmony_ci props.max_brightness = max; 125862306a36Sopenharmony_ci props.type = BACKLIGHT_PLATFORM; 125962306a36Sopenharmony_ci 126062306a36Sopenharmony_ci blightdev = backlight_device_register("ideapad", 126162306a36Sopenharmony_ci &priv->platform_device->dev, 126262306a36Sopenharmony_ci priv, 126362306a36Sopenharmony_ci &ideapad_backlight_ops, 126462306a36Sopenharmony_ci &props); 126562306a36Sopenharmony_ci if (IS_ERR(blightdev)) { 126662306a36Sopenharmony_ci err = PTR_ERR(blightdev); 126762306a36Sopenharmony_ci dev_err(&priv->platform_device->dev, 126862306a36Sopenharmony_ci "Could not register backlight device: %d\n", err); 126962306a36Sopenharmony_ci return err; 127062306a36Sopenharmony_ci } 127162306a36Sopenharmony_ci 127262306a36Sopenharmony_ci priv->blightdev = blightdev; 127362306a36Sopenharmony_ci blightdev->props.brightness = now; 127462306a36Sopenharmony_ci blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; 127562306a36Sopenharmony_ci 127662306a36Sopenharmony_ci backlight_update_status(blightdev); 127762306a36Sopenharmony_ci 127862306a36Sopenharmony_ci return 0; 127962306a36Sopenharmony_ci} 128062306a36Sopenharmony_ci 128162306a36Sopenharmony_cistatic void ideapad_backlight_exit(struct ideapad_private *priv) 128262306a36Sopenharmony_ci{ 128362306a36Sopenharmony_ci backlight_device_unregister(priv->blightdev); 128462306a36Sopenharmony_ci priv->blightdev = NULL; 128562306a36Sopenharmony_ci} 128662306a36Sopenharmony_ci 128762306a36Sopenharmony_cistatic void ideapad_backlight_notify_power(struct ideapad_private *priv) 128862306a36Sopenharmony_ci{ 128962306a36Sopenharmony_ci struct backlight_device *blightdev = priv->blightdev; 129062306a36Sopenharmony_ci unsigned long power; 129162306a36Sopenharmony_ci 129262306a36Sopenharmony_ci if (!blightdev) 129362306a36Sopenharmony_ci return; 129462306a36Sopenharmony_ci 129562306a36Sopenharmony_ci if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power)) 129662306a36Sopenharmony_ci return; 129762306a36Sopenharmony_ci 129862306a36Sopenharmony_ci blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; 129962306a36Sopenharmony_ci} 130062306a36Sopenharmony_ci 130162306a36Sopenharmony_cistatic void ideapad_backlight_notify_brightness(struct ideapad_private *priv) 130262306a36Sopenharmony_ci{ 130362306a36Sopenharmony_ci unsigned long now; 130462306a36Sopenharmony_ci 130562306a36Sopenharmony_ci /* if we control brightness via acpi video driver */ 130662306a36Sopenharmony_ci if (!priv->blightdev) 130762306a36Sopenharmony_ci read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); 130862306a36Sopenharmony_ci else 130962306a36Sopenharmony_ci backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY); 131062306a36Sopenharmony_ci} 131162306a36Sopenharmony_ci 131262306a36Sopenharmony_ci/* 131362306a36Sopenharmony_ci * keyboard backlight 131462306a36Sopenharmony_ci */ 131562306a36Sopenharmony_cistatic int ideapad_kbd_bl_check_tristate(int type) 131662306a36Sopenharmony_ci{ 131762306a36Sopenharmony_ci return (type == KBD_BL_TRISTATE) || (type == KBD_BL_TRISTATE_AUTO); 131862306a36Sopenharmony_ci} 131962306a36Sopenharmony_ci 132062306a36Sopenharmony_cistatic int ideapad_kbd_bl_brightness_get(struct ideapad_private *priv) 132162306a36Sopenharmony_ci{ 132262306a36Sopenharmony_ci unsigned long value; 132362306a36Sopenharmony_ci int err; 132462306a36Sopenharmony_ci 132562306a36Sopenharmony_ci if (ideapad_kbd_bl_check_tristate(priv->kbd_bl.type)) { 132662306a36Sopenharmony_ci err = eval_kblc(priv->adev->handle, 132762306a36Sopenharmony_ci FIELD_PREP(KBD_BL_COMMAND_TYPE, priv->kbd_bl.type) | 132862306a36Sopenharmony_ci KBD_BL_COMMAND_GET, 132962306a36Sopenharmony_ci &value); 133062306a36Sopenharmony_ci 133162306a36Sopenharmony_ci if (err) 133262306a36Sopenharmony_ci return err; 133362306a36Sopenharmony_ci 133462306a36Sopenharmony_ci /* Convert returned value to brightness level */ 133562306a36Sopenharmony_ci value = FIELD_GET(KBD_BL_GET_BRIGHTNESS, value); 133662306a36Sopenharmony_ci 133762306a36Sopenharmony_ci /* Off, low or high */ 133862306a36Sopenharmony_ci if (value <= priv->kbd_bl.led.max_brightness) 133962306a36Sopenharmony_ci return value; 134062306a36Sopenharmony_ci 134162306a36Sopenharmony_ci /* Auto, report as off */ 134262306a36Sopenharmony_ci if (value == priv->kbd_bl.led.max_brightness + 1) 134362306a36Sopenharmony_ci return 0; 134462306a36Sopenharmony_ci 134562306a36Sopenharmony_ci /* Unknown value */ 134662306a36Sopenharmony_ci dev_warn(&priv->platform_device->dev, 134762306a36Sopenharmony_ci "Unknown keyboard backlight value: %lu", value); 134862306a36Sopenharmony_ci return -EINVAL; 134962306a36Sopenharmony_ci } 135062306a36Sopenharmony_ci 135162306a36Sopenharmony_ci err = eval_hals(priv->adev->handle, &value); 135262306a36Sopenharmony_ci if (err) 135362306a36Sopenharmony_ci return err; 135462306a36Sopenharmony_ci 135562306a36Sopenharmony_ci return !!test_bit(HALS_KBD_BL_STATE_BIT, &value); 135662306a36Sopenharmony_ci} 135762306a36Sopenharmony_ci 135862306a36Sopenharmony_cistatic enum led_brightness ideapad_kbd_bl_led_cdev_brightness_get(struct led_classdev *led_cdev) 135962306a36Sopenharmony_ci{ 136062306a36Sopenharmony_ci struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, kbd_bl.led); 136162306a36Sopenharmony_ci 136262306a36Sopenharmony_ci return ideapad_kbd_bl_brightness_get(priv); 136362306a36Sopenharmony_ci} 136462306a36Sopenharmony_ci 136562306a36Sopenharmony_cistatic int ideapad_kbd_bl_brightness_set(struct ideapad_private *priv, unsigned int brightness) 136662306a36Sopenharmony_ci{ 136762306a36Sopenharmony_ci int err; 136862306a36Sopenharmony_ci unsigned long value; 136962306a36Sopenharmony_ci int type = priv->kbd_bl.type; 137062306a36Sopenharmony_ci 137162306a36Sopenharmony_ci if (ideapad_kbd_bl_check_tristate(type)) { 137262306a36Sopenharmony_ci if (brightness > priv->kbd_bl.led.max_brightness) 137362306a36Sopenharmony_ci return -EINVAL; 137462306a36Sopenharmony_ci 137562306a36Sopenharmony_ci value = FIELD_PREP(KBD_BL_SET_BRIGHTNESS, brightness) | 137662306a36Sopenharmony_ci FIELD_PREP(KBD_BL_COMMAND_TYPE, type) | 137762306a36Sopenharmony_ci KBD_BL_COMMAND_SET; 137862306a36Sopenharmony_ci err = exec_kblc(priv->adev->handle, value); 137962306a36Sopenharmony_ci } else { 138062306a36Sopenharmony_ci err = exec_sals(priv->adev->handle, brightness ? SALS_KBD_BL_ON : SALS_KBD_BL_OFF); 138162306a36Sopenharmony_ci } 138262306a36Sopenharmony_ci 138362306a36Sopenharmony_ci if (err) 138462306a36Sopenharmony_ci return err; 138562306a36Sopenharmony_ci 138662306a36Sopenharmony_ci priv->kbd_bl.last_brightness = brightness; 138762306a36Sopenharmony_ci 138862306a36Sopenharmony_ci return 0; 138962306a36Sopenharmony_ci} 139062306a36Sopenharmony_ci 139162306a36Sopenharmony_cistatic int ideapad_kbd_bl_led_cdev_brightness_set(struct led_classdev *led_cdev, 139262306a36Sopenharmony_ci enum led_brightness brightness) 139362306a36Sopenharmony_ci{ 139462306a36Sopenharmony_ci struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, kbd_bl.led); 139562306a36Sopenharmony_ci 139662306a36Sopenharmony_ci return ideapad_kbd_bl_brightness_set(priv, brightness); 139762306a36Sopenharmony_ci} 139862306a36Sopenharmony_ci 139962306a36Sopenharmony_cistatic void ideapad_kbd_bl_notify(struct ideapad_private *priv) 140062306a36Sopenharmony_ci{ 140162306a36Sopenharmony_ci int brightness; 140262306a36Sopenharmony_ci 140362306a36Sopenharmony_ci if (!priv->kbd_bl.initialized) 140462306a36Sopenharmony_ci return; 140562306a36Sopenharmony_ci 140662306a36Sopenharmony_ci brightness = ideapad_kbd_bl_brightness_get(priv); 140762306a36Sopenharmony_ci if (brightness < 0) 140862306a36Sopenharmony_ci return; 140962306a36Sopenharmony_ci 141062306a36Sopenharmony_ci if (brightness == priv->kbd_bl.last_brightness) 141162306a36Sopenharmony_ci return; 141262306a36Sopenharmony_ci 141362306a36Sopenharmony_ci priv->kbd_bl.last_brightness = brightness; 141462306a36Sopenharmony_ci 141562306a36Sopenharmony_ci led_classdev_notify_brightness_hw_changed(&priv->kbd_bl.led, brightness); 141662306a36Sopenharmony_ci} 141762306a36Sopenharmony_ci 141862306a36Sopenharmony_cistatic int ideapad_kbd_bl_init(struct ideapad_private *priv) 141962306a36Sopenharmony_ci{ 142062306a36Sopenharmony_ci int brightness, err; 142162306a36Sopenharmony_ci 142262306a36Sopenharmony_ci if (!priv->features.kbd_bl) 142362306a36Sopenharmony_ci return -ENODEV; 142462306a36Sopenharmony_ci 142562306a36Sopenharmony_ci if (WARN_ON(priv->kbd_bl.initialized)) 142662306a36Sopenharmony_ci return -EEXIST; 142762306a36Sopenharmony_ci 142862306a36Sopenharmony_ci if (ideapad_kbd_bl_check_tristate(priv->kbd_bl.type)) { 142962306a36Sopenharmony_ci priv->kbd_bl.led.max_brightness = 2; 143062306a36Sopenharmony_ci } else { 143162306a36Sopenharmony_ci priv->kbd_bl.led.max_brightness = 1; 143262306a36Sopenharmony_ci } 143362306a36Sopenharmony_ci 143462306a36Sopenharmony_ci brightness = ideapad_kbd_bl_brightness_get(priv); 143562306a36Sopenharmony_ci if (brightness < 0) 143662306a36Sopenharmony_ci return brightness; 143762306a36Sopenharmony_ci 143862306a36Sopenharmony_ci priv->kbd_bl.last_brightness = brightness; 143962306a36Sopenharmony_ci priv->kbd_bl.led.name = "platform::" LED_FUNCTION_KBD_BACKLIGHT; 144062306a36Sopenharmony_ci priv->kbd_bl.led.brightness_get = ideapad_kbd_bl_led_cdev_brightness_get; 144162306a36Sopenharmony_ci priv->kbd_bl.led.brightness_set_blocking = ideapad_kbd_bl_led_cdev_brightness_set; 144262306a36Sopenharmony_ci priv->kbd_bl.led.flags = LED_BRIGHT_HW_CHANGED; 144362306a36Sopenharmony_ci 144462306a36Sopenharmony_ci err = led_classdev_register(&priv->platform_device->dev, &priv->kbd_bl.led); 144562306a36Sopenharmony_ci if (err) 144662306a36Sopenharmony_ci return err; 144762306a36Sopenharmony_ci 144862306a36Sopenharmony_ci priv->kbd_bl.initialized = true; 144962306a36Sopenharmony_ci 145062306a36Sopenharmony_ci return 0; 145162306a36Sopenharmony_ci} 145262306a36Sopenharmony_ci 145362306a36Sopenharmony_cistatic void ideapad_kbd_bl_exit(struct ideapad_private *priv) 145462306a36Sopenharmony_ci{ 145562306a36Sopenharmony_ci if (!priv->kbd_bl.initialized) 145662306a36Sopenharmony_ci return; 145762306a36Sopenharmony_ci 145862306a36Sopenharmony_ci priv->kbd_bl.initialized = false; 145962306a36Sopenharmony_ci 146062306a36Sopenharmony_ci led_classdev_unregister(&priv->kbd_bl.led); 146162306a36Sopenharmony_ci} 146262306a36Sopenharmony_ci 146362306a36Sopenharmony_ci/* 146462306a36Sopenharmony_ci * module init/exit 146562306a36Sopenharmony_ci */ 146662306a36Sopenharmony_cistatic void ideapad_sync_touchpad_state(struct ideapad_private *priv, bool send_events) 146762306a36Sopenharmony_ci{ 146862306a36Sopenharmony_ci unsigned long value; 146962306a36Sopenharmony_ci unsigned char param; 147062306a36Sopenharmony_ci int ret; 147162306a36Sopenharmony_ci 147262306a36Sopenharmony_ci /* Without reading from EC touchpad LED doesn't switch state */ 147362306a36Sopenharmony_ci ret = read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value); 147462306a36Sopenharmony_ci if (ret) 147562306a36Sopenharmony_ci return; 147662306a36Sopenharmony_ci 147762306a36Sopenharmony_ci /* 147862306a36Sopenharmony_ci * Some IdeaPads don't really turn off touchpad - they only 147962306a36Sopenharmony_ci * switch the LED state. We (de)activate KBC AUX port to turn 148062306a36Sopenharmony_ci * touchpad off and on. We send KEY_TOUCHPAD_OFF and 148162306a36Sopenharmony_ci * KEY_TOUCHPAD_ON to not to get out of sync with LED 148262306a36Sopenharmony_ci */ 148362306a36Sopenharmony_ci if (priv->features.ctrl_ps2_aux_port) 148462306a36Sopenharmony_ci i8042_command(¶m, value ? I8042_CMD_AUX_ENABLE : I8042_CMD_AUX_DISABLE); 148562306a36Sopenharmony_ci 148662306a36Sopenharmony_ci /* 148762306a36Sopenharmony_ci * On older models the EC controls the touchpad and toggles it on/off 148862306a36Sopenharmony_ci * itself, in this case we report KEY_TOUCHPAD_ON/_OFF. Some models do 148962306a36Sopenharmony_ci * an acpi-notify with VPC bit 5 set on resume, so this function get 149062306a36Sopenharmony_ci * called with send_events=true on every resume. Therefor if the EC did 149162306a36Sopenharmony_ci * not toggle, do nothing to avoid sending spurious KEY_TOUCHPAD_TOGGLE. 149262306a36Sopenharmony_ci */ 149362306a36Sopenharmony_ci if (send_events && value != priv->r_touchpad_val) { 149462306a36Sopenharmony_ci ideapad_input_report(priv, value ? 67 : 66); 149562306a36Sopenharmony_ci sysfs_notify(&priv->platform_device->dev.kobj, NULL, "touchpad"); 149662306a36Sopenharmony_ci } 149762306a36Sopenharmony_ci 149862306a36Sopenharmony_ci priv->r_touchpad_val = value; 149962306a36Sopenharmony_ci} 150062306a36Sopenharmony_ci 150162306a36Sopenharmony_cistatic void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) 150262306a36Sopenharmony_ci{ 150362306a36Sopenharmony_ci struct ideapad_private *priv = data; 150462306a36Sopenharmony_ci unsigned long vpc1, vpc2, bit; 150562306a36Sopenharmony_ci 150662306a36Sopenharmony_ci if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1)) 150762306a36Sopenharmony_ci return; 150862306a36Sopenharmony_ci 150962306a36Sopenharmony_ci if (read_ec_data(handle, VPCCMD_R_VPC2, &vpc2)) 151062306a36Sopenharmony_ci return; 151162306a36Sopenharmony_ci 151262306a36Sopenharmony_ci vpc1 = (vpc2 << 8) | vpc1; 151362306a36Sopenharmony_ci 151462306a36Sopenharmony_ci for_each_set_bit (bit, &vpc1, 16) { 151562306a36Sopenharmony_ci switch (bit) { 151662306a36Sopenharmony_ci case 13: 151762306a36Sopenharmony_ci case 11: 151862306a36Sopenharmony_ci case 8: 151962306a36Sopenharmony_ci case 7: 152062306a36Sopenharmony_ci case 6: 152162306a36Sopenharmony_ci ideapad_input_report(priv, bit); 152262306a36Sopenharmony_ci break; 152362306a36Sopenharmony_ci case 10: 152462306a36Sopenharmony_ci /* 152562306a36Sopenharmony_ci * This event gets send on a Yoga 300-11IBR when the EC 152662306a36Sopenharmony_ci * believes that the device has changed between laptop/ 152762306a36Sopenharmony_ci * tent/stand/tablet mode. The EC relies on getting 152862306a36Sopenharmony_ci * angle info from 2 accelerometers through a special 152962306a36Sopenharmony_ci * windows service calling a DSM on the DUAL250E ACPI- 153062306a36Sopenharmony_ci * device. Linux does not do this, making the laptop/ 153162306a36Sopenharmony_ci * tent/stand/tablet mode info unreliable, so we simply 153262306a36Sopenharmony_ci * ignore these events. 153362306a36Sopenharmony_ci */ 153462306a36Sopenharmony_ci break; 153562306a36Sopenharmony_ci case 9: 153662306a36Sopenharmony_ci ideapad_sync_rfk_state(priv); 153762306a36Sopenharmony_ci break; 153862306a36Sopenharmony_ci case 5: 153962306a36Sopenharmony_ci ideapad_sync_touchpad_state(priv, true); 154062306a36Sopenharmony_ci break; 154162306a36Sopenharmony_ci case 4: 154262306a36Sopenharmony_ci ideapad_backlight_notify_brightness(priv); 154362306a36Sopenharmony_ci break; 154462306a36Sopenharmony_ci case 3: 154562306a36Sopenharmony_ci ideapad_input_novokey(priv); 154662306a36Sopenharmony_ci break; 154762306a36Sopenharmony_ci case 2: 154862306a36Sopenharmony_ci ideapad_backlight_notify_power(priv); 154962306a36Sopenharmony_ci break; 155062306a36Sopenharmony_ci case KBD_BL_KBLC_CHANGED_EVENT: 155162306a36Sopenharmony_ci case 1: 155262306a36Sopenharmony_ci /* 155362306a36Sopenharmony_ci * Some IdeaPads report event 1 every ~20 155462306a36Sopenharmony_ci * seconds while on battery power; some 155562306a36Sopenharmony_ci * report this when changing to/from tablet 155662306a36Sopenharmony_ci * mode; some report this when the keyboard 155762306a36Sopenharmony_ci * backlight has changed. 155862306a36Sopenharmony_ci */ 155962306a36Sopenharmony_ci ideapad_kbd_bl_notify(priv); 156062306a36Sopenharmony_ci break; 156162306a36Sopenharmony_ci case 0: 156262306a36Sopenharmony_ci ideapad_check_special_buttons(priv); 156362306a36Sopenharmony_ci break; 156462306a36Sopenharmony_ci default: 156562306a36Sopenharmony_ci dev_info(&priv->platform_device->dev, 156662306a36Sopenharmony_ci "Unknown event: %lu\n", bit); 156762306a36Sopenharmony_ci } 156862306a36Sopenharmony_ci } 156962306a36Sopenharmony_ci} 157062306a36Sopenharmony_ci 157162306a36Sopenharmony_ci/* On some models we need to call exec_sals(SALS_FNLOCK_ON/OFF) to set the LED */ 157262306a36Sopenharmony_cistatic const struct dmi_system_id set_fn_lock_led_list[] = { 157362306a36Sopenharmony_ci { 157462306a36Sopenharmony_ci /* https://bugzilla.kernel.org/show_bug.cgi?id=212671 */ 157562306a36Sopenharmony_ci .matches = { 157662306a36Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 157762306a36Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Legion R7000P2020H"), 157862306a36Sopenharmony_ci } 157962306a36Sopenharmony_ci }, 158062306a36Sopenharmony_ci { 158162306a36Sopenharmony_ci .matches = { 158262306a36Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 158362306a36Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Legion 5 15ARH05"), 158462306a36Sopenharmony_ci } 158562306a36Sopenharmony_ci }, 158662306a36Sopenharmony_ci {} 158762306a36Sopenharmony_ci}; 158862306a36Sopenharmony_ci 158962306a36Sopenharmony_ci/* 159062306a36Sopenharmony_ci * Some ideapads have a hardware rfkill switch, but most do not have one. 159162306a36Sopenharmony_ci * Reading VPCCMD_R_RF always results in 0 on models without a hardware rfkill, 159262306a36Sopenharmony_ci * switch causing ideapad_laptop to wrongly report all radios as hw-blocked. 159362306a36Sopenharmony_ci * There used to be a long list of DMI ids for models without a hw rfkill 159462306a36Sopenharmony_ci * switch here, but that resulted in playing whack a mole. 159562306a36Sopenharmony_ci * More importantly wrongly reporting the wifi radio as hw-blocked, results in 159662306a36Sopenharmony_ci * non working wifi. Whereas not reporting it hw-blocked, when it actually is 159762306a36Sopenharmony_ci * hw-blocked results in an empty SSID list, which is a much more benign 159862306a36Sopenharmony_ci * failure mode. 159962306a36Sopenharmony_ci * So the default now is the much safer option of assuming there is no 160062306a36Sopenharmony_ci * hardware rfkill switch. This default also actually matches most hardware, 160162306a36Sopenharmony_ci * since having a hw rfkill switch is quite rare on modern hardware, so this 160262306a36Sopenharmony_ci * also leads to a much shorter list. 160362306a36Sopenharmony_ci */ 160462306a36Sopenharmony_cistatic const struct dmi_system_id hw_rfkill_list[] = { 160562306a36Sopenharmony_ci {} 160662306a36Sopenharmony_ci}; 160762306a36Sopenharmony_ci 160862306a36Sopenharmony_ci/* 160962306a36Sopenharmony_ci * On some models the EC toggles the touchpad muted LED on touchpad toggle 161062306a36Sopenharmony_ci * hotkey presses, but the EC does not actually disable the touchpad itself. 161162306a36Sopenharmony_ci * On these models the driver needs to explicitly enable/disable the i8042 161262306a36Sopenharmony_ci * (PS/2) aux port. 161362306a36Sopenharmony_ci */ 161462306a36Sopenharmony_cistatic const struct dmi_system_id ctrl_ps2_aux_port_list[] = { 161562306a36Sopenharmony_ci { 161662306a36Sopenharmony_ci /* Lenovo Ideapad Z570 */ 161762306a36Sopenharmony_ci .matches = { 161862306a36Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 161962306a36Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_VERSION, "Ideapad Z570"), 162062306a36Sopenharmony_ci }, 162162306a36Sopenharmony_ci }, 162262306a36Sopenharmony_ci {} 162362306a36Sopenharmony_ci}; 162462306a36Sopenharmony_ci 162562306a36Sopenharmony_cistatic void ideapad_check_features(struct ideapad_private *priv) 162662306a36Sopenharmony_ci{ 162762306a36Sopenharmony_ci acpi_handle handle = priv->adev->handle; 162862306a36Sopenharmony_ci unsigned long val; 162962306a36Sopenharmony_ci 163062306a36Sopenharmony_ci priv->features.set_fn_lock_led = 163162306a36Sopenharmony_ci set_fn_lock_led || dmi_check_system(set_fn_lock_led_list); 163262306a36Sopenharmony_ci priv->features.hw_rfkill_switch = 163362306a36Sopenharmony_ci hw_rfkill_switch || dmi_check_system(hw_rfkill_list); 163462306a36Sopenharmony_ci priv->features.ctrl_ps2_aux_port = 163562306a36Sopenharmony_ci ctrl_ps2_aux_port || dmi_check_system(ctrl_ps2_aux_port_list); 163662306a36Sopenharmony_ci priv->features.touchpad_ctrl_via_ec = touchpad_ctrl_via_ec; 163762306a36Sopenharmony_ci 163862306a36Sopenharmony_ci if (!read_ec_data(handle, VPCCMD_R_FAN, &val)) 163962306a36Sopenharmony_ci priv->features.fan_mode = true; 164062306a36Sopenharmony_ci 164162306a36Sopenharmony_ci if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) 164262306a36Sopenharmony_ci priv->features.conservation_mode = true; 164362306a36Sopenharmony_ci 164462306a36Sopenharmony_ci if (acpi_has_method(handle, "DYTC")) 164562306a36Sopenharmony_ci priv->features.dytc = true; 164662306a36Sopenharmony_ci 164762306a36Sopenharmony_ci if (acpi_has_method(handle, "HALS") && acpi_has_method(handle, "SALS")) { 164862306a36Sopenharmony_ci if (!eval_hals(handle, &val)) { 164962306a36Sopenharmony_ci if (test_bit(HALS_FNLOCK_SUPPORT_BIT, &val)) 165062306a36Sopenharmony_ci priv->features.fn_lock = true; 165162306a36Sopenharmony_ci 165262306a36Sopenharmony_ci if (test_bit(HALS_KBD_BL_SUPPORT_BIT, &val)) { 165362306a36Sopenharmony_ci priv->features.kbd_bl = true; 165462306a36Sopenharmony_ci priv->kbd_bl.type = KBD_BL_STANDARD; 165562306a36Sopenharmony_ci } 165662306a36Sopenharmony_ci 165762306a36Sopenharmony_ci if (test_bit(HALS_USB_CHARGING_SUPPORT_BIT, &val)) 165862306a36Sopenharmony_ci priv->features.usb_charging = true; 165962306a36Sopenharmony_ci } 166062306a36Sopenharmony_ci } 166162306a36Sopenharmony_ci 166262306a36Sopenharmony_ci if (acpi_has_method(handle, "KBLC")) { 166362306a36Sopenharmony_ci if (!eval_kblc(priv->adev->handle, KBD_BL_QUERY_TYPE, &val)) { 166462306a36Sopenharmony_ci if (val == KBD_BL_TRISTATE_TYPE) { 166562306a36Sopenharmony_ci priv->features.kbd_bl = true; 166662306a36Sopenharmony_ci priv->kbd_bl.type = KBD_BL_TRISTATE; 166762306a36Sopenharmony_ci } else if (val == KBD_BL_TRISTATE_AUTO_TYPE) { 166862306a36Sopenharmony_ci priv->features.kbd_bl = true; 166962306a36Sopenharmony_ci priv->kbd_bl.type = KBD_BL_TRISTATE_AUTO; 167062306a36Sopenharmony_ci } else { 167162306a36Sopenharmony_ci dev_warn(&priv->platform_device->dev, 167262306a36Sopenharmony_ci "Unknown keyboard type: %lu", 167362306a36Sopenharmony_ci val); 167462306a36Sopenharmony_ci } 167562306a36Sopenharmony_ci } 167662306a36Sopenharmony_ci } 167762306a36Sopenharmony_ci} 167862306a36Sopenharmony_ci 167962306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_ACPI_WMI) 168062306a36Sopenharmony_ci/* 168162306a36Sopenharmony_ci * WMI driver 168262306a36Sopenharmony_ci */ 168362306a36Sopenharmony_cienum ideapad_wmi_event_type { 168462306a36Sopenharmony_ci IDEAPAD_WMI_EVENT_ESC, 168562306a36Sopenharmony_ci IDEAPAD_WMI_EVENT_FN_KEYS, 168662306a36Sopenharmony_ci}; 168762306a36Sopenharmony_ci 168862306a36Sopenharmony_cistruct ideapad_wmi_private { 168962306a36Sopenharmony_ci enum ideapad_wmi_event_type event; 169062306a36Sopenharmony_ci}; 169162306a36Sopenharmony_ci 169262306a36Sopenharmony_cistatic int ideapad_wmi_probe(struct wmi_device *wdev, const void *context) 169362306a36Sopenharmony_ci{ 169462306a36Sopenharmony_ci struct ideapad_wmi_private *wpriv; 169562306a36Sopenharmony_ci 169662306a36Sopenharmony_ci wpriv = devm_kzalloc(&wdev->dev, sizeof(*wpriv), GFP_KERNEL); 169762306a36Sopenharmony_ci if (!wpriv) 169862306a36Sopenharmony_ci return -ENOMEM; 169962306a36Sopenharmony_ci 170062306a36Sopenharmony_ci *wpriv = *(const struct ideapad_wmi_private *)context; 170162306a36Sopenharmony_ci 170262306a36Sopenharmony_ci dev_set_drvdata(&wdev->dev, wpriv); 170362306a36Sopenharmony_ci return 0; 170462306a36Sopenharmony_ci} 170562306a36Sopenharmony_ci 170662306a36Sopenharmony_cistatic void ideapad_wmi_notify(struct wmi_device *wdev, union acpi_object *data) 170762306a36Sopenharmony_ci{ 170862306a36Sopenharmony_ci struct ideapad_wmi_private *wpriv = dev_get_drvdata(&wdev->dev); 170962306a36Sopenharmony_ci struct ideapad_private *priv; 171062306a36Sopenharmony_ci unsigned long result; 171162306a36Sopenharmony_ci 171262306a36Sopenharmony_ci mutex_lock(&ideapad_shared_mutex); 171362306a36Sopenharmony_ci 171462306a36Sopenharmony_ci priv = ideapad_shared; 171562306a36Sopenharmony_ci if (!priv) 171662306a36Sopenharmony_ci goto unlock; 171762306a36Sopenharmony_ci 171862306a36Sopenharmony_ci switch (wpriv->event) { 171962306a36Sopenharmony_ci case IDEAPAD_WMI_EVENT_ESC: 172062306a36Sopenharmony_ci ideapad_input_report(priv, 128); 172162306a36Sopenharmony_ci break; 172262306a36Sopenharmony_ci case IDEAPAD_WMI_EVENT_FN_KEYS: 172362306a36Sopenharmony_ci if (priv->features.set_fn_lock_led && 172462306a36Sopenharmony_ci !eval_hals(priv->adev->handle, &result)) { 172562306a36Sopenharmony_ci bool state = test_bit(HALS_FNLOCK_STATE_BIT, &result); 172662306a36Sopenharmony_ci 172762306a36Sopenharmony_ci exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF); 172862306a36Sopenharmony_ci } 172962306a36Sopenharmony_ci 173062306a36Sopenharmony_ci if (data->type != ACPI_TYPE_INTEGER) { 173162306a36Sopenharmony_ci dev_warn(&wdev->dev, 173262306a36Sopenharmony_ci "WMI event data is not an integer\n"); 173362306a36Sopenharmony_ci break; 173462306a36Sopenharmony_ci } 173562306a36Sopenharmony_ci 173662306a36Sopenharmony_ci dev_dbg(&wdev->dev, "WMI fn-key event: 0x%llx\n", 173762306a36Sopenharmony_ci data->integer.value); 173862306a36Sopenharmony_ci 173962306a36Sopenharmony_ci ideapad_input_report(priv, 174062306a36Sopenharmony_ci data->integer.value | IDEAPAD_WMI_KEY); 174162306a36Sopenharmony_ci 174262306a36Sopenharmony_ci break; 174362306a36Sopenharmony_ci } 174462306a36Sopenharmony_ciunlock: 174562306a36Sopenharmony_ci mutex_unlock(&ideapad_shared_mutex); 174662306a36Sopenharmony_ci} 174762306a36Sopenharmony_ci 174862306a36Sopenharmony_cistatic const struct ideapad_wmi_private ideapad_wmi_context_esc = { 174962306a36Sopenharmony_ci .event = IDEAPAD_WMI_EVENT_ESC 175062306a36Sopenharmony_ci}; 175162306a36Sopenharmony_ci 175262306a36Sopenharmony_cistatic const struct ideapad_wmi_private ideapad_wmi_context_fn_keys = { 175362306a36Sopenharmony_ci .event = IDEAPAD_WMI_EVENT_FN_KEYS 175462306a36Sopenharmony_ci}; 175562306a36Sopenharmony_ci 175662306a36Sopenharmony_cistatic const struct wmi_device_id ideapad_wmi_ids[] = { 175762306a36Sopenharmony_ci { "26CAB2E5-5CF1-46AE-AAC3-4A12B6BA50E6", &ideapad_wmi_context_esc }, /* Yoga 3 */ 175862306a36Sopenharmony_ci { "56322276-8493-4CE8-A783-98C991274F5E", &ideapad_wmi_context_esc }, /* Yoga 700 */ 175962306a36Sopenharmony_ci { "8FC0DE0C-B4E4-43FD-B0F3-8871711C1294", &ideapad_wmi_context_fn_keys }, /* Legion 5 */ 176062306a36Sopenharmony_ci {}, 176162306a36Sopenharmony_ci}; 176262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(wmi, ideapad_wmi_ids); 176362306a36Sopenharmony_ci 176462306a36Sopenharmony_cistatic struct wmi_driver ideapad_wmi_driver = { 176562306a36Sopenharmony_ci .driver = { 176662306a36Sopenharmony_ci .name = "ideapad_wmi", 176762306a36Sopenharmony_ci }, 176862306a36Sopenharmony_ci .id_table = ideapad_wmi_ids, 176962306a36Sopenharmony_ci .probe = ideapad_wmi_probe, 177062306a36Sopenharmony_ci .notify = ideapad_wmi_notify, 177162306a36Sopenharmony_ci}; 177262306a36Sopenharmony_ci 177362306a36Sopenharmony_cistatic int ideapad_wmi_driver_register(void) 177462306a36Sopenharmony_ci{ 177562306a36Sopenharmony_ci return wmi_driver_register(&ideapad_wmi_driver); 177662306a36Sopenharmony_ci} 177762306a36Sopenharmony_ci 177862306a36Sopenharmony_cistatic void ideapad_wmi_driver_unregister(void) 177962306a36Sopenharmony_ci{ 178062306a36Sopenharmony_ci return wmi_driver_unregister(&ideapad_wmi_driver); 178162306a36Sopenharmony_ci} 178262306a36Sopenharmony_ci 178362306a36Sopenharmony_ci#else 178462306a36Sopenharmony_cistatic inline int ideapad_wmi_driver_register(void) { return 0; } 178562306a36Sopenharmony_cistatic inline void ideapad_wmi_driver_unregister(void) { } 178662306a36Sopenharmony_ci#endif 178762306a36Sopenharmony_ci 178862306a36Sopenharmony_ci/* 178962306a36Sopenharmony_ci * ACPI driver 179062306a36Sopenharmony_ci */ 179162306a36Sopenharmony_cistatic int ideapad_acpi_add(struct platform_device *pdev) 179262306a36Sopenharmony_ci{ 179362306a36Sopenharmony_ci struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); 179462306a36Sopenharmony_ci struct ideapad_private *priv; 179562306a36Sopenharmony_ci acpi_status status; 179662306a36Sopenharmony_ci unsigned long cfg; 179762306a36Sopenharmony_ci int err, i; 179862306a36Sopenharmony_ci 179962306a36Sopenharmony_ci if (!adev || eval_int(adev->handle, "_CFG", &cfg)) 180062306a36Sopenharmony_ci return -ENODEV; 180162306a36Sopenharmony_ci 180262306a36Sopenharmony_ci priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 180362306a36Sopenharmony_ci if (!priv) 180462306a36Sopenharmony_ci return -ENOMEM; 180562306a36Sopenharmony_ci 180662306a36Sopenharmony_ci dev_set_drvdata(&pdev->dev, priv); 180762306a36Sopenharmony_ci 180862306a36Sopenharmony_ci priv->cfg = cfg; 180962306a36Sopenharmony_ci priv->adev = adev; 181062306a36Sopenharmony_ci priv->platform_device = pdev; 181162306a36Sopenharmony_ci 181262306a36Sopenharmony_ci ideapad_check_features(priv); 181362306a36Sopenharmony_ci 181462306a36Sopenharmony_ci err = ideapad_sysfs_init(priv); 181562306a36Sopenharmony_ci if (err) 181662306a36Sopenharmony_ci return err; 181762306a36Sopenharmony_ci 181862306a36Sopenharmony_ci ideapad_debugfs_init(priv); 181962306a36Sopenharmony_ci 182062306a36Sopenharmony_ci err = ideapad_input_init(priv); 182162306a36Sopenharmony_ci if (err) 182262306a36Sopenharmony_ci goto input_failed; 182362306a36Sopenharmony_ci 182462306a36Sopenharmony_ci err = ideapad_kbd_bl_init(priv); 182562306a36Sopenharmony_ci if (err) { 182662306a36Sopenharmony_ci if (err != -ENODEV) 182762306a36Sopenharmony_ci dev_warn(&pdev->dev, "Could not set up keyboard backlight LED: %d\n", err); 182862306a36Sopenharmony_ci else 182962306a36Sopenharmony_ci dev_info(&pdev->dev, "Keyboard backlight control not available\n"); 183062306a36Sopenharmony_ci } 183162306a36Sopenharmony_ci 183262306a36Sopenharmony_ci /* 183362306a36Sopenharmony_ci * On some models without a hw-switch (the yoga 2 13 at least) 183462306a36Sopenharmony_ci * VPCCMD_W_RF must be explicitly set to 1 for the wifi to work. 183562306a36Sopenharmony_ci */ 183662306a36Sopenharmony_ci if (!priv->features.hw_rfkill_switch) 183762306a36Sopenharmony_ci write_ec_cmd(priv->adev->handle, VPCCMD_W_RF, 1); 183862306a36Sopenharmony_ci 183962306a36Sopenharmony_ci for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 184062306a36Sopenharmony_ci if (test_bit(ideapad_rfk_data[i].cfgbit, &priv->cfg)) 184162306a36Sopenharmony_ci ideapad_register_rfkill(priv, i); 184262306a36Sopenharmony_ci 184362306a36Sopenharmony_ci ideapad_sync_rfk_state(priv); 184462306a36Sopenharmony_ci ideapad_sync_touchpad_state(priv, false); 184562306a36Sopenharmony_ci 184662306a36Sopenharmony_ci err = ideapad_dytc_profile_init(priv); 184762306a36Sopenharmony_ci if (err) { 184862306a36Sopenharmony_ci if (err != -ENODEV) 184962306a36Sopenharmony_ci dev_warn(&pdev->dev, "Could not set up DYTC interface: %d\n", err); 185062306a36Sopenharmony_ci else 185162306a36Sopenharmony_ci dev_info(&pdev->dev, "DYTC interface is not available\n"); 185262306a36Sopenharmony_ci } 185362306a36Sopenharmony_ci 185462306a36Sopenharmony_ci if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { 185562306a36Sopenharmony_ci err = ideapad_backlight_init(priv); 185662306a36Sopenharmony_ci if (err && err != -ENODEV) 185762306a36Sopenharmony_ci goto backlight_failed; 185862306a36Sopenharmony_ci } 185962306a36Sopenharmony_ci 186062306a36Sopenharmony_ci status = acpi_install_notify_handler(adev->handle, 186162306a36Sopenharmony_ci ACPI_DEVICE_NOTIFY, 186262306a36Sopenharmony_ci ideapad_acpi_notify, priv); 186362306a36Sopenharmony_ci if (ACPI_FAILURE(status)) { 186462306a36Sopenharmony_ci err = -EIO; 186562306a36Sopenharmony_ci goto notification_failed; 186662306a36Sopenharmony_ci } 186762306a36Sopenharmony_ci 186862306a36Sopenharmony_ci err = ideapad_shared_init(priv); 186962306a36Sopenharmony_ci if (err) 187062306a36Sopenharmony_ci goto shared_init_failed; 187162306a36Sopenharmony_ci 187262306a36Sopenharmony_ci return 0; 187362306a36Sopenharmony_ci 187462306a36Sopenharmony_cishared_init_failed: 187562306a36Sopenharmony_ci acpi_remove_notify_handler(priv->adev->handle, 187662306a36Sopenharmony_ci ACPI_DEVICE_NOTIFY, 187762306a36Sopenharmony_ci ideapad_acpi_notify); 187862306a36Sopenharmony_ci 187962306a36Sopenharmony_cinotification_failed: 188062306a36Sopenharmony_ci ideapad_backlight_exit(priv); 188162306a36Sopenharmony_ci 188262306a36Sopenharmony_cibacklight_failed: 188362306a36Sopenharmony_ci ideapad_dytc_profile_exit(priv); 188462306a36Sopenharmony_ci 188562306a36Sopenharmony_ci for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 188662306a36Sopenharmony_ci ideapad_unregister_rfkill(priv, i); 188762306a36Sopenharmony_ci 188862306a36Sopenharmony_ci ideapad_kbd_bl_exit(priv); 188962306a36Sopenharmony_ci ideapad_input_exit(priv); 189062306a36Sopenharmony_ci 189162306a36Sopenharmony_ciinput_failed: 189262306a36Sopenharmony_ci ideapad_debugfs_exit(priv); 189362306a36Sopenharmony_ci ideapad_sysfs_exit(priv); 189462306a36Sopenharmony_ci 189562306a36Sopenharmony_ci return err; 189662306a36Sopenharmony_ci} 189762306a36Sopenharmony_ci 189862306a36Sopenharmony_cistatic void ideapad_acpi_remove(struct platform_device *pdev) 189962306a36Sopenharmony_ci{ 190062306a36Sopenharmony_ci struct ideapad_private *priv = dev_get_drvdata(&pdev->dev); 190162306a36Sopenharmony_ci int i; 190262306a36Sopenharmony_ci 190362306a36Sopenharmony_ci ideapad_shared_exit(priv); 190462306a36Sopenharmony_ci 190562306a36Sopenharmony_ci acpi_remove_notify_handler(priv->adev->handle, 190662306a36Sopenharmony_ci ACPI_DEVICE_NOTIFY, 190762306a36Sopenharmony_ci ideapad_acpi_notify); 190862306a36Sopenharmony_ci 190962306a36Sopenharmony_ci ideapad_backlight_exit(priv); 191062306a36Sopenharmony_ci ideapad_dytc_profile_exit(priv); 191162306a36Sopenharmony_ci 191262306a36Sopenharmony_ci for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 191362306a36Sopenharmony_ci ideapad_unregister_rfkill(priv, i); 191462306a36Sopenharmony_ci 191562306a36Sopenharmony_ci ideapad_kbd_bl_exit(priv); 191662306a36Sopenharmony_ci ideapad_input_exit(priv); 191762306a36Sopenharmony_ci ideapad_debugfs_exit(priv); 191862306a36Sopenharmony_ci ideapad_sysfs_exit(priv); 191962306a36Sopenharmony_ci} 192062306a36Sopenharmony_ci 192162306a36Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 192262306a36Sopenharmony_cistatic int ideapad_acpi_resume(struct device *dev) 192362306a36Sopenharmony_ci{ 192462306a36Sopenharmony_ci struct ideapad_private *priv = dev_get_drvdata(dev); 192562306a36Sopenharmony_ci 192662306a36Sopenharmony_ci ideapad_sync_rfk_state(priv); 192762306a36Sopenharmony_ci ideapad_sync_touchpad_state(priv, false); 192862306a36Sopenharmony_ci 192962306a36Sopenharmony_ci if (priv->dytc) 193062306a36Sopenharmony_ci dytc_profile_refresh(priv); 193162306a36Sopenharmony_ci 193262306a36Sopenharmony_ci return 0; 193362306a36Sopenharmony_ci} 193462306a36Sopenharmony_ci#endif 193562306a36Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(ideapad_pm, NULL, ideapad_acpi_resume); 193662306a36Sopenharmony_ci 193762306a36Sopenharmony_cistatic const struct acpi_device_id ideapad_device_ids[] = { 193862306a36Sopenharmony_ci {"VPC2004", 0}, 193962306a36Sopenharmony_ci {"", 0}, 194062306a36Sopenharmony_ci}; 194162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, ideapad_device_ids); 194262306a36Sopenharmony_ci 194362306a36Sopenharmony_cistatic struct platform_driver ideapad_acpi_driver = { 194462306a36Sopenharmony_ci .probe = ideapad_acpi_add, 194562306a36Sopenharmony_ci .remove_new = ideapad_acpi_remove, 194662306a36Sopenharmony_ci .driver = { 194762306a36Sopenharmony_ci .name = "ideapad_acpi", 194862306a36Sopenharmony_ci .pm = &ideapad_pm, 194962306a36Sopenharmony_ci .acpi_match_table = ACPI_PTR(ideapad_device_ids), 195062306a36Sopenharmony_ci }, 195162306a36Sopenharmony_ci}; 195262306a36Sopenharmony_ci 195362306a36Sopenharmony_cistatic int __init ideapad_laptop_init(void) 195462306a36Sopenharmony_ci{ 195562306a36Sopenharmony_ci int err; 195662306a36Sopenharmony_ci 195762306a36Sopenharmony_ci err = ideapad_wmi_driver_register(); 195862306a36Sopenharmony_ci if (err) 195962306a36Sopenharmony_ci return err; 196062306a36Sopenharmony_ci 196162306a36Sopenharmony_ci err = platform_driver_register(&ideapad_acpi_driver); 196262306a36Sopenharmony_ci if (err) { 196362306a36Sopenharmony_ci ideapad_wmi_driver_unregister(); 196462306a36Sopenharmony_ci return err; 196562306a36Sopenharmony_ci } 196662306a36Sopenharmony_ci 196762306a36Sopenharmony_ci return 0; 196862306a36Sopenharmony_ci} 196962306a36Sopenharmony_cimodule_init(ideapad_laptop_init) 197062306a36Sopenharmony_ci 197162306a36Sopenharmony_cistatic void __exit ideapad_laptop_exit(void) 197262306a36Sopenharmony_ci{ 197362306a36Sopenharmony_ci ideapad_wmi_driver_unregister(); 197462306a36Sopenharmony_ci platform_driver_unregister(&ideapad_acpi_driver); 197562306a36Sopenharmony_ci} 197662306a36Sopenharmony_cimodule_exit(ideapad_laptop_exit) 197762306a36Sopenharmony_ci 197862306a36Sopenharmony_ciMODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); 197962306a36Sopenharmony_ciMODULE_DESCRIPTION("IdeaPad ACPI Extras"); 198062306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 1981