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