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