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 #include <linux/autoconf.h> 55 56 #define COMPAL_DRIVER_VERSION "0.2.6" 57 58 #define COMPAL_LCD_LEVEL_MAX 8 59 60 #define COMPAL_EC_COMMAND_WIRELESS 0xBB 61 #define COMPAL_EC_COMMAND_LCD_LEVEL 0xB9 62 63 #define KILLSWITCH_MASK 0x10 64 #define WLAN_MASK 0x01 65 #define BT_MASK 0x02 66 67 static int force; 68 module_param(force, bool, 0); 69 MODULE_PARM_DESC(force, "Force driver load, ignore DMI data"); 70 71 /* Hardware access */ 72 73 static int set_lcd_level(int level) 74 { 75 if (level < 0 || level >= COMPAL_LCD_LEVEL_MAX) 76 return -EINVAL; 77 78 ec_write(COMPAL_EC_COMMAND_LCD_LEVEL, level); 79 80 return 0; 81 } 82 83 static int get_lcd_level(void) 84 { 85 u8 result; 86 87 ec_read(COMPAL_EC_COMMAND_LCD_LEVEL, &result); 88 89 return (int) result; 90 } 91 92 static int set_wlan_state(int state) 93 { 94 u8 result, value; 95 96 ec_read(COMPAL_EC_COMMAND_WIRELESS, &result); 97 98 if ((result & KILLSWITCH_MASK) == 0) 99 return -EINVAL; 100 else { 101 if (state) 102 value = (u8) (result | WLAN_MASK); 103 else 104 value = (u8) (result & ~WLAN_MASK); 105 ec_write(COMPAL_EC_COMMAND_WIRELESS, value); 106 } 107 108 return 0; 109 } 110 111 static int set_bluetooth_state(int state) 112 { 113 u8 result, value; 114 115 ec_read(COMPAL_EC_COMMAND_WIRELESS, &result); 116 117 if ((result & KILLSWITCH_MASK) == 0) 118 return -EINVAL; 119 else { 120 if (state) 121 value = (u8) (result | BT_MASK); 122 else 123 value = (u8) (result & ~BT_MASK); 124 ec_write(COMPAL_EC_COMMAND_WIRELESS, value); 125 } 126 127 return 0; 128 } 129 130 static int get_wireless_state(int *wlan, int *bluetooth) 131 { 132 u8 result; 133 134 ec_read(COMPAL_EC_COMMAND_WIRELESS, &result); 135 136 if (wlan) { 137 if ((result & KILLSWITCH_MASK) == 0) 138 *wlan = 0; 139 else 140 *wlan = result & WLAN_MASK; 141 } 142 143 if (bluetooth) { 144 if ((result & KILLSWITCH_MASK) == 0) 145 *bluetooth = 0; 146 else 147 *bluetooth = (result & BT_MASK) >> 1; 148 } 149 150 return 0; 151 } 152 153 /* Backlight device stuff */ 154 155 static int bl_get_brightness(struct backlight_device *b) 156 { 157 return get_lcd_level(); 158 } 159 160 161 static int bl_update_status(struct backlight_device *b) 162 { 163 return set_lcd_level(b->props.brightness); 164 } 165 166 static struct backlight_ops compalbl_ops = { 167 .get_brightness = bl_get_brightness, 168 .update_status = bl_update_status, 169 }; 170 171 static struct backlight_device *compalbl_device; 172 173 /* Platform device */ 174 175 static ssize_t show_wlan(struct device *dev, 176 struct device_attribute *attr, char *buf) 177 { 178 int ret, enabled; 179 180 ret = get_wireless_state(&enabled, NULL); 181 if (ret < 0) 182 return ret; 183 184 return sprintf(buf, "%i\n", enabled); 185 } 186 187 static ssize_t show_raw(struct device *dev, 188 struct device_attribute *attr, char *buf) 189 { 190 u8 result; 191 192 ec_read(COMPAL_EC_COMMAND_WIRELESS, &result); 193 194 return sprintf(buf, "%i\n", result); 195 } 196 197 static ssize_t show_bluetooth(struct device *dev, 198 struct device_attribute *attr, char *buf) 199 { 200 int ret, enabled; 201 202 ret = get_wireless_state(NULL, &enabled); 203 if (ret < 0) 204 return ret; 205 206 return sprintf(buf, "%i\n", enabled); 207 } 208 209 static ssize_t store_wlan_state(struct device *dev, 210 struct device_attribute *attr, const char *buf, size_t count) 211 { 212 int state, ret; 213 214 if (sscanf(buf, "%i", &state) != 1 || (state < 0 || state > 1)) 215 return -EINVAL; 216 217 ret = set_wlan_state(state); 218 if (ret < 0) 219 return ret; 220 221 return count; 222 } 223 224 static ssize_t store_bluetooth_state(struct device *dev, 225 struct device_attribute *attr, const char *buf, size_t count) 226 { 227 int state, ret; 228 229 if (sscanf(buf, "%i", &state) != 1 || (state < 0 || state > 1)) 230 return -EINVAL; 231 232 ret = set_bluetooth_state(state); 233 if (ret < 0) 234 return ret; 235 236 return count; 237 } 238 239 static DEVICE_ATTR(bluetooth, 0644, show_bluetooth, store_bluetooth_state); 240 static DEVICE_ATTR(wlan, 0644, show_wlan, store_wlan_state); 241 static DEVICE_ATTR(raw, 0444, show_raw, NULL); 242 243 static struct attribute *compal_attributes[] = { 244 &dev_attr_bluetooth.attr, 245 &dev_attr_wlan.attr, 246 &dev_attr_raw.attr, 247 NULL 248 }; 249 250 static struct attribute_group compal_attribute_group = { 251 .attrs = compal_attributes 252 }; 253 254 static struct platform_driver compal_driver = { 255 .driver = { 256 .name = "compal-laptop", 257 .owner = THIS_MODULE, 258 } 259 }; 260 261 static struct platform_device *compal_device; 262 263 /* Initialization */ 264 265 static int dmi_check_cb(const struct dmi_system_id *id) 266 { 267 printk(KERN_INFO "compal-laptop: Identified laptop model '%s'.\n", 268 id->ident); 269 270 return 0; 271 } 272 273 static struct dmi_system_id __initdata compal_dmi_table[] = { 274 { 275 .ident = "FL90/IFL90", 276 .matches = { 277 DMI_MATCH(DMI_BOARD_NAME, "IFL90"), 278 DMI_MATCH(DMI_BOARD_VERSION, "IFT00"), 279 }, 280 .callback = dmi_check_cb 281 }, 282 { 283 .ident = "FL90/IFL90", 284 .matches = { 285 DMI_MATCH(DMI_BOARD_NAME, "IFL90"), 286 DMI_MATCH(DMI_BOARD_VERSION, "REFERENCE"), 287 }, 288 .callback = dmi_check_cb 289 }, 290 { 291 .ident = "FL91/IFL91", 292 .matches = { 293 DMI_MATCH(DMI_BOARD_NAME, "IFL91"), 294 DMI_MATCH(DMI_BOARD_VERSION, "IFT00"), 295 }, 296 .callback = dmi_check_cb 297 }, 298 { 299 .ident = "FL92/JFL92", 300 .matches = { 301 DMI_MATCH(DMI_BOARD_NAME, "JFL92"), 302 DMI_MATCH(DMI_BOARD_VERSION, "IFT00"), 303 }, 304 .callback = dmi_check_cb 305 }, 306 { 307 .ident = "FT00/IFT00", 308 .matches = { 309 DMI_MATCH(DMI_BOARD_NAME, "IFT00"), 310 DMI_MATCH(DMI_BOARD_VERSION, "IFT00"), 311 }, 312 .callback = dmi_check_cb 313 }, 314 { } 315 }; 316 317 static int __init compal_init(void) 318 { 319 int ret; 320 321 if (acpi_disabled) 322 return -ENODEV; 323 324 if (!force && !dmi_check_system(compal_dmi_table)) 325 return -ENODEV; 326 327 /* Register backlight stuff */ 328 329 if (!acpi_video_backlight_support()) { 330 compalbl_device = backlight_device_register("compal-laptop", NULL, NULL, 331 &compalbl_ops); 332 if (IS_ERR(compalbl_device)) 333 return PTR_ERR(compalbl_device); 334 335 compalbl_device->props.max_brightness = COMPAL_LCD_LEVEL_MAX-1; 336 } 337 338 ret = platform_driver_register(&compal_driver); 339 if (ret) 340 goto fail_backlight; 341 342 /* Register platform stuff */ 343 344 compal_device = platform_device_alloc("compal-laptop", -1); 345 if (!compal_device) { 346 ret = -ENOMEM; 347 goto fail_platform_driver; 348 } 349 350 ret = platform_device_add(compal_device); 351 if (ret) 352 goto fail_platform_device1; 353 354 ret = sysfs_create_group(&compal_device->dev.kobj, 355 &compal_attribute_group); 356 if (ret) 357 goto fail_platform_device2; 358 359 printk(KERN_INFO "compal-laptop: driver "COMPAL_DRIVER_VERSION 360 " successfully loaded.\n"); 361 362 return 0; 363 364 fail_platform_device2: 365 366 platform_device_del(compal_device); 367 368 fail_platform_device1: 369 370 platform_device_put(compal_device); 371 372 fail_platform_driver: 373 374 platform_driver_unregister(&compal_driver); 375 376 fail_backlight: 377 378 backlight_device_unregister(compalbl_device); 379 380 return ret; 381 } 382 383 static void __exit compal_cleanup(void) 384 { 385 386 sysfs_remove_group(&compal_device->dev.kobj, &compal_attribute_group); 387 platform_device_unregister(compal_device); 388 platform_driver_unregister(&compal_driver); 389 backlight_device_unregister(compalbl_device); 390 391 printk(KERN_INFO "compal-laptop: driver unloaded.\n"); 392 } 393 394 module_init(compal_init); 395 module_exit(compal_cleanup); 396 397 MODULE_AUTHOR("Cezary Jackiewicz"); 398 MODULE_DESCRIPTION("Compal Laptop Support"); 399 MODULE_VERSION(COMPAL_DRIVER_VERSION); 400 MODULE_LICENSE("GPL"); 401 402 MODULE_ALIAS("dmi:*:rnIFL90:rvrIFT00:*"); 403 MODULE_ALIAS("dmi:*:rnIFL90:rvrREFERENCE:*"); 404 MODULE_ALIAS("dmi:*:rnIFL91:rvrIFT00:*"); 405 MODULE_ALIAS("dmi:*:rnJFL92:rvrIFT00:*"); 406 MODULE_ALIAS("dmi:*:rnIFT00:rvrIFT00:*"); 407