1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /*-*-linux-c-*-*/ |
3 | |
4 | /* |
5 | Copyright (C) 2006 Lennart Poettering <mzxreary (at) 0pointer (dot) de> |
6 | |
7 | */ |
8 | |
9 | /* |
10 | * msi-laptop.c - MSI S270 laptop support. This laptop is sold under |
11 | * various brands, including "Cytron/TCM/Medion/Tchibo MD96100". |
12 | * |
13 | * Driver also supports S271, S420 models. |
14 | * |
15 | * This driver exports a few files in /sys/devices/platform/msi-laptop-pf/: |
16 | * |
17 | * lcd_level - Screen brightness: contains a single integer in the |
18 | * range 0..8. (rw) |
19 | * |
20 | * auto_brightness - Enable automatic brightness control: contains |
21 | * either 0 or 1. If set to 1 the hardware adjusts the screen |
22 | * brightness automatically when the power cord is |
23 | * plugged/unplugged. (rw) |
24 | * |
25 | * wlan - WLAN subsystem enabled: contains either 0 or 1. (ro) |
26 | * |
27 | * bluetooth - Bluetooth subsystem enabled: contains either 0 or 1 |
28 | * Please note that this file is constantly 0 if no Bluetooth |
29 | * hardware is available. (ro) |
30 | * |
31 | * In addition to these platform device attributes the driver |
32 | * registers itself in the Linux backlight control subsystem and is |
33 | * available to userspace under /sys/class/backlight/msi-laptop-bl/. |
34 | * |
35 | * This driver might work on other laptops produced by MSI. If you |
36 | * want to try it you can pass force=1 as argument to the module which |
37 | * will force it to load even when the DMI data doesn't identify the |
38 | * laptop as MSI S270. YMMV. |
39 | */ |
40 | |
41 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
42 | |
43 | #include <linux/module.h> |
44 | #include <linux/kernel.h> |
45 | #include <linux/init.h> |
46 | #include <linux/acpi.h> |
47 | #include <linux/dmi.h> |
48 | #include <linux/backlight.h> |
49 | #include <linux/platform_device.h> |
50 | #include <linux/rfkill.h> |
51 | #include <linux/i8042.h> |
52 | #include <linux/input.h> |
53 | #include <linux/input/sparse-keymap.h> |
54 | #include <acpi/video.h> |
55 | |
56 | #define MSI_LCD_LEVEL_MAX 9 |
57 | |
58 | #define MSI_EC_COMMAND_WIRELESS 0x10 |
59 | #define MSI_EC_COMMAND_LCD_LEVEL 0x11 |
60 | |
61 | #define MSI_STANDARD_EC_COMMAND_ADDRESS 0x2e |
62 | #define MSI_STANDARD_EC_BLUETOOTH_MASK (1 << 0) |
63 | #define MSI_STANDARD_EC_WEBCAM_MASK (1 << 1) |
64 | #define MSI_STANDARD_EC_WLAN_MASK (1 << 3) |
65 | #define MSI_STANDARD_EC_3G_MASK (1 << 4) |
66 | |
67 | /* For set SCM load flag to disable BIOS fn key */ |
68 | #define MSI_STANDARD_EC_SCM_LOAD_ADDRESS 0x2d |
69 | #define MSI_STANDARD_EC_SCM_LOAD_MASK (1 << 0) |
70 | |
71 | #define MSI_STANDARD_EC_FUNCTIONS_ADDRESS 0xe4 |
72 | /* Power LED is orange - Turbo mode */ |
73 | #define MSI_STANDARD_EC_TURBO_MASK (1 << 1) |
74 | /* Power LED is green - ECO mode */ |
75 | #define MSI_STANDARD_EC_ECO_MASK (1 << 3) |
76 | /* Touchpad is turned on */ |
77 | #define MSI_STANDARD_EC_TOUCHPAD_MASK (1 << 4) |
78 | /* If this bit != bit 1, turbo mode can't be toggled */ |
79 | #define MSI_STANDARD_EC_TURBO_COOLDOWN_MASK (1 << 7) |
80 | |
81 | #define MSI_STANDARD_EC_FAN_ADDRESS 0x33 |
82 | /* If zero, fan rotates at maximal speed */ |
83 | #define MSI_STANDARD_EC_AUTOFAN_MASK (1 << 0) |
84 | |
85 | #ifdef CONFIG_PM_SLEEP |
86 | static int msi_laptop_resume(struct device *device); |
87 | #endif |
88 | static SIMPLE_DEV_PM_OPS(msi_laptop_pm, NULL, msi_laptop_resume); |
89 | |
90 | #define MSI_STANDARD_EC_DEVICES_EXISTS_ADDRESS 0x2f |
91 | |
92 | static bool force; |
93 | module_param(force, bool, 0); |
94 | MODULE_PARM_DESC(force, "Force driver load, ignore DMI data" ); |
95 | |
96 | static int auto_brightness; |
97 | module_param(auto_brightness, int, 0); |
98 | MODULE_PARM_DESC(auto_brightness, "Enable automatic brightness control (0: disabled; 1: enabled; 2: don't touch)" ); |
99 | |
100 | static const struct key_entry msi_laptop_keymap[] = { |
101 | {KE_KEY, KEY_TOUCHPAD_ON, {KEY_TOUCHPAD_ON} }, /* Touch Pad On */ |
102 | {KE_KEY, KEY_TOUCHPAD_OFF, {KEY_TOUCHPAD_OFF} },/* Touch Pad On */ |
103 | {KE_END, 0} |
104 | }; |
105 | |
106 | static struct input_dev *msi_laptop_input_dev; |
107 | |
108 | static int wlan_s, bluetooth_s, threeg_s; |
109 | static int threeg_exists; |
110 | static struct rfkill *rfk_wlan, *rfk_bluetooth, *rfk_threeg; |
111 | |
112 | /* MSI laptop quirks */ |
113 | struct quirk_entry { |
114 | bool old_ec_model; |
115 | |
116 | /* Some MSI 3G netbook only have one fn key to control |
117 | * Wlan/Bluetooth/3G, those netbook will load the SCM (windows app) to |
118 | * disable the original Wlan/Bluetooth control by BIOS when user press |
119 | * fn key, then control Wlan/Bluetooth/3G by SCM (software control by |
120 | * OS). Without SCM, user cann't on/off 3G module on those 3G netbook. |
121 | * On Linux, msi-laptop driver will do the same thing to disable the |
122 | * original BIOS control, then might need use HAL or other userland |
123 | * application to do the software control that simulate with SCM. |
124 | * e.g. MSI N034 netbook |
125 | */ |
126 | bool load_scm_model; |
127 | |
128 | /* Some MSI laptops need delay before reading from EC */ |
129 | bool ec_delay; |
130 | |
131 | /* Some MSI Wind netbooks (e.g. MSI Wind U100) need loading SCM to get |
132 | * some features working (e.g. ECO mode), but we cannot change |
133 | * Wlan/Bluetooth state in software and we can only read its state. |
134 | */ |
135 | bool ec_read_only; |
136 | }; |
137 | |
138 | static struct quirk_entry *quirks; |
139 | |
140 | /* Hardware access */ |
141 | |
142 | static int set_lcd_level(int level) |
143 | { |
144 | u8 buf[2]; |
145 | |
146 | if (level < 0 || level >= MSI_LCD_LEVEL_MAX) |
147 | return -EINVAL; |
148 | |
149 | buf[0] = 0x80; |
150 | buf[1] = (u8) (level*31); |
151 | |
152 | return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata: buf, wdata_len: sizeof(buf), |
153 | NULL, rdata_len: 0); |
154 | } |
155 | |
156 | static int get_lcd_level(void) |
157 | { |
158 | u8 wdata = 0, rdata; |
159 | int result; |
160 | |
161 | result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata: &wdata, wdata_len: 1, |
162 | rdata: &rdata, rdata_len: 1); |
163 | if (result < 0) |
164 | return result; |
165 | |
166 | return (int) rdata / 31; |
167 | } |
168 | |
169 | static int get_auto_brightness(void) |
170 | { |
171 | u8 wdata = 4, rdata; |
172 | int result; |
173 | |
174 | result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata: &wdata, wdata_len: 1, |
175 | rdata: &rdata, rdata_len: 1); |
176 | if (result < 0) |
177 | return result; |
178 | |
179 | return !!(rdata & 8); |
180 | } |
181 | |
182 | static int set_auto_brightness(int enable) |
183 | { |
184 | u8 wdata[2], rdata; |
185 | int result; |
186 | |
187 | wdata[0] = 4; |
188 | |
189 | result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, wdata_len: 1, |
190 | rdata: &rdata, rdata_len: 1); |
191 | if (result < 0) |
192 | return result; |
193 | |
194 | wdata[0] = 0x84; |
195 | wdata[1] = (rdata & 0xF7) | (enable ? 8 : 0); |
196 | |
197 | return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, wdata_len: 2, |
198 | NULL, rdata_len: 0); |
199 | } |
200 | |
201 | static ssize_t set_device_state(const char *buf, size_t count, u8 mask) |
202 | { |
203 | int status; |
204 | u8 wdata = 0, rdata; |
205 | int result; |
206 | |
207 | if (sscanf(buf, "%i" , &status) != 1 || (status < 0 || status > 1)) |
208 | return -EINVAL; |
209 | |
210 | if (quirks->ec_read_only) |
211 | return 0; |
212 | |
213 | /* read current device state */ |
214 | result = ec_read(MSI_STANDARD_EC_COMMAND_ADDRESS, val: &rdata); |
215 | if (result < 0) |
216 | return result; |
217 | |
218 | if (!!(rdata & mask) != status) { |
219 | /* reverse device bit */ |
220 | if (rdata & mask) |
221 | wdata = rdata & ~mask; |
222 | else |
223 | wdata = rdata | mask; |
224 | |
225 | result = ec_write(MSI_STANDARD_EC_COMMAND_ADDRESS, val: wdata); |
226 | if (result < 0) |
227 | return result; |
228 | } |
229 | |
230 | return count; |
231 | } |
232 | |
233 | static int get_wireless_state(int *wlan, int *bluetooth) |
234 | { |
235 | u8 wdata = 0, rdata; |
236 | int result; |
237 | |
238 | result = ec_transaction(MSI_EC_COMMAND_WIRELESS, wdata: &wdata, wdata_len: 1, rdata: &rdata, rdata_len: 1); |
239 | if (result < 0) |
240 | return result; |
241 | |
242 | if (wlan) |
243 | *wlan = !!(rdata & 8); |
244 | |
245 | if (bluetooth) |
246 | *bluetooth = !!(rdata & 128); |
247 | |
248 | return 0; |
249 | } |
250 | |
251 | static int get_wireless_state_ec_standard(void) |
252 | { |
253 | u8 rdata; |
254 | int result; |
255 | |
256 | result = ec_read(MSI_STANDARD_EC_COMMAND_ADDRESS, val: &rdata); |
257 | if (result < 0) |
258 | return result; |
259 | |
260 | wlan_s = !!(rdata & MSI_STANDARD_EC_WLAN_MASK); |
261 | |
262 | bluetooth_s = !!(rdata & MSI_STANDARD_EC_BLUETOOTH_MASK); |
263 | |
264 | threeg_s = !!(rdata & MSI_STANDARD_EC_3G_MASK); |
265 | |
266 | return 0; |
267 | } |
268 | |
269 | static int get_threeg_exists(void) |
270 | { |
271 | u8 rdata; |
272 | int result; |
273 | |
274 | result = ec_read(MSI_STANDARD_EC_DEVICES_EXISTS_ADDRESS, val: &rdata); |
275 | if (result < 0) |
276 | return result; |
277 | |
278 | threeg_exists = !!(rdata & MSI_STANDARD_EC_3G_MASK); |
279 | |
280 | return 0; |
281 | } |
282 | |
283 | /* Backlight device stuff */ |
284 | |
285 | static int bl_get_brightness(struct backlight_device *b) |
286 | { |
287 | return get_lcd_level(); |
288 | } |
289 | |
290 | |
291 | static int bl_update_status(struct backlight_device *b) |
292 | { |
293 | return set_lcd_level(b->props.brightness); |
294 | } |
295 | |
296 | static const struct backlight_ops msibl_ops = { |
297 | .get_brightness = bl_get_brightness, |
298 | .update_status = bl_update_status, |
299 | }; |
300 | |
301 | static struct backlight_device *msibl_device; |
302 | |
303 | /* Platform device */ |
304 | |
305 | static ssize_t show_wlan(struct device *dev, |
306 | struct device_attribute *attr, char *buf) |
307 | { |
308 | |
309 | int ret, enabled = 0; |
310 | |
311 | if (quirks->old_ec_model) { |
312 | ret = get_wireless_state(wlan: &enabled, NULL); |
313 | } else { |
314 | ret = get_wireless_state_ec_standard(); |
315 | enabled = wlan_s; |
316 | } |
317 | if (ret < 0) |
318 | return ret; |
319 | |
320 | return sprintf(buf, fmt: "%i\n" , enabled); |
321 | } |
322 | |
323 | static ssize_t store_wlan(struct device *dev, |
324 | struct device_attribute *attr, const char *buf, size_t count) |
325 | { |
326 | return set_device_state(buf, count, MSI_STANDARD_EC_WLAN_MASK); |
327 | } |
328 | |
329 | static ssize_t show_bluetooth(struct device *dev, |
330 | struct device_attribute *attr, char *buf) |
331 | { |
332 | |
333 | int ret, enabled = 0; |
334 | |
335 | if (quirks->old_ec_model) { |
336 | ret = get_wireless_state(NULL, bluetooth: &enabled); |
337 | } else { |
338 | ret = get_wireless_state_ec_standard(); |
339 | enabled = bluetooth_s; |
340 | } |
341 | if (ret < 0) |
342 | return ret; |
343 | |
344 | return sprintf(buf, fmt: "%i\n" , enabled); |
345 | } |
346 | |
347 | static ssize_t store_bluetooth(struct device *dev, |
348 | struct device_attribute *attr, const char *buf, size_t count) |
349 | { |
350 | return set_device_state(buf, count, MSI_STANDARD_EC_BLUETOOTH_MASK); |
351 | } |
352 | |
353 | static ssize_t show_threeg(struct device *dev, |
354 | struct device_attribute *attr, char *buf) |
355 | { |
356 | |
357 | int ret; |
358 | |
359 | /* old msi ec not support 3G */ |
360 | if (quirks->old_ec_model) |
361 | return -ENODEV; |
362 | |
363 | ret = get_wireless_state_ec_standard(); |
364 | if (ret < 0) |
365 | return ret; |
366 | |
367 | return sprintf(buf, fmt: "%i\n" , threeg_s); |
368 | } |
369 | |
370 | static ssize_t store_threeg(struct device *dev, |
371 | struct device_attribute *attr, const char *buf, size_t count) |
372 | { |
373 | return set_device_state(buf, count, MSI_STANDARD_EC_3G_MASK); |
374 | } |
375 | |
376 | static ssize_t show_lcd_level(struct device *dev, |
377 | struct device_attribute *attr, char *buf) |
378 | { |
379 | |
380 | int ret; |
381 | |
382 | ret = get_lcd_level(); |
383 | if (ret < 0) |
384 | return ret; |
385 | |
386 | return sprintf(buf, fmt: "%i\n" , ret); |
387 | } |
388 | |
389 | static ssize_t store_lcd_level(struct device *dev, |
390 | struct device_attribute *attr, const char *buf, size_t count) |
391 | { |
392 | |
393 | int level, ret; |
394 | |
395 | if (sscanf(buf, "%i" , &level) != 1 || |
396 | (level < 0 || level >= MSI_LCD_LEVEL_MAX)) |
397 | return -EINVAL; |
398 | |
399 | ret = set_lcd_level(level); |
400 | if (ret < 0) |
401 | return ret; |
402 | |
403 | return count; |
404 | } |
405 | |
406 | static ssize_t show_auto_brightness(struct device *dev, |
407 | struct device_attribute *attr, char *buf) |
408 | { |
409 | |
410 | int ret; |
411 | |
412 | ret = get_auto_brightness(); |
413 | if (ret < 0) |
414 | return ret; |
415 | |
416 | return sprintf(buf, fmt: "%i\n" , ret); |
417 | } |
418 | |
419 | static ssize_t store_auto_brightness(struct device *dev, |
420 | struct device_attribute *attr, const char *buf, size_t count) |
421 | { |
422 | |
423 | int enable, ret; |
424 | |
425 | if (sscanf(buf, "%i" , &enable) != 1 || (enable != (enable & 1))) |
426 | return -EINVAL; |
427 | |
428 | ret = set_auto_brightness(enable); |
429 | if (ret < 0) |
430 | return ret; |
431 | |
432 | return count; |
433 | } |
434 | |
435 | static ssize_t show_touchpad(struct device *dev, |
436 | struct device_attribute *attr, char *buf) |
437 | { |
438 | |
439 | u8 rdata; |
440 | int result; |
441 | |
442 | result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, val: &rdata); |
443 | if (result < 0) |
444 | return result; |
445 | |
446 | return sprintf(buf, fmt: "%i\n" , !!(rdata & MSI_STANDARD_EC_TOUCHPAD_MASK)); |
447 | } |
448 | |
449 | static ssize_t show_turbo(struct device *dev, |
450 | struct device_attribute *attr, char *buf) |
451 | { |
452 | |
453 | u8 rdata; |
454 | int result; |
455 | |
456 | result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, val: &rdata); |
457 | if (result < 0) |
458 | return result; |
459 | |
460 | return sprintf(buf, fmt: "%i\n" , !!(rdata & MSI_STANDARD_EC_TURBO_MASK)); |
461 | } |
462 | |
463 | static ssize_t show_eco(struct device *dev, |
464 | struct device_attribute *attr, char *buf) |
465 | { |
466 | |
467 | u8 rdata; |
468 | int result; |
469 | |
470 | result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, val: &rdata); |
471 | if (result < 0) |
472 | return result; |
473 | |
474 | return sprintf(buf, fmt: "%i\n" , !!(rdata & MSI_STANDARD_EC_ECO_MASK)); |
475 | } |
476 | |
477 | static ssize_t show_turbo_cooldown(struct device *dev, |
478 | struct device_attribute *attr, char *buf) |
479 | { |
480 | |
481 | u8 rdata; |
482 | int result; |
483 | |
484 | result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, val: &rdata); |
485 | if (result < 0) |
486 | return result; |
487 | |
488 | return sprintf(buf, fmt: "%i\n" , (!!(rdata & MSI_STANDARD_EC_TURBO_MASK)) | |
489 | (!!(rdata & MSI_STANDARD_EC_TURBO_COOLDOWN_MASK) << 1)); |
490 | } |
491 | |
492 | static ssize_t show_auto_fan(struct device *dev, |
493 | struct device_attribute *attr, char *buf) |
494 | { |
495 | |
496 | u8 rdata; |
497 | int result; |
498 | |
499 | result = ec_read(MSI_STANDARD_EC_FAN_ADDRESS, val: &rdata); |
500 | if (result < 0) |
501 | return result; |
502 | |
503 | return sprintf(buf, fmt: "%i\n" , !!(rdata & MSI_STANDARD_EC_AUTOFAN_MASK)); |
504 | } |
505 | |
506 | static ssize_t store_auto_fan(struct device *dev, |
507 | struct device_attribute *attr, const char *buf, size_t count) |
508 | { |
509 | |
510 | int enable, result; |
511 | |
512 | if (sscanf(buf, "%i" , &enable) != 1 || (enable != (enable & 1))) |
513 | return -EINVAL; |
514 | |
515 | result = ec_write(MSI_STANDARD_EC_FAN_ADDRESS, val: enable); |
516 | if (result < 0) |
517 | return result; |
518 | |
519 | return count; |
520 | } |
521 | |
522 | static DEVICE_ATTR(lcd_level, 0644, show_lcd_level, store_lcd_level); |
523 | static DEVICE_ATTR(auto_brightness, 0644, show_auto_brightness, |
524 | store_auto_brightness); |
525 | static DEVICE_ATTR(bluetooth, 0444, show_bluetooth, NULL); |
526 | static DEVICE_ATTR(wlan, 0444, show_wlan, NULL); |
527 | static DEVICE_ATTR(threeg, 0444, show_threeg, NULL); |
528 | static DEVICE_ATTR(touchpad, 0444, show_touchpad, NULL); |
529 | static DEVICE_ATTR(turbo_mode, 0444, show_turbo, NULL); |
530 | static DEVICE_ATTR(eco_mode, 0444, show_eco, NULL); |
531 | static DEVICE_ATTR(turbo_cooldown, 0444, show_turbo_cooldown, NULL); |
532 | static DEVICE_ATTR(auto_fan, 0644, show_auto_fan, store_auto_fan); |
533 | |
534 | static struct attribute *msipf_attributes[] = { |
535 | &dev_attr_bluetooth.attr, |
536 | &dev_attr_wlan.attr, |
537 | &dev_attr_touchpad.attr, |
538 | &dev_attr_turbo_mode.attr, |
539 | &dev_attr_eco_mode.attr, |
540 | &dev_attr_turbo_cooldown.attr, |
541 | &dev_attr_auto_fan.attr, |
542 | NULL |
543 | }; |
544 | |
545 | static struct attribute *msipf_old_attributes[] = { |
546 | &dev_attr_lcd_level.attr, |
547 | &dev_attr_auto_brightness.attr, |
548 | NULL |
549 | }; |
550 | |
551 | static const struct attribute_group msipf_attribute_group = { |
552 | .attrs = msipf_attributes |
553 | }; |
554 | |
555 | static const struct attribute_group msipf_old_attribute_group = { |
556 | .attrs = msipf_old_attributes |
557 | }; |
558 | |
559 | static struct platform_driver msipf_driver = { |
560 | .driver = { |
561 | .name = "msi-laptop-pf" , |
562 | .pm = &msi_laptop_pm, |
563 | }, |
564 | }; |
565 | |
566 | static struct platform_device *msipf_device; |
567 | |
568 | /* Initialization */ |
569 | |
570 | static struct quirk_entry quirk_old_ec_model = { |
571 | .old_ec_model = true, |
572 | }; |
573 | |
574 | static struct quirk_entry quirk_load_scm_model = { |
575 | .load_scm_model = true, |
576 | .ec_delay = true, |
577 | }; |
578 | |
579 | static struct quirk_entry quirk_load_scm_ro_model = { |
580 | .load_scm_model = true, |
581 | .ec_read_only = true, |
582 | }; |
583 | |
584 | static int dmi_check_cb(const struct dmi_system_id *dmi) |
585 | { |
586 | pr_info("Identified laptop model '%s'\n" , dmi->ident); |
587 | |
588 | quirks = dmi->driver_data; |
589 | |
590 | return 1; |
591 | } |
592 | |
593 | static unsigned long msi_work_delay(int msecs) |
594 | { |
595 | if (quirks->ec_delay) |
596 | return msecs_to_jiffies(m: msecs); |
597 | |
598 | return 0; |
599 | } |
600 | |
601 | static const struct dmi_system_id msi_dmi_table[] __initconst = { |
602 | { |
603 | .ident = "MSI S270" , |
604 | .matches = { |
605 | DMI_MATCH(DMI_SYS_VENDOR, "MICRO-STAR INT" ), |
606 | DMI_MATCH(DMI_PRODUCT_NAME, "MS-1013" ), |
607 | DMI_MATCH(DMI_PRODUCT_VERSION, "0131" ), |
608 | DMI_MATCH(DMI_CHASSIS_VENDOR, "MICRO-STAR INT" ) |
609 | }, |
610 | .driver_data = &quirk_old_ec_model, |
611 | .callback = dmi_check_cb |
612 | }, |
613 | { |
614 | .ident = "MSI S271" , |
615 | .matches = { |
616 | DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International" ), |
617 | DMI_MATCH(DMI_PRODUCT_NAME, "MS-1058" ), |
618 | DMI_MATCH(DMI_PRODUCT_VERSION, "0581" ), |
619 | DMI_MATCH(DMI_BOARD_NAME, "MS-1058" ) |
620 | }, |
621 | .driver_data = &quirk_old_ec_model, |
622 | .callback = dmi_check_cb |
623 | }, |
624 | { |
625 | .ident = "MSI S420" , |
626 | .matches = { |
627 | DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International" ), |
628 | DMI_MATCH(DMI_PRODUCT_NAME, "MS-1412" ), |
629 | DMI_MATCH(DMI_BOARD_VENDOR, "MSI" ), |
630 | DMI_MATCH(DMI_BOARD_NAME, "MS-1412" ) |
631 | }, |
632 | .driver_data = &quirk_old_ec_model, |
633 | .callback = dmi_check_cb |
634 | }, |
635 | { |
636 | .ident = "Medion MD96100" , |
637 | .matches = { |
638 | DMI_MATCH(DMI_SYS_VENDOR, "NOTEBOOK" ), |
639 | DMI_MATCH(DMI_PRODUCT_NAME, "SAM2000" ), |
640 | DMI_MATCH(DMI_PRODUCT_VERSION, "0131" ), |
641 | DMI_MATCH(DMI_CHASSIS_VENDOR, "MICRO-STAR INT" ) |
642 | }, |
643 | .driver_data = &quirk_old_ec_model, |
644 | .callback = dmi_check_cb |
645 | }, |
646 | { |
647 | .ident = "MSI N034" , |
648 | .matches = { |
649 | DMI_MATCH(DMI_SYS_VENDOR, |
650 | "MICRO-STAR INTERNATIONAL CO., LTD" ), |
651 | DMI_MATCH(DMI_PRODUCT_NAME, "MS-N034" ), |
652 | DMI_MATCH(DMI_CHASSIS_VENDOR, |
653 | "MICRO-STAR INTERNATIONAL CO., LTD" ) |
654 | }, |
655 | .driver_data = &quirk_load_scm_model, |
656 | .callback = dmi_check_cb |
657 | }, |
658 | { |
659 | .ident = "MSI N051" , |
660 | .matches = { |
661 | DMI_MATCH(DMI_SYS_VENDOR, |
662 | "MICRO-STAR INTERNATIONAL CO., LTD" ), |
663 | DMI_MATCH(DMI_PRODUCT_NAME, "MS-N051" ), |
664 | DMI_MATCH(DMI_CHASSIS_VENDOR, |
665 | "MICRO-STAR INTERNATIONAL CO., LTD" ) |
666 | }, |
667 | .driver_data = &quirk_load_scm_model, |
668 | .callback = dmi_check_cb |
669 | }, |
670 | { |
671 | .ident = "MSI N014" , |
672 | .matches = { |
673 | DMI_MATCH(DMI_SYS_VENDOR, |
674 | "MICRO-STAR INTERNATIONAL CO., LTD" ), |
675 | DMI_MATCH(DMI_PRODUCT_NAME, "MS-N014" ), |
676 | }, |
677 | .driver_data = &quirk_load_scm_model, |
678 | .callback = dmi_check_cb |
679 | }, |
680 | { |
681 | .ident = "MSI CR620" , |
682 | .matches = { |
683 | DMI_MATCH(DMI_SYS_VENDOR, |
684 | "Micro-Star International" ), |
685 | DMI_MATCH(DMI_PRODUCT_NAME, "CR620" ), |
686 | }, |
687 | .driver_data = &quirk_load_scm_model, |
688 | .callback = dmi_check_cb |
689 | }, |
690 | { |
691 | .ident = "MSI U270" , |
692 | .matches = { |
693 | DMI_MATCH(DMI_SYS_VENDOR, |
694 | "Micro-Star International Co., Ltd." ), |
695 | DMI_MATCH(DMI_PRODUCT_NAME, "U270 series" ), |
696 | }, |
697 | .driver_data = &quirk_load_scm_model, |
698 | .callback = dmi_check_cb |
699 | }, |
700 | { |
701 | .ident = "MSI U90/U100" , |
702 | .matches = { |
703 | DMI_MATCH(DMI_SYS_VENDOR, |
704 | "MICRO-STAR INTERNATIONAL CO., LTD" ), |
705 | DMI_MATCH(DMI_PRODUCT_NAME, "U90/U100" ), |
706 | }, |
707 | .driver_data = &quirk_load_scm_ro_model, |
708 | .callback = dmi_check_cb |
709 | }, |
710 | { } |
711 | }; |
712 | MODULE_DEVICE_TABLE(dmi, msi_dmi_table); |
713 | |
714 | static int rfkill_bluetooth_set(void *data, bool blocked) |
715 | { |
716 | /* Do something with blocked...*/ |
717 | /* |
718 | * blocked == false is on |
719 | * blocked == true is off |
720 | */ |
721 | int result = set_device_state(buf: blocked ? "0" : "1" , count: 0, |
722 | MSI_STANDARD_EC_BLUETOOTH_MASK); |
723 | |
724 | return min(result, 0); |
725 | } |
726 | |
727 | static int rfkill_wlan_set(void *data, bool blocked) |
728 | { |
729 | int result = set_device_state(buf: blocked ? "0" : "1" , count: 0, |
730 | MSI_STANDARD_EC_WLAN_MASK); |
731 | |
732 | return min(result, 0); |
733 | } |
734 | |
735 | static int rfkill_threeg_set(void *data, bool blocked) |
736 | { |
737 | int result = set_device_state(buf: blocked ? "0" : "1" , count: 0, |
738 | MSI_STANDARD_EC_3G_MASK); |
739 | |
740 | return min(result, 0); |
741 | } |
742 | |
743 | static const struct rfkill_ops rfkill_bluetooth_ops = { |
744 | .set_block = rfkill_bluetooth_set |
745 | }; |
746 | |
747 | static const struct rfkill_ops rfkill_wlan_ops = { |
748 | .set_block = rfkill_wlan_set |
749 | }; |
750 | |
751 | static const struct rfkill_ops rfkill_threeg_ops = { |
752 | .set_block = rfkill_threeg_set |
753 | }; |
754 | |
755 | static void rfkill_cleanup(void) |
756 | { |
757 | if (rfk_bluetooth) { |
758 | rfkill_unregister(rfkill: rfk_bluetooth); |
759 | rfkill_destroy(rfkill: rfk_bluetooth); |
760 | } |
761 | |
762 | if (rfk_threeg) { |
763 | rfkill_unregister(rfkill: rfk_threeg); |
764 | rfkill_destroy(rfkill: rfk_threeg); |
765 | } |
766 | |
767 | if (rfk_wlan) { |
768 | rfkill_unregister(rfkill: rfk_wlan); |
769 | rfkill_destroy(rfkill: rfk_wlan); |
770 | } |
771 | } |
772 | |
773 | static bool msi_rfkill_set_state(struct rfkill *rfkill, bool blocked) |
774 | { |
775 | if (quirks->ec_read_only) |
776 | return rfkill_set_hw_state(rfkill, blocked); |
777 | else |
778 | return rfkill_set_sw_state(rfkill, blocked); |
779 | } |
780 | |
781 | static void msi_update_rfkill(struct work_struct *ignored) |
782 | { |
783 | get_wireless_state_ec_standard(); |
784 | |
785 | if (rfk_wlan) |
786 | msi_rfkill_set_state(rfkill: rfk_wlan, blocked: !wlan_s); |
787 | if (rfk_bluetooth) |
788 | msi_rfkill_set_state(rfkill: rfk_bluetooth, blocked: !bluetooth_s); |
789 | if (rfk_threeg) |
790 | msi_rfkill_set_state(rfkill: rfk_threeg, blocked: !threeg_s); |
791 | } |
792 | static DECLARE_DELAYED_WORK(msi_rfkill_dwork, msi_update_rfkill); |
793 | |
794 | static void msi_send_touchpad_key(struct work_struct *ignored) |
795 | { |
796 | u8 rdata; |
797 | int result; |
798 | |
799 | result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, val: &rdata); |
800 | if (result < 0) |
801 | return; |
802 | |
803 | sparse_keymap_report_event(dev: msi_laptop_input_dev, |
804 | code: (rdata & MSI_STANDARD_EC_TOUCHPAD_MASK) ? |
805 | KEY_TOUCHPAD_ON : KEY_TOUCHPAD_OFF, value: 1, autorelease: true); |
806 | } |
807 | static DECLARE_DELAYED_WORK(msi_touchpad_dwork, msi_send_touchpad_key); |
808 | |
809 | static bool msi_laptop_i8042_filter(unsigned char data, unsigned char str, |
810 | struct serio *port) |
811 | { |
812 | static bool extended; |
813 | |
814 | if (str & I8042_STR_AUXDATA) |
815 | return false; |
816 | |
817 | /* 0x54 wwan, 0x62 bluetooth, 0x76 wlan, 0xE4 touchpad toggle*/ |
818 | if (unlikely(data == 0xe0)) { |
819 | extended = true; |
820 | return false; |
821 | } else if (unlikely(extended)) { |
822 | extended = false; |
823 | switch (data) { |
824 | case 0xE4: |
825 | schedule_delayed_work(dwork: &msi_touchpad_dwork, delay: msi_work_delay(msecs: 500)); |
826 | break; |
827 | case 0x54: |
828 | case 0x62: |
829 | case 0x76: |
830 | schedule_delayed_work(dwork: &msi_rfkill_dwork, delay: msi_work_delay(msecs: 500)); |
831 | break; |
832 | } |
833 | } |
834 | |
835 | return false; |
836 | } |
837 | |
838 | static void msi_init_rfkill(struct work_struct *ignored) |
839 | { |
840 | if (rfk_wlan) { |
841 | msi_rfkill_set_state(rfkill: rfk_wlan, blocked: !wlan_s); |
842 | rfkill_wlan_set(NULL, blocked: !wlan_s); |
843 | } |
844 | if (rfk_bluetooth) { |
845 | msi_rfkill_set_state(rfkill: rfk_bluetooth, blocked: !bluetooth_s); |
846 | rfkill_bluetooth_set(NULL, blocked: !bluetooth_s); |
847 | } |
848 | if (rfk_threeg) { |
849 | msi_rfkill_set_state(rfkill: rfk_threeg, blocked: !threeg_s); |
850 | rfkill_threeg_set(NULL, blocked: !threeg_s); |
851 | } |
852 | } |
853 | static DECLARE_DELAYED_WORK(msi_rfkill_init, msi_init_rfkill); |
854 | |
855 | static int rfkill_init(struct platform_device *sdev) |
856 | { |
857 | /* add rfkill */ |
858 | int retval; |
859 | |
860 | /* keep the hardware wireless state */ |
861 | get_wireless_state_ec_standard(); |
862 | |
863 | rfk_bluetooth = rfkill_alloc(name: "msi-bluetooth" , parent: &sdev->dev, |
864 | type: RFKILL_TYPE_BLUETOOTH, |
865 | ops: &rfkill_bluetooth_ops, NULL); |
866 | if (!rfk_bluetooth) { |
867 | retval = -ENOMEM; |
868 | goto err_bluetooth; |
869 | } |
870 | retval = rfkill_register(rfkill: rfk_bluetooth); |
871 | if (retval) |
872 | goto err_bluetooth; |
873 | |
874 | rfk_wlan = rfkill_alloc(name: "msi-wlan" , parent: &sdev->dev, type: RFKILL_TYPE_WLAN, |
875 | ops: &rfkill_wlan_ops, NULL); |
876 | if (!rfk_wlan) { |
877 | retval = -ENOMEM; |
878 | goto err_wlan; |
879 | } |
880 | retval = rfkill_register(rfkill: rfk_wlan); |
881 | if (retval) |
882 | goto err_wlan; |
883 | |
884 | if (threeg_exists) { |
885 | rfk_threeg = rfkill_alloc(name: "msi-threeg" , parent: &sdev->dev, |
886 | type: RFKILL_TYPE_WWAN, ops: &rfkill_threeg_ops, NULL); |
887 | if (!rfk_threeg) { |
888 | retval = -ENOMEM; |
889 | goto err_threeg; |
890 | } |
891 | retval = rfkill_register(rfkill: rfk_threeg); |
892 | if (retval) |
893 | goto err_threeg; |
894 | } |
895 | |
896 | /* schedule to run rfkill state initial */ |
897 | schedule_delayed_work(dwork: &msi_rfkill_init, delay: msi_work_delay(msecs: 1000)); |
898 | return 0; |
899 | |
900 | err_threeg: |
901 | rfkill_destroy(rfkill: rfk_threeg); |
902 | if (rfk_wlan) |
903 | rfkill_unregister(rfkill: rfk_wlan); |
904 | err_wlan: |
905 | rfkill_destroy(rfkill: rfk_wlan); |
906 | if (rfk_bluetooth) |
907 | rfkill_unregister(rfkill: rfk_bluetooth); |
908 | err_bluetooth: |
909 | rfkill_destroy(rfkill: rfk_bluetooth); |
910 | |
911 | return retval; |
912 | } |
913 | |
914 | static int msi_scm_disable_hw_fn_handling(void) |
915 | { |
916 | u8 data; |
917 | int result; |
918 | |
919 | if (!quirks->load_scm_model) |
920 | return 0; |
921 | |
922 | /* set load SCM to disable hardware control by fn key */ |
923 | result = ec_read(MSI_STANDARD_EC_SCM_LOAD_ADDRESS, val: &data); |
924 | if (result < 0) |
925 | return result; |
926 | |
927 | result = ec_write(MSI_STANDARD_EC_SCM_LOAD_ADDRESS, |
928 | val: data | MSI_STANDARD_EC_SCM_LOAD_MASK); |
929 | if (result < 0) |
930 | return result; |
931 | |
932 | return 0; |
933 | } |
934 | |
935 | #ifdef CONFIG_PM_SLEEP |
936 | static int msi_laptop_resume(struct device *device) |
937 | { |
938 | return msi_scm_disable_hw_fn_handling(); |
939 | } |
940 | #endif |
941 | |
942 | static int __init msi_laptop_input_setup(void) |
943 | { |
944 | int err; |
945 | |
946 | msi_laptop_input_dev = input_allocate_device(); |
947 | if (!msi_laptop_input_dev) |
948 | return -ENOMEM; |
949 | |
950 | msi_laptop_input_dev->name = "MSI Laptop hotkeys" ; |
951 | msi_laptop_input_dev->phys = "msi-laptop/input0" ; |
952 | msi_laptop_input_dev->id.bustype = BUS_HOST; |
953 | |
954 | err = sparse_keymap_setup(dev: msi_laptop_input_dev, |
955 | keymap: msi_laptop_keymap, NULL); |
956 | if (err) |
957 | goto err_free_dev; |
958 | |
959 | err = input_register_device(msi_laptop_input_dev); |
960 | if (err) |
961 | goto err_free_dev; |
962 | |
963 | return 0; |
964 | |
965 | err_free_dev: |
966 | input_free_device(dev: msi_laptop_input_dev); |
967 | return err; |
968 | } |
969 | |
970 | static int __init load_scm_model_init(struct platform_device *sdev) |
971 | { |
972 | int result; |
973 | |
974 | if (!quirks->ec_read_only) { |
975 | /* allow userland write sysfs file */ |
976 | dev_attr_bluetooth.store = store_bluetooth; |
977 | dev_attr_wlan.store = store_wlan; |
978 | dev_attr_threeg.store = store_threeg; |
979 | dev_attr_bluetooth.attr.mode |= S_IWUSR; |
980 | dev_attr_wlan.attr.mode |= S_IWUSR; |
981 | dev_attr_threeg.attr.mode |= S_IWUSR; |
982 | } |
983 | |
984 | /* disable hardware control by fn key */ |
985 | result = msi_scm_disable_hw_fn_handling(); |
986 | if (result < 0) |
987 | return result; |
988 | |
989 | /* initial rfkill */ |
990 | result = rfkill_init(sdev); |
991 | if (result < 0) |
992 | goto fail_rfkill; |
993 | |
994 | /* setup input device */ |
995 | result = msi_laptop_input_setup(); |
996 | if (result) |
997 | goto fail_input; |
998 | |
999 | result = i8042_install_filter(filter: msi_laptop_i8042_filter); |
1000 | if (result) { |
1001 | pr_err("Unable to install key filter\n" ); |
1002 | goto fail_filter; |
1003 | } |
1004 | |
1005 | return 0; |
1006 | |
1007 | fail_filter: |
1008 | input_unregister_device(msi_laptop_input_dev); |
1009 | |
1010 | fail_input: |
1011 | rfkill_cleanup(); |
1012 | |
1013 | fail_rfkill: |
1014 | return result; |
1015 | } |
1016 | |
1017 | static void msi_scm_model_exit(void) |
1018 | { |
1019 | if (!quirks->load_scm_model) |
1020 | return; |
1021 | |
1022 | i8042_remove_filter(filter: msi_laptop_i8042_filter); |
1023 | cancel_delayed_work_sync(dwork: &msi_touchpad_dwork); |
1024 | input_unregister_device(msi_laptop_input_dev); |
1025 | cancel_delayed_work_sync(dwork: &msi_rfkill_dwork); |
1026 | rfkill_cleanup(); |
1027 | } |
1028 | |
1029 | static int __init msi_init(void) |
1030 | { |
1031 | int ret; |
1032 | |
1033 | if (acpi_disabled) |
1034 | return -ENODEV; |
1035 | |
1036 | dmi_check_system(list: msi_dmi_table); |
1037 | if (!quirks) |
1038 | /* quirks may be NULL if no match in DMI table */ |
1039 | quirks = &quirk_load_scm_model; |
1040 | if (force) |
1041 | quirks = &quirk_old_ec_model; |
1042 | |
1043 | if (!quirks->old_ec_model) |
1044 | get_threeg_exists(); |
1045 | |
1046 | if (auto_brightness < 0 || auto_brightness > 2) |
1047 | return -EINVAL; |
1048 | |
1049 | /* Register backlight stuff */ |
1050 | if (quirks->old_ec_model && |
1051 | acpi_video_get_backlight_type() == acpi_backlight_vendor) { |
1052 | struct backlight_properties props; |
1053 | memset(&props, 0, sizeof(struct backlight_properties)); |
1054 | props.type = BACKLIGHT_PLATFORM; |
1055 | props.max_brightness = MSI_LCD_LEVEL_MAX - 1; |
1056 | msibl_device = backlight_device_register(name: "msi-laptop-bl" , NULL, |
1057 | NULL, ops: &msibl_ops, |
1058 | props: &props); |
1059 | if (IS_ERR(ptr: msibl_device)) |
1060 | return PTR_ERR(ptr: msibl_device); |
1061 | } |
1062 | |
1063 | ret = platform_driver_register(&msipf_driver); |
1064 | if (ret) |
1065 | goto fail_backlight; |
1066 | |
1067 | /* Register platform stuff */ |
1068 | |
1069 | msipf_device = platform_device_alloc(name: "msi-laptop-pf" , PLATFORM_DEVID_NONE); |
1070 | if (!msipf_device) { |
1071 | ret = -ENOMEM; |
1072 | goto fail_platform_driver; |
1073 | } |
1074 | |
1075 | ret = platform_device_add(pdev: msipf_device); |
1076 | if (ret) |
1077 | goto fail_device_add; |
1078 | |
1079 | if (quirks->load_scm_model && (load_scm_model_init(sdev: msipf_device) < 0)) { |
1080 | ret = -EINVAL; |
1081 | goto fail_scm_model_init; |
1082 | } |
1083 | |
1084 | ret = sysfs_create_group(kobj: &msipf_device->dev.kobj, |
1085 | grp: &msipf_attribute_group); |
1086 | if (ret) |
1087 | goto fail_create_group; |
1088 | |
1089 | if (!quirks->old_ec_model) { |
1090 | if (threeg_exists) |
1091 | ret = device_create_file(device: &msipf_device->dev, |
1092 | entry: &dev_attr_threeg); |
1093 | if (ret) |
1094 | goto fail_create_attr; |
1095 | } else { |
1096 | ret = sysfs_create_group(kobj: &msipf_device->dev.kobj, |
1097 | grp: &msipf_old_attribute_group); |
1098 | if (ret) |
1099 | goto fail_create_attr; |
1100 | |
1101 | /* Disable automatic brightness control by default because |
1102 | * this module was probably loaded to do brightness control in |
1103 | * software. */ |
1104 | |
1105 | if (auto_brightness != 2) |
1106 | set_auto_brightness(auto_brightness); |
1107 | } |
1108 | |
1109 | return 0; |
1110 | |
1111 | fail_create_attr: |
1112 | sysfs_remove_group(kobj: &msipf_device->dev.kobj, grp: &msipf_attribute_group); |
1113 | fail_create_group: |
1114 | msi_scm_model_exit(); |
1115 | fail_scm_model_init: |
1116 | platform_device_del(pdev: msipf_device); |
1117 | fail_device_add: |
1118 | platform_device_put(pdev: msipf_device); |
1119 | fail_platform_driver: |
1120 | platform_driver_unregister(&msipf_driver); |
1121 | fail_backlight: |
1122 | backlight_device_unregister(bd: msibl_device); |
1123 | |
1124 | return ret; |
1125 | } |
1126 | |
1127 | static void __exit msi_cleanup(void) |
1128 | { |
1129 | msi_scm_model_exit(); |
1130 | sysfs_remove_group(kobj: &msipf_device->dev.kobj, grp: &msipf_attribute_group); |
1131 | if (!quirks->old_ec_model && threeg_exists) |
1132 | device_remove_file(dev: &msipf_device->dev, attr: &dev_attr_threeg); |
1133 | platform_device_unregister(msipf_device); |
1134 | platform_driver_unregister(&msipf_driver); |
1135 | backlight_device_unregister(bd: msibl_device); |
1136 | |
1137 | if (quirks->old_ec_model) { |
1138 | /* Enable automatic brightness control again */ |
1139 | if (auto_brightness != 2) |
1140 | set_auto_brightness(1); |
1141 | } |
1142 | } |
1143 | |
1144 | module_init(msi_init); |
1145 | module_exit(msi_cleanup); |
1146 | |
1147 | MODULE_AUTHOR("Lennart Poettering" ); |
1148 | MODULE_DESCRIPTION("MSI Laptop Support" ); |
1149 | MODULE_LICENSE("GPL" ); |
1150 | |