blob: b08a108c977688202541a7dfae11c1b87e22db1c [file] [log] [blame]
Lennart Poettering8c4c7312006-10-06 01:27:02 -04001/*-*-linux-c-*-*/
2
3/*
4 Copyright (C) 2006 Lennart Poettering <mzxreary (at) 0pointer (dot) de>
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301, USA.
20 */
21
22/*
23 * msi-laptop.c - MSI S270 laptop support. This laptop is sold under
24 * various brands, including "Cytron/TCM/Medion/Tchibo MD96100".
25 *
Lennart Poettering4f5c7912007-05-08 22:07:02 +020026 * Driver also supports S271, S420 models.
27 *
Lennart Poettering8c4c7312006-10-06 01:27:02 -040028 * This driver exports a few files in /sys/devices/platform/msi-laptop-pf/:
29 *
30 * lcd_level - Screen brightness: contains a single integer in the
31 * range 0..8. (rw)
32 *
33 * auto_brightness - Enable automatic brightness control: contains
34 * either 0 or 1. If set to 1 the hardware adjusts the screen
35 * brightness automatically when the power cord is
36 * plugged/unplugged. (rw)
37 *
38 * wlan - WLAN subsystem enabled: contains either 0 or 1. (ro)
39 *
40 * bluetooth - Bluetooth subsystem enabled: contains either 0 or 1
41 * Please note that this file is constantly 0 if no Bluetooth
42 * hardware is available. (ro)
43 *
44 * In addition to these platform device attributes the driver
45 * registers itself in the Linux backlight control subsystem and is
46 * available to userspace under /sys/class/backlight/msi-laptop-bl/.
47 *
48 * This driver might work on other laptops produced by MSI. If you
49 * want to try it you can pass force=1 as argument to the module which
50 * will force it to load even when the DMI data doesn't identify the
51 * laptop as MSI S270. YMMV.
52 */
53
54#include <linux/module.h>
55#include <linux/kernel.h>
56#include <linux/init.h>
57#include <linux/acpi.h>
58#include <linux/dmi.h>
59#include <linux/backlight.h>
60#include <linux/platform_device.h>
Lee, Chun-Yi472ea122010-01-22 00:15:59 +080061#include <linux/rfkill.h>
Lee, Chun-Yi339e7532010-05-12 09:58:10 -070062#include <linux/i8042.h>
Lennart Poettering8c4c7312006-10-06 01:27:02 -040063
64#define MSI_DRIVER_VERSION "0.5"
65
66#define MSI_LCD_LEVEL_MAX 9
67
68#define MSI_EC_COMMAND_WIRELESS 0x10
69#define MSI_EC_COMMAND_LCD_LEVEL 0x11
70
Lee, Chun-Yi46d0e9e2010-01-09 21:16:52 +080071#define MSI_STANDARD_EC_COMMAND_ADDRESS 0x2e
72#define MSI_STANDARD_EC_BLUETOOTH_MASK (1 << 0)
73#define MSI_STANDARD_EC_WEBCAM_MASK (1 << 1)
74#define MSI_STANDARD_EC_WLAN_MASK (1 << 3)
Lee, Chun-Yifc0dc4c2010-01-09 23:17:07 +080075#define MSI_STANDARD_EC_3G_MASK (1 << 4)
Lee, Chun-Yi46d0e9e2010-01-09 21:16:52 +080076
Lee, Chun-Yi472ea122010-01-22 00:15:59 +080077/* For set SCM load flag to disable BIOS fn key */
78#define MSI_STANDARD_EC_SCM_LOAD_ADDRESS 0x2d
79#define MSI_STANDARD_EC_SCM_LOAD_MASK (1 << 0)
80
Lee, Chun-Yiec766272010-01-27 00:13:45 +080081static int msi_laptop_resume(struct platform_device *device);
82
Lee, Chun-Yie22388e2010-01-27 12:23:00 +080083#define MSI_STANDARD_EC_DEVICES_EXISTS_ADDRESS 0x2f
84
Lennart Poettering8c4c7312006-10-06 01:27:02 -040085static int force;
86module_param(force, bool, 0);
87MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
88
89static int auto_brightness;
90module_param(auto_brightness, int, 0);
91MODULE_PARM_DESC(auto_brightness, "Enable automatic brightness control (0: disabled; 1: enabled; 2: don't touch)");
92
Lee, Chun-Yi46d0e9e2010-01-09 21:16:52 +080093static bool old_ec_model;
Lee, Chun-Yifc0dc4c2010-01-09 23:17:07 +080094static int wlan_s, bluetooth_s, threeg_s;
Lee, Chun-Yie22388e2010-01-27 12:23:00 +080095static int threeg_exists;
Lee, Chun-Yi46d0e9e2010-01-09 21:16:52 +080096
Lee, Chun-Yi472ea122010-01-22 00:15:59 +080097/* Some MSI 3G netbook only have one fn key to control Wlan/Bluetooth/3G,
98 * those netbook will load the SCM (windows app) to disable the original
99 * Wlan/Bluetooth control by BIOS when user press fn key, then control
100 * Wlan/Bluetooth/3G by SCM (software control by OS). Without SCM, user
101 * cann't on/off 3G module on those 3G netbook.
102 * On Linux, msi-laptop driver will do the same thing to disable the
103 * original BIOS control, then might need use HAL or other userland
104 * application to do the software control that simulate with SCM.
105 * e.g. MSI N034 netbook
106 */
107static bool load_scm_model;
108static struct rfkill *rfk_wlan, *rfk_bluetooth, *rfk_threeg;
109
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400110/* Hardware access */
111
112static int set_lcd_level(int level)
113{
114 u8 buf[2];
115
116 if (level < 0 || level >= MSI_LCD_LEVEL_MAX)
117 return -EINVAL;
118
119 buf[0] = 0x80;
120 buf[1] = (u8) (level*31);
121
Lennart Poettering00eb43a2007-05-04 14:16:19 +0200122 return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, buf, sizeof(buf), NULL, 0, 1);
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400123}
124
125static int get_lcd_level(void)
126{
127 u8 wdata = 0, rdata;
128 int result;
129
Lennart Poettering00eb43a2007-05-04 14:16:19 +0200130 result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, &rdata, 1, 1);
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400131 if (result < 0)
132 return result;
133
134 return (int) rdata / 31;
135}
136
137static int get_auto_brightness(void)
138{
139 u8 wdata = 4, rdata;
140 int result;
141
Lennart Poettering00eb43a2007-05-04 14:16:19 +0200142 result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, &rdata, 1, 1);
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400143 if (result < 0)
144 return result;
145
146 return !!(rdata & 8);
147}
148
149static int set_auto_brightness(int enable)
150{
151 u8 wdata[2], rdata;
152 int result;
153
154 wdata[0] = 4;
155
Lennart Poettering00eb43a2007-05-04 14:16:19 +0200156 result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 1, &rdata, 1, 1);
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400157 if (result < 0)
158 return result;
159
160 wdata[0] = 0x84;
161 wdata[1] = (rdata & 0xF7) | (enable ? 8 : 0);
162
Lennart Poettering00eb43a2007-05-04 14:16:19 +0200163 return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 2, NULL, 0, 1);
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400164}
165
Lee, Chun-Yi472ea122010-01-22 00:15:59 +0800166static ssize_t set_device_state(const char *buf, size_t count, u8 mask)
167{
168 int status;
169 u8 wdata = 0, rdata;
170 int result;
171
172 if (sscanf(buf, "%i", &status) != 1 || (status < 0 || status > 1))
173 return -EINVAL;
174
175 /* read current device state */
176 result = ec_read(MSI_STANDARD_EC_COMMAND_ADDRESS, &rdata);
177 if (result < 0)
178 return -EINVAL;
179
180 if (!!(rdata & mask) != status) {
181 /* reverse device bit */
182 if (rdata & mask)
183 wdata = rdata & ~mask;
184 else
185 wdata = rdata | mask;
186
187 result = ec_write(MSI_STANDARD_EC_COMMAND_ADDRESS, wdata);
188 if (result < 0)
189 return -EINVAL;
190 }
191
192 return count;
193}
194
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400195static int get_wireless_state(int *wlan, int *bluetooth)
196{
197 u8 wdata = 0, rdata;
198 int result;
199
Lennart Poettering00eb43a2007-05-04 14:16:19 +0200200 result = ec_transaction(MSI_EC_COMMAND_WIRELESS, &wdata, 1, &rdata, 1, 1);
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400201 if (result < 0)
202 return -1;
203
204 if (wlan)
205 *wlan = !!(rdata & 8);
206
207 if (bluetooth)
208 *bluetooth = !!(rdata & 128);
209
210 return 0;
211}
212
Lee, Chun-Yi46d0e9e2010-01-09 21:16:52 +0800213static int get_wireless_state_ec_standard(void)
214{
215 u8 rdata;
216 int result;
217
218 result = ec_read(MSI_STANDARD_EC_COMMAND_ADDRESS, &rdata);
219 if (result < 0)
220 return -1;
221
222 wlan_s = !!(rdata & MSI_STANDARD_EC_WLAN_MASK);
223
224 bluetooth_s = !!(rdata & MSI_STANDARD_EC_BLUETOOTH_MASK);
225
Lee, Chun-Yifc0dc4c2010-01-09 23:17:07 +0800226 threeg_s = !!(rdata & MSI_STANDARD_EC_3G_MASK);
227
Lee, Chun-Yi46d0e9e2010-01-09 21:16:52 +0800228 return 0;
229}
230
Lee, Chun-Yie22388e2010-01-27 12:23:00 +0800231static int get_threeg_exists(void)
232{
233 u8 rdata;
234 int result;
235
236 result = ec_read(MSI_STANDARD_EC_DEVICES_EXISTS_ADDRESS, &rdata);
237 if (result < 0)
238 return -1;
239
240 threeg_exists = !!(rdata & MSI_STANDARD_EC_3G_MASK);
241
242 return 0;
243}
244
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400245/* Backlight device stuff */
246
247static int bl_get_brightness(struct backlight_device *b)
248{
249 return get_lcd_level();
250}
251
252
253static int bl_update_status(struct backlight_device *b)
254{
Richard Purdie599a52d2007-02-10 23:07:48 +0000255 return set_lcd_level(b->props.brightness);
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400256}
257
Richard Purdie599a52d2007-02-10 23:07:48 +0000258static struct backlight_ops msibl_ops = {
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400259 .get_brightness = bl_get_brightness,
260 .update_status = bl_update_status,
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400261};
262
263static struct backlight_device *msibl_device;
264
265/* Platform device */
266
267static ssize_t show_wlan(struct device *dev,
268 struct device_attribute *attr, char *buf)
269{
270
271 int ret, enabled;
272
Lee, Chun-Yi46d0e9e2010-01-09 21:16:52 +0800273 if (old_ec_model) {
274 ret = get_wireless_state(&enabled, NULL);
275 } else {
276 ret = get_wireless_state_ec_standard();
277 enabled = wlan_s;
278 }
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400279 if (ret < 0)
280 return ret;
281
282 return sprintf(buf, "%i\n", enabled);
283}
284
Lee, Chun-Yi472ea122010-01-22 00:15:59 +0800285static ssize_t store_wlan(struct device *dev,
286 struct device_attribute *attr, const char *buf, size_t count)
287{
288 return set_device_state(buf, count, MSI_STANDARD_EC_WLAN_MASK);
289}
290
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400291static ssize_t show_bluetooth(struct device *dev,
292 struct device_attribute *attr, char *buf)
293{
294
295 int ret, enabled;
296
Lee, Chun-Yi46d0e9e2010-01-09 21:16:52 +0800297 if (old_ec_model) {
298 ret = get_wireless_state(NULL, &enabled);
299 } else {
300 ret = get_wireless_state_ec_standard();
301 enabled = bluetooth_s;
302 }
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400303 if (ret < 0)
304 return ret;
305
306 return sprintf(buf, "%i\n", enabled);
307}
308
Lee, Chun-Yi472ea122010-01-22 00:15:59 +0800309static ssize_t store_bluetooth(struct device *dev,
310 struct device_attribute *attr, const char *buf, size_t count)
311{
312 return set_device_state(buf, count, MSI_STANDARD_EC_BLUETOOTH_MASK);
313}
314
Lee, Chun-Yifc0dc4c2010-01-09 23:17:07 +0800315static ssize_t show_threeg(struct device *dev,
316 struct device_attribute *attr, char *buf)
317{
318
319 int ret;
320
321 /* old msi ec not support 3G */
322 if (old_ec_model)
323 return -1;
324
325 ret = get_wireless_state_ec_standard();
326 if (ret < 0)
327 return ret;
328
329 return sprintf(buf, "%i\n", threeg_s);
330}
331
Lee, Chun-Yi472ea122010-01-22 00:15:59 +0800332static ssize_t store_threeg(struct device *dev,
333 struct device_attribute *attr, const char *buf, size_t count)
334{
335 return set_device_state(buf, count, MSI_STANDARD_EC_3G_MASK);
336}
337
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400338static ssize_t show_lcd_level(struct device *dev,
339 struct device_attribute *attr, char *buf)
340{
341
342 int ret;
343
344 ret = get_lcd_level();
345 if (ret < 0)
346 return ret;
347
348 return sprintf(buf, "%i\n", ret);
349}
350
351static ssize_t store_lcd_level(struct device *dev,
352 struct device_attribute *attr, const char *buf, size_t count)
353{
354
355 int level, ret;
356
357 if (sscanf(buf, "%i", &level) != 1 || (level < 0 || level >= MSI_LCD_LEVEL_MAX))
358 return -EINVAL;
359
360 ret = set_lcd_level(level);
361 if (ret < 0)
362 return ret;
363
364 return count;
365}
366
367static ssize_t show_auto_brightness(struct device *dev,
368 struct device_attribute *attr, char *buf)
369{
370
371 int ret;
372
373 ret = get_auto_brightness();
374 if (ret < 0)
375 return ret;
376
377 return sprintf(buf, "%i\n", ret);
378}
379
380static ssize_t store_auto_brightness(struct device *dev,
381 struct device_attribute *attr, const char *buf, size_t count)
382{
383
384 int enable, ret;
385
386 if (sscanf(buf, "%i", &enable) != 1 || (enable != (enable & 1)))
387 return -EINVAL;
388
389 ret = set_auto_brightness(enable);
390 if (ret < 0)
391 return ret;
392
393 return count;
394}
395
396static DEVICE_ATTR(lcd_level, 0644, show_lcd_level, store_lcd_level);
397static DEVICE_ATTR(auto_brightness, 0644, show_auto_brightness, store_auto_brightness);
398static DEVICE_ATTR(bluetooth, 0444, show_bluetooth, NULL);
399static DEVICE_ATTR(wlan, 0444, show_wlan, NULL);
Lee, Chun-Yifc0dc4c2010-01-09 23:17:07 +0800400static DEVICE_ATTR(threeg, 0444, show_threeg, NULL);
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400401
402static struct attribute *msipf_attributes[] = {
403 &dev_attr_lcd_level.attr,
404 &dev_attr_auto_brightness.attr,
405 &dev_attr_bluetooth.attr,
406 &dev_attr_wlan.attr,
407 NULL
408};
409
410static struct attribute_group msipf_attribute_group = {
411 .attrs = msipf_attributes
412};
413
414static struct platform_driver msipf_driver = {
415 .driver = {
416 .name = "msi-laptop-pf",
417 .owner = THIS_MODULE,
Lee, Chun-Yiec766272010-01-27 00:13:45 +0800418 },
419 .resume = msi_laptop_resume,
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400420};
421
422static struct platform_device *msipf_device;
423
424/* Initialization */
425
Jeff Garzik18552562007-10-03 15:15:40 -0400426static int dmi_check_cb(const struct dmi_system_id *id)
Lennart Poettering4f5c7912007-05-08 22:07:02 +0200427{
428 printk("msi-laptop: Identified laptop model '%s'.\n", id->ident);
429 return 0;
430}
431
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400432static struct dmi_system_id __initdata msi_dmi_table[] = {
433 {
434 .ident = "MSI S270",
435 .matches = {
436 DMI_MATCH(DMI_SYS_VENDOR, "MICRO-STAR INT'L CO.,LTD"),
437 DMI_MATCH(DMI_PRODUCT_NAME, "MS-1013"),
Lennart Poettering4f5c7912007-05-08 22:07:02 +0200438 DMI_MATCH(DMI_PRODUCT_VERSION, "0131"),
439 DMI_MATCH(DMI_CHASSIS_VENDOR, "MICRO-STAR INT'L CO.,LTD")
440 },
441 .callback = dmi_check_cb
442 },
443 {
444 .ident = "MSI S271",
445 .matches = {
446 DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"),
447 DMI_MATCH(DMI_PRODUCT_NAME, "MS-1058"),
448 DMI_MATCH(DMI_PRODUCT_VERSION, "0581"),
449 DMI_MATCH(DMI_BOARD_NAME, "MS-1058")
450 },
451 .callback = dmi_check_cb
452 },
453 {
454 .ident = "MSI S420",
455 .matches = {
456 DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"),
457 DMI_MATCH(DMI_PRODUCT_NAME, "MS-1412"),
458 DMI_MATCH(DMI_BOARD_VENDOR, "MSI"),
459 DMI_MATCH(DMI_BOARD_NAME, "MS-1412")
460 },
461 .callback = dmi_check_cb
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400462 },
463 {
464 .ident = "Medion MD96100",
465 .matches = {
466 DMI_MATCH(DMI_SYS_VENDOR, "NOTEBOOK"),
467 DMI_MATCH(DMI_PRODUCT_NAME, "SAM2000"),
Lennart Poettering4f5c7912007-05-08 22:07:02 +0200468 DMI_MATCH(DMI_PRODUCT_VERSION, "0131"),
469 DMI_MATCH(DMI_CHASSIS_VENDOR, "MICRO-STAR INT'L CO.,LTD")
470 },
471 .callback = dmi_check_cb
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400472 },
Lee, Chun-Yid0a4aa22010-05-12 09:58:07 -0700473 {
474 .ident = "MSI N051",
475 .matches = {
476 DMI_MATCH(DMI_SYS_VENDOR,
477 "MICRO-STAR INTERNATIONAL CO., LTD"),
478 DMI_MATCH(DMI_PRODUCT_NAME, "MS-N051"),
479 DMI_MATCH(DMI_CHASSIS_VENDOR,
480 "MICRO-STAR INTERNATIONAL CO., LTD")
481 },
482 .callback = dmi_check_cb
483 },
484 {
485 .ident = "MSI N014",
486 .matches = {
487 DMI_MATCH(DMI_SYS_VENDOR,
488 "MICRO-STAR INTERNATIONAL CO., LTD"),
489 DMI_MATCH(DMI_PRODUCT_NAME, "MS-N014"),
490 },
491 .callback = dmi_check_cb
492 },
Lee, Chun-Yi1f27e172010-05-12 09:58:08 -0700493 {
494 .ident = "MSI CR620",
495 .matches = {
496 DMI_MATCH(DMI_SYS_VENDOR,
497 "Micro-Star International"),
498 DMI_MATCH(DMI_PRODUCT_NAME, "CR620"),
499 },
500 .callback = dmi_check_cb
501 },
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400502 { }
503};
504
Lee, Chun-Yi472ea122010-01-22 00:15:59 +0800505static struct dmi_system_id __initdata msi_load_scm_models_dmi_table[] = {
506 {
507 .ident = "MSI N034",
508 .matches = {
509 DMI_MATCH(DMI_SYS_VENDOR,
510 "MICRO-STAR INTERNATIONAL CO., LTD"),
511 DMI_MATCH(DMI_PRODUCT_NAME, "MS-N034"),
512 DMI_MATCH(DMI_CHASSIS_VENDOR,
513 "MICRO-STAR INTERNATIONAL CO., LTD")
514 },
515 .callback = dmi_check_cb
516 },
517 { }
518};
519
520static int rfkill_bluetooth_set(void *data, bool blocked)
521{
522 /* Do something with blocked...*/
523 /*
524 * blocked == false is on
525 * blocked == true is off
526 */
527 if (blocked)
528 set_device_state("0", 0, MSI_STANDARD_EC_BLUETOOTH_MASK);
529 else
530 set_device_state("1", 0, MSI_STANDARD_EC_BLUETOOTH_MASK);
531
532 return 0;
533}
534
535static int rfkill_wlan_set(void *data, bool blocked)
536{
537 if (blocked)
538 set_device_state("0", 0, MSI_STANDARD_EC_WLAN_MASK);
539 else
540 set_device_state("1", 0, MSI_STANDARD_EC_WLAN_MASK);
541
542 return 0;
543}
544
545static int rfkill_threeg_set(void *data, bool blocked)
546{
547 if (blocked)
548 set_device_state("0", 0, MSI_STANDARD_EC_3G_MASK);
549 else
550 set_device_state("1", 0, MSI_STANDARD_EC_3G_MASK);
551
552 return 0;
553}
554
555static struct rfkill_ops rfkill_bluetooth_ops = {
556 .set_block = rfkill_bluetooth_set
557};
558
559static struct rfkill_ops rfkill_wlan_ops = {
560 .set_block = rfkill_wlan_set
561};
562
563static struct rfkill_ops rfkill_threeg_ops = {
564 .set_block = rfkill_threeg_set
565};
566
567static void rfkill_cleanup(void)
568{
569 if (rfk_bluetooth) {
570 rfkill_unregister(rfk_bluetooth);
571 rfkill_destroy(rfk_bluetooth);
572 }
573
574 if (rfk_threeg) {
575 rfkill_unregister(rfk_threeg);
576 rfkill_destroy(rfk_threeg);
577 }
578
579 if (rfk_wlan) {
580 rfkill_unregister(rfk_wlan);
581 rfkill_destroy(rfk_wlan);
582 }
583}
584
Lee, Chun-Yi339e7532010-05-12 09:58:10 -0700585static void msi_update_rfkill(struct work_struct *ignored)
586{
587 get_wireless_state_ec_standard();
588
589 if (rfk_wlan)
590 rfkill_set_sw_state(rfk_wlan, !wlan_s);
591 if (rfk_bluetooth)
592 rfkill_set_sw_state(rfk_bluetooth, !bluetooth_s);
593 if (rfk_threeg)
594 rfkill_set_sw_state(rfk_threeg, !threeg_s);
595}
596static DECLARE_DELAYED_WORK(msi_rfkill_work, msi_update_rfkill);
597
598static bool msi_laptop_i8042_filter(unsigned char data, unsigned char str,
599 struct serio *port)
600{
601 static bool extended;
602
603 if (str & 0x20)
604 return false;
605
606 /* 0x54 wwan, 0x62 bluetooth, 0x76 wlan*/
607 if (unlikely(data == 0xe0)) {
608 extended = true;
609 return false;
610 } else if (unlikely(extended)) {
611 switch (data) {
612 case 0x54:
613 case 0x62:
614 case 0x76:
615 schedule_delayed_work(&msi_rfkill_work,
616 round_jiffies_relative(0.5 * HZ));
617 break;
618 }
619 extended = false;
620 }
621
622 return false;
623}
624
Lee, Chun-Yi3bb97022010-05-12 09:58:09 -0700625static void msi_init_rfkill(struct work_struct *ignored)
626{
627 if (rfk_wlan) {
628 rfkill_set_sw_state(rfk_wlan, !wlan_s);
629 rfkill_wlan_set(NULL, !wlan_s);
630 }
631 if (rfk_bluetooth) {
632 rfkill_set_sw_state(rfk_bluetooth, !bluetooth_s);
633 rfkill_bluetooth_set(NULL, !bluetooth_s);
634 }
635 if (rfk_threeg) {
636 rfkill_set_sw_state(rfk_threeg, !threeg_s);
637 rfkill_threeg_set(NULL, !threeg_s);
638 }
639}
640static DECLARE_DELAYED_WORK(msi_rfkill_init, msi_init_rfkill);
641
Lee, Chun-Yi472ea122010-01-22 00:15:59 +0800642static int rfkill_init(struct platform_device *sdev)
643{
644 /* add rfkill */
645 int retval;
646
Lee, Chun-Yi3bb97022010-05-12 09:58:09 -0700647 /* keep the hardware wireless state */
648 get_wireless_state_ec_standard();
649
Lee, Chun-Yi472ea122010-01-22 00:15:59 +0800650 rfk_bluetooth = rfkill_alloc("msi-bluetooth", &sdev->dev,
651 RFKILL_TYPE_BLUETOOTH,
652 &rfkill_bluetooth_ops, NULL);
653 if (!rfk_bluetooth) {
654 retval = -ENOMEM;
655 goto err_bluetooth;
656 }
657 retval = rfkill_register(rfk_bluetooth);
658 if (retval)
659 goto err_bluetooth;
660
661 rfk_wlan = rfkill_alloc("msi-wlan", &sdev->dev, RFKILL_TYPE_WLAN,
662 &rfkill_wlan_ops, NULL);
663 if (!rfk_wlan) {
664 retval = -ENOMEM;
665 goto err_wlan;
666 }
667 retval = rfkill_register(rfk_wlan);
668 if (retval)
669 goto err_wlan;
670
Lee, Chun-Yie22388e2010-01-27 12:23:00 +0800671 if (threeg_exists) {
672 rfk_threeg = rfkill_alloc("msi-threeg", &sdev->dev,
673 RFKILL_TYPE_WWAN, &rfkill_threeg_ops, NULL);
674 if (!rfk_threeg) {
675 retval = -ENOMEM;
676 goto err_threeg;
677 }
678 retval = rfkill_register(rfk_threeg);
679 if (retval)
680 goto err_threeg;
Lee, Chun-Yi472ea122010-01-22 00:15:59 +0800681 }
Lee, Chun-Yi472ea122010-01-22 00:15:59 +0800682
Lee, Chun-Yi3bb97022010-05-12 09:58:09 -0700683 /* schedule to run rfkill state initial */
684 schedule_delayed_work(&msi_rfkill_init,
685 round_jiffies_relative(1 * HZ));
686
Lee, Chun-Yi472ea122010-01-22 00:15:59 +0800687 return 0;
688
689err_threeg:
690 rfkill_destroy(rfk_threeg);
691 if (rfk_wlan)
692 rfkill_unregister(rfk_wlan);
693err_wlan:
694 rfkill_destroy(rfk_wlan);
695 if (rfk_bluetooth)
696 rfkill_unregister(rfk_bluetooth);
697err_bluetooth:
698 rfkill_destroy(rfk_bluetooth);
699
700 return retval;
701}
702
Lee, Chun-Yiec766272010-01-27 00:13:45 +0800703static int msi_laptop_resume(struct platform_device *device)
704{
705 u8 data;
706 int result;
707
708 if (!load_scm_model)
709 return 0;
710
711 /* set load SCM to disable hardware control by fn key */
712 result = ec_read(MSI_STANDARD_EC_SCM_LOAD_ADDRESS, &data);
713 if (result < 0)
714 return result;
715
716 result = ec_write(MSI_STANDARD_EC_SCM_LOAD_ADDRESS,
717 data | MSI_STANDARD_EC_SCM_LOAD_MASK);
718 if (result < 0)
719 return result;
720
721 return 0;
722}
723
Lee, Chun-Yi472ea122010-01-22 00:15:59 +0800724static int load_scm_model_init(struct platform_device *sdev)
725{
726 u8 data;
727 int result;
728
729 /* allow userland write sysfs file */
730 dev_attr_bluetooth.store = store_bluetooth;
731 dev_attr_wlan.store = store_wlan;
732 dev_attr_threeg.store = store_threeg;
733 dev_attr_bluetooth.attr.mode |= S_IWUSR;
734 dev_attr_wlan.attr.mode |= S_IWUSR;
735 dev_attr_threeg.attr.mode |= S_IWUSR;
736
737 /* disable hardware control by fn key */
738 result = ec_read(MSI_STANDARD_EC_SCM_LOAD_ADDRESS, &data);
739 if (result < 0)
740 return result;
741
742 result = ec_write(MSI_STANDARD_EC_SCM_LOAD_ADDRESS,
743 data | MSI_STANDARD_EC_SCM_LOAD_MASK);
744 if (result < 0)
745 return result;
746
747 /* initial rfkill */
748 result = rfkill_init(sdev);
749 if (result < 0)
Lee, Chun-Yi339e7532010-05-12 09:58:10 -0700750 goto fail_rfkill;
751
752 result = i8042_install_filter(msi_laptop_i8042_filter);
753 if (result) {
754 printk(KERN_ERR
755 "msi-laptop: Unable to install key filter\n");
756 goto fail_filter;
757 }
Lee, Chun-Yi472ea122010-01-22 00:15:59 +0800758
759 return 0;
Lee, Chun-Yi339e7532010-05-12 09:58:10 -0700760
761fail_filter:
762 rfkill_cleanup();
763
764fail_rfkill:
765
766 return result;
767
Lee, Chun-Yi472ea122010-01-22 00:15:59 +0800768}
769
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400770static int __init msi_init(void)
771{
772 int ret;
773
774 if (acpi_disabled)
775 return -ENODEV;
776
Lee, Chun-Yi46d0e9e2010-01-09 21:16:52 +0800777 if (force || dmi_check_system(msi_dmi_table))
778 old_ec_model = 1;
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400779
Lee, Chun-Yie22388e2010-01-27 12:23:00 +0800780 if (!old_ec_model)
781 get_threeg_exists();
782
Lee, Chun-Yi472ea122010-01-22 00:15:59 +0800783 if (!old_ec_model && dmi_check_system(msi_load_scm_models_dmi_table))
784 load_scm_model = 1;
785
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400786 if (auto_brightness < 0 || auto_brightness > 2)
787 return -EINVAL;
788
789 /* Register backlight stuff */
790
Thomas Renningera598c822008-08-01 17:38:01 +0200791 if (acpi_video_backlight_support()) {
792 printk(KERN_INFO "MSI: Brightness ignored, must be controlled "
793 "by ACPI video driver\n");
794 } else {
Matthew Garretta19a6ee2010-02-17 16:39:44 -0500795 struct backlight_properties props;
796 memset(&props, 0, sizeof(struct backlight_properties));
797 props.max_brightness = MSI_LCD_LEVEL_MAX - 1;
Thomas Renningera598c822008-08-01 17:38:01 +0200798 msibl_device = backlight_device_register("msi-laptop-bl", NULL,
Matthew Garretta19a6ee2010-02-17 16:39:44 -0500799 NULL, &msibl_ops,
800 &props);
Thomas Renningera598c822008-08-01 17:38:01 +0200801 if (IS_ERR(msibl_device))
802 return PTR_ERR(msibl_device);
Thomas Renningera598c822008-08-01 17:38:01 +0200803 }
Richard Purdie599a52d2007-02-10 23:07:48 +0000804
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400805 ret = platform_driver_register(&msipf_driver);
806 if (ret)
807 goto fail_backlight;
808
809 /* Register platform stuff */
810
811 msipf_device = platform_device_alloc("msi-laptop-pf", -1);
812 if (!msipf_device) {
813 ret = -ENOMEM;
814 goto fail_platform_driver;
815 }
816
817 ret = platform_device_add(msipf_device);
818 if (ret)
819 goto fail_platform_device1;
820
Lee, Chun-Yi472ea122010-01-22 00:15:59 +0800821 if (load_scm_model && (load_scm_model_init(msipf_device) < 0)) {
822 ret = -EINVAL;
823 goto fail_platform_device1;
824 }
825
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400826 ret = sysfs_create_group(&msipf_device->dev.kobj, &msipf_attribute_group);
827 if (ret)
828 goto fail_platform_device2;
829
Lee, Chun-Yifc0dc4c2010-01-09 23:17:07 +0800830 if (!old_ec_model) {
Lee, Chun-Yie22388e2010-01-27 12:23:00 +0800831 if (threeg_exists)
832 ret = device_create_file(&msipf_device->dev,
833 &dev_attr_threeg);
Lee, Chun-Yifc0dc4c2010-01-09 23:17:07 +0800834 if (ret)
835 goto fail_platform_device2;
836 }
837
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400838 /* Disable automatic brightness control by default because
839 * this module was probably loaded to do brightness control in
840 * software. */
841
842 if (auto_brightness != 2)
843 set_auto_brightness(auto_brightness);
844
845 printk(KERN_INFO "msi-laptop: driver "MSI_DRIVER_VERSION" successfully loaded.\n");
846
847 return 0;
848
849fail_platform_device2:
850
851 platform_device_del(msipf_device);
852
853fail_platform_device1:
854
855 platform_device_put(msipf_device);
856
857fail_platform_driver:
858
859 platform_driver_unregister(&msipf_driver);
860
861fail_backlight:
862
863 backlight_device_unregister(msibl_device);
864
865 return ret;
866}
867
868static void __exit msi_cleanup(void)
869{
870
871 sysfs_remove_group(&msipf_device->dev.kobj, &msipf_attribute_group);
Lee, Chun-Yie22388e2010-01-27 12:23:00 +0800872 if (!old_ec_model && threeg_exists)
Lee, Chun-Yifc0dc4c2010-01-09 23:17:07 +0800873 device_remove_file(&msipf_device->dev, &dev_attr_threeg);
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400874 platform_device_unregister(msipf_device);
875 platform_driver_unregister(&msipf_driver);
876 backlight_device_unregister(msibl_device);
877
Lee, Chun-Yi339e7532010-05-12 09:58:10 -0700878 i8042_remove_filter(msi_laptop_i8042_filter);
Lee, Chun-Yi472ea122010-01-22 00:15:59 +0800879 rfkill_cleanup();
880
Lennart Poettering8c4c7312006-10-06 01:27:02 -0400881 /* Enable automatic brightness control again */
882 if (auto_brightness != 2)
883 set_auto_brightness(1);
884
885 printk(KERN_INFO "msi-laptop: driver unloaded.\n");
886}
887
888module_init(msi_init);
889module_exit(msi_cleanup);
890
891MODULE_AUTHOR("Lennart Poettering");
892MODULE_DESCRIPTION("MSI Laptop Support");
893MODULE_VERSION(MSI_DRIVER_VERSION);
894MODULE_LICENSE("GPL");
Lennart Poettering4f5c7912007-05-08 22:07:02 +0200895
896MODULE_ALIAS("dmi:*:svnMICRO-STARINT'LCO.,LTD:pnMS-1013:pvr0131*:cvnMICRO-STARINT'LCO.,LTD:ct10:*");
897MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1058:pvr0581:rvnMSI:rnMS-1058:*:ct10:*");
898MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1412:*:rvnMSI:rnMS-1412:*:cvnMICRO-STARINT'LCO.,LTD:ct10:*");
899MODULE_ALIAS("dmi:*:svnNOTEBOOK:pnSAM2000:pvr0131*:cvnMICRO-STARINT'LCO.,LTD:ct10:*");
Lee, Chun-Yi46d0e9e2010-01-09 21:16:52 +0800900MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N034:*");
Lee, Chun-Yid0a4aa22010-05-12 09:58:07 -0700901MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N051:*");
902MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N014:*");
Lee, Chun-Yi1f27e172010-05-12 09:58:08 -0700903MODULE_ALIAS("dmi:*:svnMicro-StarInternational*:pnCR620:*");