1 /*-*-linux-c-*-*/ 2 3 /* 4 Copyright (C) 2008 Cezary Jackiewicz <cezary.jackiewicz (at) gmail.com> 5 6 based on MSI driver 7 8 Copyright (C) 2006 Lennart Poettering <mzxreary (at) 0pointer (dot) de> 9 10 This program is free software; you can redistribute it and/or modify 11 it under the terms of the GNU General Public License as published by 12 the Free Software Foundation; either version 2 of the License, or 13 (at your option) any later version. 14 15 This program is distributed in the hope that it will be useful, but 16 WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 General Public License for more details. 19 20 You should have received a copy of the GNU General Public License 21 along with this program; if not, write to the Free Software 22 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 23 02110-1301, USA. 24 */ 25 26 /* 27 * comapl-laptop.c - Compal laptop support. 28 * 29 * This driver exports a few files in /sys/devices/platform/compal-laptop/: 30 * 31 * wlan - wlan subsystem state: contains 0 or 1 (rw) 32 * 33 * bluetooth - Bluetooth subsystem state: contains 0 or 1 (rw) 34 * 35 * raw - raw value taken from embedded controller register (ro) 36 * 37 * In addition to these platform device attributes the driver 38 * registers itself in the Linux backlight control subsystem and is 39 * available to userspace under /sys/class/backlight/compal-laptop/. 40 * 41 * This driver might work on other laptops produced by Compal. If you 42 * want to try it you can pass force=1 as argument to the module which 43 * will force it to load even when the DMI data doesn't identify the 44 * laptop as FL9x. 45 */ 46 47 #include <linux/module.h> 48 #include <linux/kernel.h> 49 #include <linux/init.h> 50 #include <linux/acpi.h> 51 #include <linux/dmi.h> 52 #include <linux/backlight.h> 53 #include <linux/platform_device.h> 54 55 #define COMPAL_DRIVER_VERSION "0.2.6" 56 57 #define COMPAL_LCD_LEVEL_MAX 8 58 59 #define COMPAL_EC_COMMAND_WIRELESS 0xBB 60 #define COMPAL_EC_COMMAND_LCD_LEVEL 0xB9 61 62 #define KILLSWITCH_MASK 0x10 63 #define WLAN_MASK 0x01 64 #define BT_MASK 0x02 65 66 static int force; 67 module_param(force, bool, 0); 68 MODULE_PARM_DESC(force, "Force driver load, ignore DMI data"); 69 70 /* Hardware access */ 71 72 static int set_lcd_level(int level) 73 { 74 if (level < 0 || level >= COMPAL_LCD_LEVEL_MAX) 75 return -EINVAL; 76 77 ec_write(COMPAL_EC_COMMAND_LCD_LEVEL, level); 78 79 return 0; 80 } 81 82 static int get_lcd_level(void) 83 { 84 u8 result; 85 86 ec_read(COMPAL_EC_COMMAND_LCD_LEVEL, &result); 87 88 return (int) result; 89 } 90 91 static int set_wlan_state(int state) 92 { 93 u8 result, value; 94 95 ec_read(COMPAL_EC_COMMAND_WIRELESS, &result); 96 97 if ((result & KILLSWITCH_MASK) == 0) 98 return -EINVAL; 99 else { 100 if (state) 101 value = (u8) (result | WLAN_MASK); 102 else 103 value = (u8) (result & ~WLAN_MASK); 104 ec_write(COMPAL_EC_COMMAND_WIRELESS, value); 105 } 106 107 return 0; 108 } 109 110 static int set_bluetooth_state(int state) 111 { 112 u8 result, value; 113 114 ec_read(COMPAL_EC_COMMAND_WIRELESS, &result); 115 116 if ((result & KILLSWITCH_MASK) == 0) 117 return -EINVAL; 118 else { 119 if (state) 120 value = (u8) (result | BT_MASK); 121 else 122 value = (u8) (result & ~BT_MASK); 123 ec_write(COMPAL_EC_COMMAND_WIRELESS, value); 124 } 125 126 return 0; 127 } 128 129 static int get_wireless_state(int *wlan, int *bluetooth) 130 { 131 u8 result; 132 133 ec_read(COMPAL_EC_COMMAND_WIRELESS, &result); 134 135 if (wlan) { 136 if ((result & KILLSWITCH_MASK) == 0) 137 *wlan = 0; 138 else 139 *wlan = result & WLAN_MASK; 140 } 141 142 if (bluetooth) { 143 if ((result & KILLSWITCH_MASK) == 0) 144 *bluetooth = 0; 145 else 146 *bluetooth = (result & BT_MASK) >> 1; 147 } 148 149 return 0; 150 } 151 152 /* Backlight device stuff */ 153 154 static int bl_get_brightness(struct backlight_device *b) 155 { 156 return get_lcd_level(); 157 } 158 159 160 static int bl_update_status(struct backlight_device *b) 161 { 162 return set_lcd_level(b->props.brightness); 163 } 164 165 static struct backlight_ops compalbl_ops = { 166 .get_brightness = bl_get_brightness, 167 .update_status = bl_update_status, 168 }; 169 170 static struct backlight_device *compalbl_device; 171 172 /* Platform device */ 173 174 static ssize_t show_wlan(struct device *dev, 175 struct device_attribute *attr, char *buf) 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_raw(struct device *dev, 187 struct device_attribute *attr, char *buf) 188 { 189 u8 result; 190 191 ec_read(COMPAL_EC_COMMAND_WIRELESS, &result); 192 193 return sprintf(buf, "%i\n", result); 194 } 195 196 static ssize_t show_bluetooth(struct device *dev, 197 struct device_attribute *attr, char *buf) 198 { 199 int ret, enabled; 200 201 ret = get_wireless_state(NULL, &enabled); 202 if (ret < 0) 203 return ret; 204 205 return sprintf(buf, "%i\n", enabled); 206 } 207 208 static ssize_t store_wlan_state(struct device *dev, 209 struct device_attribute *attr, const char *buf, size_t count) 210 { 211 int state, ret; 212 213 if (sscanf(buf, "%i", &state) != 1 || (state < 0 || state > 1)) 214 return -EINVAL; 215 216 ret = set_wlan_state(state); 217 if (ret < 0) 218 return ret; 219 220 return count; 221 } 222 223 static ssize_t store_bluetooth_state(struct device *dev, 224 struct device_attribute *attr, const char *buf, size_t count) 225 { 226 int state, ret; 227 228 if (sscanf(buf, "%i", &state) != 1 || (state < 0 || state > 1)) 229 return -EINVAL; 230 231 ret = set_bluetooth_state(state); 232 if (ret < 0) 233 return ret; 234 235 return count; 236 } 237 238 static DEVICE_ATTR(bluetooth, 0644, show_bluetooth, store_bluetooth_state); 239 static DEVICE_ATTR(wlan, 0644, show_wlan, store_wlan_state); 240 static DEVICE_ATTR(raw, 0444, show_raw, NULL); 241 242 static struct attribute *compal_attributes[] = { 243 &dev_attr_bluetooth.attr, 244 &dev_attr_wlan.attr, 245 &dev_attr_raw.attr, 246 NULL 247 }; 248 249 static struct attribute_group compal_attribute_group = { 250 .attrs = compal_attributes 251 }; 252 253 static struct platform_driver compal_driver = { 254 .driver = { 255 .name = "compal-laptop", 256 .owner = THIS_MODULE, 257 } 258 }; 259 260 static struct platform_device *compal_device; 261 262 /* Initialization */ 263 264 static int dmi_check_cb(const struct dmi_system_id *id) 265 { 266 printk(KERN_INFO "compal-laptop: Identified laptop model '%s'.\n", 267 id->ident); 268 269 return 0; 270 } 271 272 static struct dmi_system_id __initdata compal_dmi_table[] = { 273 { 274 .ident = "FL90/IFL90", 275 .matches = { 276 DMI_MATCH(DMI_BOARD_NAME, "IFL90"), 277 DMI_MATCH(DMI_BOARD_VERSION, "IFT00"), 278 }, 279 .callback = dmi_check_cb 280 }, 281 { 282 .ident = "FL90/IFL90", 283 .matches = { 284 DMI_MATCH(DMI_BOARD_NAME, "IFL90"), 285 DMI_MATCH(DMI_BOARD_VERSION, "REFERENCE"), 286 }, 287 .callback = dmi_check_cb 288 }, 289 { 290 .ident = "FL91/IFL91", 291 .matches = { 292 DMI_MATCH(DMI_BOARD_NAME, "IFL91"), 293 DMI_MATCH(DMI_BOARD_VERSION, "IFT00"), 294 }, 295 .callback = dmi_check_cb 296 }, 297 { 298 .ident = "FL92/JFL92", 299 .matches = { 300 DMI_MATCH(DMI_BOARD_NAME, "JFL92"), 301 DMI_MATCH(DMI_BOARD_VERSION, "IFT00"), 302 }, 303 .callback = dmi_check_cb 304 }, 305 { 306 .ident = "FT00/IFT00", 307 .matches = { 308 DMI_MATCH(DMI_BOARD_NAME, "IFT00"), 309 DMI_MATCH(DMI_BOARD_VERSION, "IFT00"), 310 }, 311 .callback = dmi_check_cb 312 }, 313 { } 314 }; 315 316 static int __init compal_init(void) 317 { 318 int ret; 319 320 if (acpi_disabled) 321 return -ENODEV; 322 323 if (!force && !dmi_check_system(compal_dmi_table)) 324 return -ENODEV; 325 326 /* Register backlight stuff */ 327 328 if (!acpi_video_backlight_support()) { 329 compalbl_device = backlight_device_register("compal-laptop", NULL, NULL, 330 &compalbl_ops); 331 if (IS_ERR(compalbl_device)) 332 return PTR_ERR(compalbl_device); 333 334 compalbl_device->props.max_brightness = COMPAL_LCD_LEVEL_MAX-1; 335 } 336 337 ret = platform_driver_register(&compal_driver); 338 if (ret) 339 goto fail_backlight; 340 341 /* Register platform stuff */ 342 343 compal_device = platform_device_alloc("compal-laptop", -1); 344 if (!compal_device) { 345 ret = -ENOMEM; 346 goto fail_platform_driver; 347 } 348 349 ret = platform_device_add(compal_device); 350 if (ret) 351 goto fail_platform_device1; 352 353 ret = sysfs_create_group(&compal_device->dev.kobj, 354 &compal_attribute_group); 355 if (ret) 356 goto fail_platform_device2; 357 358 printk(KERN_INFO "compal-laptop: driver "COMPAL_DRIVER_VERSION 359 " successfully loaded.\n"); 360 361 return 0; 362 363 fail_platform_device2: 364 365 platform_device_del(compal_device); 366 367 fail_platform_device1: 368 369 platform_device_put(compal_device); 370 371 fail_platform_driver: 372 373 platform_driver_unregister(&compal_driver); 374 375 fail_backlight: 376 377 backlight_device_unregister(compalbl_device); 378 379 return ret; 380 } 381 382 static void __exit compal_cleanup(void) 383 { 384 385 sysfs_remove_group(&compal_device->dev.kobj, &compal_attribute_group); 386 platform_device_unregister(compal_device); 387 platform_driver_unregister(&compal_driver); 388 backlight_device_unregister(compalbl_device); 389 390 printk(KERN_INFO "compal-laptop: driver unloaded.\n"); 391 } 392 393 module_init(compal_init); 394 module_exit(compal_cleanup); 395 396 MODULE_AUTHOR("Cezary Jackiewicz"); 397 MODULE_DESCRIPTION("Compal Laptop Support"); 398 MODULE_VERSION(COMPAL_DRIVER_VERSION); 399 MODULE_LICENSE("GPL"); 400 401 MODULE_ALIAS("dmi:*:rnIFL90:rvrIFT00:*"); 402 MODULE_ALIAS("dmi:*:rnIFL90:rvrREFERENCE:*"); 403 MODULE_ALIAS("dmi:*:rnIFL91:rvrIFT00:*"); 404 MODULE_ALIAS("dmi:*:rnJFL92:rvrIFT00:*"); 405 MODULE_ALIAS("dmi:*:rnIFT00:rvrIFT00:*"); 406