xref: /linux/drivers/platform/x86/bitland-mifs-wmi.c (revision da6b5aae84beb0917ecb0c9fbc71169d145397ff)
1dc1ec4faSMingyou Chen // SPDX-License-Identifier: GPL-2.0-or-later
2dc1ec4faSMingyou Chen /*
3dc1ec4faSMingyou Chen  * Linux driver for Bitland notebooks.
4dc1ec4faSMingyou Chen  *
5dc1ec4faSMingyou Chen  * Copyright (C) 2026 2 Mingyou Chen <qby140326@gmail.com>
6dc1ec4faSMingyou Chen  */
7dc1ec4faSMingyou Chen 
8dc1ec4faSMingyou Chen #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
9dc1ec4faSMingyou Chen 
10dc1ec4faSMingyou Chen #include <linux/acpi.h>
11dc1ec4faSMingyou Chen #include <linux/array_size.h>
12dc1ec4faSMingyou Chen #include <linux/bits.h>
13dc1ec4faSMingyou Chen #include <linux/container_of.h>
14dc1ec4faSMingyou Chen #include <linux/dev_printk.h>
15dc1ec4faSMingyou Chen #include <linux/device.h>
16dc1ec4faSMingyou Chen #include <linux/device/devres.h>
17dc1ec4faSMingyou Chen #include <linux/err.h>
18dc1ec4faSMingyou Chen #include <linux/hwmon.h>
19dc1ec4faSMingyou Chen #include <linux/init.h>
20dc1ec4faSMingyou Chen #include <linux/input-event-codes.h>
21dc1ec4faSMingyou Chen #include <linux/input.h>
22dc1ec4faSMingyou Chen #include <linux/input/sparse-keymap.h>
23dc1ec4faSMingyou Chen #include <linux/kernel.h>
24dc1ec4faSMingyou Chen #include <linux/leds.h>
25dc1ec4faSMingyou Chen #include <linux/module.h>
26dc1ec4faSMingyou Chen #include <linux/notifier.h>
27dc1ec4faSMingyou Chen #include <linux/platform_profile.h>
28dc1ec4faSMingyou Chen #include <linux/pm.h>
29dc1ec4faSMingyou Chen #include <linux/power_supply.h>
30dc1ec4faSMingyou Chen #include <linux/stddef.h>
31dc1ec4faSMingyou Chen #include <linux/string.h>
32dc1ec4faSMingyou Chen #include <linux/sysfs.h>
33dc1ec4faSMingyou Chen #include <linux/unaligned.h>
34dc1ec4faSMingyou Chen #include <linux/units.h>
35dc1ec4faSMingyou Chen #include <linux/wmi.h>
36dc1ec4faSMingyou Chen 
37dc1ec4faSMingyou Chen #define DRV_NAME		"bitland-mifs-wmi"
38dc1ec4faSMingyou Chen #define BITLAND_MIFS_GUID	"B60BFB48-3E5B-49E4-A0E9-8CFFE1B3434B"
39dc1ec4faSMingyou Chen #define BITLAND_EVENT_GUID	"46C93E13-EE9B-4262-8488-563BCA757FEF"
40dc1ec4faSMingyou Chen 
41dc1ec4faSMingyou Chen enum bitland_mifs_operation {
42dc1ec4faSMingyou Chen 	WMI_METHOD_GET	= 250,
43dc1ec4faSMingyou Chen 	WMI_METHOD_SET	= 251,
44dc1ec4faSMingyou Chen };
45dc1ec4faSMingyou Chen 
46dc1ec4faSMingyou Chen enum bitland_mifs_function {
47dc1ec4faSMingyou Chen 	WMI_FN_SYSTEM_PER_MODE		= 8,
48dc1ec4faSMingyou Chen 	WMI_FN_GPU_MODE			= 9,
49dc1ec4faSMingyou Chen 	WMI_FN_KBD_TYPE			= 10,
50dc1ec4faSMingyou Chen 	WMI_FN_FN_LOCK			= 11,
51dc1ec4faSMingyou Chen 	WMI_FN_TP_LOCK			= 12,
52dc1ec4faSMingyou Chen 	WMI_FN_FAN_SPEEDS		= 13,
53dc1ec4faSMingyou Chen 	WMI_FN_RGB_KB_MODE		= 16,
54dc1ec4faSMingyou Chen 	WMI_FN_RGB_KB_COLOR		= 17,
55dc1ec4faSMingyou Chen 	WMI_FN_RGB_KB_BRIGHTNESS	= 18,
56dc1ec4faSMingyou Chen 	WMI_FN_SYSTEM_AC_TYPE		= 19,
57dc1ec4faSMingyou Chen 	WMI_FN_MAX_FAN_SWITCH		= 20,
58dc1ec4faSMingyou Chen 	WMI_FN_MAX_FAN_SPEED		= 21,
59dc1ec4faSMingyou Chen 	WMI_FN_CPU_THERMOMETER		= 22,
60dc1ec4faSMingyou Chen 	WMI_FN_CPU_POWER		= 23,
61dc1ec4faSMingyou Chen };
62dc1ec4faSMingyou Chen 
63dc1ec4faSMingyou Chen enum bitland_system_ac_mode {
64dc1ec4faSMingyou Chen 	WMI_SYSTEM_AC_TYPEC		= 1,
65dc1ec4faSMingyou Chen 	/* Unknown type, this is unused in the original driver */
66dc1ec4faSMingyou Chen 	WMI_SYSTEM_AC_CIRCULARHOLE	= 2,
67dc1ec4faSMingyou Chen };
68dc1ec4faSMingyou Chen 
69dc1ec4faSMingyou Chen enum bitland_mifs_power_profile {
70dc1ec4faSMingyou Chen 	WMI_PP_BALANCED		= 0,
71dc1ec4faSMingyou Chen 	WMI_PP_PERFORMANCE	= 1,
72dc1ec4faSMingyou Chen 	WMI_PP_QUIET		= 2,
73dc1ec4faSMingyou Chen 	WMI_PP_FULL_SPEED	= 3,
74dc1ec4faSMingyou Chen };
75dc1ec4faSMingyou Chen 
76dc1ec4faSMingyou Chen enum bitland_mifs_event_id {
77dc1ec4faSMingyou Chen 	WMI_EVENT_RESERVED_1		= 1,
78dc1ec4faSMingyou Chen 	WMI_EVENT_RESERVED_2		= 2,
79dc1ec4faSMingyou Chen 	WMI_EVENT_RESERVED_3		= 3,
80dc1ec4faSMingyou Chen 	WMI_EVENT_AIRPLANE_MODE		= 4,
81dc1ec4faSMingyou Chen 	WMI_EVENT_KBD_BRIGHTNESS	= 5,
82dc1ec4faSMingyou Chen 	WMI_EVENT_TOUCHPAD_STATE	= 6,
83dc1ec4faSMingyou Chen 	WMI_EVENT_FNLOCK_STATE		= 7,
84dc1ec4faSMingyou Chen 	WMI_EVENT_KBD_MODE		= 8,
85dc1ec4faSMingyou Chen 	WMI_EVENT_CAPSLOCK_STATE	= 9,
86dc1ec4faSMingyou Chen 	WMI_EVENT_CALCULATOR_START	= 11,
87dc1ec4faSMingyou Chen 	WMI_EVENT_BROWSER_START		= 12,
88dc1ec4faSMingyou Chen 	WMI_EVENT_NUMLOCK_STATE		= 13,
89dc1ec4faSMingyou Chen 	WMI_EVENT_SCROLLLOCK_STATE	= 14,
90dc1ec4faSMingyou Chen 	WMI_EVENT_PERFORMANCE_PLAN	= 15,
91dc1ec4faSMingyou Chen 	WMI_EVENT_FN_J			= 16,
92dc1ec4faSMingyou Chen 	WMI_EVENT_FN_F			= 17,
93dc1ec4faSMingyou Chen 	WMI_EVENT_FN_0			= 18,
94dc1ec4faSMingyou Chen 	WMI_EVENT_FN_1			= 19,
95dc1ec4faSMingyou Chen 	WMI_EVENT_FN_2			= 20,
96dc1ec4faSMingyou Chen 	WMI_EVENT_FN_3			= 21,
97dc1ec4faSMingyou Chen 	WMI_EVENT_FN_4			= 22,
98dc1ec4faSMingyou Chen 	WMI_EVENT_FN_5			= 24,
99dc1ec4faSMingyou Chen 	WMI_EVENT_REFRESH_RATE		= 25,
100dc1ec4faSMingyou Chen 	WMI_EVENT_CPU_FAN_SPEED		= 26,
101dc1ec4faSMingyou Chen 	WMI_EVENT_GPU_FAN_SPEED		= 32,
102dc1ec4faSMingyou Chen 	WMI_EVENT_WIN_KEY_LOCK		= 33,
103dc1ec4faSMingyou Chen 	WMI_EVENT_RESERVED_23		= 34,
104dc1ec4faSMingyou Chen 	WMI_EVENT_OPEN_APP		= 35,
105dc1ec4faSMingyou Chen };
106dc1ec4faSMingyou Chen 
107dc1ec4faSMingyou Chen enum bitland_mifs_event_type {
108dc1ec4faSMingyou Chen 	WMI_EVENT_TYPE_HOTKEY	= 1,
109dc1ec4faSMingyou Chen };
110dc1ec4faSMingyou Chen 
111dc1ec4faSMingyou Chen enum bitland_wmi_device_type {
112dc1ec4faSMingyou Chen 	BITLAND_WMI_CONTROL	= 0,
113dc1ec4faSMingyou Chen 	BITLAND_WMI_EVENT	= 1,
114dc1ec4faSMingyou Chen };
115dc1ec4faSMingyou Chen 
116dc1ec4faSMingyou Chen struct bitland_mifs_input {
117dc1ec4faSMingyou Chen 	u8 reserved1;
118dc1ec4faSMingyou Chen 	u8 operation;
119dc1ec4faSMingyou Chen 	u8 reserved2;
120dc1ec4faSMingyou Chen 	u8 function;
121dc1ec4faSMingyou Chen 	u8 payload[28];
122dc1ec4faSMingyou Chen } __packed;
123dc1ec4faSMingyou Chen 
124dc1ec4faSMingyou Chen struct bitland_mifs_output {
125dc1ec4faSMingyou Chen 	u8 reserved1;
126dc1ec4faSMingyou Chen 	u8 operation;
127dc1ec4faSMingyou Chen 	u8 reserved2;
128dc1ec4faSMingyou Chen 	u8 function;
129dc1ec4faSMingyou Chen 	u8 data[28];
130dc1ec4faSMingyou Chen } __packed;
131dc1ec4faSMingyou Chen 
132dc1ec4faSMingyou Chen struct bitland_mifs_event {
133dc1ec4faSMingyou Chen 	u8 event_type;
134dc1ec4faSMingyou Chen 	u8 event_id;
135dc1ec4faSMingyou Chen 	u8 value_low;	/* For most events, this is the value */
136dc1ec4faSMingyou Chen 	u8 value_high;	/* For fan speed events, combined with value_low */
137dc1ec4faSMingyou Chen 	u8 reserved[4];
138dc1ec4faSMingyou Chen } __packed;
139dc1ec4faSMingyou Chen 
140dc1ec4faSMingyou Chen static BLOCKING_NOTIFIER_HEAD(bitland_notifier_list);
141dc1ec4faSMingyou Chen 
142dc1ec4faSMingyou Chen enum bitland_notifier_actions {
143dc1ec4faSMingyou Chen 	BITLAND_NOTIFY_KBD_BRIGHTNESS,
144dc1ec4faSMingyou Chen 	BITLAND_NOTIFY_PLATFORM_PROFILE,
145dc1ec4faSMingyou Chen 	BITLAND_NOTIFY_HWMON,
146dc1ec4faSMingyou Chen };
147dc1ec4faSMingyou Chen 
148dc1ec4faSMingyou Chen struct bitland_fan_notify_data {
149dc1ec4faSMingyou Chen 	int channel; /* 0 = CPU, 1 = GPU */
150dc1ec4faSMingyou Chen 	u16 speed;
151dc1ec4faSMingyou Chen };
152dc1ec4faSMingyou Chen 
153dc1ec4faSMingyou Chen struct bitland_mifs_wmi_data {
154dc1ec4faSMingyou Chen 	struct wmi_device *wdev;
155dc1ec4faSMingyou Chen 	struct mutex lock;		/* Protects WMI calls */
156dc1ec4faSMingyou Chen 	struct led_classdev kbd_led;
157dc1ec4faSMingyou Chen 	struct notifier_block notifier;
158dc1ec4faSMingyou Chen 	struct input_dev *input_dev;
159dc1ec4faSMingyou Chen 	struct device *hwmon_dev;
160dc1ec4faSMingyou Chen 	struct device *pp_dev;
161dc1ec4faSMingyou Chen 	enum platform_profile_option saved_profile;
162dc1ec4faSMingyou Chen };
163dc1ec4faSMingyou Chen 
164dc1ec4faSMingyou Chen static int bitland_mifs_wmi_call(struct bitland_mifs_wmi_data *data,
165dc1ec4faSMingyou Chen 				 const struct bitland_mifs_input *input,
166dc1ec4faSMingyou Chen 				 struct bitland_mifs_output *output)
167dc1ec4faSMingyou Chen {
168dc1ec4faSMingyou Chen 	struct wmi_buffer in_buf = { .length = sizeof(*input), .data = (void *)input };
169dc1ec4faSMingyou Chen 	struct wmi_buffer out_buf = { 0 };
170dc1ec4faSMingyou Chen 	int ret;
171dc1ec4faSMingyou Chen 
172dc1ec4faSMingyou Chen 	guard(mutex)(&data->lock);
173dc1ec4faSMingyou Chen 
174578bc2a5SArmin Wolf 	if (!output)
175578bc2a5SArmin Wolf 		return wmidev_invoke_procedure(data->wdev, 0, 1, &in_buf);
176578bc2a5SArmin Wolf 
17796b1b053SArmin Wolf 	ret = wmidev_invoke_method(data->wdev, 0, 1, &in_buf, &out_buf, sizeof(*output));
178dc1ec4faSMingyou Chen 	if (ret)
179dc1ec4faSMingyou Chen 		return ret;
180dc1ec4faSMingyou Chen 
18196b1b053SArmin Wolf 	memcpy(output, out_buf.data, sizeof(*output));
18296b1b053SArmin Wolf 	kfree(out_buf.data);
183dc1ec4faSMingyou Chen 
184dc1ec4faSMingyou Chen 	return 0;
185dc1ec4faSMingyou Chen }
186dc1ec4faSMingyou Chen 
187dc1ec4faSMingyou Chen static int laptop_profile_get(struct device *dev,
188dc1ec4faSMingyou Chen 			      enum platform_profile_option *profile)
189dc1ec4faSMingyou Chen {
190dc1ec4faSMingyou Chen 	struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
191dc1ec4faSMingyou Chen 	struct bitland_mifs_input input = {
192dc1ec4faSMingyou Chen 		.reserved1 = 0,
193dc1ec4faSMingyou Chen 		.operation = WMI_METHOD_GET,
194dc1ec4faSMingyou Chen 		.reserved2 = 0,
195dc1ec4faSMingyou Chen 		.function = WMI_FN_SYSTEM_PER_MODE,
196dc1ec4faSMingyou Chen 	};
197dc1ec4faSMingyou Chen 	struct bitland_mifs_output result;
198dc1ec4faSMingyou Chen 	int ret;
199dc1ec4faSMingyou Chen 
200dc1ec4faSMingyou Chen 	ret = bitland_mifs_wmi_call(data, &input, &result);
201dc1ec4faSMingyou Chen 	if (ret)
202dc1ec4faSMingyou Chen 		return ret;
203dc1ec4faSMingyou Chen 
204dc1ec4faSMingyou Chen 	switch (result.data[0]) {
205dc1ec4faSMingyou Chen 	case WMI_PP_BALANCED:
206dc1ec4faSMingyou Chen 		*profile = PLATFORM_PROFILE_BALANCED;
207dc1ec4faSMingyou Chen 		break;
208dc1ec4faSMingyou Chen 	case WMI_PP_PERFORMANCE:
209dc1ec4faSMingyou Chen 		*profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
210dc1ec4faSMingyou Chen 		break;
211dc1ec4faSMingyou Chen 	case WMI_PP_QUIET:
212dc1ec4faSMingyou Chen 		*profile = PLATFORM_PROFILE_LOW_POWER;
213dc1ec4faSMingyou Chen 		break;
214dc1ec4faSMingyou Chen 	case WMI_PP_FULL_SPEED:
215dc1ec4faSMingyou Chen 		*profile = PLATFORM_PROFILE_PERFORMANCE;
216dc1ec4faSMingyou Chen 		break;
217dc1ec4faSMingyou Chen 	default:
218dc1ec4faSMingyou Chen 		return -EINVAL;
219dc1ec4faSMingyou Chen 	}
220dc1ec4faSMingyou Chen 	return 0;
221dc1ec4faSMingyou Chen }
222dc1ec4faSMingyou Chen 
223dc1ec4faSMingyou Chen static int bitland_check_performance_capability(struct bitland_mifs_wmi_data *data)
224dc1ec4faSMingyou Chen {
225dc1ec4faSMingyou Chen 	struct bitland_mifs_input input = {
226dc1ec4faSMingyou Chen 		.operation = WMI_METHOD_GET,
227dc1ec4faSMingyou Chen 		.function = WMI_FN_SYSTEM_AC_TYPE,
228dc1ec4faSMingyou Chen 	};
229dc1ec4faSMingyou Chen 	struct bitland_mifs_output output;
230dc1ec4faSMingyou Chen 	int ret;
231dc1ec4faSMingyou Chen 
232dc1ec4faSMingyou Chen 	/* Full-speed/performance mode requires DC power (not USB-C) */
233dc1ec4faSMingyou Chen 	if (!power_supply_is_system_supplied())
234dc1ec4faSMingyou Chen 		return -EOPNOTSUPP;
235dc1ec4faSMingyou Chen 
236dc1ec4faSMingyou Chen 	ret = bitland_mifs_wmi_call(data, &input, &output);
237dc1ec4faSMingyou Chen 	if (ret)
238dc1ec4faSMingyou Chen 		return ret;
239dc1ec4faSMingyou Chen 
240dc1ec4faSMingyou Chen 	if (output.data[0] != WMI_SYSTEM_AC_CIRCULARHOLE)
241dc1ec4faSMingyou Chen 		return -EOPNOTSUPP;
242dc1ec4faSMingyou Chen 
243dc1ec4faSMingyou Chen 	return 0;
244dc1ec4faSMingyou Chen }
245dc1ec4faSMingyou Chen 
246dc1ec4faSMingyou Chen static int laptop_profile_set(struct device *dev,
247dc1ec4faSMingyou Chen 			      enum platform_profile_option profile)
248dc1ec4faSMingyou Chen {
249dc1ec4faSMingyou Chen 	struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
250dc1ec4faSMingyou Chen 	struct bitland_mifs_input input = {
251dc1ec4faSMingyou Chen 		.reserved1 = 0,
252dc1ec4faSMingyou Chen 		.operation = WMI_METHOD_SET,
253dc1ec4faSMingyou Chen 		.reserved2 = 0,
254dc1ec4faSMingyou Chen 		.function = WMI_FN_SYSTEM_PER_MODE,
255dc1ec4faSMingyou Chen 	};
256dc1ec4faSMingyou Chen 	int ret;
257dc1ec4faSMingyou Chen 	u8 val;
258dc1ec4faSMingyou Chen 
259dc1ec4faSMingyou Chen 	switch (profile) {
260dc1ec4faSMingyou Chen 	case PLATFORM_PROFILE_LOW_POWER:
261dc1ec4faSMingyou Chen 		val = WMI_PP_QUIET;
262dc1ec4faSMingyou Chen 		break;
263dc1ec4faSMingyou Chen 	case PLATFORM_PROFILE_BALANCED:
264dc1ec4faSMingyou Chen 		val = WMI_PP_BALANCED;
265dc1ec4faSMingyou Chen 		break;
266dc1ec4faSMingyou Chen 	case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
267dc1ec4faSMingyou Chen 		ret = bitland_check_performance_capability(data);
268dc1ec4faSMingyou Chen 		if (ret)
269dc1ec4faSMingyou Chen 			return ret;
270dc1ec4faSMingyou Chen 		val = WMI_PP_PERFORMANCE;
271dc1ec4faSMingyou Chen 		break;
272dc1ec4faSMingyou Chen 	case PLATFORM_PROFILE_PERFORMANCE:
273dc1ec4faSMingyou Chen 		ret = bitland_check_performance_capability(data);
274dc1ec4faSMingyou Chen 		if (ret)
275dc1ec4faSMingyou Chen 			return ret;
276dc1ec4faSMingyou Chen 		val = WMI_PP_FULL_SPEED;
277dc1ec4faSMingyou Chen 		break;
278dc1ec4faSMingyou Chen 	default:
279dc1ec4faSMingyou Chen 		return -EOPNOTSUPP;
280dc1ec4faSMingyou Chen 	}
281dc1ec4faSMingyou Chen 
282dc1ec4faSMingyou Chen 	input.payload[0] = val;
283dc1ec4faSMingyou Chen 
284dc1ec4faSMingyou Chen 	return bitland_mifs_wmi_call(data, &input, NULL);
285dc1ec4faSMingyou Chen }
286dc1ec4faSMingyou Chen 
287dc1ec4faSMingyou Chen static int platform_profile_probe(void *drvdata, unsigned long *choices)
288dc1ec4faSMingyou Chen {
289dc1ec4faSMingyou Chen 	set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
290dc1ec4faSMingyou Chen 	set_bit(PLATFORM_PROFILE_BALANCED, choices);
291dc1ec4faSMingyou Chen 	set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
292dc1ec4faSMingyou Chen 	set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
293dc1ec4faSMingyou Chen 
294dc1ec4faSMingyou Chen 	return 0;
295dc1ec4faSMingyou Chen }
296dc1ec4faSMingyou Chen 
297dc1ec4faSMingyou Chen static int bitland_mifs_wmi_suspend(struct device *dev)
298dc1ec4faSMingyou Chen {
299dc1ec4faSMingyou Chen 	struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
300dc1ec4faSMingyou Chen 	enum platform_profile_option profile;
301dc1ec4faSMingyou Chen 	int ret;
302dc1ec4faSMingyou Chen 
303dc1ec4faSMingyou Chen 	ret = laptop_profile_get(data->pp_dev, &profile);
304dc1ec4faSMingyou Chen 	if (ret == 0)
305dc1ec4faSMingyou Chen 		data->saved_profile = profile;
306dc1ec4faSMingyou Chen 
307dc1ec4faSMingyou Chen 	return ret;
308dc1ec4faSMingyou Chen }
309dc1ec4faSMingyou Chen 
310dc1ec4faSMingyou Chen static int bitland_mifs_wmi_resume(struct device *dev)
311dc1ec4faSMingyou Chen {
312dc1ec4faSMingyou Chen 	struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
313dc1ec4faSMingyou Chen 
314dc1ec4faSMingyou Chen 	dev_dbg(dev, "Resuming, restoring profile %d\n", data->saved_profile);
315dc1ec4faSMingyou Chen 	return laptop_profile_set(dev, data->saved_profile);
316dc1ec4faSMingyou Chen }
317dc1ec4faSMingyou Chen 
318dc1ec4faSMingyou Chen static DEFINE_SIMPLE_DEV_PM_OPS(bitland_mifs_wmi_pm_ops,
319dc1ec4faSMingyou Chen 				bitland_mifs_wmi_suspend,
320dc1ec4faSMingyou Chen 				bitland_mifs_wmi_resume);
321dc1ec4faSMingyou Chen 
322dc1ec4faSMingyou Chen static const struct platform_profile_ops laptop_profile_ops = {
323dc1ec4faSMingyou Chen 	.probe = platform_profile_probe,
324dc1ec4faSMingyou Chen 	.profile_get = laptop_profile_get,
325dc1ec4faSMingyou Chen 	.profile_set = laptop_profile_set,
326dc1ec4faSMingyou Chen };
327dc1ec4faSMingyou Chen 
328dc1ec4faSMingyou Chen static const char *const fan_labels[] = {
329dc1ec4faSMingyou Chen 	"CPU", /* 0 */
330dc1ec4faSMingyou Chen 	"GPU", /* 1 */
331dc1ec4faSMingyou Chen 	"SYS", /* 2 */
332dc1ec4faSMingyou Chen };
333dc1ec4faSMingyou Chen 
334dc1ec4faSMingyou Chen static int laptop_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
335dc1ec4faSMingyou Chen 			     u32 attr, int channel, long *val)
336dc1ec4faSMingyou Chen {
337dc1ec4faSMingyou Chen 	struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
338dc1ec4faSMingyou Chen 	struct bitland_mifs_input input = {
339dc1ec4faSMingyou Chen 		.reserved1 = 0,
340dc1ec4faSMingyou Chen 		.operation = WMI_METHOD_GET,
341dc1ec4faSMingyou Chen 		.reserved2 = 0,
342dc1ec4faSMingyou Chen 	};
343dc1ec4faSMingyou Chen 	struct bitland_mifs_output res;
344dc1ec4faSMingyou Chen 	int ret;
345dc1ec4faSMingyou Chen 
346dc1ec4faSMingyou Chen 	switch (type) {
347dc1ec4faSMingyou Chen 	case hwmon_temp:
348dc1ec4faSMingyou Chen 		input.function = WMI_FN_CPU_THERMOMETER;
349dc1ec4faSMingyou Chen 		ret = bitland_mifs_wmi_call(data, &input, &res);
350dc1ec4faSMingyou Chen 		if (!ret)
351dc1ec4faSMingyou Chen 			*val = res.data[0] * MILLIDEGREE_PER_DEGREE;
352dc1ec4faSMingyou Chen 		return ret;
353dc1ec4faSMingyou Chen 	case hwmon_fan:
354dc1ec4faSMingyou Chen 		input.function = WMI_FN_FAN_SPEEDS;
355dc1ec4faSMingyou Chen 		ret = bitland_mifs_wmi_call(data, &input, &res);
356dc1ec4faSMingyou Chen 		if (ret)
357dc1ec4faSMingyou Chen 			return ret;
358dc1ec4faSMingyou Chen 
359dc1ec4faSMingyou Chen 		switch (channel) {
360dc1ec4faSMingyou Chen 		case 0: /* CPU */
361dc1ec4faSMingyou Chen 			*val = get_unaligned_le16(&res.data[0]);
362dc1ec4faSMingyou Chen 			return 0;
363dc1ec4faSMingyou Chen 		case 1: /* GPU */
364dc1ec4faSMingyou Chen 			*val = get_unaligned_le16(&res.data[2]);
365dc1ec4faSMingyou Chen 			return 0;
366dc1ec4faSMingyou Chen 		case 2: /* SYS */
367dc1ec4faSMingyou Chen 			*val = get_unaligned_le16(&res.data[6]);
368dc1ec4faSMingyou Chen 			return 0;
369dc1ec4faSMingyou Chen 		default:
370dc1ec4faSMingyou Chen 			return -EINVAL;
371dc1ec4faSMingyou Chen 		}
372dc1ec4faSMingyou Chen 	default:
373dc1ec4faSMingyou Chen 		return -EINVAL;
374dc1ec4faSMingyou Chen 	}
375dc1ec4faSMingyou Chen }
376dc1ec4faSMingyou Chen 
377dc1ec4faSMingyou Chen static int laptop_hwmon_read_string(struct device *dev,
378dc1ec4faSMingyou Chen 				    enum hwmon_sensor_types type, u32 attr,
379dc1ec4faSMingyou Chen 				    int channel, const char **str)
380dc1ec4faSMingyou Chen {
381dc1ec4faSMingyou Chen 	if (type == hwmon_fan && attr == hwmon_fan_label) {
382dc1ec4faSMingyou Chen 		if (channel >= 0 && channel < ARRAY_SIZE(fan_labels)) {
383dc1ec4faSMingyou Chen 			*str = fan_labels[channel];
384dc1ec4faSMingyou Chen 			return 0;
385dc1ec4faSMingyou Chen 		}
386dc1ec4faSMingyou Chen 	}
387dc1ec4faSMingyou Chen 	return -EINVAL;
388dc1ec4faSMingyou Chen }
389dc1ec4faSMingyou Chen 
390dc1ec4faSMingyou Chen static const struct hwmon_channel_info *laptop_hwmon_info[] = {
391dc1ec4faSMingyou Chen 	HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
392dc1ec4faSMingyou Chen 	HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL,
393dc1ec4faSMingyou Chen 				HWMON_F_INPUT | HWMON_F_LABEL,
394dc1ec4faSMingyou Chen 				HWMON_F_INPUT | HWMON_F_LABEL),
395dc1ec4faSMingyou Chen 	NULL
396dc1ec4faSMingyou Chen };
397dc1ec4faSMingyou Chen 
398dc1ec4faSMingyou Chen static const struct hwmon_ops laptop_hwmon_ops = {
399dc1ec4faSMingyou Chen 	.visible = 0444,
400dc1ec4faSMingyou Chen 	.read = laptop_hwmon_read,
401dc1ec4faSMingyou Chen 	.read_string = laptop_hwmon_read_string,
402dc1ec4faSMingyou Chen };
403dc1ec4faSMingyou Chen 
404dc1ec4faSMingyou Chen static const struct hwmon_chip_info laptop_chip_info = {
405dc1ec4faSMingyou Chen 	.ops = &laptop_hwmon_ops,
406dc1ec4faSMingyou Chen 	.info = laptop_hwmon_info,
407dc1ec4faSMingyou Chen };
408dc1ec4faSMingyou Chen 
409dc1ec4faSMingyou Chen static int laptop_kbd_led_set(struct led_classdev *led_cdev,
410dc1ec4faSMingyou Chen 			      enum led_brightness value)
411dc1ec4faSMingyou Chen {
412dc1ec4faSMingyou Chen 	struct bitland_mifs_wmi_data *data =
413dc1ec4faSMingyou Chen 		container_of(led_cdev, struct bitland_mifs_wmi_data, kbd_led);
414dc1ec4faSMingyou Chen 	struct bitland_mifs_input input = {
415dc1ec4faSMingyou Chen 		.reserved1 = 0,
416dc1ec4faSMingyou Chen 		.operation = WMI_METHOD_SET,
417dc1ec4faSMingyou Chen 		.reserved2 = 0,
418dc1ec4faSMingyou Chen 		.function = WMI_FN_RGB_KB_BRIGHTNESS,
419dc1ec4faSMingyou Chen 	};
420dc1ec4faSMingyou Chen 
421dc1ec4faSMingyou Chen 	input.payload[0] = (u8)value;
422dc1ec4faSMingyou Chen 
423dc1ec4faSMingyou Chen 	return bitland_mifs_wmi_call(data, &input, NULL);
424dc1ec4faSMingyou Chen }
425dc1ec4faSMingyou Chen 
426dc1ec4faSMingyou Chen static enum led_brightness laptop_kbd_led_get(struct led_classdev *led_cdev)
427dc1ec4faSMingyou Chen {
428dc1ec4faSMingyou Chen 	struct bitland_mifs_wmi_data *data =
429dc1ec4faSMingyou Chen 		container_of(led_cdev, struct bitland_mifs_wmi_data, kbd_led);
430dc1ec4faSMingyou Chen 	struct bitland_mifs_input input = {
431dc1ec4faSMingyou Chen 		.reserved1 = 0,
432dc1ec4faSMingyou Chen 		.operation = WMI_METHOD_GET,
433dc1ec4faSMingyou Chen 		.reserved2 = 0,
434dc1ec4faSMingyou Chen 		.function = WMI_FN_RGB_KB_BRIGHTNESS,
435dc1ec4faSMingyou Chen 	};
436dc1ec4faSMingyou Chen 	struct bitland_mifs_output res;
437dc1ec4faSMingyou Chen 	int ret;
438dc1ec4faSMingyou Chen 
439dc1ec4faSMingyou Chen 	ret = bitland_mifs_wmi_call(data, &input, &res);
440dc1ec4faSMingyou Chen 	if (ret)
441dc1ec4faSMingyou Chen 		return ret;
442dc1ec4faSMingyou Chen 
443dc1ec4faSMingyou Chen 	return res.data[0];
444dc1ec4faSMingyou Chen }
445dc1ec4faSMingyou Chen 
446dc1ec4faSMingyou Chen static const char *const gpu_mode_strings[] = {
447dc1ec4faSMingyou Chen 	"hybrid",
448dc1ec4faSMingyou Chen 	"discrete",
449dc1ec4faSMingyou Chen 	"uma",
450dc1ec4faSMingyou Chen };
451dc1ec4faSMingyou Chen 
452dc1ec4faSMingyou Chen /* GPU Mode: 0:Hybrid, 1:Discrete, 2:UMA */
453dc1ec4faSMingyou Chen static ssize_t gpu_mode_show(struct device *dev, struct device_attribute *attr,
454dc1ec4faSMingyou Chen 			     char *buf)
455dc1ec4faSMingyou Chen {
456dc1ec4faSMingyou Chen 	struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
457dc1ec4faSMingyou Chen 	struct bitland_mifs_input input = {
458dc1ec4faSMingyou Chen 		.reserved1 = 0,
459dc1ec4faSMingyou Chen 		.operation = WMI_METHOD_GET,
460dc1ec4faSMingyou Chen 		.reserved2 = 0,
461dc1ec4faSMingyou Chen 		.function = WMI_FN_GPU_MODE,
462dc1ec4faSMingyou Chen 	};
463dc1ec4faSMingyou Chen 	struct bitland_mifs_output res;
464dc1ec4faSMingyou Chen 	u8 mode_val;
465dc1ec4faSMingyou Chen 	int ret;
466dc1ec4faSMingyou Chen 
467dc1ec4faSMingyou Chen 	ret = bitland_mifs_wmi_call(data, &input, &res);
468dc1ec4faSMingyou Chen 	if (ret)
469dc1ec4faSMingyou Chen 		return ret;
470dc1ec4faSMingyou Chen 
471dc1ec4faSMingyou Chen 	mode_val = res.data[0];
472dc1ec4faSMingyou Chen 	if (mode_val >= ARRAY_SIZE(gpu_mode_strings))
473dc1ec4faSMingyou Chen 		return -EPROTO;
474dc1ec4faSMingyou Chen 
475dc1ec4faSMingyou Chen 	return sysfs_emit(buf, "%s\n", gpu_mode_strings[mode_val]);
476dc1ec4faSMingyou Chen }
477dc1ec4faSMingyou Chen 
478dc1ec4faSMingyou Chen static ssize_t gpu_mode_store(struct device *dev, struct device_attribute *attr,
479dc1ec4faSMingyou Chen 			      const char *buf, size_t count)
480dc1ec4faSMingyou Chen {
481dc1ec4faSMingyou Chen 	struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
482dc1ec4faSMingyou Chen 	struct bitland_mifs_input input = {
483dc1ec4faSMingyou Chen 		.reserved1 = 0,
484dc1ec4faSMingyou Chen 		.operation = WMI_METHOD_SET,
485dc1ec4faSMingyou Chen 		.reserved2 = 0,
486dc1ec4faSMingyou Chen 		.function = WMI_FN_GPU_MODE,
487dc1ec4faSMingyou Chen 	};
488dc1ec4faSMingyou Chen 	int val;
489dc1ec4faSMingyou Chen 	int ret;
490dc1ec4faSMingyou Chen 
491dc1ec4faSMingyou Chen 	val = sysfs_match_string(gpu_mode_strings, buf);
492dc1ec4faSMingyou Chen 	if (val < 0)
493dc1ec4faSMingyou Chen 		return -EINVAL;
494dc1ec4faSMingyou Chen 
495dc1ec4faSMingyou Chen 	input.payload[0] = (u8)val;
496dc1ec4faSMingyou Chen 
497dc1ec4faSMingyou Chen 	ret = bitland_mifs_wmi_call(data, &input, NULL);
498dc1ec4faSMingyou Chen 	if (ret)
499dc1ec4faSMingyou Chen 		return ret;
500dc1ec4faSMingyou Chen 
501dc1ec4faSMingyou Chen 	return count;
502dc1ec4faSMingyou Chen }
503dc1ec4faSMingyou Chen 
504dc1ec4faSMingyou Chen static const char *const kb_mode_strings[] = {
505dc1ec4faSMingyou Chen 	"off",		/* 0 */
506dc1ec4faSMingyou Chen 	"cyclic",	/* 1 */
507dc1ec4faSMingyou Chen 	"fixed",	/* 2 */
508dc1ec4faSMingyou Chen 	"custom",	/* 3 */
509dc1ec4faSMingyou Chen };
510dc1ec4faSMingyou Chen 
511dc1ec4faSMingyou Chen static ssize_t kb_mode_show(struct device *dev, struct device_attribute *attr,
512dc1ec4faSMingyou Chen 			    char *buf)
513dc1ec4faSMingyou Chen {
514dc1ec4faSMingyou Chen 	struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
515dc1ec4faSMingyou Chen 	struct bitland_mifs_input input = {
516dc1ec4faSMingyou Chen 		.reserved1 = 0,
517dc1ec4faSMingyou Chen 		.operation = WMI_METHOD_GET,
518dc1ec4faSMingyou Chen 		.reserved2 = 0,
519dc1ec4faSMingyou Chen 		.function = WMI_FN_RGB_KB_MODE,
520dc1ec4faSMingyou Chen 	};
521dc1ec4faSMingyou Chen 	struct bitland_mifs_output res;
522dc1ec4faSMingyou Chen 	u8 mode_val;
523dc1ec4faSMingyou Chen 	int ret;
524dc1ec4faSMingyou Chen 
525dc1ec4faSMingyou Chen 	ret = bitland_mifs_wmi_call(data, &input, &res);
526dc1ec4faSMingyou Chen 	if (ret)
527dc1ec4faSMingyou Chen 		return ret;
528dc1ec4faSMingyou Chen 
529dc1ec4faSMingyou Chen 	mode_val = res.data[0];
530dc1ec4faSMingyou Chen 	if (mode_val >= ARRAY_SIZE(kb_mode_strings))
531dc1ec4faSMingyou Chen 		return -EPROTO;
532dc1ec4faSMingyou Chen 
533dc1ec4faSMingyou Chen 	return sysfs_emit(buf, "%s\n", kb_mode_strings[mode_val]);
534dc1ec4faSMingyou Chen }
535dc1ec4faSMingyou Chen 
536dc1ec4faSMingyou Chen static ssize_t kb_mode_store(struct device *dev, struct device_attribute *attr,
537dc1ec4faSMingyou Chen 			     const char *buf, size_t count)
538dc1ec4faSMingyou Chen {
539dc1ec4faSMingyou Chen 	struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
540dc1ec4faSMingyou Chen 	struct bitland_mifs_input input = {
541dc1ec4faSMingyou Chen 		.reserved1 = 0,
542dc1ec4faSMingyou Chen 		.operation = WMI_METHOD_SET,
543dc1ec4faSMingyou Chen 		.reserved2 = 0,
544dc1ec4faSMingyou Chen 		.function = WMI_FN_RGB_KB_MODE,
545dc1ec4faSMingyou Chen 	};
546dc1ec4faSMingyou Chen 	// the wmi value (0, 1, 2 or 3)
547dc1ec4faSMingyou Chen 	int val;
548dc1ec4faSMingyou Chen 	int ret;
549dc1ec4faSMingyou Chen 
550dc1ec4faSMingyou Chen 	val = sysfs_match_string(kb_mode_strings, buf);
551dc1ec4faSMingyou Chen 	if (val < 0)
552dc1ec4faSMingyou Chen 		return -EINVAL;
553dc1ec4faSMingyou Chen 
554dc1ec4faSMingyou Chen 	input.payload[0] = (u8)val;
555dc1ec4faSMingyou Chen 
556dc1ec4faSMingyou Chen 	ret = bitland_mifs_wmi_call(data, &input, NULL);
557dc1ec4faSMingyou Chen 	if (ret)
558dc1ec4faSMingyou Chen 		return ret;
559dc1ec4faSMingyou Chen 
560dc1ec4faSMingyou Chen 	return count;
561dc1ec4faSMingyou Chen }
562dc1ec4faSMingyou Chen 
563dc1ec4faSMingyou Chen /* Fan Boost: 0:Normal, 1:Max Speed */
564dc1ec4faSMingyou Chen static ssize_t fan_boost_store(struct device *dev,
565dc1ec4faSMingyou Chen 			       struct device_attribute *attr, const char *buf,
566dc1ec4faSMingyou Chen 			       size_t count)
567dc1ec4faSMingyou Chen {
568dc1ec4faSMingyou Chen 	struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
569dc1ec4faSMingyou Chen 	struct bitland_mifs_input input = {
570dc1ec4faSMingyou Chen 		.reserved1 = 0,
571dc1ec4faSMingyou Chen 		.operation = WMI_METHOD_SET,
572dc1ec4faSMingyou Chen 		.reserved2 = 0,
573dc1ec4faSMingyou Chen 		.function = WMI_FN_MAX_FAN_SWITCH,
574dc1ec4faSMingyou Chen 	};
575dc1ec4faSMingyou Chen 	bool val;
576dc1ec4faSMingyou Chen 	int ret;
577dc1ec4faSMingyou Chen 
578dc1ec4faSMingyou Chen 	if (kstrtobool(buf, &val))
579dc1ec4faSMingyou Chen 		return -EINVAL;
580dc1ec4faSMingyou Chen 
581dc1ec4faSMingyou Chen 	input.payload[0] = 0;	/* CPU/GPU Fan */
582dc1ec4faSMingyou Chen 	input.payload[1] = val;
583dc1ec4faSMingyou Chen 
584dc1ec4faSMingyou Chen 	ret = bitland_mifs_wmi_call(data, &input, NULL);
585dc1ec4faSMingyou Chen 	if (ret)
586dc1ec4faSMingyou Chen 		return ret;
587dc1ec4faSMingyou Chen 
588dc1ec4faSMingyou Chen 	return count;
589dc1ec4faSMingyou Chen }
590dc1ec4faSMingyou Chen 
591dc1ec4faSMingyou Chen static const DEVICE_ATTR_RW(gpu_mode);
592dc1ec4faSMingyou Chen static const DEVICE_ATTR_RW(kb_mode);
593dc1ec4faSMingyou Chen static const DEVICE_ATTR_WO(fan_boost);
594dc1ec4faSMingyou Chen 
595dc1ec4faSMingyou Chen static const struct attribute *const laptop_attrs[] = {
596dc1ec4faSMingyou Chen 	&dev_attr_gpu_mode.attr,
597dc1ec4faSMingyou Chen 	&dev_attr_kb_mode.attr,
598dc1ec4faSMingyou Chen 	&dev_attr_fan_boost.attr,
599dc1ec4faSMingyou Chen 	NULL,
600dc1ec4faSMingyou Chen };
601dc1ec4faSMingyou Chen ATTRIBUTE_GROUPS(laptop);
602dc1ec4faSMingyou Chen 
603dc1ec4faSMingyou Chen static const struct key_entry bitland_mifs_wmi_keymap[] = {
604dc1ec4faSMingyou Chen 	{ KE_KEY, WMI_EVENT_OPEN_APP, { KEY_PROG1 } },
605dc1ec4faSMingyou Chen 	{ KE_KEY, WMI_EVENT_CALCULATOR_START, { KEY_CALC } },
606dc1ec4faSMingyou Chen 	{ KE_KEY, WMI_EVENT_BROWSER_START, { KEY_WWW } },
607dc1ec4faSMingyou Chen 	{ KE_IGNORE, WMI_EVENT_FN_J, { KEY_RESERVED } },
608dc1ec4faSMingyou Chen 	{ KE_IGNORE, WMI_EVENT_FN_F, { KEY_RESERVED } },
609dc1ec4faSMingyou Chen 	{ KE_IGNORE, WMI_EVENT_FN_0, { KEY_RESERVED } },
610dc1ec4faSMingyou Chen 	{ KE_IGNORE, WMI_EVENT_FN_1, { KEY_RESERVED } },
611dc1ec4faSMingyou Chen 	{ KE_IGNORE, WMI_EVENT_FN_2, { KEY_RESERVED } },
612dc1ec4faSMingyou Chen 	{ KE_IGNORE, WMI_EVENT_FN_3, { KEY_RESERVED } },
613dc1ec4faSMingyou Chen 	{ KE_IGNORE, WMI_EVENT_FN_4, { KEY_RESERVED } },
614dc1ec4faSMingyou Chen 	{ KE_IGNORE, WMI_EVENT_FN_5, { KEY_RESERVED } },
615dc1ec4faSMingyou Chen 	{ KE_END, 0 }
616dc1ec4faSMingyou Chen };
617dc1ec4faSMingyou Chen 
618dc1ec4faSMingyou Chen static void bitland_notifier_unregister(void *data)
619dc1ec4faSMingyou Chen {
620dc1ec4faSMingyou Chen 	struct notifier_block *nb = data;
621dc1ec4faSMingyou Chen 
622dc1ec4faSMingyou Chen 	blocking_notifier_chain_unregister(&bitland_notifier_list, nb);
623dc1ec4faSMingyou Chen }
624dc1ec4faSMingyou Chen 
625dc1ec4faSMingyou Chen static int bitland_notifier_callback(struct notifier_block *nb,
626dc1ec4faSMingyou Chen 				     unsigned long action, void *data)
627dc1ec4faSMingyou Chen {
628dc1ec4faSMingyou Chen 	struct bitland_mifs_wmi_data *data_ctx =
629dc1ec4faSMingyou Chen 		container_of(nb, struct bitland_mifs_wmi_data, notifier);
630dc1ec4faSMingyou Chen 	struct bitland_fan_notify_data *fan_info;
631dc1ec4faSMingyou Chen 	u8 *brightness;
632dc1ec4faSMingyou Chen 
633dc1ec4faSMingyou Chen 	switch (action) {
634dc1ec4faSMingyou Chen 	case BITLAND_NOTIFY_KBD_BRIGHTNESS:
635dc1ec4faSMingyou Chen 		brightness = data;
636dc1ec4faSMingyou Chen 		led_classdev_notify_brightness_hw_changed(&data_ctx->kbd_led,
637dc1ec4faSMingyou Chen 							  *brightness);
638dc1ec4faSMingyou Chen 		break;
639dc1ec4faSMingyou Chen 	case BITLAND_NOTIFY_PLATFORM_PROFILE:
640dc1ec4faSMingyou Chen 		platform_profile_notify(data_ctx->pp_dev);
641dc1ec4faSMingyou Chen 		break;
642dc1ec4faSMingyou Chen 	case BITLAND_NOTIFY_HWMON:
643dc1ec4faSMingyou Chen 		fan_info = data;
644dc1ec4faSMingyou Chen 
645dc1ec4faSMingyou Chen 		hwmon_notify_event(data_ctx->hwmon_dev, hwmon_fan,
646dc1ec4faSMingyou Chen 				   hwmon_fan_input, fan_info->channel);
647dc1ec4faSMingyou Chen 		break;
648dc1ec4faSMingyou Chen 	}
649dc1ec4faSMingyou Chen 
650dc1ec4faSMingyou Chen 	return NOTIFY_OK;
651dc1ec4faSMingyou Chen }
652dc1ec4faSMingyou Chen 
653dc1ec4faSMingyou Chen static int bitland_mifs_wmi_probe(struct wmi_device *wdev, const void *context)
654dc1ec4faSMingyou Chen {
655dc1ec4faSMingyou Chen 	struct bitland_mifs_wmi_data *drv_data;
656dc1ec4faSMingyou Chen 	enum bitland_wmi_device_type dev_type =
657dc1ec4faSMingyou Chen 		(enum bitland_wmi_device_type)(unsigned long)context;
658dc1ec4faSMingyou Chen 	struct led_init_data init_data = {
659dc1ec4faSMingyou Chen 		.devicename = DRV_NAME,
660dc1ec4faSMingyou Chen 		.default_label = ":" LED_FUNCTION_KBD_BACKLIGHT,
661dc1ec4faSMingyou Chen 		.devname_mandatory = true,
662dc1ec4faSMingyou Chen 	};
663dc1ec4faSMingyou Chen 	int ret;
664dc1ec4faSMingyou Chen 
665dc1ec4faSMingyou Chen 	drv_data = devm_kzalloc(&wdev->dev, sizeof(*drv_data), GFP_KERNEL);
666dc1ec4faSMingyou Chen 	if (!drv_data)
667dc1ec4faSMingyou Chen 		return -ENOMEM;
668dc1ec4faSMingyou Chen 
669dc1ec4faSMingyou Chen 	drv_data->wdev = wdev;
670dc1ec4faSMingyou Chen 
671dc1ec4faSMingyou Chen 	ret = devm_mutex_init(&wdev->dev, &drv_data->lock);
672dc1ec4faSMingyou Chen 	if (ret)
673dc1ec4faSMingyou Chen 		return ret;
674dc1ec4faSMingyou Chen 
675dc1ec4faSMingyou Chen 	dev_set_drvdata(&wdev->dev, drv_data);
676dc1ec4faSMingyou Chen 
677dc1ec4faSMingyou Chen 	if (dev_type == BITLAND_WMI_EVENT) {
678dc1ec4faSMingyou Chen 		/* Register input device for hotkeys */
679dc1ec4faSMingyou Chen 		drv_data->input_dev = devm_input_allocate_device(&wdev->dev);
680dc1ec4faSMingyou Chen 		if (!drv_data->input_dev)
681dc1ec4faSMingyou Chen 			return -ENOMEM;
682dc1ec4faSMingyou Chen 
683dc1ec4faSMingyou Chen 		drv_data->input_dev->name = "Bitland MIFS WMI hotkeys";
684dc1ec4faSMingyou Chen 		drv_data->input_dev->phys = "wmi/input0";
685dc1ec4faSMingyou Chen 		drv_data->input_dev->id.bustype = BUS_HOST;
686dc1ec4faSMingyou Chen 		drv_data->input_dev->dev.parent = &wdev->dev;
687dc1ec4faSMingyou Chen 
688dc1ec4faSMingyou Chen 		ret = sparse_keymap_setup(drv_data->input_dev,
689dc1ec4faSMingyou Chen 					  bitland_mifs_wmi_keymap, NULL);
690dc1ec4faSMingyou Chen 		if (ret)
691dc1ec4faSMingyou Chen 			return ret;
692dc1ec4faSMingyou Chen 
693dc1ec4faSMingyou Chen 		return input_register_device(drv_data->input_dev);
694dc1ec4faSMingyou Chen 	}
695dc1ec4faSMingyou Chen 
696dc1ec4faSMingyou Chen 	/* Register platform profile */
697dc1ec4faSMingyou Chen 	drv_data->pp_dev = devm_platform_profile_register(&wdev->dev, DRV_NAME, drv_data,
698dc1ec4faSMingyou Chen 							  &laptop_profile_ops);
699dc1ec4faSMingyou Chen 	if (IS_ERR(drv_data->pp_dev))
700dc1ec4faSMingyou Chen 		return PTR_ERR(drv_data->pp_dev);
701dc1ec4faSMingyou Chen 
702dc1ec4faSMingyou Chen 	/* Register hwmon */
703dc1ec4faSMingyou Chen 	drv_data->hwmon_dev = devm_hwmon_device_register_with_info(&wdev->dev,
704dc1ec4faSMingyou Chen 								   "bitland_mifs",
705dc1ec4faSMingyou Chen 								   drv_data,
706dc1ec4faSMingyou Chen 								   &laptop_chip_info,
707dc1ec4faSMingyou Chen 								   NULL);
708dc1ec4faSMingyou Chen 	if (IS_ERR(drv_data->hwmon_dev))
709dc1ec4faSMingyou Chen 		return PTR_ERR(drv_data->hwmon_dev);
710dc1ec4faSMingyou Chen 
711dc1ec4faSMingyou Chen 	/* Register keyboard LED */
712dc1ec4faSMingyou Chen 	drv_data->kbd_led.max_brightness = 3;
713dc1ec4faSMingyou Chen 	drv_data->kbd_led.brightness_set_blocking = laptop_kbd_led_set;
714dc1ec4faSMingyou Chen 	drv_data->kbd_led.brightness_get = laptop_kbd_led_get;
715dc1ec4faSMingyou Chen 	drv_data->kbd_led.brightness = laptop_kbd_led_get(&drv_data->kbd_led);
716dc1ec4faSMingyou Chen 	drv_data->kbd_led.flags = LED_CORE_SUSPENDRESUME |
717dc1ec4faSMingyou Chen 				  LED_BRIGHT_HW_CHANGED |
718dc1ec4faSMingyou Chen 				  LED_REJECT_NAME_CONFLICT;
719dc1ec4faSMingyou Chen 	ret = devm_led_classdev_register_ext(&wdev->dev, &drv_data->kbd_led, &init_data);
720dc1ec4faSMingyou Chen 	if (ret)
721dc1ec4faSMingyou Chen 		return ret;
722dc1ec4faSMingyou Chen 
723dc1ec4faSMingyou Chen 	drv_data->notifier.notifier_call = bitland_notifier_callback;
724dc1ec4faSMingyou Chen 	ret = blocking_notifier_chain_register(&bitland_notifier_list, &drv_data->notifier);
725dc1ec4faSMingyou Chen 	if (ret)
726dc1ec4faSMingyou Chen 		return ret;
727dc1ec4faSMingyou Chen 
728dc1ec4faSMingyou Chen 	return devm_add_action_or_reset(&wdev->dev,
729dc1ec4faSMingyou Chen 				       bitland_notifier_unregister,
730dc1ec4faSMingyou Chen 				       &drv_data->notifier);
731dc1ec4faSMingyou Chen }
732dc1ec4faSMingyou Chen 
733dc1ec4faSMingyou Chen static void bitland_mifs_wmi_notify(struct wmi_device *wdev,
734dc1ec4faSMingyou Chen 				    const struct wmi_buffer *buffer)
735dc1ec4faSMingyou Chen {
736dc1ec4faSMingyou Chen 	struct bitland_mifs_wmi_data *data = dev_get_drvdata(&wdev->dev);
737*2e2a3914SArmin Wolf 	const struct bitland_mifs_event *event = buffer->data;
738dc1ec4faSMingyou Chen 	struct bitland_fan_notify_data fan_data;
739dc1ec4faSMingyou Chen 	u8 brightness;
740dc1ec4faSMingyou Chen 
741dc1ec4faSMingyou Chen 	/* Validate event type */
742dc1ec4faSMingyou Chen 	if (event->event_type != WMI_EVENT_TYPE_HOTKEY)
743dc1ec4faSMingyou Chen 		return;
744dc1ec4faSMingyou Chen 
745dc1ec4faSMingyou Chen 	dev_dbg(&wdev->dev,
746dc1ec4faSMingyou Chen 		"WMI event: id=0x%02x value_low=0x%02x value_high=0x%02x\n",
747dc1ec4faSMingyou Chen 		event->event_id, event->value_low, event->value_high);
748dc1ec4faSMingyou Chen 
749dc1ec4faSMingyou Chen 	switch (event->event_id) {
750dc1ec4faSMingyou Chen 	case WMI_EVENT_KBD_BRIGHTNESS:
751dc1ec4faSMingyou Chen 		brightness = event->value_low;
752dc1ec4faSMingyou Chen 		blocking_notifier_call_chain(&bitland_notifier_list,
753dc1ec4faSMingyou Chen 					     BITLAND_NOTIFY_KBD_BRIGHTNESS,
754dc1ec4faSMingyou Chen 					     &brightness);
755dc1ec4faSMingyou Chen 		break;
756dc1ec4faSMingyou Chen 
757dc1ec4faSMingyou Chen 	case WMI_EVENT_PERFORMANCE_PLAN:
758dc1ec4faSMingyou Chen 		blocking_notifier_call_chain(&bitland_notifier_list,
759dc1ec4faSMingyou Chen 					     BITLAND_NOTIFY_PLATFORM_PROFILE,
760dc1ec4faSMingyou Chen 					     NULL);
761dc1ec4faSMingyou Chen 		break;
762dc1ec4faSMingyou Chen 
763dc1ec4faSMingyou Chen 	case WMI_EVENT_OPEN_APP:
764dc1ec4faSMingyou Chen 	case WMI_EVENT_CALCULATOR_START:
765dc1ec4faSMingyou Chen 	case WMI_EVENT_BROWSER_START: {
766dc1ec4faSMingyou Chen 		guard(mutex)(&data->lock);
767dc1ec4faSMingyou Chen 		if (!sparse_keymap_report_event(data->input_dev,
768dc1ec4faSMingyou Chen 						event->event_id, 1, true))
769dc1ec4faSMingyou Chen 			dev_warn(&wdev->dev, "Unknown key pressed: 0x%02x\n",
770dc1ec4faSMingyou Chen 				 event->event_id);
771dc1ec4faSMingyou Chen 		break;
772dc1ec4faSMingyou Chen 	}
773dc1ec4faSMingyou Chen 
774dc1ec4faSMingyou Chen 	/*
775dc1ec4faSMingyou Chen 	 * The device has 3 fans (CPU, GPU, SYS),
776dc1ec4faSMingyou Chen 	 * but there are only the CPU and GPU fan has events
777dc1ec4faSMingyou Chen 	 */
778dc1ec4faSMingyou Chen 	case WMI_EVENT_CPU_FAN_SPEED:
779dc1ec4faSMingyou Chen 	case WMI_EVENT_GPU_FAN_SPEED:
780dc1ec4faSMingyou Chen 		if (event->event_id == WMI_EVENT_CPU_FAN_SPEED)
781dc1ec4faSMingyou Chen 			fan_data.channel = 0;
782dc1ec4faSMingyou Chen 		else
783dc1ec4faSMingyou Chen 			fan_data.channel = 1;
784dc1ec4faSMingyou Chen 
785dc1ec4faSMingyou Chen 		/* Fan speed is 16-bit value (value_low is LSB, value_high is MSB) */
786dc1ec4faSMingyou Chen 		fan_data.speed = (event->value_high << 8) | event->value_low;
787dc1ec4faSMingyou Chen 		blocking_notifier_call_chain(&bitland_notifier_list,
788dc1ec4faSMingyou Chen 					     BITLAND_NOTIFY_HWMON,
789dc1ec4faSMingyou Chen 					     &fan_data);
790dc1ec4faSMingyou Chen 		break;
791dc1ec4faSMingyou Chen 
792dc1ec4faSMingyou Chen 	case WMI_EVENT_AIRPLANE_MODE:
793dc1ec4faSMingyou Chen 	case WMI_EVENT_TOUCHPAD_STATE:
794dc1ec4faSMingyou Chen 	case WMI_EVENT_FNLOCK_STATE:
795dc1ec4faSMingyou Chen 	case WMI_EVENT_KBD_MODE:
796dc1ec4faSMingyou Chen 	case WMI_EVENT_CAPSLOCK_STATE:
797dc1ec4faSMingyou Chen 	case WMI_EVENT_NUMLOCK_STATE:
798dc1ec4faSMingyou Chen 	case WMI_EVENT_SCROLLLOCK_STATE:
799dc1ec4faSMingyou Chen 	case WMI_EVENT_REFRESH_RATE:
800dc1ec4faSMingyou Chen 	case WMI_EVENT_WIN_KEY_LOCK:
801dc1ec4faSMingyou Chen 		/* These events are informational or handled by firmware */
802dc1ec4faSMingyou Chen 		dev_dbg(&wdev->dev, "State change event: id=%d value=%d\n",
803dc1ec4faSMingyou Chen 			event->event_id, event->value_low);
804dc1ec4faSMingyou Chen 		break;
805dc1ec4faSMingyou Chen 
806dc1ec4faSMingyou Chen 	default:
807dc1ec4faSMingyou Chen 		dev_dbg(&wdev->dev, "Unknown event: id=0x%02x value=0x%02x\n",
808dc1ec4faSMingyou Chen 			event->event_id, event->value_low);
809dc1ec4faSMingyou Chen 		break;
810dc1ec4faSMingyou Chen 	}
811dc1ec4faSMingyou Chen }
812dc1ec4faSMingyou Chen 
813dc1ec4faSMingyou Chen static const struct wmi_device_id bitland_mifs_wmi_id_table[] = {
814dc1ec4faSMingyou Chen 	{ BITLAND_MIFS_GUID, (void *)BITLAND_WMI_CONTROL },
815dc1ec4faSMingyou Chen 	{ BITLAND_EVENT_GUID, (void *)BITLAND_WMI_EVENT },
816dc1ec4faSMingyou Chen 	{}
817dc1ec4faSMingyou Chen };
818dc1ec4faSMingyou Chen MODULE_DEVICE_TABLE(wmi, bitland_mifs_wmi_id_table);
819dc1ec4faSMingyou Chen 
820dc1ec4faSMingyou Chen static struct wmi_driver bitland_mifs_wmi_driver = {
821dc1ec4faSMingyou Chen 	.no_singleton = true,
822dc1ec4faSMingyou Chen 	.driver = {
823dc1ec4faSMingyou Chen 		.name = DRV_NAME,
824dc1ec4faSMingyou Chen 		.dev_groups = laptop_groups,
825dc1ec4faSMingyou Chen 		.pm = pm_sleep_ptr(&bitland_mifs_wmi_pm_ops),
826dc1ec4faSMingyou Chen 	},
827dc1ec4faSMingyou Chen 	.id_table = bitland_mifs_wmi_id_table,
828*2e2a3914SArmin Wolf 	.min_event_size = sizeof(struct bitland_mifs_event),
829dc1ec4faSMingyou Chen 	.probe = bitland_mifs_wmi_probe,
830dc1ec4faSMingyou Chen 	.notify_new = bitland_mifs_wmi_notify,
831dc1ec4faSMingyou Chen };
832dc1ec4faSMingyou Chen 
833dc1ec4faSMingyou Chen module_wmi_driver(bitland_mifs_wmi_driver);
834dc1ec4faSMingyou Chen 
835dc1ec4faSMingyou Chen MODULE_AUTHOR("Mingyou Chen <qby140326@gmail.com>");
836dc1ec4faSMingyou Chen MODULE_DESCRIPTION("Bitland MIFS (MiInterface) WMI driver");
837dc1ec4faSMingyou Chen MODULE_LICENSE("GPL");
838