xref: /openbmc/linux/drivers/platform/x86/msi-ec.c (revision 392cacf2aa10de005e58b68a58012c0c81a100c0)
1*392cacf2SNikita Kravets // SPDX-License-Identifier: GPL-2.0-or-later
2*392cacf2SNikita Kravets 
3*392cacf2SNikita Kravets /*
4*392cacf2SNikita Kravets  * msi-ec: MSI laptops' embedded controller driver.
5*392cacf2SNikita Kravets  *
6*392cacf2SNikita Kravets  * This driver allows various MSI laptops' functionalities to be
7*392cacf2SNikita Kravets  * controlled from userspace.
8*392cacf2SNikita Kravets  *
9*392cacf2SNikita Kravets  * It contains EC memory configurations for different firmware versions
10*392cacf2SNikita Kravets  * and exports battery charge thresholds to userspace.
11*392cacf2SNikita Kravets  *
12*392cacf2SNikita Kravets  * Copyright (C) 2023 Jose Angel Pastrana <japp0005@red.ujaen.es>
13*392cacf2SNikita Kravets  * Copyright (C) 2023 Aakash Singh <mail@singhaakash.dev>
14*392cacf2SNikita Kravets  * Copyright (C) 2023 Nikita Kravets <teackot@gmail.com>
15*392cacf2SNikita Kravets  */
16*392cacf2SNikita Kravets 
17*392cacf2SNikita Kravets #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
18*392cacf2SNikita Kravets 
19*392cacf2SNikita Kravets #include "msi-ec.h"
20*392cacf2SNikita Kravets 
21*392cacf2SNikita Kravets #include <acpi/battery.h>
22*392cacf2SNikita Kravets #include <linux/acpi.h>
23*392cacf2SNikita Kravets #include <linux/init.h>
24*392cacf2SNikita Kravets #include <linux/kernel.h>
25*392cacf2SNikita Kravets #include <linux/module.h>
26*392cacf2SNikita Kravets #include <linux/platform_device.h>
27*392cacf2SNikita Kravets #include <linux/seq_file.h>
28*392cacf2SNikita Kravets #include <linux/string.h>
29*392cacf2SNikita Kravets 
30*392cacf2SNikita Kravets static const char *const SM_ECO_NAME       = "eco";
31*392cacf2SNikita Kravets static const char *const SM_COMFORT_NAME   = "comfort";
32*392cacf2SNikita Kravets static const char *const SM_SPORT_NAME     = "sport";
33*392cacf2SNikita Kravets static const char *const SM_TURBO_NAME     = "turbo";
34*392cacf2SNikita Kravets 
35*392cacf2SNikita Kravets static const char *const FM_AUTO_NAME     = "auto";
36*392cacf2SNikita Kravets static const char *const FM_SILENT_NAME   = "silent";
37*392cacf2SNikita Kravets static const char *const FM_BASIC_NAME    = "basic";
38*392cacf2SNikita Kravets static const char *const FM_ADVANCED_NAME = "advanced";
39*392cacf2SNikita Kravets 
40*392cacf2SNikita Kravets static const char * const ALLOWED_FW_0[] __initconst = {
41*392cacf2SNikita Kravets 	"14C1EMS1.012",
42*392cacf2SNikita Kravets 	"14C1EMS1.101",
43*392cacf2SNikita Kravets 	"14C1EMS1.102",
44*392cacf2SNikita Kravets 	NULL
45*392cacf2SNikita Kravets };
46*392cacf2SNikita Kravets 
47*392cacf2SNikita Kravets static struct msi_ec_conf CONF0 __initdata = {
48*392cacf2SNikita Kravets 	.allowed_fw = ALLOWED_FW_0,
49*392cacf2SNikita Kravets 	.charge_control = {
50*392cacf2SNikita Kravets 		.address      = 0xef,
51*392cacf2SNikita Kravets 		.offset_start = 0x8a,
52*392cacf2SNikita Kravets 		.offset_end   = 0x80,
53*392cacf2SNikita Kravets 		.range_min    = 0x8a,
54*392cacf2SNikita Kravets 		.range_max    = 0xe4,
55*392cacf2SNikita Kravets 	},
56*392cacf2SNikita Kravets 	.webcam = {
57*392cacf2SNikita Kravets 		.address       = 0x2e,
58*392cacf2SNikita Kravets 		.block_address = 0x2f,
59*392cacf2SNikita Kravets 		.bit           = 1,
60*392cacf2SNikita Kravets 	},
61*392cacf2SNikita Kravets 	.fn_super_swap = {
62*392cacf2SNikita Kravets 		.address = 0xbf,
63*392cacf2SNikita Kravets 		.bit     = 4,
64*392cacf2SNikita Kravets 	},
65*392cacf2SNikita Kravets 	.cooler_boost = {
66*392cacf2SNikita Kravets 		.address = 0x98,
67*392cacf2SNikita Kravets 		.bit     = 7,
68*392cacf2SNikita Kravets 	},
69*392cacf2SNikita Kravets 	.shift_mode = {
70*392cacf2SNikita Kravets 		.address = 0xf2,
71*392cacf2SNikita Kravets 		.modes = {
72*392cacf2SNikita Kravets 			{ SM_ECO_NAME,     0xc2 },
73*392cacf2SNikita Kravets 			{ SM_COMFORT_NAME, 0xc1 },
74*392cacf2SNikita Kravets 			{ SM_SPORT_NAME,   0xc0 },
75*392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
76*392cacf2SNikita Kravets 		},
77*392cacf2SNikita Kravets 	},
78*392cacf2SNikita Kravets 	.super_battery = {
79*392cacf2SNikita Kravets 		.address = MSI_EC_ADDR_UNKNOWN, // 0xd5 needs testing
80*392cacf2SNikita Kravets 	},
81*392cacf2SNikita Kravets 	.fan_mode = {
82*392cacf2SNikita Kravets 		.address = 0xf4,
83*392cacf2SNikita Kravets 		.modes = {
84*392cacf2SNikita Kravets 			{ FM_AUTO_NAME,     0x0d },
85*392cacf2SNikita Kravets 			{ FM_SILENT_NAME,   0x1d },
86*392cacf2SNikita Kravets 			{ FM_BASIC_NAME,    0x4d },
87*392cacf2SNikita Kravets 			{ FM_ADVANCED_NAME, 0x8d },
88*392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
89*392cacf2SNikita Kravets 		},
90*392cacf2SNikita Kravets 	},
91*392cacf2SNikita Kravets 	.cpu = {
92*392cacf2SNikita Kravets 		.rt_temp_address       = 0x68,
93*392cacf2SNikita Kravets 		.rt_fan_speed_address  = 0x71,
94*392cacf2SNikita Kravets 		.rt_fan_speed_base_min = 0x19,
95*392cacf2SNikita Kravets 		.rt_fan_speed_base_max = 0x37,
96*392cacf2SNikita Kravets 		.bs_fan_speed_address  = 0x89,
97*392cacf2SNikita Kravets 		.bs_fan_speed_base_min = 0x00,
98*392cacf2SNikita Kravets 		.bs_fan_speed_base_max = 0x0f,
99*392cacf2SNikita Kravets 	},
100*392cacf2SNikita Kravets 	.gpu = {
101*392cacf2SNikita Kravets 		.rt_temp_address      = 0x80,
102*392cacf2SNikita Kravets 		.rt_fan_speed_address = 0x89,
103*392cacf2SNikita Kravets 	},
104*392cacf2SNikita Kravets 	.leds = {
105*392cacf2SNikita Kravets 		.micmute_led_address = 0x2b,
106*392cacf2SNikita Kravets 		.mute_led_address    = 0x2c,
107*392cacf2SNikita Kravets 		.bit                 = 2,
108*392cacf2SNikita Kravets 	},
109*392cacf2SNikita Kravets 	.kbd_bl = {
110*392cacf2SNikita Kravets 		.bl_mode_address  = 0x2c, // ?
111*392cacf2SNikita Kravets 		.bl_modes         = { 0x00, 0x08 }, // ?
112*392cacf2SNikita Kravets 		.max_mode         = 1, // ?
113*392cacf2SNikita Kravets 		.bl_state_address = 0xf3,
114*392cacf2SNikita Kravets 		.state_base_value = 0x80,
115*392cacf2SNikita Kravets 		.max_state        = 3,
116*392cacf2SNikita Kravets 	},
117*392cacf2SNikita Kravets };
118*392cacf2SNikita Kravets 
119*392cacf2SNikita Kravets static const char * const ALLOWED_FW_1[] __initconst = {
120*392cacf2SNikita Kravets 	"17F2EMS1.103",
121*392cacf2SNikita Kravets 	"17F2EMS1.104",
122*392cacf2SNikita Kravets 	"17F2EMS1.106",
123*392cacf2SNikita Kravets 	"17F2EMS1.107",
124*392cacf2SNikita Kravets 	NULL
125*392cacf2SNikita Kravets };
126*392cacf2SNikita Kravets 
127*392cacf2SNikita Kravets static struct msi_ec_conf CONF1 __initdata = {
128*392cacf2SNikita Kravets 	.allowed_fw = ALLOWED_FW_1,
129*392cacf2SNikita Kravets 	.charge_control = {
130*392cacf2SNikita Kravets 		.address      = 0xef,
131*392cacf2SNikita Kravets 		.offset_start = 0x8a,
132*392cacf2SNikita Kravets 		.offset_end   = 0x80,
133*392cacf2SNikita Kravets 		.range_min    = 0x8a,
134*392cacf2SNikita Kravets 		.range_max    = 0xe4,
135*392cacf2SNikita Kravets 	},
136*392cacf2SNikita Kravets 	.webcam = {
137*392cacf2SNikita Kravets 		.address       = 0x2e,
138*392cacf2SNikita Kravets 		.block_address = 0x2f,
139*392cacf2SNikita Kravets 		.bit           = 1,
140*392cacf2SNikita Kravets 	},
141*392cacf2SNikita Kravets 	.fn_super_swap = {
142*392cacf2SNikita Kravets 		.address = 0xbf,
143*392cacf2SNikita Kravets 		.bit     = 4,
144*392cacf2SNikita Kravets 	},
145*392cacf2SNikita Kravets 	.cooler_boost = {
146*392cacf2SNikita Kravets 		.address = 0x98,
147*392cacf2SNikita Kravets 		.bit     = 7,
148*392cacf2SNikita Kravets 	},
149*392cacf2SNikita Kravets 	.shift_mode = {
150*392cacf2SNikita Kravets 		.address = 0xf2,
151*392cacf2SNikita Kravets 		.modes = {
152*392cacf2SNikita Kravets 			{ SM_ECO_NAME,     0xc2 },
153*392cacf2SNikita Kravets 			{ SM_COMFORT_NAME, 0xc1 },
154*392cacf2SNikita Kravets 			{ SM_SPORT_NAME,   0xc0 },
155*392cacf2SNikita Kravets 			{ SM_TURBO_NAME,   0xc4 },
156*392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
157*392cacf2SNikita Kravets 		},
158*392cacf2SNikita Kravets 	},
159*392cacf2SNikita Kravets 	.super_battery = {
160*392cacf2SNikita Kravets 		.address = MSI_EC_ADDR_UNKNOWN,
161*392cacf2SNikita Kravets 	},
162*392cacf2SNikita Kravets 	.fan_mode = {
163*392cacf2SNikita Kravets 		.address = 0xf4,
164*392cacf2SNikita Kravets 		.modes = {
165*392cacf2SNikita Kravets 			{ FM_AUTO_NAME,     0x0d },
166*392cacf2SNikita Kravets 			{ FM_BASIC_NAME,    0x4d },
167*392cacf2SNikita Kravets 			{ FM_ADVANCED_NAME, 0x8d },
168*392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
169*392cacf2SNikita Kravets 		},
170*392cacf2SNikita Kravets 	},
171*392cacf2SNikita Kravets 	.cpu = {
172*392cacf2SNikita Kravets 		.rt_temp_address       = 0x68,
173*392cacf2SNikita Kravets 		.rt_fan_speed_address  = 0x71,
174*392cacf2SNikita Kravets 		.rt_fan_speed_base_min = 0x19,
175*392cacf2SNikita Kravets 		.rt_fan_speed_base_max = 0x37,
176*392cacf2SNikita Kravets 		.bs_fan_speed_address  = 0x89,
177*392cacf2SNikita Kravets 		.bs_fan_speed_base_min = 0x00,
178*392cacf2SNikita Kravets 		.bs_fan_speed_base_max = 0x0f,
179*392cacf2SNikita Kravets 	},
180*392cacf2SNikita Kravets 	.gpu = {
181*392cacf2SNikita Kravets 		.rt_temp_address      = 0x80,
182*392cacf2SNikita Kravets 		.rt_fan_speed_address = 0x89,
183*392cacf2SNikita Kravets 	},
184*392cacf2SNikita Kravets 	.leds = {
185*392cacf2SNikita Kravets 		.micmute_led_address = 0x2b,
186*392cacf2SNikita Kravets 		.mute_led_address    = 0x2c,
187*392cacf2SNikita Kravets 		.bit                 = 2,
188*392cacf2SNikita Kravets 	},
189*392cacf2SNikita Kravets 	.kbd_bl = {
190*392cacf2SNikita Kravets 		.bl_mode_address  = 0x2c, // ?
191*392cacf2SNikita Kravets 		.bl_modes         = { 0x00, 0x08 }, // ?
192*392cacf2SNikita Kravets 		.max_mode         = 1, // ?
193*392cacf2SNikita Kravets 		.bl_state_address = 0xf3,
194*392cacf2SNikita Kravets 		.state_base_value = 0x80,
195*392cacf2SNikita Kravets 		.max_state        = 3,
196*392cacf2SNikita Kravets 	},
197*392cacf2SNikita Kravets };
198*392cacf2SNikita Kravets 
199*392cacf2SNikita Kravets static const char * const ALLOWED_FW_2[] __initconst = {
200*392cacf2SNikita Kravets 	"1552EMS1.118",
201*392cacf2SNikita Kravets 	NULL
202*392cacf2SNikita Kravets };
203*392cacf2SNikita Kravets 
204*392cacf2SNikita Kravets static struct msi_ec_conf CONF2 __initdata = {
205*392cacf2SNikita Kravets 	.allowed_fw = ALLOWED_FW_2,
206*392cacf2SNikita Kravets 	.charge_control = {
207*392cacf2SNikita Kravets 		.address      = 0xd7,
208*392cacf2SNikita Kravets 		.offset_start = 0x8a,
209*392cacf2SNikita Kravets 		.offset_end   = 0x80,
210*392cacf2SNikita Kravets 		.range_min    = 0x8a,
211*392cacf2SNikita Kravets 		.range_max    = 0xe4,
212*392cacf2SNikita Kravets 	},
213*392cacf2SNikita Kravets 	.webcam = {
214*392cacf2SNikita Kravets 		.address       = 0x2e,
215*392cacf2SNikita Kravets 		.block_address = 0x2f,
216*392cacf2SNikita Kravets 		.bit           = 1,
217*392cacf2SNikita Kravets 	},
218*392cacf2SNikita Kravets 	.fn_super_swap = {
219*392cacf2SNikita Kravets 		.address = 0xe8,
220*392cacf2SNikita Kravets 		.bit     = 4,
221*392cacf2SNikita Kravets 	},
222*392cacf2SNikita Kravets 	.cooler_boost = {
223*392cacf2SNikita Kravets 		.address = 0x98,
224*392cacf2SNikita Kravets 		.bit     = 7,
225*392cacf2SNikita Kravets 	},
226*392cacf2SNikita Kravets 	.shift_mode = {
227*392cacf2SNikita Kravets 		.address = 0xf2,
228*392cacf2SNikita Kravets 		.modes = {
229*392cacf2SNikita Kravets 			{ SM_ECO_NAME,     0xc2 },
230*392cacf2SNikita Kravets 			{ SM_COMFORT_NAME, 0xc1 },
231*392cacf2SNikita Kravets 			{ SM_SPORT_NAME,   0xc0 },
232*392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
233*392cacf2SNikita Kravets 		},
234*392cacf2SNikita Kravets 	},
235*392cacf2SNikita Kravets 	.super_battery = {
236*392cacf2SNikita Kravets 		.address = 0xeb,
237*392cacf2SNikita Kravets 		.mask    = 0x0f,
238*392cacf2SNikita Kravets 	},
239*392cacf2SNikita Kravets 	.fan_mode = {
240*392cacf2SNikita Kravets 		.address = 0xd4,
241*392cacf2SNikita Kravets 		.modes = {
242*392cacf2SNikita Kravets 			{ FM_AUTO_NAME,     0x0d },
243*392cacf2SNikita Kravets 			{ FM_SILENT_NAME,   0x1d },
244*392cacf2SNikita Kravets 			{ FM_BASIC_NAME,    0x4d },
245*392cacf2SNikita Kravets 			{ FM_ADVANCED_NAME, 0x8d },
246*392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
247*392cacf2SNikita Kravets 		},
248*392cacf2SNikita Kravets 	},
249*392cacf2SNikita Kravets 	.cpu = {
250*392cacf2SNikita Kravets 		.rt_temp_address       = 0x68,
251*392cacf2SNikita Kravets 		.rt_fan_speed_address  = 0x71,
252*392cacf2SNikita Kravets 		.rt_fan_speed_base_min = 0x19,
253*392cacf2SNikita Kravets 		.rt_fan_speed_base_max = 0x37,
254*392cacf2SNikita Kravets 		.bs_fan_speed_address  = 0x89,
255*392cacf2SNikita Kravets 		.bs_fan_speed_base_min = 0x00,
256*392cacf2SNikita Kravets 		.bs_fan_speed_base_max = 0x0f,
257*392cacf2SNikita Kravets 	},
258*392cacf2SNikita Kravets 	.gpu = {
259*392cacf2SNikita Kravets 		.rt_temp_address      = 0x80,
260*392cacf2SNikita Kravets 		.rt_fan_speed_address = 0x89,
261*392cacf2SNikita Kravets 	},
262*392cacf2SNikita Kravets 	.leds = {
263*392cacf2SNikita Kravets 		.micmute_led_address = 0x2c,
264*392cacf2SNikita Kravets 		.mute_led_address    = 0x2d,
265*392cacf2SNikita Kravets 		.bit                 = 1,
266*392cacf2SNikita Kravets 	},
267*392cacf2SNikita Kravets 	.kbd_bl = {
268*392cacf2SNikita Kravets 		.bl_mode_address  = 0x2c, // ?
269*392cacf2SNikita Kravets 		.bl_modes         = { 0x00, 0x08 }, // ?
270*392cacf2SNikita Kravets 		.max_mode         = 1, // ?
271*392cacf2SNikita Kravets 		.bl_state_address = 0xd3,
272*392cacf2SNikita Kravets 		.state_base_value = 0x80,
273*392cacf2SNikita Kravets 		.max_state        = 3,
274*392cacf2SNikita Kravets 	},
275*392cacf2SNikita Kravets };
276*392cacf2SNikita Kravets 
277*392cacf2SNikita Kravets static const char * const ALLOWED_FW_3[] __initconst = {
278*392cacf2SNikita Kravets 	"1592EMS1.111",
279*392cacf2SNikita Kravets 	"E1592IMS.10C",
280*392cacf2SNikita Kravets 	NULL
281*392cacf2SNikita Kravets };
282*392cacf2SNikita Kravets 
283*392cacf2SNikita Kravets static struct msi_ec_conf CONF3 __initdata = {
284*392cacf2SNikita Kravets 	.allowed_fw = ALLOWED_FW_3,
285*392cacf2SNikita Kravets 	.charge_control = {
286*392cacf2SNikita Kravets 		.address      = 0xef,
287*392cacf2SNikita Kravets 		.offset_start = 0x8a,
288*392cacf2SNikita Kravets 		.offset_end   = 0x80,
289*392cacf2SNikita Kravets 		.range_min    = 0x8a,
290*392cacf2SNikita Kravets 		.range_max    = 0xe4,
291*392cacf2SNikita Kravets 	},
292*392cacf2SNikita Kravets 	.webcam = {
293*392cacf2SNikita Kravets 		.address       = 0x2e,
294*392cacf2SNikita Kravets 		.block_address = 0x2f,
295*392cacf2SNikita Kravets 		.bit           = 1,
296*392cacf2SNikita Kravets 	},
297*392cacf2SNikita Kravets 	.fn_super_swap = {
298*392cacf2SNikita Kravets 		.address = 0xe8,
299*392cacf2SNikita Kravets 		.bit     = 4,
300*392cacf2SNikita Kravets 	},
301*392cacf2SNikita Kravets 	.cooler_boost = {
302*392cacf2SNikita Kravets 		.address = 0x98,
303*392cacf2SNikita Kravets 		.bit     = 7,
304*392cacf2SNikita Kravets 	},
305*392cacf2SNikita Kravets 	.shift_mode = {
306*392cacf2SNikita Kravets 		.address = 0xd2,
307*392cacf2SNikita Kravets 		.modes = {
308*392cacf2SNikita Kravets 			{ SM_ECO_NAME,     0xc2 },
309*392cacf2SNikita Kravets 			{ SM_COMFORT_NAME, 0xc1 },
310*392cacf2SNikita Kravets 			{ SM_SPORT_NAME,   0xc0 },
311*392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
312*392cacf2SNikita Kravets 		},
313*392cacf2SNikita Kravets 	},
314*392cacf2SNikita Kravets 	.super_battery = {
315*392cacf2SNikita Kravets 		.address = 0xeb,
316*392cacf2SNikita Kravets 		.mask    = 0x0f,
317*392cacf2SNikita Kravets 	},
318*392cacf2SNikita Kravets 	.fan_mode = {
319*392cacf2SNikita Kravets 		.address = 0xd4,
320*392cacf2SNikita Kravets 		.modes = {
321*392cacf2SNikita Kravets 			{ FM_AUTO_NAME,     0x0d },
322*392cacf2SNikita Kravets 			{ FM_SILENT_NAME,   0x1d },
323*392cacf2SNikita Kravets 			{ FM_BASIC_NAME,    0x4d },
324*392cacf2SNikita Kravets 			{ FM_ADVANCED_NAME, 0x8d },
325*392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
326*392cacf2SNikita Kravets 		},
327*392cacf2SNikita Kravets 	},
328*392cacf2SNikita Kravets 	.cpu = {
329*392cacf2SNikita Kravets 		.rt_temp_address       = 0x68,
330*392cacf2SNikita Kravets 		.rt_fan_speed_address  = 0xc9,
331*392cacf2SNikita Kravets 		.rt_fan_speed_base_min = 0x19,
332*392cacf2SNikita Kravets 		.rt_fan_speed_base_max = 0x37,
333*392cacf2SNikita Kravets 		.bs_fan_speed_address  = 0x89, // ?
334*392cacf2SNikita Kravets 		.bs_fan_speed_base_min = 0x00,
335*392cacf2SNikita Kravets 		.bs_fan_speed_base_max = 0x0f,
336*392cacf2SNikita Kravets 	},
337*392cacf2SNikita Kravets 	.gpu = {
338*392cacf2SNikita Kravets 		.rt_temp_address      = 0x80,
339*392cacf2SNikita Kravets 		.rt_fan_speed_address = 0x89,
340*392cacf2SNikita Kravets 	},
341*392cacf2SNikita Kravets 	.leds = {
342*392cacf2SNikita Kravets 		.micmute_led_address = 0x2b,
343*392cacf2SNikita Kravets 		.mute_led_address    = 0x2c,
344*392cacf2SNikita Kravets 		.bit                 = 1,
345*392cacf2SNikita Kravets 	},
346*392cacf2SNikita Kravets 	.kbd_bl = {
347*392cacf2SNikita Kravets 		.bl_mode_address  = 0x2c, // ?
348*392cacf2SNikita Kravets 		.bl_modes         = { 0x00, 0x08 }, // ?
349*392cacf2SNikita Kravets 		.max_mode         = 1, // ?
350*392cacf2SNikita Kravets 		.bl_state_address = 0xd3,
351*392cacf2SNikita Kravets 		.state_base_value = 0x80,
352*392cacf2SNikita Kravets 		.max_state        = 3,
353*392cacf2SNikita Kravets 	},
354*392cacf2SNikita Kravets };
355*392cacf2SNikita Kravets 
356*392cacf2SNikita Kravets static const char * const ALLOWED_FW_4[] __initconst = {
357*392cacf2SNikita Kravets 	"16V4EMS1.114",
358*392cacf2SNikita Kravets 	NULL
359*392cacf2SNikita Kravets };
360*392cacf2SNikita Kravets 
361*392cacf2SNikita Kravets static struct msi_ec_conf CONF4 __initdata = {
362*392cacf2SNikita Kravets 	.allowed_fw = ALLOWED_FW_4,
363*392cacf2SNikita Kravets 	.charge_control = {
364*392cacf2SNikita Kravets 		.address      = 0xd7,
365*392cacf2SNikita Kravets 		.offset_start = 0x8a,
366*392cacf2SNikita Kravets 		.offset_end   = 0x80,
367*392cacf2SNikita Kravets 		.range_min    = 0x8a,
368*392cacf2SNikita Kravets 		.range_max    = 0xe4,
369*392cacf2SNikita Kravets 	},
370*392cacf2SNikita Kravets 	.webcam = {
371*392cacf2SNikita Kravets 		.address       = 0x2e,
372*392cacf2SNikita Kravets 		.block_address = 0x2f,
373*392cacf2SNikita Kravets 		.bit           = 1,
374*392cacf2SNikita Kravets 	},
375*392cacf2SNikita Kravets 	.fn_super_swap = {
376*392cacf2SNikita Kravets 		.address = MSI_EC_ADDR_UNKNOWN, // supported, but unknown
377*392cacf2SNikita Kravets 		.bit     = 4,
378*392cacf2SNikita Kravets 	},
379*392cacf2SNikita Kravets 	.cooler_boost = {
380*392cacf2SNikita Kravets 		.address = 0x98,
381*392cacf2SNikita Kravets 		.bit     = 7,
382*392cacf2SNikita Kravets 	},
383*392cacf2SNikita Kravets 	.shift_mode = {
384*392cacf2SNikita Kravets 		.address = 0xd2,
385*392cacf2SNikita Kravets 		.modes = {
386*392cacf2SNikita Kravets 			{ SM_ECO_NAME,     0xc2 },
387*392cacf2SNikita Kravets 			{ SM_COMFORT_NAME, 0xc1 },
388*392cacf2SNikita Kravets 			{ SM_SPORT_NAME,   0xc0 },
389*392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
390*392cacf2SNikita Kravets 		},
391*392cacf2SNikita Kravets 	},
392*392cacf2SNikita Kravets 	.super_battery = { // may be supported, but address is unknown
393*392cacf2SNikita Kravets 		.address = MSI_EC_ADDR_UNKNOWN,
394*392cacf2SNikita Kravets 		.mask    = 0x0f,
395*392cacf2SNikita Kravets 	},
396*392cacf2SNikita Kravets 	.fan_mode = {
397*392cacf2SNikita Kravets 		.address = 0xd4,
398*392cacf2SNikita Kravets 		.modes = {
399*392cacf2SNikita Kravets 			{ FM_AUTO_NAME,     0x0d },
400*392cacf2SNikita Kravets 			{ FM_SILENT_NAME,   0x1d },
401*392cacf2SNikita Kravets 			{ FM_ADVANCED_NAME, 0x8d },
402*392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
403*392cacf2SNikita Kravets 		},
404*392cacf2SNikita Kravets 	},
405*392cacf2SNikita Kravets 	.cpu = {
406*392cacf2SNikita Kravets 		.rt_temp_address       = 0x68, // needs testing
407*392cacf2SNikita Kravets 		.rt_fan_speed_address  = 0x71, // needs testing
408*392cacf2SNikita Kravets 		.rt_fan_speed_base_min = 0x19,
409*392cacf2SNikita Kravets 		.rt_fan_speed_base_max = 0x37,
410*392cacf2SNikita Kravets 		.bs_fan_speed_address  = MSI_EC_ADDR_UNKNOWN,
411*392cacf2SNikita Kravets 		.bs_fan_speed_base_min = 0x00,
412*392cacf2SNikita Kravets 		.bs_fan_speed_base_max = 0x0f,
413*392cacf2SNikita Kravets 	},
414*392cacf2SNikita Kravets 	.gpu = {
415*392cacf2SNikita Kravets 		.rt_temp_address      = 0x80,
416*392cacf2SNikita Kravets 		.rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
417*392cacf2SNikita Kravets 	},
418*392cacf2SNikita Kravets 	.leds = {
419*392cacf2SNikita Kravets 		.micmute_led_address = MSI_EC_ADDR_UNKNOWN,
420*392cacf2SNikita Kravets 		.mute_led_address    = MSI_EC_ADDR_UNKNOWN,
421*392cacf2SNikita Kravets 		.bit                 = 1,
422*392cacf2SNikita Kravets 	},
423*392cacf2SNikita Kravets 	.kbd_bl = {
424*392cacf2SNikita Kravets 		.bl_mode_address  = MSI_EC_ADDR_UNKNOWN, // ?
425*392cacf2SNikita Kravets 		.bl_modes         = { 0x00, 0x08 }, // ?
426*392cacf2SNikita Kravets 		.max_mode         = 1, // ?
427*392cacf2SNikita Kravets 		.bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xd3, not functional
428*392cacf2SNikita Kravets 		.state_base_value = 0x80,
429*392cacf2SNikita Kravets 		.max_state        = 3,
430*392cacf2SNikita Kravets 	},
431*392cacf2SNikita Kravets };
432*392cacf2SNikita Kravets 
433*392cacf2SNikita Kravets static const char * const ALLOWED_FW_5[] __initconst = {
434*392cacf2SNikita Kravets 	"158LEMS1.103",
435*392cacf2SNikita Kravets 	"158LEMS1.105",
436*392cacf2SNikita Kravets 	"158LEMS1.106",
437*392cacf2SNikita Kravets 	NULL
438*392cacf2SNikita Kravets };
439*392cacf2SNikita Kravets 
440*392cacf2SNikita Kravets static struct msi_ec_conf CONF5 __initdata = {
441*392cacf2SNikita Kravets 	.allowed_fw = ALLOWED_FW_5,
442*392cacf2SNikita Kravets 	.charge_control = {
443*392cacf2SNikita Kravets 		.address      = 0xef,
444*392cacf2SNikita Kravets 		.offset_start = 0x8a,
445*392cacf2SNikita Kravets 		.offset_end   = 0x80,
446*392cacf2SNikita Kravets 		.range_min    = 0x8a,
447*392cacf2SNikita Kravets 		.range_max    = 0xe4,
448*392cacf2SNikita Kravets 	},
449*392cacf2SNikita Kravets 	.webcam = {
450*392cacf2SNikita Kravets 		.address       = 0x2e,
451*392cacf2SNikita Kravets 		.block_address = 0x2f,
452*392cacf2SNikita Kravets 		.bit           = 1,
453*392cacf2SNikita Kravets 	},
454*392cacf2SNikita Kravets 	.fn_super_swap = { // todo: reverse
455*392cacf2SNikita Kravets 		.address = 0xbf,
456*392cacf2SNikita Kravets 		.bit     = 4,
457*392cacf2SNikita Kravets 	},
458*392cacf2SNikita Kravets 	.cooler_boost = {
459*392cacf2SNikita Kravets 		.address = 0x98,
460*392cacf2SNikita Kravets 		.bit     = 7,
461*392cacf2SNikita Kravets 	},
462*392cacf2SNikita Kravets 	.shift_mode = {
463*392cacf2SNikita Kravets 		.address = 0xf2,
464*392cacf2SNikita Kravets 		.modes = {
465*392cacf2SNikita Kravets 			{ SM_ECO_NAME,     0xc2 },
466*392cacf2SNikita Kravets 			{ SM_COMFORT_NAME, 0xc1 },
467*392cacf2SNikita Kravets 			{ SM_TURBO_NAME,   0xc4 },
468*392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
469*392cacf2SNikita Kravets 		},
470*392cacf2SNikita Kravets 	},
471*392cacf2SNikita Kravets 	.super_battery = { // unsupported?
472*392cacf2SNikita Kravets 		.address = MSI_EC_ADDR_UNKNOWN,
473*392cacf2SNikita Kravets 		.mask    = 0x0f,
474*392cacf2SNikita Kravets 	},
475*392cacf2SNikita Kravets 	.fan_mode = {
476*392cacf2SNikita Kravets 		.address = 0xf4,
477*392cacf2SNikita Kravets 		.modes = {
478*392cacf2SNikita Kravets 			{ FM_AUTO_NAME,     0x0d },
479*392cacf2SNikita Kravets 			{ FM_SILENT_NAME,   0x1d },
480*392cacf2SNikita Kravets 			{ FM_ADVANCED_NAME, 0x8d },
481*392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
482*392cacf2SNikita Kravets 		},
483*392cacf2SNikita Kravets 	},
484*392cacf2SNikita Kravets 	.cpu = {
485*392cacf2SNikita Kravets 		.rt_temp_address       = 0x68, // needs testing
486*392cacf2SNikita Kravets 		.rt_fan_speed_address  = 0x71, // needs testing
487*392cacf2SNikita Kravets 		.rt_fan_speed_base_min = 0x19,
488*392cacf2SNikita Kravets 		.rt_fan_speed_base_max = 0x37,
489*392cacf2SNikita Kravets 		.bs_fan_speed_address  = MSI_EC_ADDR_UNSUPP,
490*392cacf2SNikita Kravets 		.bs_fan_speed_base_min = 0x00,
491*392cacf2SNikita Kravets 		.bs_fan_speed_base_max = 0x0f,
492*392cacf2SNikita Kravets 	},
493*392cacf2SNikita Kravets 	.gpu = {
494*392cacf2SNikita Kravets 		.rt_temp_address      = MSI_EC_ADDR_UNKNOWN,
495*392cacf2SNikita Kravets 		.rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
496*392cacf2SNikita Kravets 	},
497*392cacf2SNikita Kravets 	.leds = {
498*392cacf2SNikita Kravets 		.micmute_led_address = 0x2b,
499*392cacf2SNikita Kravets 		.mute_led_address    = 0x2c,
500*392cacf2SNikita Kravets 		.bit                 = 2,
501*392cacf2SNikita Kravets 	},
502*392cacf2SNikita Kravets 	.kbd_bl = {
503*392cacf2SNikita Kravets 		.bl_mode_address  = MSI_EC_ADDR_UNKNOWN, // ?
504*392cacf2SNikita Kravets 		.bl_modes         = { 0x00, 0x08 }, // ?
505*392cacf2SNikita Kravets 		.max_mode         = 1, // ?
506*392cacf2SNikita Kravets 		.bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xf3, not functional
507*392cacf2SNikita Kravets 		.state_base_value = 0x80,
508*392cacf2SNikita Kravets 		.max_state        = 3,
509*392cacf2SNikita Kravets 	},
510*392cacf2SNikita Kravets };
511*392cacf2SNikita Kravets 
512*392cacf2SNikita Kravets static const char * const ALLOWED_FW_6[] __initconst = {
513*392cacf2SNikita Kravets 	"1542EMS1.102",
514*392cacf2SNikita Kravets 	"1542EMS1.104",
515*392cacf2SNikita Kravets 	NULL
516*392cacf2SNikita Kravets };
517*392cacf2SNikita Kravets 
518*392cacf2SNikita Kravets static struct msi_ec_conf CONF6 __initdata = {
519*392cacf2SNikita Kravets 	.allowed_fw = ALLOWED_FW_6,
520*392cacf2SNikita Kravets 	.charge_control = {
521*392cacf2SNikita Kravets 		.address      = 0xef,
522*392cacf2SNikita Kravets 		.offset_start = 0x8a,
523*392cacf2SNikita Kravets 		.offset_end   = 0x80,
524*392cacf2SNikita Kravets 		.range_min    = 0x8a,
525*392cacf2SNikita Kravets 		.range_max    = 0xe4,
526*392cacf2SNikita Kravets 	},
527*392cacf2SNikita Kravets 	.webcam = {
528*392cacf2SNikita Kravets 		.address       = 0x2e,
529*392cacf2SNikita Kravets 		.block_address = MSI_EC_ADDR_UNSUPP,
530*392cacf2SNikita Kravets 		.bit           = 1,
531*392cacf2SNikita Kravets 	},
532*392cacf2SNikita Kravets 	.fn_super_swap = {
533*392cacf2SNikita Kravets 		.address = 0xbf, // todo: reverse
534*392cacf2SNikita Kravets 		.bit     = 4,
535*392cacf2SNikita Kravets 	},
536*392cacf2SNikita Kravets 	.cooler_boost = {
537*392cacf2SNikita Kravets 		.address = 0x98,
538*392cacf2SNikita Kravets 		.bit     = 7,
539*392cacf2SNikita Kravets 	},
540*392cacf2SNikita Kravets 	.shift_mode = {
541*392cacf2SNikita Kravets 		.address = 0xf2,
542*392cacf2SNikita Kravets 		.modes = {
543*392cacf2SNikita Kravets 			{ SM_ECO_NAME,     0xc2 },
544*392cacf2SNikita Kravets 			{ SM_COMFORT_NAME, 0xc1 },
545*392cacf2SNikita Kravets 			{ SM_SPORT_NAME,   0xc0 },
546*392cacf2SNikita Kravets 			{ SM_TURBO_NAME,   0xc4 },
547*392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
548*392cacf2SNikita Kravets 		},
549*392cacf2SNikita Kravets 	},
550*392cacf2SNikita Kravets 	.super_battery = {
551*392cacf2SNikita Kravets 		.address = 0xd5,
552*392cacf2SNikita Kravets 		.mask    = 0x0f,
553*392cacf2SNikita Kravets 	},
554*392cacf2SNikita Kravets 	.fan_mode = {
555*392cacf2SNikita Kravets 		.address = 0xf4,
556*392cacf2SNikita Kravets 		.modes = {
557*392cacf2SNikita Kravets 			{ FM_AUTO_NAME,     0x0d },
558*392cacf2SNikita Kravets 			{ FM_SILENT_NAME,   0x1d },
559*392cacf2SNikita Kravets 			{ FM_ADVANCED_NAME, 0x8d },
560*392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
561*392cacf2SNikita Kravets 		},
562*392cacf2SNikita Kravets 	},
563*392cacf2SNikita Kravets 	.cpu = {
564*392cacf2SNikita Kravets 		.rt_temp_address       = 0x68,
565*392cacf2SNikita Kravets 		.rt_fan_speed_address  = 0xc9,
566*392cacf2SNikita Kravets 		.rt_fan_speed_base_min = 0x19,
567*392cacf2SNikita Kravets 		.rt_fan_speed_base_max = 0x37,
568*392cacf2SNikita Kravets 		.bs_fan_speed_address  = MSI_EC_ADDR_UNSUPP,
569*392cacf2SNikita Kravets 		.bs_fan_speed_base_min = 0x00,
570*392cacf2SNikita Kravets 		.bs_fan_speed_base_max = 0x0f,
571*392cacf2SNikita Kravets 	},
572*392cacf2SNikita Kravets 	.gpu = {
573*392cacf2SNikita Kravets 		.rt_temp_address      = 0x80,
574*392cacf2SNikita Kravets 		.rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
575*392cacf2SNikita Kravets 	},
576*392cacf2SNikita Kravets 	.leds = {
577*392cacf2SNikita Kravets 		.micmute_led_address = MSI_EC_ADDR_UNSUPP,
578*392cacf2SNikita Kravets 		.mute_led_address    = MSI_EC_ADDR_UNSUPP,
579*392cacf2SNikita Kravets 		.bit                 = 2,
580*392cacf2SNikita Kravets 	},
581*392cacf2SNikita Kravets 	.kbd_bl = {
582*392cacf2SNikita Kravets 		.bl_mode_address  = MSI_EC_ADDR_UNKNOWN, // ?
583*392cacf2SNikita Kravets 		.bl_modes         = { 0x00, 0x08 }, // ?
584*392cacf2SNikita Kravets 		.max_mode         = 1, // ?
585*392cacf2SNikita Kravets 		.bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xf3, not functional
586*392cacf2SNikita Kravets 		.state_base_value = 0x80,
587*392cacf2SNikita Kravets 		.max_state        = 3,
588*392cacf2SNikita Kravets 	},
589*392cacf2SNikita Kravets };
590*392cacf2SNikita Kravets 
591*392cacf2SNikita Kravets static const char * const ALLOWED_FW_7[] __initconst = {
592*392cacf2SNikita Kravets 	"17FKEMS1.108",
593*392cacf2SNikita Kravets 	"17FKEMS1.109",
594*392cacf2SNikita Kravets 	"17FKEMS1.10A",
595*392cacf2SNikita Kravets 	NULL
596*392cacf2SNikita Kravets };
597*392cacf2SNikita Kravets 
598*392cacf2SNikita Kravets static struct msi_ec_conf CONF7 __initdata = {
599*392cacf2SNikita Kravets 	.allowed_fw = ALLOWED_FW_7,
600*392cacf2SNikita Kravets 	.charge_control = {
601*392cacf2SNikita Kravets 		.address      = 0xef,
602*392cacf2SNikita Kravets 		.offset_start = 0x8a,
603*392cacf2SNikita Kravets 		.offset_end   = 0x80,
604*392cacf2SNikita Kravets 		.range_min    = 0x8a,
605*392cacf2SNikita Kravets 		.range_max    = 0xe4,
606*392cacf2SNikita Kravets 	},
607*392cacf2SNikita Kravets 	.webcam = {
608*392cacf2SNikita Kravets 		.address       = 0x2e,
609*392cacf2SNikita Kravets 		.block_address = MSI_EC_ADDR_UNSUPP,
610*392cacf2SNikita Kravets 		.bit           = 1,
611*392cacf2SNikita Kravets 	},
612*392cacf2SNikita Kravets 	.fn_super_swap = {
613*392cacf2SNikita Kravets 		.address = 0xbf, // needs testing
614*392cacf2SNikita Kravets 		.bit     = 4,
615*392cacf2SNikita Kravets 	},
616*392cacf2SNikita Kravets 	.cooler_boost = {
617*392cacf2SNikita Kravets 		.address = 0x98,
618*392cacf2SNikita Kravets 		.bit     = 7,
619*392cacf2SNikita Kravets 	},
620*392cacf2SNikita Kravets 	.shift_mode = {
621*392cacf2SNikita Kravets 		.address = 0xf2,
622*392cacf2SNikita Kravets 		.modes = {
623*392cacf2SNikita Kravets 			{ SM_ECO_NAME,     0xc2 },
624*392cacf2SNikita Kravets 			{ SM_COMFORT_NAME, 0xc1 },
625*392cacf2SNikita Kravets 			{ SM_SPORT_NAME,   0xc0 },
626*392cacf2SNikita Kravets 			{ SM_TURBO_NAME,   0xc4 },
627*392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
628*392cacf2SNikita Kravets 		},
629*392cacf2SNikita Kravets 	},
630*392cacf2SNikita Kravets 	.super_battery = {
631*392cacf2SNikita Kravets 		.address = MSI_EC_ADDR_UNKNOWN, // 0xd5 but has its own wet of modes
632*392cacf2SNikita Kravets 		.mask    = 0x0f,
633*392cacf2SNikita Kravets 	},
634*392cacf2SNikita Kravets 	.fan_mode = {
635*392cacf2SNikita Kravets 		.address = 0xf4,
636*392cacf2SNikita Kravets 		.modes = {
637*392cacf2SNikita Kravets 			{ FM_AUTO_NAME,     0x0d }, // d may not be relevant
638*392cacf2SNikita Kravets 			{ FM_SILENT_NAME,   0x1d },
639*392cacf2SNikita Kravets 			{ FM_ADVANCED_NAME, 0x8d },
640*392cacf2SNikita Kravets 			MSI_EC_MODE_NULL
641*392cacf2SNikita Kravets 		},
642*392cacf2SNikita Kravets 	},
643*392cacf2SNikita Kravets 	.cpu = {
644*392cacf2SNikita Kravets 		.rt_temp_address       = 0x68,
645*392cacf2SNikita Kravets 		.rt_fan_speed_address  = 0xc9, // needs testing
646*392cacf2SNikita Kravets 		.rt_fan_speed_base_min = 0x19,
647*392cacf2SNikita Kravets 		.rt_fan_speed_base_max = 0x37,
648*392cacf2SNikita Kravets 		.bs_fan_speed_address  = MSI_EC_ADDR_UNSUPP,
649*392cacf2SNikita Kravets 		.bs_fan_speed_base_min = 0x00,
650*392cacf2SNikita Kravets 		.bs_fan_speed_base_max = 0x0f,
651*392cacf2SNikita Kravets 	},
652*392cacf2SNikita Kravets 	.gpu = {
653*392cacf2SNikita Kravets 		.rt_temp_address      = MSI_EC_ADDR_UNKNOWN,
654*392cacf2SNikita Kravets 		.rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
655*392cacf2SNikita Kravets 	},
656*392cacf2SNikita Kravets 	.leds = {
657*392cacf2SNikita Kravets 		.micmute_led_address = MSI_EC_ADDR_UNSUPP,
658*392cacf2SNikita Kravets 		.mute_led_address    = 0x2c,
659*392cacf2SNikita Kravets 		.bit                 = 2,
660*392cacf2SNikita Kravets 	},
661*392cacf2SNikita Kravets 	.kbd_bl = {
662*392cacf2SNikita Kravets 		.bl_mode_address  = MSI_EC_ADDR_UNKNOWN, // ?
663*392cacf2SNikita Kravets 		.bl_modes         = { 0x00, 0x08 }, // ?
664*392cacf2SNikita Kravets 		.max_mode         = 1, // ?
665*392cacf2SNikita Kravets 		.bl_state_address = 0xf3,
666*392cacf2SNikita Kravets 		.state_base_value = 0x80,
667*392cacf2SNikita Kravets 		.max_state        = 3,
668*392cacf2SNikita Kravets 	},
669*392cacf2SNikita Kravets };
670*392cacf2SNikita Kravets 
671*392cacf2SNikita Kravets static struct msi_ec_conf *CONFIGS[] __initdata = {
672*392cacf2SNikita Kravets 	&CONF0,
673*392cacf2SNikita Kravets 	&CONF1,
674*392cacf2SNikita Kravets 	&CONF2,
675*392cacf2SNikita Kravets 	&CONF3,
676*392cacf2SNikita Kravets 	&CONF4,
677*392cacf2SNikita Kravets 	&CONF5,
678*392cacf2SNikita Kravets 	&CONF6,
679*392cacf2SNikita Kravets 	&CONF7,
680*392cacf2SNikita Kravets 	NULL
681*392cacf2SNikita Kravets };
682*392cacf2SNikita Kravets 
683*392cacf2SNikita Kravets static struct msi_ec_conf conf; // current configuration
684*392cacf2SNikita Kravets 
685*392cacf2SNikita Kravets /*
686*392cacf2SNikita Kravets  * Helper functions
687*392cacf2SNikita Kravets  */
688*392cacf2SNikita Kravets 
689*392cacf2SNikita Kravets static int ec_read_seq(u8 addr, u8 *buf, u8 len)
690*392cacf2SNikita Kravets {
691*392cacf2SNikita Kravets 	int result;
692*392cacf2SNikita Kravets 
693*392cacf2SNikita Kravets 	for (u8 i = 0; i < len; i++) {
694*392cacf2SNikita Kravets 		result = ec_read(addr + i, buf + i);
695*392cacf2SNikita Kravets 		if (result < 0)
696*392cacf2SNikita Kravets 			return result;
697*392cacf2SNikita Kravets 	}
698*392cacf2SNikita Kravets 
699*392cacf2SNikita Kravets 	return 0;
700*392cacf2SNikita Kravets }
701*392cacf2SNikita Kravets 
702*392cacf2SNikita Kravets static int ec_get_firmware_version(u8 buf[MSI_EC_FW_VERSION_LENGTH + 1])
703*392cacf2SNikita Kravets {
704*392cacf2SNikita Kravets 	int result;
705*392cacf2SNikita Kravets 
706*392cacf2SNikita Kravets 	memset(buf, 0, MSI_EC_FW_VERSION_LENGTH + 1);
707*392cacf2SNikita Kravets 	result = ec_read_seq(MSI_EC_FW_VERSION_ADDRESS,
708*392cacf2SNikita Kravets 			     buf,
709*392cacf2SNikita Kravets 			     MSI_EC_FW_VERSION_LENGTH);
710*392cacf2SNikita Kravets 	if (result < 0)
711*392cacf2SNikita Kravets 		return result;
712*392cacf2SNikita Kravets 
713*392cacf2SNikita Kravets 	return MSI_EC_FW_VERSION_LENGTH + 1;
714*392cacf2SNikita Kravets }
715*392cacf2SNikita Kravets 
716*392cacf2SNikita Kravets /*
717*392cacf2SNikita Kravets  * Sysfs power_supply subsystem
718*392cacf2SNikita Kravets  */
719*392cacf2SNikita Kravets 
720*392cacf2SNikita Kravets static ssize_t charge_control_threshold_show(u8 offset,
721*392cacf2SNikita Kravets 					     struct device *device,
722*392cacf2SNikita Kravets 					     struct device_attribute *attr,
723*392cacf2SNikita Kravets 					     char *buf)
724*392cacf2SNikita Kravets {
725*392cacf2SNikita Kravets 	u8 rdata;
726*392cacf2SNikita Kravets 	int result;
727*392cacf2SNikita Kravets 
728*392cacf2SNikita Kravets 	result = ec_read(conf.charge_control.address, &rdata);
729*392cacf2SNikita Kravets 	if (result < 0)
730*392cacf2SNikita Kravets 		return result;
731*392cacf2SNikita Kravets 
732*392cacf2SNikita Kravets 	return sysfs_emit(buf, "%i\n", rdata - offset);
733*392cacf2SNikita Kravets }
734*392cacf2SNikita Kravets 
735*392cacf2SNikita Kravets static ssize_t charge_control_threshold_store(u8 offset,
736*392cacf2SNikita Kravets 					      struct device *dev,
737*392cacf2SNikita Kravets 					      struct device_attribute *attr,
738*392cacf2SNikita Kravets 					      const char *buf, size_t count)
739*392cacf2SNikita Kravets {
740*392cacf2SNikita Kravets 	u8 wdata;
741*392cacf2SNikita Kravets 	int result;
742*392cacf2SNikita Kravets 
743*392cacf2SNikita Kravets 	result = kstrtou8(buf, 10, &wdata);
744*392cacf2SNikita Kravets 	if (result < 0)
745*392cacf2SNikita Kravets 		return result;
746*392cacf2SNikita Kravets 
747*392cacf2SNikita Kravets 	wdata += offset;
748*392cacf2SNikita Kravets 	if (wdata < conf.charge_control.range_min ||
749*392cacf2SNikita Kravets 	    wdata > conf.charge_control.range_max)
750*392cacf2SNikita Kravets 		return -EINVAL;
751*392cacf2SNikita Kravets 
752*392cacf2SNikita Kravets 	result = ec_write(conf.charge_control.address, wdata);
753*392cacf2SNikita Kravets 	if (result < 0)
754*392cacf2SNikita Kravets 		return result;
755*392cacf2SNikita Kravets 
756*392cacf2SNikita Kravets 	return count;
757*392cacf2SNikita Kravets }
758*392cacf2SNikita Kravets 
759*392cacf2SNikita Kravets static ssize_t charge_control_start_threshold_show(struct device *device,
760*392cacf2SNikita Kravets 						   struct device_attribute *attr,
761*392cacf2SNikita Kravets 						   char *buf)
762*392cacf2SNikita Kravets {
763*392cacf2SNikita Kravets 	return charge_control_threshold_show(conf.charge_control.offset_start,
764*392cacf2SNikita Kravets 					     device, attr, buf);
765*392cacf2SNikita Kravets }
766*392cacf2SNikita Kravets 
767*392cacf2SNikita Kravets static ssize_t charge_control_start_threshold_store(struct device *dev,
768*392cacf2SNikita Kravets 						    struct device_attribute *attr,
769*392cacf2SNikita Kravets 						    const char *buf, size_t count)
770*392cacf2SNikita Kravets {
771*392cacf2SNikita Kravets 	return charge_control_threshold_store(conf.charge_control.offset_start,
772*392cacf2SNikita Kravets 					      dev, attr, buf, count);
773*392cacf2SNikita Kravets }
774*392cacf2SNikita Kravets 
775*392cacf2SNikita Kravets static ssize_t charge_control_end_threshold_show(struct device *device,
776*392cacf2SNikita Kravets 						 struct device_attribute *attr,
777*392cacf2SNikita Kravets 						 char *buf)
778*392cacf2SNikita Kravets {
779*392cacf2SNikita Kravets 	return charge_control_threshold_show(conf.charge_control.offset_end,
780*392cacf2SNikita Kravets 					     device, attr, buf);
781*392cacf2SNikita Kravets }
782*392cacf2SNikita Kravets 
783*392cacf2SNikita Kravets static ssize_t charge_control_end_threshold_store(struct device *dev,
784*392cacf2SNikita Kravets 						  struct device_attribute *attr,
785*392cacf2SNikita Kravets 						  const char *buf, size_t count)
786*392cacf2SNikita Kravets {
787*392cacf2SNikita Kravets 	return charge_control_threshold_store(conf.charge_control.offset_end,
788*392cacf2SNikita Kravets 					      dev, attr, buf, count);
789*392cacf2SNikita Kravets }
790*392cacf2SNikita Kravets 
791*392cacf2SNikita Kravets static DEVICE_ATTR_RW(charge_control_start_threshold);
792*392cacf2SNikita Kravets static DEVICE_ATTR_RW(charge_control_end_threshold);
793*392cacf2SNikita Kravets 
794*392cacf2SNikita Kravets static struct attribute *msi_battery_attrs[] = {
795*392cacf2SNikita Kravets 	&dev_attr_charge_control_start_threshold.attr,
796*392cacf2SNikita Kravets 	&dev_attr_charge_control_end_threshold.attr,
797*392cacf2SNikita Kravets 	NULL
798*392cacf2SNikita Kravets };
799*392cacf2SNikita Kravets 
800*392cacf2SNikita Kravets ATTRIBUTE_GROUPS(msi_battery);
801*392cacf2SNikita Kravets 
802*392cacf2SNikita Kravets static int msi_battery_add(struct power_supply *battery,
803*392cacf2SNikita Kravets 			   struct acpi_battery_hook *hook)
804*392cacf2SNikita Kravets {
805*392cacf2SNikita Kravets 	return device_add_groups(&battery->dev, msi_battery_groups);
806*392cacf2SNikita Kravets }
807*392cacf2SNikita Kravets 
808*392cacf2SNikita Kravets static int msi_battery_remove(struct power_supply *battery,
809*392cacf2SNikita Kravets 			      struct acpi_battery_hook *hook)
810*392cacf2SNikita Kravets {
811*392cacf2SNikita Kravets 	device_remove_groups(&battery->dev, msi_battery_groups);
812*392cacf2SNikita Kravets 	return 0;
813*392cacf2SNikita Kravets }
814*392cacf2SNikita Kravets 
815*392cacf2SNikita Kravets static struct acpi_battery_hook battery_hook = {
816*392cacf2SNikita Kravets 	.add_battery = msi_battery_add,
817*392cacf2SNikita Kravets 	.remove_battery = msi_battery_remove,
818*392cacf2SNikita Kravets 	.name = MSI_EC_DRIVER_NAME,
819*392cacf2SNikita Kravets };
820*392cacf2SNikita Kravets 
821*392cacf2SNikita Kravets /*
822*392cacf2SNikita Kravets  * Module load/unload
823*392cacf2SNikita Kravets  */
824*392cacf2SNikita Kravets 
825*392cacf2SNikita Kravets static const struct dmi_system_id msi_dmi_table[] __initconst __maybe_unused = {
826*392cacf2SNikita Kravets 	{
827*392cacf2SNikita Kravets 		.matches = {
828*392cacf2SNikita Kravets 			DMI_MATCH(DMI_SYS_VENDOR, "MICRO-STAR INT"),
829*392cacf2SNikita Kravets 		},
830*392cacf2SNikita Kravets 	},
831*392cacf2SNikita Kravets 	{
832*392cacf2SNikita Kravets 		.matches = {
833*392cacf2SNikita Kravets 			DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"),
834*392cacf2SNikita Kravets 		},
835*392cacf2SNikita Kravets 	},
836*392cacf2SNikita Kravets 	{}
837*392cacf2SNikita Kravets };
838*392cacf2SNikita Kravets MODULE_DEVICE_TABLE(dmi, msi_dmi_table);
839*392cacf2SNikita Kravets 
840*392cacf2SNikita Kravets static int __init load_configuration(void)
841*392cacf2SNikita Kravets {
842*392cacf2SNikita Kravets 	int result;
843*392cacf2SNikita Kravets 
844*392cacf2SNikita Kravets 	u8 fw_version[MSI_EC_FW_VERSION_LENGTH + 1];
845*392cacf2SNikita Kravets 
846*392cacf2SNikita Kravets 	/* get firmware version */
847*392cacf2SNikita Kravets 	result = ec_get_firmware_version(fw_version);
848*392cacf2SNikita Kravets 	if (result < 0)
849*392cacf2SNikita Kravets 		return result;
850*392cacf2SNikita Kravets 
851*392cacf2SNikita Kravets 	/* load the suitable configuration, if exists */
852*392cacf2SNikita Kravets 	for (int i = 0; CONFIGS[i]; i++) {
853*392cacf2SNikita Kravets 		if (match_string(CONFIGS[i]->allowed_fw, -1, fw_version) != -EINVAL) {
854*392cacf2SNikita Kravets 			conf = *CONFIGS[i];
855*392cacf2SNikita Kravets 			conf.allowed_fw = NULL;
856*392cacf2SNikita Kravets 			return 0;
857*392cacf2SNikita Kravets 		}
858*392cacf2SNikita Kravets 	}
859*392cacf2SNikita Kravets 
860*392cacf2SNikita Kravets 	/* config not found */
861*392cacf2SNikita Kravets 
862*392cacf2SNikita Kravets 	for (int i = 0; i < MSI_EC_FW_VERSION_LENGTH; i++) {
863*392cacf2SNikita Kravets 		if (!isgraph(fw_version[i])) {
864*392cacf2SNikita Kravets 			pr_warn("Unable to find a valid firmware version!\n");
865*392cacf2SNikita Kravets 			return -EOPNOTSUPP;
866*392cacf2SNikita Kravets 		}
867*392cacf2SNikita Kravets 	}
868*392cacf2SNikita Kravets 
869*392cacf2SNikita Kravets 	pr_warn("Firmware version is not supported: '%s'\n", fw_version);
870*392cacf2SNikita Kravets 	return -EOPNOTSUPP;
871*392cacf2SNikita Kravets }
872*392cacf2SNikita Kravets 
873*392cacf2SNikita Kravets static int __init msi_ec_init(void)
874*392cacf2SNikita Kravets {
875*392cacf2SNikita Kravets 	int result;
876*392cacf2SNikita Kravets 
877*392cacf2SNikita Kravets 	result = load_configuration();
878*392cacf2SNikita Kravets 	if (result < 0)
879*392cacf2SNikita Kravets 		return result;
880*392cacf2SNikita Kravets 
881*392cacf2SNikita Kravets 	battery_hook_register(&battery_hook);
882*392cacf2SNikita Kravets 	return 0;
883*392cacf2SNikita Kravets }
884*392cacf2SNikita Kravets 
885*392cacf2SNikita Kravets static void __exit msi_ec_exit(void)
886*392cacf2SNikita Kravets {
887*392cacf2SNikita Kravets 	battery_hook_unregister(&battery_hook);
888*392cacf2SNikita Kravets }
889*392cacf2SNikita Kravets 
890*392cacf2SNikita Kravets MODULE_LICENSE("GPL");
891*392cacf2SNikita Kravets MODULE_AUTHOR("Jose Angel Pastrana <japp0005@red.ujaen.es>");
892*392cacf2SNikita Kravets MODULE_AUTHOR("Aakash Singh <mail@singhaakash.dev>");
893*392cacf2SNikita Kravets MODULE_AUTHOR("Nikita Kravets <teackot@gmail.com>");
894*392cacf2SNikita Kravets MODULE_DESCRIPTION("MSI Embedded Controller");
895*392cacf2SNikita Kravets 
896*392cacf2SNikita Kravets module_init(msi_ec_init);
897*392cacf2SNikita Kravets module_exit(msi_ec_exit);
898