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(&param, 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