Commit | Line | Data |
---|---|---|
8c4c731a LP |
1 | /*-*-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 | * | |
4f5c791a LP |
26 | * Driver also supports S271, S420 models. |
27 | * | |
8c4c731a LP |
28 | * 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> | |
8c4c731a LP |
61 | |
62 | #define MSI_DRIVER_VERSION "0.5" | |
63 | ||
64 | #define MSI_LCD_LEVEL_MAX 9 | |
65 | ||
66 | #define MSI_EC_COMMAND_WIRELESS 0x10 | |
67 | #define MSI_EC_COMMAND_LCD_LEVEL 0x11 | |
68 | ||
69 | static int force; | |
70 | module_param(force, bool, 0); | |
71 | MODULE_PARM_DESC(force, "Force driver load, ignore DMI data"); | |
72 | ||
73 | static int auto_brightness; | |
74 | module_param(auto_brightness, int, 0); | |
75 | MODULE_PARM_DESC(auto_brightness, "Enable automatic brightness control (0: disabled; 1: enabled; 2: don't touch)"); | |
76 | ||
77 | /* Hardware access */ | |
78 | ||
79 | static int set_lcd_level(int level) | |
80 | { | |
81 | u8 buf[2]; | |
82 | ||
83 | if (level < 0 || level >= MSI_LCD_LEVEL_MAX) | |
84 | return -EINVAL; | |
85 | ||
86 | buf[0] = 0x80; | |
87 | buf[1] = (u8) (level*31); | |
88 | ||
00eb43a1 | 89 | return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, buf, sizeof(buf), NULL, 0, 1); |
8c4c731a LP |
90 | } |
91 | ||
92 | static int get_lcd_level(void) | |
93 | { | |
94 | u8 wdata = 0, rdata; | |
95 | int result; | |
96 | ||
00eb43a1 | 97 | result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, &rdata, 1, 1); |
8c4c731a LP |
98 | if (result < 0) |
99 | return result; | |
100 | ||
101 | return (int) rdata / 31; | |
102 | } | |
103 | ||
104 | static int get_auto_brightness(void) | |
105 | { | |
106 | u8 wdata = 4, rdata; | |
107 | int result; | |
108 | ||
00eb43a1 | 109 | result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, &rdata, 1, 1); |
8c4c731a LP |
110 | if (result < 0) |
111 | return result; | |
112 | ||
113 | return !!(rdata & 8); | |
114 | } | |
115 | ||
116 | static int set_auto_brightness(int enable) | |
117 | { | |
118 | u8 wdata[2], rdata; | |
119 | int result; | |
120 | ||
121 | wdata[0] = 4; | |
122 | ||
00eb43a1 | 123 | result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 1, &rdata, 1, 1); |
8c4c731a LP |
124 | if (result < 0) |
125 | return result; | |
126 | ||
127 | wdata[0] = 0x84; | |
128 | wdata[1] = (rdata & 0xF7) | (enable ? 8 : 0); | |
129 | ||
00eb43a1 | 130 | return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 2, NULL, 0, 1); |
8c4c731a LP |
131 | } |
132 | ||
133 | static int get_wireless_state(int *wlan, int *bluetooth) | |
134 | { | |
135 | u8 wdata = 0, rdata; | |
136 | int result; | |
137 | ||
00eb43a1 | 138 | result = ec_transaction(MSI_EC_COMMAND_WIRELESS, &wdata, 1, &rdata, 1, 1); |
8c4c731a LP |
139 | if (result < 0) |
140 | return -1; | |
141 | ||
142 | if (wlan) | |
143 | *wlan = !!(rdata & 8); | |
144 | ||
145 | if (bluetooth) | |
146 | *bluetooth = !!(rdata & 128); | |
147 | ||
148 | return 0; | |
149 | } | |
150 | ||
151 | /* Backlight device stuff */ | |
152 | ||
153 | static int bl_get_brightness(struct backlight_device *b) | |
154 | { | |
155 | return get_lcd_level(); | |
156 | } | |
157 | ||
158 | ||
159 | static int bl_update_status(struct backlight_device *b) | |
160 | { | |
599a52d1 | 161 | return set_lcd_level(b->props.brightness); |
8c4c731a LP |
162 | } |
163 | ||
599a52d1 | 164 | static struct backlight_ops msibl_ops = { |
8c4c731a LP |
165 | .get_brightness = bl_get_brightness, |
166 | .update_status = bl_update_status, | |
8c4c731a LP |
167 | }; |
168 | ||
169 | static struct backlight_device *msibl_device; | |
170 | ||
171 | /* Platform device */ | |
172 | ||
173 | static ssize_t show_wlan(struct device *dev, | |
174 | struct device_attribute *attr, char *buf) | |
175 | { | |
176 | ||
177 | int ret, enabled; | |
178 | ||
179 | ret = get_wireless_state(&enabled, NULL); | |
180 | if (ret < 0) | |
181 | return ret; | |
182 | ||
183 | return sprintf(buf, "%i\n", enabled); | |
184 | } | |
185 | ||
186 | static ssize_t show_bluetooth(struct device *dev, | |
187 | struct device_attribute *attr, char *buf) | |
188 | { | |
189 | ||
190 | int ret, enabled; | |
191 | ||
192 | ret = get_wireless_state(NULL, &enabled); | |
193 | if (ret < 0) | |
194 | return ret; | |
195 | ||
196 | return sprintf(buf, "%i\n", enabled); | |
197 | } | |
198 | ||
199 | static ssize_t show_lcd_level(struct device *dev, | |
200 | struct device_attribute *attr, char *buf) | |
201 | { | |
202 | ||
203 | int ret; | |
204 | ||
205 | ret = get_lcd_level(); | |
206 | if (ret < 0) | |
207 | return ret; | |
208 | ||
209 | return sprintf(buf, "%i\n", ret); | |
210 | } | |
211 | ||
212 | static ssize_t store_lcd_level(struct device *dev, | |
213 | struct device_attribute *attr, const char *buf, size_t count) | |
214 | { | |
215 | ||
216 | int level, ret; | |
217 | ||
218 | if (sscanf(buf, "%i", &level) != 1 || (level < 0 || level >= MSI_LCD_LEVEL_MAX)) | |
219 | return -EINVAL; | |
220 | ||
221 | ret = set_lcd_level(level); | |
222 | if (ret < 0) | |
223 | return ret; | |
224 | ||
225 | return count; | |
226 | } | |
227 | ||
228 | static ssize_t show_auto_brightness(struct device *dev, | |
229 | struct device_attribute *attr, char *buf) | |
230 | { | |
231 | ||
232 | int ret; | |
233 | ||
234 | ret = get_auto_brightness(); | |
235 | if (ret < 0) | |
236 | return ret; | |
237 | ||
238 | return sprintf(buf, "%i\n", ret); | |
239 | } | |
240 | ||
241 | static ssize_t store_auto_brightness(struct device *dev, | |
242 | struct device_attribute *attr, const char *buf, size_t count) | |
243 | { | |
244 | ||
245 | int enable, ret; | |
246 | ||
247 | if (sscanf(buf, "%i", &enable) != 1 || (enable != (enable & 1))) | |
248 | return -EINVAL; | |
249 | ||
250 | ret = set_auto_brightness(enable); | |
251 | if (ret < 0) | |
252 | return ret; | |
253 | ||
254 | return count; | |
255 | } | |
256 | ||
257 | static DEVICE_ATTR(lcd_level, 0644, show_lcd_level, store_lcd_level); | |
258 | static DEVICE_ATTR(auto_brightness, 0644, show_auto_brightness, store_auto_brightness); | |
259 | static DEVICE_ATTR(bluetooth, 0444, show_bluetooth, NULL); | |
260 | static DEVICE_ATTR(wlan, 0444, show_wlan, NULL); | |
261 | ||
262 | static struct attribute *msipf_attributes[] = { | |
263 | &dev_attr_lcd_level.attr, | |
264 | &dev_attr_auto_brightness.attr, | |
265 | &dev_attr_bluetooth.attr, | |
266 | &dev_attr_wlan.attr, | |
267 | NULL | |
268 | }; | |
269 | ||
270 | static struct attribute_group msipf_attribute_group = { | |
271 | .attrs = msipf_attributes | |
272 | }; | |
273 | ||
274 | static struct platform_driver msipf_driver = { | |
275 | .driver = { | |
276 | .name = "msi-laptop-pf", | |
277 | .owner = THIS_MODULE, | |
278 | } | |
279 | }; | |
280 | ||
281 | static struct platform_device *msipf_device; | |
282 | ||
283 | /* Initialization */ | |
284 | ||
1855256c | 285 | static int dmi_check_cb(const struct dmi_system_id *id) |
4f5c791a LP |
286 | { |
287 | printk("msi-laptop: Identified laptop model '%s'.\n", id->ident); | |
288 | return 0; | |
289 | } | |
290 | ||
8c4c731a LP |
291 | static struct dmi_system_id __initdata msi_dmi_table[] = { |
292 | { | |
293 | .ident = "MSI S270", | |
294 | .matches = { | |
295 | DMI_MATCH(DMI_SYS_VENDOR, "MICRO-STAR INT'L CO.,LTD"), | |
296 | DMI_MATCH(DMI_PRODUCT_NAME, "MS-1013"), | |
4f5c791a LP |
297 | DMI_MATCH(DMI_PRODUCT_VERSION, "0131"), |
298 | DMI_MATCH(DMI_CHASSIS_VENDOR, "MICRO-STAR INT'L CO.,LTD") | |
299 | }, | |
300 | .callback = dmi_check_cb | |
301 | }, | |
302 | { | |
303 | .ident = "MSI S271", | |
304 | .matches = { | |
305 | DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"), | |
306 | DMI_MATCH(DMI_PRODUCT_NAME, "MS-1058"), | |
307 | DMI_MATCH(DMI_PRODUCT_VERSION, "0581"), | |
308 | DMI_MATCH(DMI_BOARD_NAME, "MS-1058") | |
309 | }, | |
310 | .callback = dmi_check_cb | |
311 | }, | |
312 | { | |
313 | .ident = "MSI S420", | |
314 | .matches = { | |
315 | DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"), | |
316 | DMI_MATCH(DMI_PRODUCT_NAME, "MS-1412"), | |
317 | DMI_MATCH(DMI_BOARD_VENDOR, "MSI"), | |
318 | DMI_MATCH(DMI_BOARD_NAME, "MS-1412") | |
319 | }, | |
320 | .callback = dmi_check_cb | |
8c4c731a LP |
321 | }, |
322 | { | |
323 | .ident = "Medion MD96100", | |
324 | .matches = { | |
325 | DMI_MATCH(DMI_SYS_VENDOR, "NOTEBOOK"), | |
326 | DMI_MATCH(DMI_PRODUCT_NAME, "SAM2000"), | |
4f5c791a LP |
327 | DMI_MATCH(DMI_PRODUCT_VERSION, "0131"), |
328 | DMI_MATCH(DMI_CHASSIS_VENDOR, "MICRO-STAR INT'L CO.,LTD") | |
329 | }, | |
330 | .callback = dmi_check_cb | |
8c4c731a LP |
331 | }, |
332 | { } | |
333 | }; | |
334 | ||
8c4c731a LP |
335 | static int __init msi_init(void) |
336 | { | |
337 | int ret; | |
338 | ||
339 | if (acpi_disabled) | |
340 | return -ENODEV; | |
341 | ||
342 | if (!force && !dmi_check_system(msi_dmi_table)) | |
343 | return -ENODEV; | |
344 | ||
345 | if (auto_brightness < 0 || auto_brightness > 2) | |
346 | return -EINVAL; | |
347 | ||
348 | /* Register backlight stuff */ | |
349 | ||
519ab5f2 | 350 | msibl_device = backlight_device_register("msi-laptop-bl", NULL, NULL, |
599a52d1 | 351 | &msibl_ops); |
8c4c731a LP |
352 | if (IS_ERR(msibl_device)) |
353 | return PTR_ERR(msibl_device); | |
354 | ||
cb90ab5b | 355 | msibl_device->props.max_brightness = MSI_LCD_LEVEL_MAX-1; |
599a52d1 | 356 | |
8c4c731a LP |
357 | ret = platform_driver_register(&msipf_driver); |
358 | if (ret) | |
359 | goto fail_backlight; | |
360 | ||
361 | /* Register platform stuff */ | |
362 | ||
363 | msipf_device = platform_device_alloc("msi-laptop-pf", -1); | |
364 | if (!msipf_device) { | |
365 | ret = -ENOMEM; | |
366 | goto fail_platform_driver; | |
367 | } | |
368 | ||
369 | ret = platform_device_add(msipf_device); | |
370 | if (ret) | |
371 | goto fail_platform_device1; | |
372 | ||
373 | ret = sysfs_create_group(&msipf_device->dev.kobj, &msipf_attribute_group); | |
374 | if (ret) | |
375 | goto fail_platform_device2; | |
376 | ||
377 | /* Disable automatic brightness control by default because | |
378 | * this module was probably loaded to do brightness control in | |
379 | * software. */ | |
380 | ||
381 | if (auto_brightness != 2) | |
382 | set_auto_brightness(auto_brightness); | |
383 | ||
384 | printk(KERN_INFO "msi-laptop: driver "MSI_DRIVER_VERSION" successfully loaded.\n"); | |
385 | ||
386 | return 0; | |
387 | ||
388 | fail_platform_device2: | |
389 | ||
390 | platform_device_del(msipf_device); | |
391 | ||
392 | fail_platform_device1: | |
393 | ||
394 | platform_device_put(msipf_device); | |
395 | ||
396 | fail_platform_driver: | |
397 | ||
398 | platform_driver_unregister(&msipf_driver); | |
399 | ||
400 | fail_backlight: | |
401 | ||
402 | backlight_device_unregister(msibl_device); | |
403 | ||
404 | return ret; | |
405 | } | |
406 | ||
407 | static void __exit msi_cleanup(void) | |
408 | { | |
409 | ||
410 | sysfs_remove_group(&msipf_device->dev.kobj, &msipf_attribute_group); | |
411 | platform_device_unregister(msipf_device); | |
412 | platform_driver_unregister(&msipf_driver); | |
413 | backlight_device_unregister(msibl_device); | |
414 | ||
415 | /* Enable automatic brightness control again */ | |
416 | if (auto_brightness != 2) | |
417 | set_auto_brightness(1); | |
418 | ||
419 | printk(KERN_INFO "msi-laptop: driver unloaded.\n"); | |
420 | } | |
421 | ||
422 | module_init(msi_init); | |
423 | module_exit(msi_cleanup); | |
424 | ||
425 | MODULE_AUTHOR("Lennart Poettering"); | |
426 | MODULE_DESCRIPTION("MSI Laptop Support"); | |
427 | MODULE_VERSION(MSI_DRIVER_VERSION); | |
428 | MODULE_LICENSE("GPL"); | |
4f5c791a LP |
429 | |
430 | MODULE_ALIAS("dmi:*:svnMICRO-STARINT'LCO.,LTD:pnMS-1013:pvr0131*:cvnMICRO-STARINT'LCO.,LTD:ct10:*"); | |
431 | MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1058:pvr0581:rvnMSI:rnMS-1058:*:ct10:*"); | |
432 | MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1412:*:rvnMSI:rnMS-1412:*:cvnMICRO-STARINT'LCO.,LTD:ct10:*"); | |
433 | MODULE_ALIAS("dmi:*:svnNOTEBOOK:pnSAM2000:pvr0131*:cvnMICRO-STARINT'LCO.,LTD:ct10:*"); |