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