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 * 26 * Driver also supports S271, S420 models. 27 * 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> 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 89 return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, buf, sizeof(buf), NULL, 0, 1); 90 } 91 92 static int get_lcd_level(void) 93 { 94 u8 wdata = 0, rdata; 95 int result; 96 97 result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, &rdata, 1, 1); 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 109 result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, &rdata, 1, 1); 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 123 result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 1, &rdata, 1, 1); 124 if (result < 0) 125 return result; 126 127 wdata[0] = 0x84; 128 wdata[1] = (rdata & 0xF7) | (enable ? 8 : 0); 129 130 return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 2, NULL, 0, 1); 131 } 132 133 static int get_wireless_state(int *wlan, int *bluetooth) 134 { 135 u8 wdata = 0, rdata; 136 int result; 137 138 result = ec_transaction(MSI_EC_COMMAND_WIRELESS, &wdata, 1, &rdata, 1, 1); 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 { 161 return set_lcd_level(b->props.brightness); 162 } 163 164 static struct backlight_ops msibl_ops = { 165 .get_brightness = bl_get_brightness, 166 .update_status = bl_update_status, 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 285 static int dmi_check_cb(const struct dmi_system_id *id) 286 { 287 printk("msi-laptop: Identified laptop model '%s'.\n", id->ident); 288 return 0; 289 } 290 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"), 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 321 }, 322 { 323 .ident = "Medion MD96100", 324 .matches = { 325 DMI_MATCH(DMI_SYS_VENDOR, "NOTEBOOK"), 326 DMI_MATCH(DMI_PRODUCT_NAME, "SAM2000"), 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 331 }, 332 { } 333 }; 334 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 350 if (acpi_video_backlight_support()) { 351 printk(KERN_INFO "MSI: Brightness ignored, must be controlled " 352 "by ACPI video driver\n"); 353 } else { 354 msibl_device = backlight_device_register("msi-laptop-bl", NULL, 355 NULL, &msibl_ops); 356 if (IS_ERR(msibl_device)) 357 return PTR_ERR(msibl_device); 358 msibl_device->props.max_brightness = MSI_LCD_LEVEL_MAX-1; 359 } 360 361 ret = platform_driver_register(&msipf_driver); 362 if (ret) 363 goto fail_backlight; 364 365 /* Register platform stuff */ 366 367 msipf_device = platform_device_alloc("msi-laptop-pf", -1); 368 if (!msipf_device) { 369 ret = -ENOMEM; 370 goto fail_platform_driver; 371 } 372 373 ret = platform_device_add(msipf_device); 374 if (ret) 375 goto fail_platform_device1; 376 377 ret = sysfs_create_group(&msipf_device->dev.kobj, &msipf_attribute_group); 378 if (ret) 379 goto fail_platform_device2; 380 381 /* Disable automatic brightness control by default because 382 * this module was probably loaded to do brightness control in 383 * software. */ 384 385 if (auto_brightness != 2) 386 set_auto_brightness(auto_brightness); 387 388 printk(KERN_INFO "msi-laptop: driver "MSI_DRIVER_VERSION" successfully loaded.\n"); 389 390 return 0; 391 392 fail_platform_device2: 393 394 platform_device_del(msipf_device); 395 396 fail_platform_device1: 397 398 platform_device_put(msipf_device); 399 400 fail_platform_driver: 401 402 platform_driver_unregister(&msipf_driver); 403 404 fail_backlight: 405 406 backlight_device_unregister(msibl_device); 407 408 return ret; 409 } 410 411 static void __exit msi_cleanup(void) 412 { 413 414 sysfs_remove_group(&msipf_device->dev.kobj, &msipf_attribute_group); 415 platform_device_unregister(msipf_device); 416 platform_driver_unregister(&msipf_driver); 417 backlight_device_unregister(msibl_device); 418 419 /* Enable automatic brightness control again */ 420 if (auto_brightness != 2) 421 set_auto_brightness(1); 422 423 printk(KERN_INFO "msi-laptop: driver unloaded.\n"); 424 } 425 426 module_init(msi_init); 427 module_exit(msi_cleanup); 428 429 MODULE_AUTHOR("Lennart Poettering"); 430 MODULE_DESCRIPTION("MSI Laptop Support"); 431 MODULE_VERSION(MSI_DRIVER_VERSION); 432 MODULE_LICENSE("GPL"); 433 434 MODULE_ALIAS("dmi:*:svnMICRO-STARINT'LCO.,LTD:pnMS-1013:pvr0131*:cvnMICRO-STARINT'LCO.,LTD:ct10:*"); 435 MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1058:pvr0581:rvnMSI:rnMS-1058:*:ct10:*"); 436 MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1412:*:rvnMSI:rnMS-1412:*:cvnMICRO-STARINT'LCO.,LTD:ct10:*"); 437 MODULE_ALIAS("dmi:*:svnNOTEBOOK:pnSAM2000:pvr0131*:cvnMICRO-STARINT'LCO.,LTD:ct10:*"); 438