Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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
30static const char *const SM_ECO_NAME = "eco";
31static const char *const SM_COMFORT_NAME = "comfort";
32static const char *const SM_SPORT_NAME = "sport";
33static const char *const SM_TURBO_NAME = "turbo";
34
35static const char *const FM_AUTO_NAME = "auto";
36static const char *const FM_SILENT_NAME = "silent";
37static const char *const FM_BASIC_NAME = "basic";
38static const char *const 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 "E1592IMS.10C",
280 NULL
281};
282
283static 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
356static const char * const ALLOWED_FW_4[] __initconst = {
357 "16V4EMS1.114",
358 NULL
359};
360
361static 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
433static const char * const ALLOWED_FW_5[] __initconst = {
434 "158LEMS1.103",
435 "158LEMS1.105",
436 "158LEMS1.106",
437 NULL
438};
439
440static 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
512static const char * const ALLOWED_FW_6[] __initconst = {
513 "1542EMS1.102",
514 "1542EMS1.104",
515 NULL
516};
517
518static 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
591static const char * const ALLOWED_FW_7[] __initconst = {
592 "17FKEMS1.108",
593 "17FKEMS1.109",
594 "17FKEMS1.10A",
595 NULL
596};
597
598static 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
671static 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
683static struct msi_ec_conf conf; // current configuration
684
685/*
686 * Helper functions
687 */
688
689static 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
702static 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
720static 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
735static 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
759static 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
767static 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
775static 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
783static 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
791static DEVICE_ATTR_RW(charge_control_start_threshold);
792static DEVICE_ATTR_RW(charge_control_end_threshold);
793
794static 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
800ATTRIBUTE_GROUPS(msi_battery);
801
802static 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
808static 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
815static 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
825static 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};
838MODULE_DEVICE_TABLE(dmi, msi_dmi_table);
839
840static 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
873static 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
885static void __exit msi_ec_exit(void)
886{
887 battery_hook_unregister(&battery_hook);
888}
889
890MODULE_LICENSE("GPL");
891MODULE_AUTHOR("Jose Angel Pastrana <japp0005@red.ujaen.es>");
892MODULE_AUTHOR("Aakash Singh <mail@singhaakash.dev>");
893MODULE_AUTHOR("Nikita Kravets <teackot@gmail.com>");
894MODULE_DESCRIPTION("MSI Embedded Controller");
895
896module_init(msi_ec_init);
897module_exit(msi_ec_exit);