xref: /openbmc/linux/drivers/platform/x86/msi-ec.c (revision 5a66d59b5ff537ddae84a1f175c3f8eb1140a562)
1392cacf2SNikita Kravets // SPDX-License-Identifier: GPL-2.0-or-later
2392cacf2SNikita Kravets 
3392cacf2SNikita Kravets /*
4392cacf2SNikita Kravets  * msi-ec: MSI laptops' embedded controller driver.
5392cacf2SNikita Kravets  *
6392cacf2SNikita Kravets  * This driver allows various MSI laptops' functionalities to be
7392cacf2SNikita Kravets  * controlled from userspace.
8392cacf2SNikita Kravets  *
9392cacf2SNikita Kravets  * It contains EC memory configurations for different firmware versions
10392cacf2SNikita Kravets  * and exports battery charge thresholds to userspace.
11392cacf2SNikita Kravets  *
12392cacf2SNikita Kravets  * Copyright (C) 2023 Jose Angel Pastrana <japp0005@red.ujaen.es>
13392cacf2SNikita Kravets  * Copyright (C) 2023 Aakash Singh <mail@singhaakash.dev>
14392cacf2SNikita Kravets  * Copyright (C) 2023 Nikita Kravets <teackot@gmail.com>
15392cacf2SNikita Kravets  */
16392cacf2SNikita Kravets 
17392cacf2SNikita Kravets #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
18392cacf2SNikita Kravets 
19392cacf2SNikita Kravets #include "msi-ec.h"
20392cacf2SNikita Kravets 
21392cacf2SNikita Kravets #include <acpi/battery.h>
22392cacf2SNikita Kravets #include <linux/acpi.h>
23392cacf2SNikita Kravets #include <linux/init.h>
24392cacf2SNikita Kravets #include <linux/kernel.h>
25392cacf2SNikita Kravets #include <linux/module.h>
26392cacf2SNikita Kravets #include <linux/platform_device.h>
27392cacf2SNikita Kravets #include <linux/seq_file.h>
28392cacf2SNikita Kravets #include <linux/string.h>
29392cacf2SNikita Kravets 
30*5a66d59bSJean Delvare #define SM_ECO_NAME		"eco"
31*5a66d59bSJean Delvare #define SM_COMFORT_NAME		"comfort"
32*5a66d59bSJean Delvare #define SM_SPORT_NAME		"sport"
33*5a66d59bSJean Delvare #define SM_TURBO_NAME		"turbo"
34392cacf2SNikita Kravets 
35*5a66d59bSJean Delvare #define FM_AUTO_NAME		"auto"
36*5a66d59bSJean Delvare #define FM_SILENT_NAME		"silent"
37*5a66d59bSJean Delvare #define FM_BASIC_NAME		"basic"
38*5a66d59bSJean Delvare #define FM_ADVANCED_NAME	"advanced"
39392cacf2SNikita Kravets 
40392cacf2SNikita Kravets static const char * const ALLOWED_FW_0[] __initconst = {
41392cacf2SNikita Kravets 	"14C1EMS1.012",
42392cacf2SNikita Kravets 	"14C1EMS1.101",
43392cacf2SNikita Kravets 	"14C1EMS1.102",
44392cacf2SNikita Kravets 	NULL
45392cacf2SNikita Kravets };
46392cacf2SNikita Kravets 
47392cacf2SNikita Kravets static struct msi_ec_conf CONF0 __initdata = {
48392cacf2SNikita Kravets 	.allowed_fw = ALLOWED_FW_0,
49392cacf2SNikita Kravets 	.charge_control = {
50392cacf2SNikita Kravets 		.address      = 0xef,
51392cacf2SNikita Kravets 		.offset_start = 0x8a,
52392cacf2SNikita Kravets 		.offset_end   = 0x80,
53392cacf2SNikita Kravets 		.range_min    = 0x8a,
54392cacf2SNikita Kravets 		.range_max    = 0xe4,
55392cacf2SNikita Kravets 	},
56392cacf2SNikita Kravets 	.webcam = {
57392cacf2SNikita Kravets 		.address       = 0x2e,
58392cacf2SNikita Kravets 		.block_address = 0x2f,
59392cacf2SNikita Kravets 		.bit           = 1,
60392cacf2SNikita Kravets 	},
61392cacf2SNikita Kravets 	.fn_super_swap = {
62392cacf2SNikita Kravets 		.address = 0xbf,
63392cacf2SNikita Kravets 		.bit     = 4,
64392cacf2SNikita Kravets 	},
65392cacf2SNikita Kravets 	.cooler_boost = {
66392cacf2SNikita Kravets 		.address = 0x98,
67392cacf2SNikita Kravets 		.bit     = 7,
68392cacf2SNikita Kravets 	},
69392cacf2SNikita Kravets 	.shift_mode = {
70392cacf2SNikita Kravets 		.address = 0xf2,
71392cacf2SNikita Kravets 		.modes = {
72392cacf2SNikita Kravets 			{ SM_ECO_NAME,     0xc2 },
73392cacf2SNikita Kravets 			{ SM_COMFORT_NAME, 0xc1 },
74392cacf2SNikita Kravets 			{ SM_SPORT_NAME,   0xc0 },
75392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
76392cacf2SNikita Kravets 		},
77392cacf2SNikita Kravets 	},
78392cacf2SNikita Kravets 	.super_battery = {
79392cacf2SNikita Kravets 		.address = MSI_EC_ADDR_UNKNOWN, // 0xd5 needs testing
80392cacf2SNikita Kravets 	},
81392cacf2SNikita Kravets 	.fan_mode = {
82392cacf2SNikita Kravets 		.address = 0xf4,
83392cacf2SNikita Kravets 		.modes = {
84392cacf2SNikita Kravets 			{ FM_AUTO_NAME,     0x0d },
85392cacf2SNikita Kravets 			{ FM_SILENT_NAME,   0x1d },
86392cacf2SNikita Kravets 			{ FM_BASIC_NAME,    0x4d },
87392cacf2SNikita Kravets 			{ FM_ADVANCED_NAME, 0x8d },
88392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
89392cacf2SNikita Kravets 		},
90392cacf2SNikita Kravets 	},
91392cacf2SNikita Kravets 	.cpu = {
92392cacf2SNikita Kravets 		.rt_temp_address       = 0x68,
93392cacf2SNikita Kravets 		.rt_fan_speed_address  = 0x71,
94392cacf2SNikita Kravets 		.rt_fan_speed_base_min = 0x19,
95392cacf2SNikita Kravets 		.rt_fan_speed_base_max = 0x37,
96392cacf2SNikita Kravets 		.bs_fan_speed_address  = 0x89,
97392cacf2SNikita Kravets 		.bs_fan_speed_base_min = 0x00,
98392cacf2SNikita Kravets 		.bs_fan_speed_base_max = 0x0f,
99392cacf2SNikita Kravets 	},
100392cacf2SNikita Kravets 	.gpu = {
101392cacf2SNikita Kravets 		.rt_temp_address      = 0x80,
102392cacf2SNikita Kravets 		.rt_fan_speed_address = 0x89,
103392cacf2SNikita Kravets 	},
104392cacf2SNikita Kravets 	.leds = {
105392cacf2SNikita Kravets 		.micmute_led_address = 0x2b,
106392cacf2SNikita Kravets 		.mute_led_address    = 0x2c,
107392cacf2SNikita Kravets 		.bit                 = 2,
108392cacf2SNikita Kravets 	},
109392cacf2SNikita Kravets 	.kbd_bl = {
110392cacf2SNikita Kravets 		.bl_mode_address  = 0x2c, // ?
111392cacf2SNikita Kravets 		.bl_modes         = { 0x00, 0x08 }, // ?
112392cacf2SNikita Kravets 		.max_mode         = 1, // ?
113392cacf2SNikita Kravets 		.bl_state_address = 0xf3,
114392cacf2SNikita Kravets 		.state_base_value = 0x80,
115392cacf2SNikita Kravets 		.max_state        = 3,
116392cacf2SNikita Kravets 	},
117392cacf2SNikita Kravets };
118392cacf2SNikita Kravets 
119392cacf2SNikita Kravets static const char * const ALLOWED_FW_1[] __initconst = {
120392cacf2SNikita Kravets 	"17F2EMS1.103",
121392cacf2SNikita Kravets 	"17F2EMS1.104",
122392cacf2SNikita Kravets 	"17F2EMS1.106",
123392cacf2SNikita Kravets 	"17F2EMS1.107",
124392cacf2SNikita Kravets 	NULL
125392cacf2SNikita Kravets };
126392cacf2SNikita Kravets 
127392cacf2SNikita Kravets static struct msi_ec_conf CONF1 __initdata = {
128392cacf2SNikita Kravets 	.allowed_fw = ALLOWED_FW_1,
129392cacf2SNikita Kravets 	.charge_control = {
130392cacf2SNikita Kravets 		.address      = 0xef,
131392cacf2SNikita Kravets 		.offset_start = 0x8a,
132392cacf2SNikita Kravets 		.offset_end   = 0x80,
133392cacf2SNikita Kravets 		.range_min    = 0x8a,
134392cacf2SNikita Kravets 		.range_max    = 0xe4,
135392cacf2SNikita Kravets 	},
136392cacf2SNikita Kravets 	.webcam = {
137392cacf2SNikita Kravets 		.address       = 0x2e,
138392cacf2SNikita Kravets 		.block_address = 0x2f,
139392cacf2SNikita Kravets 		.bit           = 1,
140392cacf2SNikita Kravets 	},
141392cacf2SNikita Kravets 	.fn_super_swap = {
142392cacf2SNikita Kravets 		.address = 0xbf,
143392cacf2SNikita Kravets 		.bit     = 4,
144392cacf2SNikita Kravets 	},
145392cacf2SNikita Kravets 	.cooler_boost = {
146392cacf2SNikita Kravets 		.address = 0x98,
147392cacf2SNikita Kravets 		.bit     = 7,
148392cacf2SNikita Kravets 	},
149392cacf2SNikita Kravets 	.shift_mode = {
150392cacf2SNikita Kravets 		.address = 0xf2,
151392cacf2SNikita Kravets 		.modes = {
152392cacf2SNikita Kravets 			{ SM_ECO_NAME,     0xc2 },
153392cacf2SNikita Kravets 			{ SM_COMFORT_NAME, 0xc1 },
154392cacf2SNikita Kravets 			{ SM_SPORT_NAME,   0xc0 },
155392cacf2SNikita Kravets 			{ SM_TURBO_NAME,   0xc4 },
156392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
157392cacf2SNikita Kravets 		},
158392cacf2SNikita Kravets 	},
159392cacf2SNikita Kravets 	.super_battery = {
160392cacf2SNikita Kravets 		.address = MSI_EC_ADDR_UNKNOWN,
161392cacf2SNikita Kravets 	},
162392cacf2SNikita Kravets 	.fan_mode = {
163392cacf2SNikita Kravets 		.address = 0xf4,
164392cacf2SNikita Kravets 		.modes = {
165392cacf2SNikita Kravets 			{ FM_AUTO_NAME,     0x0d },
166392cacf2SNikita Kravets 			{ FM_BASIC_NAME,    0x4d },
167392cacf2SNikita Kravets 			{ FM_ADVANCED_NAME, 0x8d },
168392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
169392cacf2SNikita Kravets 		},
170392cacf2SNikita Kravets 	},
171392cacf2SNikita Kravets 	.cpu = {
172392cacf2SNikita Kravets 		.rt_temp_address       = 0x68,
173392cacf2SNikita Kravets 		.rt_fan_speed_address  = 0x71,
174392cacf2SNikita Kravets 		.rt_fan_speed_base_min = 0x19,
175392cacf2SNikita Kravets 		.rt_fan_speed_base_max = 0x37,
176392cacf2SNikita Kravets 		.bs_fan_speed_address  = 0x89,
177392cacf2SNikita Kravets 		.bs_fan_speed_base_min = 0x00,
178392cacf2SNikita Kravets 		.bs_fan_speed_base_max = 0x0f,
179392cacf2SNikita Kravets 	},
180392cacf2SNikita Kravets 	.gpu = {
181392cacf2SNikita Kravets 		.rt_temp_address      = 0x80,
182392cacf2SNikita Kravets 		.rt_fan_speed_address = 0x89,
183392cacf2SNikita Kravets 	},
184392cacf2SNikita Kravets 	.leds = {
185392cacf2SNikita Kravets 		.micmute_led_address = 0x2b,
186392cacf2SNikita Kravets 		.mute_led_address    = 0x2c,
187392cacf2SNikita Kravets 		.bit                 = 2,
188392cacf2SNikita Kravets 	},
189392cacf2SNikita Kravets 	.kbd_bl = {
190392cacf2SNikita Kravets 		.bl_mode_address  = 0x2c, // ?
191392cacf2SNikita Kravets 		.bl_modes         = { 0x00, 0x08 }, // ?
192392cacf2SNikita Kravets 		.max_mode         = 1, // ?
193392cacf2SNikita Kravets 		.bl_state_address = 0xf3,
194392cacf2SNikita Kravets 		.state_base_value = 0x80,
195392cacf2SNikita Kravets 		.max_state        = 3,
196392cacf2SNikita Kravets 	},
197392cacf2SNikita Kravets };
198392cacf2SNikita Kravets 
199392cacf2SNikita Kravets static const char * const ALLOWED_FW_2[] __initconst = {
200392cacf2SNikita Kravets 	"1552EMS1.118",
201392cacf2SNikita Kravets 	NULL
202392cacf2SNikita Kravets };
203392cacf2SNikita Kravets 
204392cacf2SNikita Kravets static struct msi_ec_conf CONF2 __initdata = {
205392cacf2SNikita Kravets 	.allowed_fw = ALLOWED_FW_2,
206392cacf2SNikita Kravets 	.charge_control = {
207392cacf2SNikita Kravets 		.address      = 0xd7,
208392cacf2SNikita Kravets 		.offset_start = 0x8a,
209392cacf2SNikita Kravets 		.offset_end   = 0x80,
210392cacf2SNikita Kravets 		.range_min    = 0x8a,
211392cacf2SNikita Kravets 		.range_max    = 0xe4,
212392cacf2SNikita Kravets 	},
213392cacf2SNikita Kravets 	.webcam = {
214392cacf2SNikita Kravets 		.address       = 0x2e,
215392cacf2SNikita Kravets 		.block_address = 0x2f,
216392cacf2SNikita Kravets 		.bit           = 1,
217392cacf2SNikita Kravets 	},
218392cacf2SNikita Kravets 	.fn_super_swap = {
219392cacf2SNikita Kravets 		.address = 0xe8,
220392cacf2SNikita Kravets 		.bit     = 4,
221392cacf2SNikita Kravets 	},
222392cacf2SNikita Kravets 	.cooler_boost = {
223392cacf2SNikita Kravets 		.address = 0x98,
224392cacf2SNikita Kravets 		.bit     = 7,
225392cacf2SNikita Kravets 	},
226392cacf2SNikita Kravets 	.shift_mode = {
227392cacf2SNikita Kravets 		.address = 0xf2,
228392cacf2SNikita Kravets 		.modes = {
229392cacf2SNikita Kravets 			{ SM_ECO_NAME,     0xc2 },
230392cacf2SNikita Kravets 			{ SM_COMFORT_NAME, 0xc1 },
231392cacf2SNikita Kravets 			{ SM_SPORT_NAME,   0xc0 },
232392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
233392cacf2SNikita Kravets 		},
234392cacf2SNikita Kravets 	},
235392cacf2SNikita Kravets 	.super_battery = {
236392cacf2SNikita Kravets 		.address = 0xeb,
237392cacf2SNikita Kravets 		.mask    = 0x0f,
238392cacf2SNikita Kravets 	},
239392cacf2SNikita Kravets 	.fan_mode = {
240392cacf2SNikita Kravets 		.address = 0xd4,
241392cacf2SNikita Kravets 		.modes = {
242392cacf2SNikita Kravets 			{ FM_AUTO_NAME,     0x0d },
243392cacf2SNikita Kravets 			{ FM_SILENT_NAME,   0x1d },
244392cacf2SNikita Kravets 			{ FM_BASIC_NAME,    0x4d },
245392cacf2SNikita Kravets 			{ FM_ADVANCED_NAME, 0x8d },
246392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
247392cacf2SNikita Kravets 		},
248392cacf2SNikita Kravets 	},
249392cacf2SNikita Kravets 	.cpu = {
250392cacf2SNikita Kravets 		.rt_temp_address       = 0x68,
251392cacf2SNikita Kravets 		.rt_fan_speed_address  = 0x71,
252392cacf2SNikita Kravets 		.rt_fan_speed_base_min = 0x19,
253392cacf2SNikita Kravets 		.rt_fan_speed_base_max = 0x37,
254392cacf2SNikita Kravets 		.bs_fan_speed_address  = 0x89,
255392cacf2SNikita Kravets 		.bs_fan_speed_base_min = 0x00,
256392cacf2SNikita Kravets 		.bs_fan_speed_base_max = 0x0f,
257392cacf2SNikita Kravets 	},
258392cacf2SNikita Kravets 	.gpu = {
259392cacf2SNikita Kravets 		.rt_temp_address      = 0x80,
260392cacf2SNikita Kravets 		.rt_fan_speed_address = 0x89,
261392cacf2SNikita Kravets 	},
262392cacf2SNikita Kravets 	.leds = {
263392cacf2SNikita Kravets 		.micmute_led_address = 0x2c,
264392cacf2SNikita Kravets 		.mute_led_address    = 0x2d,
265392cacf2SNikita Kravets 		.bit                 = 1,
266392cacf2SNikita Kravets 	},
267392cacf2SNikita Kravets 	.kbd_bl = {
268392cacf2SNikita Kravets 		.bl_mode_address  = 0x2c, // ?
269392cacf2SNikita Kravets 		.bl_modes         = { 0x00, 0x08 }, // ?
270392cacf2SNikita Kravets 		.max_mode         = 1, // ?
271392cacf2SNikita Kravets 		.bl_state_address = 0xd3,
272392cacf2SNikita Kravets 		.state_base_value = 0x80,
273392cacf2SNikita Kravets 		.max_state        = 3,
274392cacf2SNikita Kravets 	},
275392cacf2SNikita Kravets };
276392cacf2SNikita Kravets 
277392cacf2SNikita Kravets static const char * const ALLOWED_FW_3[] __initconst = {
278392cacf2SNikita Kravets 	"1592EMS1.111",
279392cacf2SNikita Kravets 	"E1592IMS.10C",
280392cacf2SNikita Kravets 	NULL
281392cacf2SNikita Kravets };
282392cacf2SNikita Kravets 
283392cacf2SNikita Kravets static struct msi_ec_conf CONF3 __initdata = {
284392cacf2SNikita Kravets 	.allowed_fw = ALLOWED_FW_3,
285392cacf2SNikita Kravets 	.charge_control = {
286392cacf2SNikita Kravets 		.address      = 0xef,
287392cacf2SNikita Kravets 		.offset_start = 0x8a,
288392cacf2SNikita Kravets 		.offset_end   = 0x80,
289392cacf2SNikita Kravets 		.range_min    = 0x8a,
290392cacf2SNikita Kravets 		.range_max    = 0xe4,
291392cacf2SNikita Kravets 	},
292392cacf2SNikita Kravets 	.webcam = {
293392cacf2SNikita Kravets 		.address       = 0x2e,
294392cacf2SNikita Kravets 		.block_address = 0x2f,
295392cacf2SNikita Kravets 		.bit           = 1,
296392cacf2SNikita Kravets 	},
297392cacf2SNikita Kravets 	.fn_super_swap = {
298392cacf2SNikita Kravets 		.address = 0xe8,
299392cacf2SNikita Kravets 		.bit     = 4,
300392cacf2SNikita Kravets 	},
301392cacf2SNikita Kravets 	.cooler_boost = {
302392cacf2SNikita Kravets 		.address = 0x98,
303392cacf2SNikita Kravets 		.bit     = 7,
304392cacf2SNikita Kravets 	},
305392cacf2SNikita Kravets 	.shift_mode = {
306392cacf2SNikita Kravets 		.address = 0xd2,
307392cacf2SNikita Kravets 		.modes = {
308392cacf2SNikita Kravets 			{ SM_ECO_NAME,     0xc2 },
309392cacf2SNikita Kravets 			{ SM_COMFORT_NAME, 0xc1 },
310392cacf2SNikita Kravets 			{ SM_SPORT_NAME,   0xc0 },
311392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
312392cacf2SNikita Kravets 		},
313392cacf2SNikita Kravets 	},
314392cacf2SNikita Kravets 	.super_battery = {
315392cacf2SNikita Kravets 		.address = 0xeb,
316392cacf2SNikita Kravets 		.mask    = 0x0f,
317392cacf2SNikita Kravets 	},
318392cacf2SNikita Kravets 	.fan_mode = {
319392cacf2SNikita Kravets 		.address = 0xd4,
320392cacf2SNikita Kravets 		.modes = {
321392cacf2SNikita Kravets 			{ FM_AUTO_NAME,     0x0d },
322392cacf2SNikita Kravets 			{ FM_SILENT_NAME,   0x1d },
323392cacf2SNikita Kravets 			{ FM_BASIC_NAME,    0x4d },
324392cacf2SNikita Kravets 			{ FM_ADVANCED_NAME, 0x8d },
325392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
326392cacf2SNikita Kravets 		},
327392cacf2SNikita Kravets 	},
328392cacf2SNikita Kravets 	.cpu = {
329392cacf2SNikita Kravets 		.rt_temp_address       = 0x68,
330392cacf2SNikita Kravets 		.rt_fan_speed_address  = 0xc9,
331392cacf2SNikita Kravets 		.rt_fan_speed_base_min = 0x19,
332392cacf2SNikita Kravets 		.rt_fan_speed_base_max = 0x37,
333392cacf2SNikita Kravets 		.bs_fan_speed_address  = 0x89, // ?
334392cacf2SNikita Kravets 		.bs_fan_speed_base_min = 0x00,
335392cacf2SNikita Kravets 		.bs_fan_speed_base_max = 0x0f,
336392cacf2SNikita Kravets 	},
337392cacf2SNikita Kravets 	.gpu = {
338392cacf2SNikita Kravets 		.rt_temp_address      = 0x80,
339392cacf2SNikita Kravets 		.rt_fan_speed_address = 0x89,
340392cacf2SNikita Kravets 	},
341392cacf2SNikita Kravets 	.leds = {
342392cacf2SNikita Kravets 		.micmute_led_address = 0x2b,
343392cacf2SNikita Kravets 		.mute_led_address    = 0x2c,
344392cacf2SNikita Kravets 		.bit                 = 1,
345392cacf2SNikita Kravets 	},
346392cacf2SNikita Kravets 	.kbd_bl = {
347392cacf2SNikita Kravets 		.bl_mode_address  = 0x2c, // ?
348392cacf2SNikita Kravets 		.bl_modes         = { 0x00, 0x08 }, // ?
349392cacf2SNikita Kravets 		.max_mode         = 1, // ?
350392cacf2SNikita Kravets 		.bl_state_address = 0xd3,
351392cacf2SNikita Kravets 		.state_base_value = 0x80,
352392cacf2SNikita Kravets 		.max_state        = 3,
353392cacf2SNikita Kravets 	},
354392cacf2SNikita Kravets };
355392cacf2SNikita Kravets 
356392cacf2SNikita Kravets static const char * const ALLOWED_FW_4[] __initconst = {
357392cacf2SNikita Kravets 	"16V4EMS1.114",
358392cacf2SNikita Kravets 	NULL
359392cacf2SNikita Kravets };
360392cacf2SNikita Kravets 
361392cacf2SNikita Kravets static struct msi_ec_conf CONF4 __initdata = {
362392cacf2SNikita Kravets 	.allowed_fw = ALLOWED_FW_4,
363392cacf2SNikita Kravets 	.charge_control = {
364392cacf2SNikita Kravets 		.address      = 0xd7,
365392cacf2SNikita Kravets 		.offset_start = 0x8a,
366392cacf2SNikita Kravets 		.offset_end   = 0x80,
367392cacf2SNikita Kravets 		.range_min    = 0x8a,
368392cacf2SNikita Kravets 		.range_max    = 0xe4,
369392cacf2SNikita Kravets 	},
370392cacf2SNikita Kravets 	.webcam = {
371392cacf2SNikita Kravets 		.address       = 0x2e,
372392cacf2SNikita Kravets 		.block_address = 0x2f,
373392cacf2SNikita Kravets 		.bit           = 1,
374392cacf2SNikita Kravets 	},
375392cacf2SNikita Kravets 	.fn_super_swap = {
376392cacf2SNikita Kravets 		.address = MSI_EC_ADDR_UNKNOWN, // supported, but unknown
377392cacf2SNikita Kravets 		.bit     = 4,
378392cacf2SNikita Kravets 	},
379392cacf2SNikita Kravets 	.cooler_boost = {
380392cacf2SNikita Kravets 		.address = 0x98,
381392cacf2SNikita Kravets 		.bit     = 7,
382392cacf2SNikita Kravets 	},
383392cacf2SNikita Kravets 	.shift_mode = {
384392cacf2SNikita Kravets 		.address = 0xd2,
385392cacf2SNikita Kravets 		.modes = {
386392cacf2SNikita Kravets 			{ SM_ECO_NAME,     0xc2 },
387392cacf2SNikita Kravets 			{ SM_COMFORT_NAME, 0xc1 },
388392cacf2SNikita Kravets 			{ SM_SPORT_NAME,   0xc0 },
389392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
390392cacf2SNikita Kravets 		},
391392cacf2SNikita Kravets 	},
392392cacf2SNikita Kravets 	.super_battery = { // may be supported, but address is unknown
393392cacf2SNikita Kravets 		.address = MSI_EC_ADDR_UNKNOWN,
394392cacf2SNikita Kravets 		.mask    = 0x0f,
395392cacf2SNikita Kravets 	},
396392cacf2SNikita Kravets 	.fan_mode = {
397392cacf2SNikita Kravets 		.address = 0xd4,
398392cacf2SNikita Kravets 		.modes = {
399392cacf2SNikita Kravets 			{ FM_AUTO_NAME,     0x0d },
400392cacf2SNikita Kravets 			{ FM_SILENT_NAME,   0x1d },
401392cacf2SNikita Kravets 			{ FM_ADVANCED_NAME, 0x8d },
402392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
403392cacf2SNikita Kravets 		},
404392cacf2SNikita Kravets 	},
405392cacf2SNikita Kravets 	.cpu = {
406392cacf2SNikita Kravets 		.rt_temp_address       = 0x68, // needs testing
407392cacf2SNikita Kravets 		.rt_fan_speed_address  = 0x71, // needs testing
408392cacf2SNikita Kravets 		.rt_fan_speed_base_min = 0x19,
409392cacf2SNikita Kravets 		.rt_fan_speed_base_max = 0x37,
410392cacf2SNikita Kravets 		.bs_fan_speed_address  = MSI_EC_ADDR_UNKNOWN,
411392cacf2SNikita Kravets 		.bs_fan_speed_base_min = 0x00,
412392cacf2SNikita Kravets 		.bs_fan_speed_base_max = 0x0f,
413392cacf2SNikita Kravets 	},
414392cacf2SNikita Kravets 	.gpu = {
415392cacf2SNikita Kravets 		.rt_temp_address      = 0x80,
416392cacf2SNikita Kravets 		.rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
417392cacf2SNikita Kravets 	},
418392cacf2SNikita Kravets 	.leds = {
419392cacf2SNikita Kravets 		.micmute_led_address = MSI_EC_ADDR_UNKNOWN,
420392cacf2SNikita Kravets 		.mute_led_address    = MSI_EC_ADDR_UNKNOWN,
421392cacf2SNikita Kravets 		.bit                 = 1,
422392cacf2SNikita Kravets 	},
423392cacf2SNikita Kravets 	.kbd_bl = {
424392cacf2SNikita Kravets 		.bl_mode_address  = MSI_EC_ADDR_UNKNOWN, // ?
425392cacf2SNikita Kravets 		.bl_modes         = { 0x00, 0x08 }, // ?
426392cacf2SNikita Kravets 		.max_mode         = 1, // ?
427392cacf2SNikita Kravets 		.bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xd3, not functional
428392cacf2SNikita Kravets 		.state_base_value = 0x80,
429392cacf2SNikita Kravets 		.max_state        = 3,
430392cacf2SNikita Kravets 	},
431392cacf2SNikita Kravets };
432392cacf2SNikita Kravets 
433392cacf2SNikita Kravets static const char * const ALLOWED_FW_5[] __initconst = {
434392cacf2SNikita Kravets 	"158LEMS1.103",
435392cacf2SNikita Kravets 	"158LEMS1.105",
436392cacf2SNikita Kravets 	"158LEMS1.106",
437392cacf2SNikita Kravets 	NULL
438392cacf2SNikita Kravets };
439392cacf2SNikita Kravets 
440392cacf2SNikita Kravets static struct msi_ec_conf CONF5 __initdata = {
441392cacf2SNikita Kravets 	.allowed_fw = ALLOWED_FW_5,
442392cacf2SNikita Kravets 	.charge_control = {
443392cacf2SNikita Kravets 		.address      = 0xef,
444392cacf2SNikita Kravets 		.offset_start = 0x8a,
445392cacf2SNikita Kravets 		.offset_end   = 0x80,
446392cacf2SNikita Kravets 		.range_min    = 0x8a,
447392cacf2SNikita Kravets 		.range_max    = 0xe4,
448392cacf2SNikita Kravets 	},
449392cacf2SNikita Kravets 	.webcam = {
450392cacf2SNikita Kravets 		.address       = 0x2e,
451392cacf2SNikita Kravets 		.block_address = 0x2f,
452392cacf2SNikita Kravets 		.bit           = 1,
453392cacf2SNikita Kravets 	},
454392cacf2SNikita Kravets 	.fn_super_swap = { // todo: reverse
455392cacf2SNikita Kravets 		.address = 0xbf,
456392cacf2SNikita Kravets 		.bit     = 4,
457392cacf2SNikita Kravets 	},
458392cacf2SNikita Kravets 	.cooler_boost = {
459392cacf2SNikita Kravets 		.address = 0x98,
460392cacf2SNikita Kravets 		.bit     = 7,
461392cacf2SNikita Kravets 	},
462392cacf2SNikita Kravets 	.shift_mode = {
463392cacf2SNikita Kravets 		.address = 0xf2,
464392cacf2SNikita Kravets 		.modes = {
465392cacf2SNikita Kravets 			{ SM_ECO_NAME,     0xc2 },
466392cacf2SNikita Kravets 			{ SM_COMFORT_NAME, 0xc1 },
467392cacf2SNikita Kravets 			{ SM_TURBO_NAME,   0xc4 },
468392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
469392cacf2SNikita Kravets 		},
470392cacf2SNikita Kravets 	},
471392cacf2SNikita Kravets 	.super_battery = { // unsupported?
472392cacf2SNikita Kravets 		.address = MSI_EC_ADDR_UNKNOWN,
473392cacf2SNikita Kravets 		.mask    = 0x0f,
474392cacf2SNikita Kravets 	},
475392cacf2SNikita Kravets 	.fan_mode = {
476392cacf2SNikita Kravets 		.address = 0xf4,
477392cacf2SNikita Kravets 		.modes = {
478392cacf2SNikita Kravets 			{ FM_AUTO_NAME,     0x0d },
479392cacf2SNikita Kravets 			{ FM_SILENT_NAME,   0x1d },
480392cacf2SNikita Kravets 			{ FM_ADVANCED_NAME, 0x8d },
481392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
482392cacf2SNikita Kravets 		},
483392cacf2SNikita Kravets 	},
484392cacf2SNikita Kravets 	.cpu = {
485392cacf2SNikita Kravets 		.rt_temp_address       = 0x68, // needs testing
486392cacf2SNikita Kravets 		.rt_fan_speed_address  = 0x71, // needs testing
487392cacf2SNikita Kravets 		.rt_fan_speed_base_min = 0x19,
488392cacf2SNikita Kravets 		.rt_fan_speed_base_max = 0x37,
489392cacf2SNikita Kravets 		.bs_fan_speed_address  = MSI_EC_ADDR_UNSUPP,
490392cacf2SNikita Kravets 		.bs_fan_speed_base_min = 0x00,
491392cacf2SNikita Kravets 		.bs_fan_speed_base_max = 0x0f,
492392cacf2SNikita Kravets 	},
493392cacf2SNikita Kravets 	.gpu = {
494392cacf2SNikita Kravets 		.rt_temp_address      = MSI_EC_ADDR_UNKNOWN,
495392cacf2SNikita Kravets 		.rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
496392cacf2SNikita Kravets 	},
497392cacf2SNikita Kravets 	.leds = {
498392cacf2SNikita Kravets 		.micmute_led_address = 0x2b,
499392cacf2SNikita Kravets 		.mute_led_address    = 0x2c,
500392cacf2SNikita Kravets 		.bit                 = 2,
501392cacf2SNikita Kravets 	},
502392cacf2SNikita Kravets 	.kbd_bl = {
503392cacf2SNikita Kravets 		.bl_mode_address  = MSI_EC_ADDR_UNKNOWN, // ?
504392cacf2SNikita Kravets 		.bl_modes         = { 0x00, 0x08 }, // ?
505392cacf2SNikita Kravets 		.max_mode         = 1, // ?
506392cacf2SNikita Kravets 		.bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xf3, not functional
507392cacf2SNikita Kravets 		.state_base_value = 0x80,
508392cacf2SNikita Kravets 		.max_state        = 3,
509392cacf2SNikita Kravets 	},
510392cacf2SNikita Kravets };
511392cacf2SNikita Kravets 
512392cacf2SNikita Kravets static const char * const ALLOWED_FW_6[] __initconst = {
513392cacf2SNikita Kravets 	"1542EMS1.102",
514392cacf2SNikita Kravets 	"1542EMS1.104",
515392cacf2SNikita Kravets 	NULL
516392cacf2SNikita Kravets };
517392cacf2SNikita Kravets 
518392cacf2SNikita Kravets static struct msi_ec_conf CONF6 __initdata = {
519392cacf2SNikita Kravets 	.allowed_fw = ALLOWED_FW_6,
520392cacf2SNikita Kravets 	.charge_control = {
521392cacf2SNikita Kravets 		.address      = 0xef,
522392cacf2SNikita Kravets 		.offset_start = 0x8a,
523392cacf2SNikita Kravets 		.offset_end   = 0x80,
524392cacf2SNikita Kravets 		.range_min    = 0x8a,
525392cacf2SNikita Kravets 		.range_max    = 0xe4,
526392cacf2SNikita Kravets 	},
527392cacf2SNikita Kravets 	.webcam = {
528392cacf2SNikita Kravets 		.address       = 0x2e,
529392cacf2SNikita Kravets 		.block_address = MSI_EC_ADDR_UNSUPP,
530392cacf2SNikita Kravets 		.bit           = 1,
531392cacf2SNikita Kravets 	},
532392cacf2SNikita Kravets 	.fn_super_swap = {
533392cacf2SNikita Kravets 		.address = 0xbf, // todo: reverse
534392cacf2SNikita Kravets 		.bit     = 4,
535392cacf2SNikita Kravets 	},
536392cacf2SNikita Kravets 	.cooler_boost = {
537392cacf2SNikita Kravets 		.address = 0x98,
538392cacf2SNikita Kravets 		.bit     = 7,
539392cacf2SNikita Kravets 	},
540392cacf2SNikita Kravets 	.shift_mode = {
541392cacf2SNikita Kravets 		.address = 0xf2,
542392cacf2SNikita Kravets 		.modes = {
543392cacf2SNikita Kravets 			{ SM_ECO_NAME,     0xc2 },
544392cacf2SNikita Kravets 			{ SM_COMFORT_NAME, 0xc1 },
545392cacf2SNikita Kravets 			{ SM_SPORT_NAME,   0xc0 },
546392cacf2SNikita Kravets 			{ SM_TURBO_NAME,   0xc4 },
547392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
548392cacf2SNikita Kravets 		},
549392cacf2SNikita Kravets 	},
550392cacf2SNikita Kravets 	.super_battery = {
551392cacf2SNikita Kravets 		.address = 0xd5,
552392cacf2SNikita Kravets 		.mask    = 0x0f,
553392cacf2SNikita Kravets 	},
554392cacf2SNikita Kravets 	.fan_mode = {
555392cacf2SNikita Kravets 		.address = 0xf4,
556392cacf2SNikita Kravets 		.modes = {
557392cacf2SNikita Kravets 			{ FM_AUTO_NAME,     0x0d },
558392cacf2SNikita Kravets 			{ FM_SILENT_NAME,   0x1d },
559392cacf2SNikita Kravets 			{ FM_ADVANCED_NAME, 0x8d },
560392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
561392cacf2SNikita Kravets 		},
562392cacf2SNikita Kravets 	},
563392cacf2SNikita Kravets 	.cpu = {
564392cacf2SNikita Kravets 		.rt_temp_address       = 0x68,
565392cacf2SNikita Kravets 		.rt_fan_speed_address  = 0xc9,
566392cacf2SNikita Kravets 		.rt_fan_speed_base_min = 0x19,
567392cacf2SNikita Kravets 		.rt_fan_speed_base_max = 0x37,
568392cacf2SNikita Kravets 		.bs_fan_speed_address  = MSI_EC_ADDR_UNSUPP,
569392cacf2SNikita Kravets 		.bs_fan_speed_base_min = 0x00,
570392cacf2SNikita Kravets 		.bs_fan_speed_base_max = 0x0f,
571392cacf2SNikita Kravets 	},
572392cacf2SNikita Kravets 	.gpu = {
573392cacf2SNikita Kravets 		.rt_temp_address      = 0x80,
574392cacf2SNikita Kravets 		.rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
575392cacf2SNikita Kravets 	},
576392cacf2SNikita Kravets 	.leds = {
577392cacf2SNikita Kravets 		.micmute_led_address = MSI_EC_ADDR_UNSUPP,
578392cacf2SNikita Kravets 		.mute_led_address    = MSI_EC_ADDR_UNSUPP,
579392cacf2SNikita Kravets 		.bit                 = 2,
580392cacf2SNikita Kravets 	},
581392cacf2SNikita Kravets 	.kbd_bl = {
582392cacf2SNikita Kravets 		.bl_mode_address  = MSI_EC_ADDR_UNKNOWN, // ?
583392cacf2SNikita Kravets 		.bl_modes         = { 0x00, 0x08 }, // ?
584392cacf2SNikita Kravets 		.max_mode         = 1, // ?
585392cacf2SNikita Kravets 		.bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xf3, not functional
586392cacf2SNikita Kravets 		.state_base_value = 0x80,
587392cacf2SNikita Kravets 		.max_state        = 3,
588392cacf2SNikita Kravets 	},
589392cacf2SNikita Kravets };
590392cacf2SNikita Kravets 
591392cacf2SNikita Kravets static const char * const ALLOWED_FW_7[] __initconst = {
592392cacf2SNikita Kravets 	"17FKEMS1.108",
593392cacf2SNikita Kravets 	"17FKEMS1.109",
594392cacf2SNikita Kravets 	"17FKEMS1.10A",
595392cacf2SNikita Kravets 	NULL
596392cacf2SNikita Kravets };
597392cacf2SNikita Kravets 
598392cacf2SNikita Kravets static struct msi_ec_conf CONF7 __initdata = {
599392cacf2SNikita Kravets 	.allowed_fw = ALLOWED_FW_7,
600392cacf2SNikita Kravets 	.charge_control = {
601392cacf2SNikita Kravets 		.address      = 0xef,
602392cacf2SNikita Kravets 		.offset_start = 0x8a,
603392cacf2SNikita Kravets 		.offset_end   = 0x80,
604392cacf2SNikita Kravets 		.range_min    = 0x8a,
605392cacf2SNikita Kravets 		.range_max    = 0xe4,
606392cacf2SNikita Kravets 	},
607392cacf2SNikita Kravets 	.webcam = {
608392cacf2SNikita Kravets 		.address       = 0x2e,
609392cacf2SNikita Kravets 		.block_address = MSI_EC_ADDR_UNSUPP,
610392cacf2SNikita Kravets 		.bit           = 1,
611392cacf2SNikita Kravets 	},
612392cacf2SNikita Kravets 	.fn_super_swap = {
613392cacf2SNikita Kravets 		.address = 0xbf, // needs testing
614392cacf2SNikita Kravets 		.bit     = 4,
615392cacf2SNikita Kravets 	},
616392cacf2SNikita Kravets 	.cooler_boost = {
617392cacf2SNikita Kravets 		.address = 0x98,
618392cacf2SNikita Kravets 		.bit     = 7,
619392cacf2SNikita Kravets 	},
620392cacf2SNikita Kravets 	.shift_mode = {
621392cacf2SNikita Kravets 		.address = 0xf2,
622392cacf2SNikita Kravets 		.modes = {
623392cacf2SNikita Kravets 			{ SM_ECO_NAME,     0xc2 },
624392cacf2SNikita Kravets 			{ SM_COMFORT_NAME, 0xc1 },
625392cacf2SNikita Kravets 			{ SM_SPORT_NAME,   0xc0 },
626392cacf2SNikita Kravets 			{ SM_TURBO_NAME,   0xc4 },
627392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
628392cacf2SNikita Kravets 		},
629392cacf2SNikita Kravets 	},
630392cacf2SNikita Kravets 	.super_battery = {
631392cacf2SNikita Kravets 		.address = MSI_EC_ADDR_UNKNOWN, // 0xd5 but has its own wet of modes
632392cacf2SNikita Kravets 		.mask    = 0x0f,
633392cacf2SNikita Kravets 	},
634392cacf2SNikita Kravets 	.fan_mode = {
635392cacf2SNikita Kravets 		.address = 0xf4,
636392cacf2SNikita Kravets 		.modes = {
637392cacf2SNikita Kravets 			{ FM_AUTO_NAME,     0x0d }, // d may not be relevant
638392cacf2SNikita Kravets 			{ FM_SILENT_NAME,   0x1d },
639392cacf2SNikita Kravets 			{ FM_ADVANCED_NAME, 0x8d },
640392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
641392cacf2SNikita Kravets 		},
642392cacf2SNikita Kravets 	},
643392cacf2SNikita Kravets 	.cpu = {
644392cacf2SNikita Kravets 		.rt_temp_address       = 0x68,
645392cacf2SNikita Kravets 		.rt_fan_speed_address  = 0xc9, // needs testing
646392cacf2SNikita Kravets 		.rt_fan_speed_base_min = 0x19,
647392cacf2SNikita Kravets 		.rt_fan_speed_base_max = 0x37,
648392cacf2SNikita Kravets 		.bs_fan_speed_address  = MSI_EC_ADDR_UNSUPP,
649392cacf2SNikita Kravets 		.bs_fan_speed_base_min = 0x00,
650392cacf2SNikita Kravets 		.bs_fan_speed_base_max = 0x0f,
651392cacf2SNikita Kravets 	},
652392cacf2SNikita Kravets 	.gpu = {
653392cacf2SNikita Kravets 		.rt_temp_address      = MSI_EC_ADDR_UNKNOWN,
654392cacf2SNikita Kravets 		.rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
655392cacf2SNikita Kravets 	},
656392cacf2SNikita Kravets 	.leds = {
657392cacf2SNikita Kravets 		.micmute_led_address = MSI_EC_ADDR_UNSUPP,
658392cacf2SNikita Kravets 		.mute_led_address    = 0x2c,
659392cacf2SNikita Kravets 		.bit                 = 2,
660392cacf2SNikita Kravets 	},
661392cacf2SNikita Kravets 	.kbd_bl = {
662392cacf2SNikita Kravets 		.bl_mode_address  = MSI_EC_ADDR_UNKNOWN, // ?
663392cacf2SNikita Kravets 		.bl_modes         = { 0x00, 0x08 }, // ?
664392cacf2SNikita Kravets 		.max_mode         = 1, // ?
665392cacf2SNikita Kravets 		.bl_state_address = 0xf3,
666392cacf2SNikita Kravets 		.state_base_value = 0x80,
667392cacf2SNikita Kravets 		.max_state        = 3,
668392cacf2SNikita Kravets 	},
669392cacf2SNikita Kravets };
670392cacf2SNikita Kravets 
671392cacf2SNikita Kravets static struct msi_ec_conf *CONFIGS[] __initdata = {
672392cacf2SNikita Kravets 	&CONF0,
673392cacf2SNikita Kravets 	&CONF1,
674392cacf2SNikita Kravets 	&CONF2,
675392cacf2SNikita Kravets 	&CONF3,
676392cacf2SNikita Kravets 	&CONF4,
677392cacf2SNikita Kravets 	&CONF5,
678392cacf2SNikita Kravets 	&CONF6,
679392cacf2SNikita Kravets 	&CONF7,
680392cacf2SNikita Kravets 	NULL
681392cacf2SNikita Kravets };
682392cacf2SNikita Kravets 
683392cacf2SNikita Kravets static struct msi_ec_conf conf; // current configuration
684392cacf2SNikita Kravets 
685392cacf2SNikita Kravets /*
686392cacf2SNikita Kravets  * Helper functions
687392cacf2SNikita Kravets  */
688392cacf2SNikita Kravets 
689392cacf2SNikita Kravets static int ec_read_seq(u8 addr, u8 *buf, u8 len)
690392cacf2SNikita Kravets {
691392cacf2SNikita Kravets 	int result;
692392cacf2SNikita Kravets 
693392cacf2SNikita Kravets 	for (u8 i = 0; i < len; i++) {
694392cacf2SNikita Kravets 		result = ec_read(addr + i, buf + i);
695392cacf2SNikita Kravets 		if (result < 0)
696392cacf2SNikita Kravets 			return result;
697392cacf2SNikita Kravets 	}
698392cacf2SNikita Kravets 
699392cacf2SNikita Kravets 	return 0;
700392cacf2SNikita Kravets }
701392cacf2SNikita Kravets 
702392cacf2SNikita Kravets static int ec_get_firmware_version(u8 buf[MSI_EC_FW_VERSION_LENGTH + 1])
703392cacf2SNikita Kravets {
704392cacf2SNikita Kravets 	int result;
705392cacf2SNikita Kravets 
706392cacf2SNikita Kravets 	memset(buf, 0, MSI_EC_FW_VERSION_LENGTH + 1);
707392cacf2SNikita Kravets 	result = ec_read_seq(MSI_EC_FW_VERSION_ADDRESS,
708392cacf2SNikita Kravets 			     buf,
709392cacf2SNikita Kravets 			     MSI_EC_FW_VERSION_LENGTH);
710392cacf2SNikita Kravets 	if (result < 0)
711392cacf2SNikita Kravets 		return result;
712392cacf2SNikita Kravets 
713392cacf2SNikita Kravets 	return MSI_EC_FW_VERSION_LENGTH + 1;
714392cacf2SNikita Kravets }
715392cacf2SNikita Kravets 
716392cacf2SNikita Kravets /*
717392cacf2SNikita Kravets  * Sysfs power_supply subsystem
718392cacf2SNikita Kravets  */
719392cacf2SNikita Kravets 
720392cacf2SNikita Kravets static ssize_t charge_control_threshold_show(u8 offset,
721392cacf2SNikita Kravets 					     struct device *device,
722392cacf2SNikita Kravets 					     struct device_attribute *attr,
723392cacf2SNikita Kravets 					     char *buf)
724392cacf2SNikita Kravets {
725392cacf2SNikita Kravets 	u8 rdata;
726392cacf2SNikita Kravets 	int result;
727392cacf2SNikita Kravets 
728392cacf2SNikita Kravets 	result = ec_read(conf.charge_control.address, &rdata);
729392cacf2SNikita Kravets 	if (result < 0)
730392cacf2SNikita Kravets 		return result;
731392cacf2SNikita Kravets 
732392cacf2SNikita Kravets 	return sysfs_emit(buf, "%i\n", rdata - offset);
733392cacf2SNikita Kravets }
734392cacf2SNikita Kravets 
735392cacf2SNikita Kravets static ssize_t charge_control_threshold_store(u8 offset,
736392cacf2SNikita Kravets 					      struct device *dev,
737392cacf2SNikita Kravets 					      struct device_attribute *attr,
738392cacf2SNikita Kravets 					      const char *buf, size_t count)
739392cacf2SNikita Kravets {
740392cacf2SNikita Kravets 	u8 wdata;
741392cacf2SNikita Kravets 	int result;
742392cacf2SNikita Kravets 
743392cacf2SNikita Kravets 	result = kstrtou8(buf, 10, &wdata);
744392cacf2SNikita Kravets 	if (result < 0)
745392cacf2SNikita Kravets 		return result;
746392cacf2SNikita Kravets 
747392cacf2SNikita Kravets 	wdata += offset;
748392cacf2SNikita Kravets 	if (wdata < conf.charge_control.range_min ||
749392cacf2SNikita Kravets 	    wdata > conf.charge_control.range_max)
750392cacf2SNikita Kravets 		return -EINVAL;
751392cacf2SNikita Kravets 
752392cacf2SNikita Kravets 	result = ec_write(conf.charge_control.address, wdata);
753392cacf2SNikita Kravets 	if (result < 0)
754392cacf2SNikita Kravets 		return result;
755392cacf2SNikita Kravets 
756392cacf2SNikita Kravets 	return count;
757392cacf2SNikita Kravets }
758392cacf2SNikita Kravets 
759392cacf2SNikita Kravets static ssize_t charge_control_start_threshold_show(struct device *device,
760392cacf2SNikita Kravets 						   struct device_attribute *attr,
761392cacf2SNikita Kravets 						   char *buf)
762392cacf2SNikita Kravets {
763392cacf2SNikita Kravets 	return charge_control_threshold_show(conf.charge_control.offset_start,
764392cacf2SNikita Kravets 					     device, attr, buf);
765392cacf2SNikita Kravets }
766392cacf2SNikita Kravets 
767392cacf2SNikita Kravets static ssize_t charge_control_start_threshold_store(struct device *dev,
768392cacf2SNikita Kravets 						    struct device_attribute *attr,
769392cacf2SNikita Kravets 						    const char *buf, size_t count)
770392cacf2SNikita Kravets {
771392cacf2SNikita Kravets 	return charge_control_threshold_store(conf.charge_control.offset_start,
772392cacf2SNikita Kravets 					      dev, attr, buf, count);
773392cacf2SNikita Kravets }
774392cacf2SNikita Kravets 
775392cacf2SNikita Kravets static ssize_t charge_control_end_threshold_show(struct device *device,
776392cacf2SNikita Kravets 						 struct device_attribute *attr,
777392cacf2SNikita Kravets 						 char *buf)
778392cacf2SNikita Kravets {
779392cacf2SNikita Kravets 	return charge_control_threshold_show(conf.charge_control.offset_end,
780392cacf2SNikita Kravets 					     device, attr, buf);
781392cacf2SNikita Kravets }
782392cacf2SNikita Kravets 
783392cacf2SNikita Kravets static ssize_t charge_control_end_threshold_store(struct device *dev,
784392cacf2SNikita Kravets 						  struct device_attribute *attr,
785392cacf2SNikita Kravets 						  const char *buf, size_t count)
786392cacf2SNikita Kravets {
787392cacf2SNikita Kravets 	return charge_control_threshold_store(conf.charge_control.offset_end,
788392cacf2SNikita Kravets 					      dev, attr, buf, count);
789392cacf2SNikita Kravets }
790392cacf2SNikita Kravets 
791392cacf2SNikita Kravets static DEVICE_ATTR_RW(charge_control_start_threshold);
792392cacf2SNikita Kravets static DEVICE_ATTR_RW(charge_control_end_threshold);
793392cacf2SNikita Kravets 
794392cacf2SNikita Kravets static struct attribute *msi_battery_attrs[] = {
795392cacf2SNikita Kravets 	&dev_attr_charge_control_start_threshold.attr,
796392cacf2SNikita Kravets 	&dev_attr_charge_control_end_threshold.attr,
797392cacf2SNikita Kravets 	NULL
798392cacf2SNikita Kravets };
799392cacf2SNikita Kravets 
800392cacf2SNikita Kravets ATTRIBUTE_GROUPS(msi_battery);
801392cacf2SNikita Kravets 
802392cacf2SNikita Kravets static int msi_battery_add(struct power_supply *battery,
803392cacf2SNikita Kravets 			   struct acpi_battery_hook *hook)
804392cacf2SNikita Kravets {
805392cacf2SNikita Kravets 	return device_add_groups(&battery->dev, msi_battery_groups);
806392cacf2SNikita Kravets }
807392cacf2SNikita Kravets 
808392cacf2SNikita Kravets static int msi_battery_remove(struct power_supply *battery,
809392cacf2SNikita Kravets 			      struct acpi_battery_hook *hook)
810392cacf2SNikita Kravets {
811392cacf2SNikita Kravets 	device_remove_groups(&battery->dev, msi_battery_groups);
812392cacf2SNikita Kravets 	return 0;
813392cacf2SNikita Kravets }
814392cacf2SNikita Kravets 
815392cacf2SNikita Kravets static struct acpi_battery_hook battery_hook = {
816392cacf2SNikita Kravets 	.add_battery = msi_battery_add,
817392cacf2SNikita Kravets 	.remove_battery = msi_battery_remove,
818392cacf2SNikita Kravets 	.name = MSI_EC_DRIVER_NAME,
819392cacf2SNikita Kravets };
820392cacf2SNikita Kravets 
821392cacf2SNikita Kravets /*
822392cacf2SNikita Kravets  * Module load/unload
823392cacf2SNikita Kravets  */
824392cacf2SNikita Kravets 
825392cacf2SNikita Kravets static const struct dmi_system_id msi_dmi_table[] __initconst __maybe_unused = {
826392cacf2SNikita Kravets 	{
827392cacf2SNikita Kravets 		.matches = {
828392cacf2SNikita Kravets 			DMI_MATCH(DMI_SYS_VENDOR, "MICRO-STAR INT"),
829392cacf2SNikita Kravets 		},
830392cacf2SNikita Kravets 	},
831392cacf2SNikita Kravets 	{
832392cacf2SNikita Kravets 		.matches = {
833392cacf2SNikita Kravets 			DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"),
834392cacf2SNikita Kravets 		},
835392cacf2SNikita Kravets 	},
836392cacf2SNikita Kravets 	{}
837392cacf2SNikita Kravets };
838392cacf2SNikita Kravets MODULE_DEVICE_TABLE(dmi, msi_dmi_table);
839392cacf2SNikita Kravets 
840392cacf2SNikita Kravets static int __init load_configuration(void)
841392cacf2SNikita Kravets {
842392cacf2SNikita Kravets 	int result;
843392cacf2SNikita Kravets 
844392cacf2SNikita Kravets 	u8 fw_version[MSI_EC_FW_VERSION_LENGTH + 1];
845392cacf2SNikita Kravets 
846392cacf2SNikita Kravets 	/* get firmware version */
847392cacf2SNikita Kravets 	result = ec_get_firmware_version(fw_version);
848392cacf2SNikita Kravets 	if (result < 0)
849392cacf2SNikita Kravets 		return result;
850392cacf2SNikita Kravets 
851392cacf2SNikita Kravets 	/* load the suitable configuration, if exists */
852392cacf2SNikita Kravets 	for (int i = 0; CONFIGS[i]; i++) {
853392cacf2SNikita Kravets 		if (match_string(CONFIGS[i]->allowed_fw, -1, fw_version) != -EINVAL) {
854392cacf2SNikita Kravets 			conf = *CONFIGS[i];
855392cacf2SNikita Kravets 			conf.allowed_fw = NULL;
856392cacf2SNikita Kravets 			return 0;
857392cacf2SNikita Kravets 		}
858392cacf2SNikita Kravets 	}
859392cacf2SNikita Kravets 
860392cacf2SNikita Kravets 	/* config not found */
861392cacf2SNikita Kravets 
862392cacf2SNikita Kravets 	for (int i = 0; i < MSI_EC_FW_VERSION_LENGTH; i++) {
863392cacf2SNikita Kravets 		if (!isgraph(fw_version[i])) {
864392cacf2SNikita Kravets 			pr_warn("Unable to find a valid firmware version!\n");
865392cacf2SNikita Kravets 			return -EOPNOTSUPP;
866392cacf2SNikita Kravets 		}
867392cacf2SNikita Kravets 	}
868392cacf2SNikita Kravets 
869392cacf2SNikita Kravets 	pr_warn("Firmware version is not supported: '%s'\n", fw_version);
870392cacf2SNikita Kravets 	return -EOPNOTSUPP;
871392cacf2SNikita Kravets }
872392cacf2SNikita Kravets 
873392cacf2SNikita Kravets static int __init msi_ec_init(void)
874392cacf2SNikita Kravets {
875392cacf2SNikita Kravets 	int result;
876392cacf2SNikita Kravets 
877392cacf2SNikita Kravets 	result = load_configuration();
878392cacf2SNikita Kravets 	if (result < 0)
879392cacf2SNikita Kravets 		return result;
880392cacf2SNikita Kravets 
881392cacf2SNikita Kravets 	battery_hook_register(&battery_hook);
882392cacf2SNikita Kravets 	return 0;
883392cacf2SNikita Kravets }
884392cacf2SNikita Kravets 
885392cacf2SNikita Kravets static void __exit msi_ec_exit(void)
886392cacf2SNikita Kravets {
887392cacf2SNikita Kravets 	battery_hook_unregister(&battery_hook);
888392cacf2SNikita Kravets }
889392cacf2SNikita Kravets 
890392cacf2SNikita Kravets MODULE_LICENSE("GPL");
891392cacf2SNikita Kravets MODULE_AUTHOR("Jose Angel Pastrana <japp0005@red.ujaen.es>");
892392cacf2SNikita Kravets MODULE_AUTHOR("Aakash Singh <mail@singhaakash.dev>");
893392cacf2SNikita Kravets MODULE_AUTHOR("Nikita Kravets <teackot@gmail.com>");
894392cacf2SNikita Kravets MODULE_DESCRIPTION("MSI Embedded Controller");
895392cacf2SNikita Kravets 
896392cacf2SNikita Kravets module_init(msi_ec_init);
897392cacf2SNikita Kravets module_exit(msi_ec_exit);
898