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#define SM_ECO_NAME		"eco"
31#define SM_COMFORT_NAME		"comfort"
32#define SM_SPORT_NAME		"sport"
33#define SM_TURBO_NAME		"turbo"
34
35#define FM_AUTO_NAME		"auto"
36#define FM_SILENT_NAME		"silent"
37#define FM_BASIC_NAME		"basic"
38#define FM_ADVANCED_NAME	"advanced"
39
40static const char * const ALLOWED_FW_0[] __initconst = {
41	"14C1EMS1.012",
42	"14C1EMS1.101",
43	"14C1EMS1.102",
44	NULL
45};
46
47static 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
119static const char * const ALLOWED_FW_1[] __initconst = {
120	"17F2EMS1.103",
121	"17F2EMS1.104",
122	"17F2EMS1.106",
123	"17F2EMS1.107",
124	NULL
125};
126
127static 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
199static const char * const ALLOWED_FW_2[] __initconst = {
200	"1552EMS1.118",
201	NULL
202};
203
204static 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
277static const char * const ALLOWED_FW_3[] __initconst = {
278	"1592EMS1.111",
279	NULL
280};
281
282static struct msi_ec_conf CONF3 __initdata = {
283	.allowed_fw = ALLOWED_FW_3,
284	.charge_control = {
285		.address      = 0xd7,
286		.offset_start = 0x8a,
287		.offset_end   = 0x80,
288		.range_min    = 0x8a,
289		.range_max    = 0xe4,
290	},
291	.webcam = {
292		.address       = 0x2e,
293		.block_address = 0x2f,
294		.bit           = 1,
295	},
296	.fn_super_swap = {
297		.address = 0xe8,
298		.bit     = 4,
299	},
300	.cooler_boost = {
301		.address = 0x98,
302		.bit     = 7,
303	},
304	.shift_mode = {
305		.address = 0xd2,
306		.modes = {
307			{ SM_ECO_NAME,     0xc2 },
308			{ SM_COMFORT_NAME, 0xc1 },
309			{ SM_SPORT_NAME,   0xc0 },
310			MSI_EC_MODE_NULL
311		},
312	},
313	.super_battery = {
314		.address = 0xeb,
315		.mask    = 0x0f,
316	},
317	.fan_mode = {
318		.address = 0xd4,
319		.modes = {
320			{ FM_AUTO_NAME,     0x0d },
321			{ FM_SILENT_NAME,   0x1d },
322			{ FM_BASIC_NAME,    0x4d },
323			{ FM_ADVANCED_NAME, 0x8d },
324			MSI_EC_MODE_NULL
325		},
326	},
327	.cpu = {
328		.rt_temp_address       = 0x68,
329		.rt_fan_speed_address  = 0xc9,
330		.rt_fan_speed_base_min = 0x19,
331		.rt_fan_speed_base_max = 0x37,
332		.bs_fan_speed_address  = 0x89, // ?
333		.bs_fan_speed_base_min = 0x00,
334		.bs_fan_speed_base_max = 0x0f,
335	},
336	.gpu = {
337		.rt_temp_address      = 0x80,
338		.rt_fan_speed_address = 0x89,
339	},
340	.leds = {
341		.micmute_led_address = 0x2b,
342		.mute_led_address    = 0x2c,
343		.bit                 = 1,
344	},
345	.kbd_bl = {
346		.bl_mode_address  = 0x2c, // ?
347		.bl_modes         = { 0x00, 0x08 }, // ?
348		.max_mode         = 1, // ?
349		.bl_state_address = 0xd3,
350		.state_base_value = 0x80,
351		.max_state        = 3,
352	},
353};
354
355static const char * const ALLOWED_FW_4[] __initconst = {
356	"16V4EMS1.114",
357	NULL
358};
359
360static struct msi_ec_conf CONF4 __initdata = {
361	.allowed_fw = ALLOWED_FW_4,
362	.charge_control = {
363		.address      = 0xd7,
364		.offset_start = 0x8a,
365		.offset_end   = 0x80,
366		.range_min    = 0x8a,
367		.range_max    = 0xe4,
368	},
369	.webcam = {
370		.address       = 0x2e,
371		.block_address = 0x2f,
372		.bit           = 1,
373	},
374	.fn_super_swap = {
375		.address = MSI_EC_ADDR_UNKNOWN, // supported, but unknown
376		.bit     = 4,
377	},
378	.cooler_boost = {
379		.address = 0x98,
380		.bit     = 7,
381	},
382	.shift_mode = {
383		.address = 0xd2,
384		.modes = {
385			{ SM_ECO_NAME,     0xc2 },
386			{ SM_COMFORT_NAME, 0xc1 },
387			{ SM_SPORT_NAME,   0xc0 },
388			MSI_EC_MODE_NULL
389		},
390	},
391	.super_battery = { // may be supported, but address is unknown
392		.address = MSI_EC_ADDR_UNKNOWN,
393		.mask    = 0x0f,
394	},
395	.fan_mode = {
396		.address = 0xd4,
397		.modes = {
398			{ FM_AUTO_NAME,     0x0d },
399			{ FM_SILENT_NAME,   0x1d },
400			{ FM_ADVANCED_NAME, 0x8d },
401			MSI_EC_MODE_NULL
402		},
403	},
404	.cpu = {
405		.rt_temp_address       = 0x68, // needs testing
406		.rt_fan_speed_address  = 0x71, // needs testing
407		.rt_fan_speed_base_min = 0x19,
408		.rt_fan_speed_base_max = 0x37,
409		.bs_fan_speed_address  = MSI_EC_ADDR_UNKNOWN,
410		.bs_fan_speed_base_min = 0x00,
411		.bs_fan_speed_base_max = 0x0f,
412	},
413	.gpu = {
414		.rt_temp_address      = 0x80,
415		.rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
416	},
417	.leds = {
418		.micmute_led_address = MSI_EC_ADDR_UNKNOWN,
419		.mute_led_address    = MSI_EC_ADDR_UNKNOWN,
420		.bit                 = 1,
421	},
422	.kbd_bl = {
423		.bl_mode_address  = MSI_EC_ADDR_UNKNOWN, // ?
424		.bl_modes         = { 0x00, 0x08 }, // ?
425		.max_mode         = 1, // ?
426		.bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xd3, not functional
427		.state_base_value = 0x80,
428		.max_state        = 3,
429	},
430};
431
432static const char * const ALLOWED_FW_5[] __initconst = {
433	"158LEMS1.103",
434	"158LEMS1.105",
435	"158LEMS1.106",
436	NULL
437};
438
439static struct msi_ec_conf CONF5 __initdata = {
440	.allowed_fw = ALLOWED_FW_5,
441	.charge_control = {
442		.address      = 0xef,
443		.offset_start = 0x8a,
444		.offset_end   = 0x80,
445		.range_min    = 0x8a,
446		.range_max    = 0xe4,
447	},
448	.webcam = {
449		.address       = 0x2e,
450		.block_address = 0x2f,
451		.bit           = 1,
452	},
453	.fn_super_swap = { // todo: reverse
454		.address = 0xbf,
455		.bit     = 4,
456	},
457	.cooler_boost = {
458		.address = 0x98,
459		.bit     = 7,
460	},
461	.shift_mode = {
462		.address = 0xf2,
463		.modes = {
464			{ SM_ECO_NAME,     0xc2 },
465			{ SM_COMFORT_NAME, 0xc1 },
466			{ SM_TURBO_NAME,   0xc4 },
467			MSI_EC_MODE_NULL
468		},
469	},
470	.super_battery = { // unsupported?
471		.address = MSI_EC_ADDR_UNKNOWN,
472		.mask    = 0x0f,
473	},
474	.fan_mode = {
475		.address = 0xf4,
476		.modes = {
477			{ FM_AUTO_NAME,     0x0d },
478			{ FM_SILENT_NAME,   0x1d },
479			{ FM_ADVANCED_NAME, 0x8d },
480			MSI_EC_MODE_NULL
481		},
482	},
483	.cpu = {
484		.rt_temp_address       = 0x68, // needs testing
485		.rt_fan_speed_address  = 0x71, // needs testing
486		.rt_fan_speed_base_min = 0x19,
487		.rt_fan_speed_base_max = 0x37,
488		.bs_fan_speed_address  = MSI_EC_ADDR_UNSUPP,
489		.bs_fan_speed_base_min = 0x00,
490		.bs_fan_speed_base_max = 0x0f,
491	},
492	.gpu = {
493		.rt_temp_address      = MSI_EC_ADDR_UNKNOWN,
494		.rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
495	},
496	.leds = {
497		.micmute_led_address = 0x2b,
498		.mute_led_address    = 0x2c,
499		.bit                 = 2,
500	},
501	.kbd_bl = {
502		.bl_mode_address  = MSI_EC_ADDR_UNKNOWN, // ?
503		.bl_modes         = { 0x00, 0x08 }, // ?
504		.max_mode         = 1, // ?
505		.bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xf3, not functional
506		.state_base_value = 0x80,
507		.max_state        = 3,
508	},
509};
510
511static const char * const ALLOWED_FW_6[] __initconst = {
512	"1542EMS1.102",
513	"1542EMS1.104",
514	NULL
515};
516
517static struct msi_ec_conf CONF6 __initdata = {
518	.allowed_fw = ALLOWED_FW_6,
519	.charge_control = {
520		.address      = 0xef,
521		.offset_start = 0x8a,
522		.offset_end   = 0x80,
523		.range_min    = 0x8a,
524		.range_max    = 0xe4,
525	},
526	.webcam = {
527		.address       = 0x2e,
528		.block_address = MSI_EC_ADDR_UNSUPP,
529		.bit           = 1,
530	},
531	.fn_super_swap = {
532		.address = 0xbf, // todo: reverse
533		.bit     = 4,
534	},
535	.cooler_boost = {
536		.address = 0x98,
537		.bit     = 7,
538	},
539	.shift_mode = {
540		.address = 0xf2,
541		.modes = {
542			{ SM_ECO_NAME,     0xc2 },
543			{ SM_COMFORT_NAME, 0xc1 },
544			{ SM_SPORT_NAME,   0xc0 },
545			{ SM_TURBO_NAME,   0xc4 },
546			MSI_EC_MODE_NULL
547		},
548	},
549	.super_battery = {
550		.address = 0xd5,
551		.mask    = 0x0f,
552	},
553	.fan_mode = {
554		.address = 0xf4,
555		.modes = {
556			{ FM_AUTO_NAME,     0x0d },
557			{ FM_SILENT_NAME,   0x1d },
558			{ FM_ADVANCED_NAME, 0x8d },
559			MSI_EC_MODE_NULL
560		},
561	},
562	.cpu = {
563		.rt_temp_address       = 0x68,
564		.rt_fan_speed_address  = 0xc9,
565		.rt_fan_speed_base_min = 0x19,
566		.rt_fan_speed_base_max = 0x37,
567		.bs_fan_speed_address  = MSI_EC_ADDR_UNSUPP,
568		.bs_fan_speed_base_min = 0x00,
569		.bs_fan_speed_base_max = 0x0f,
570	},
571	.gpu = {
572		.rt_temp_address      = 0x80,
573		.rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
574	},
575	.leds = {
576		.micmute_led_address = MSI_EC_ADDR_UNSUPP,
577		.mute_led_address    = MSI_EC_ADDR_UNSUPP,
578		.bit                 = 2,
579	},
580	.kbd_bl = {
581		.bl_mode_address  = MSI_EC_ADDR_UNKNOWN, // ?
582		.bl_modes         = { 0x00, 0x08 }, // ?
583		.max_mode         = 1, // ?
584		.bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xf3, not functional
585		.state_base_value = 0x80,
586		.max_state        = 3,
587	},
588};
589
590static const char * const ALLOWED_FW_7[] __initconst = {
591	"17FKEMS1.108",
592	"17FKEMS1.109",
593	"17FKEMS1.10A",
594	NULL
595};
596
597static struct msi_ec_conf CONF7 __initdata = {
598	.allowed_fw = ALLOWED_FW_7,
599	.charge_control = {
600		.address      = 0xef,
601		.offset_start = 0x8a,
602		.offset_end   = 0x80,
603		.range_min    = 0x8a,
604		.range_max    = 0xe4,
605	},
606	.webcam = {
607		.address       = 0x2e,
608		.block_address = MSI_EC_ADDR_UNSUPP,
609		.bit           = 1,
610	},
611	.fn_super_swap = {
612		.address = 0xbf, // needs testing
613		.bit     = 4,
614	},
615	.cooler_boost = {
616		.address = 0x98,
617		.bit     = 7,
618	},
619	.shift_mode = {
620		.address = 0xf2,
621		.modes = {
622			{ SM_ECO_NAME,     0xc2 },
623			{ SM_COMFORT_NAME, 0xc1 },
624			{ SM_SPORT_NAME,   0xc0 },
625			{ SM_TURBO_NAME,   0xc4 },
626			MSI_EC_MODE_NULL
627		},
628	},
629	.super_battery = {
630		.address = MSI_EC_ADDR_UNKNOWN, // 0xd5 but has its own wet of modes
631		.mask    = 0x0f,
632	},
633	.fan_mode = {
634		.address = 0xf4,
635		.modes = {
636			{ FM_AUTO_NAME,     0x0d }, // d may not be relevant
637			{ FM_SILENT_NAME,   0x1d },
638			{ FM_ADVANCED_NAME, 0x8d },
639			MSI_EC_MODE_NULL
640		},
641	},
642	.cpu = {
643		.rt_temp_address       = 0x68,
644		.rt_fan_speed_address  = 0xc9, // needs testing
645		.rt_fan_speed_base_min = 0x19,
646		.rt_fan_speed_base_max = 0x37,
647		.bs_fan_speed_address  = MSI_EC_ADDR_UNSUPP,
648		.bs_fan_speed_base_min = 0x00,
649		.bs_fan_speed_base_max = 0x0f,
650	},
651	.gpu = {
652		.rt_temp_address      = MSI_EC_ADDR_UNKNOWN,
653		.rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
654	},
655	.leds = {
656		.micmute_led_address = MSI_EC_ADDR_UNSUPP,
657		.mute_led_address    = 0x2c,
658		.bit                 = 2,
659	},
660	.kbd_bl = {
661		.bl_mode_address  = MSI_EC_ADDR_UNKNOWN, // ?
662		.bl_modes         = { 0x00, 0x08 }, // ?
663		.max_mode         = 1, // ?
664		.bl_state_address = 0xf3,
665		.state_base_value = 0x80,
666		.max_state        = 3,
667	},
668};
669
670static struct msi_ec_conf *CONFIGS[] __initdata = {
671	&CONF0,
672	&CONF1,
673	&CONF2,
674	&CONF3,
675	&CONF4,
676	&CONF5,
677	&CONF6,
678	&CONF7,
679	NULL
680};
681
682static struct msi_ec_conf conf; // current configuration
683
684/*
685 * Helper functions
686 */
687
688static int ec_read_seq(u8 addr, u8 *buf, u8 len)
689{
690	int result;
691
692	for (u8 i = 0; i < len; i++) {
693		result = ec_read(addr + i, buf + i);
694		if (result < 0)
695			return result;
696	}
697
698	return 0;
699}
700
701static int ec_get_firmware_version(u8 buf[MSI_EC_FW_VERSION_LENGTH + 1])
702{
703	int result;
704
705	memset(buf, 0, MSI_EC_FW_VERSION_LENGTH + 1);
706	result = ec_read_seq(MSI_EC_FW_VERSION_ADDRESS,
707			     buf,
708			     MSI_EC_FW_VERSION_LENGTH);
709	if (result < 0)
710		return result;
711
712	return MSI_EC_FW_VERSION_LENGTH + 1;
713}
714
715/*
716 * Sysfs power_supply subsystem
717 */
718
719static ssize_t charge_control_threshold_show(u8 offset,
720					     struct device *device,
721					     struct device_attribute *attr,
722					     char *buf)
723{
724	u8 rdata;
725	int result;
726
727	result = ec_read(conf.charge_control.address, &rdata);
728	if (result < 0)
729		return result;
730
731	return sysfs_emit(buf, "%i\n", rdata - offset);
732}
733
734static ssize_t charge_control_threshold_store(u8 offset,
735					      struct device *dev,
736					      struct device_attribute *attr,
737					      const char *buf, size_t count)
738{
739	u8 wdata;
740	int result;
741
742	result = kstrtou8(buf, 10, &wdata);
743	if (result < 0)
744		return result;
745
746	wdata += offset;
747	if (wdata < conf.charge_control.range_min ||
748	    wdata > conf.charge_control.range_max)
749		return -EINVAL;
750
751	result = ec_write(conf.charge_control.address, wdata);
752	if (result < 0)
753		return result;
754
755	return count;
756}
757
758static ssize_t charge_control_start_threshold_show(struct device *device,
759						   struct device_attribute *attr,
760						   char *buf)
761{
762	return charge_control_threshold_show(conf.charge_control.offset_start,
763					     device, attr, buf);
764}
765
766static ssize_t charge_control_start_threshold_store(struct device *dev,
767						    struct device_attribute *attr,
768						    const char *buf, size_t count)
769{
770	return charge_control_threshold_store(conf.charge_control.offset_start,
771					      dev, attr, buf, count);
772}
773
774static ssize_t charge_control_end_threshold_show(struct device *device,
775						 struct device_attribute *attr,
776						 char *buf)
777{
778	return charge_control_threshold_show(conf.charge_control.offset_end,
779					     device, attr, buf);
780}
781
782static ssize_t charge_control_end_threshold_store(struct device *dev,
783						  struct device_attribute *attr,
784						  const char *buf, size_t count)
785{
786	return charge_control_threshold_store(conf.charge_control.offset_end,
787					      dev, attr, buf, count);
788}
789
790static DEVICE_ATTR_RW(charge_control_start_threshold);
791static DEVICE_ATTR_RW(charge_control_end_threshold);
792
793static struct attribute *msi_battery_attrs[] = {
794	&dev_attr_charge_control_start_threshold.attr,
795	&dev_attr_charge_control_end_threshold.attr,
796	NULL
797};
798
799ATTRIBUTE_GROUPS(msi_battery);
800
801static int msi_battery_add(struct power_supply *battery,
802			   struct acpi_battery_hook *hook)
803{
804	return device_add_groups(&battery->dev, msi_battery_groups);
805}
806
807static int msi_battery_remove(struct power_supply *battery,
808			      struct acpi_battery_hook *hook)
809{
810	device_remove_groups(&battery->dev, msi_battery_groups);
811	return 0;
812}
813
814static struct acpi_battery_hook battery_hook = {
815	.add_battery = msi_battery_add,
816	.remove_battery = msi_battery_remove,
817	.name = MSI_EC_DRIVER_NAME,
818};
819
820/*
821 * Module load/unload
822 */
823
824static const struct dmi_system_id msi_dmi_table[] __initconst __maybe_unused = {
825	{
826		.matches = {
827			DMI_MATCH(DMI_SYS_VENDOR, "MICRO-STAR INT"),
828		},
829	},
830	{
831		.matches = {
832			DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"),
833		},
834	},
835	{}
836};
837MODULE_DEVICE_TABLE(dmi, msi_dmi_table);
838
839static int __init load_configuration(void)
840{
841	int result;
842
843	u8 fw_version[MSI_EC_FW_VERSION_LENGTH + 1];
844
845	/* get firmware version */
846	result = ec_get_firmware_version(fw_version);
847	if (result < 0)
848		return result;
849
850	/* load the suitable configuration, if exists */
851	for (int i = 0; CONFIGS[i]; i++) {
852		if (match_string(CONFIGS[i]->allowed_fw, -1, fw_version) != -EINVAL) {
853			conf = *CONFIGS[i];
854			conf.allowed_fw = NULL;
855			return 0;
856		}
857	}
858
859	/* config not found */
860
861	for (int i = 0; i < MSI_EC_FW_VERSION_LENGTH; i++) {
862		if (!isgraph(fw_version[i])) {
863			pr_warn("Unable to find a valid firmware version!\n");
864			return -EOPNOTSUPP;
865		}
866	}
867
868	pr_warn("Firmware version is not supported: '%s'\n", fw_version);
869	return -EOPNOTSUPP;
870}
871
872static int __init msi_ec_init(void)
873{
874	int result;
875
876	result = load_configuration();
877	if (result < 0)
878		return result;
879
880	battery_hook_register(&battery_hook);
881	return 0;
882}
883
884static void __exit msi_ec_exit(void)
885{
886	battery_hook_unregister(&battery_hook);
887}
888
889MODULE_LICENSE("GPL");
890MODULE_AUTHOR("Jose Angel Pastrana <japp0005@red.ujaen.es>");
891MODULE_AUTHOR("Aakash Singh <mail@singhaakash.dev>");
892MODULE_AUTHOR("Nikita Kravets <teackot@gmail.com>");
893MODULE_DESCRIPTION("MSI Embedded Controller");
894
895module_init(msi_ec_init);
896module_exit(msi_ec_exit);
897