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 * The driver registers itself with the rfkill subsystem and 30 * the Linux backlight control subsystem. 31 * 32 * This driver might work on other laptops produced by Compal. If you 33 * want to try it you can pass force=1 as argument to the module which 34 * will force it to load even when the DMI data doesn't identify the 35 * laptop as FL9x. 36 */ 37 38 #include <linux/module.h> 39 #include <linux/kernel.h> 40 #include <linux/init.h> 41 #include <linux/acpi.h> 42 #include <linux/dmi.h> 43 #include <linux/backlight.h> 44 #include <linux/platform_device.h> 45 #include <linux/rfkill.h> 46 47 #define COMPAL_DRIVER_VERSION "0.2.6" 48 49 #define COMPAL_LCD_LEVEL_MAX 8 50 51 #define COMPAL_EC_COMMAND_WIRELESS 0xBB 52 #define COMPAL_EC_COMMAND_LCD_LEVEL 0xB9 53 54 #define KILLSWITCH_MASK 0x10 55 #define WLAN_MASK 0x01 56 #define BT_MASK 0x02 57 58 static struct rfkill *wifi_rfkill; 59 static struct rfkill *bt_rfkill; 60 static struct platform_device *compal_device; 61 62 static int force; 63 module_param(force, bool, 0); 64 MODULE_PARM_DESC(force, "Force driver load, ignore DMI data"); 65 66 /* Hardware access */ 67 68 static int set_lcd_level(int level) 69 { 70 if (level < 0 || level >= COMPAL_LCD_LEVEL_MAX) 71 return -EINVAL; 72 73 ec_write(COMPAL_EC_COMMAND_LCD_LEVEL, level); 74 75 return 0; 76 } 77 78 static int get_lcd_level(void) 79 { 80 u8 result; 81 82 ec_read(COMPAL_EC_COMMAND_LCD_LEVEL, &result); 83 84 return (int) result; 85 } 86 87 static int compal_rfkill_set(void *data, bool blocked) 88 { 89 unsigned long radio = (unsigned long) data; 90 u8 result, value; 91 92 ec_read(COMPAL_EC_COMMAND_WIRELESS, &result); 93 94 if (!blocked) 95 value = (u8) (result | radio); 96 else 97 value = (u8) (result & ~radio); 98 ec_write(COMPAL_EC_COMMAND_WIRELESS, value); 99 100 return 0; 101 } 102 103 static void compal_rfkill_poll(struct rfkill *rfkill, void *data) 104 { 105 u8 result; 106 bool hw_blocked; 107 108 ec_read(COMPAL_EC_COMMAND_WIRELESS, &result); 109 110 hw_blocked = !(result & KILLSWITCH_MASK); 111 rfkill_set_hw_state(rfkill, hw_blocked); 112 } 113 114 static const struct rfkill_ops compal_rfkill_ops = { 115 .poll = compal_rfkill_poll, 116 .set_block = compal_rfkill_set, 117 }; 118 119 static int setup_rfkill(void) 120 { 121 int ret; 122 123 wifi_rfkill = rfkill_alloc("compal-wifi", &compal_device->dev, 124 RFKILL_TYPE_WLAN, &compal_rfkill_ops, 125 (void *) WLAN_MASK); 126 if (!wifi_rfkill) 127 return -ENOMEM; 128 129 ret = rfkill_register(wifi_rfkill); 130 if (ret) 131 goto err_wifi; 132 133 bt_rfkill = rfkill_alloc("compal-bluetooth", &compal_device->dev, 134 RFKILL_TYPE_BLUETOOTH, &compal_rfkill_ops, 135 (void *) BT_MASK); 136 if (!bt_rfkill) { 137 ret = -ENOMEM; 138 goto err_allocate_bt; 139 } 140 ret = rfkill_register(bt_rfkill); 141 if (ret) 142 goto err_register_bt; 143 144 return 0; 145 146 err_register_bt: 147 rfkill_destroy(bt_rfkill); 148 149 err_allocate_bt: 150 rfkill_unregister(wifi_rfkill); 151 152 err_wifi: 153 rfkill_destroy(wifi_rfkill); 154 155 return ret; 156 } 157 158 /* Backlight device stuff */ 159 160 static int bl_get_brightness(struct backlight_device *b) 161 { 162 return get_lcd_level(); 163 } 164 165 166 static int bl_update_status(struct backlight_device *b) 167 { 168 return set_lcd_level(b->props.brightness); 169 } 170 171 static struct backlight_ops compalbl_ops = { 172 .get_brightness = bl_get_brightness, 173 .update_status = bl_update_status, 174 }; 175 176 static struct backlight_device *compalbl_device; 177 178 179 static struct platform_driver compal_driver = { 180 .driver = { 181 .name = "compal-laptop", 182 .owner = THIS_MODULE, 183 } 184 }; 185 186 /* Initialization */ 187 188 static int dmi_check_cb(const struct dmi_system_id *id) 189 { 190 printk(KERN_INFO "compal-laptop: Identified laptop model '%s'.\n", 191 id->ident); 192 193 return 0; 194 } 195 196 static struct dmi_system_id __initdata compal_dmi_table[] = { 197 { 198 .ident = "FL90/IFL90", 199 .matches = { 200 DMI_MATCH(DMI_BOARD_NAME, "IFL90"), 201 DMI_MATCH(DMI_BOARD_VERSION, "IFT00"), 202 }, 203 .callback = dmi_check_cb 204 }, 205 { 206 .ident = "FL90/IFL90", 207 .matches = { 208 DMI_MATCH(DMI_BOARD_NAME, "IFL90"), 209 DMI_MATCH(DMI_BOARD_VERSION, "REFERENCE"), 210 }, 211 .callback = dmi_check_cb 212 }, 213 { 214 .ident = "FL91/IFL91", 215 .matches = { 216 DMI_MATCH(DMI_BOARD_NAME, "IFL91"), 217 DMI_MATCH(DMI_BOARD_VERSION, "IFT00"), 218 }, 219 .callback = dmi_check_cb 220 }, 221 { 222 .ident = "FL92/JFL92", 223 .matches = { 224 DMI_MATCH(DMI_BOARD_NAME, "JFL92"), 225 DMI_MATCH(DMI_BOARD_VERSION, "IFT00"), 226 }, 227 .callback = dmi_check_cb 228 }, 229 { 230 .ident = "FT00/IFT00", 231 .matches = { 232 DMI_MATCH(DMI_BOARD_NAME, "IFT00"), 233 DMI_MATCH(DMI_BOARD_VERSION, "IFT00"), 234 }, 235 .callback = dmi_check_cb 236 }, 237 { 238 .ident = "Dell Mini 9", 239 .matches = { 240 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 241 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 910"), 242 }, 243 .callback = dmi_check_cb 244 }, 245 { 246 .ident = "Dell Mini 10", 247 .matches = { 248 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 249 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1010"), 250 }, 251 .callback = dmi_check_cb 252 }, 253 { 254 .ident = "Dell Mini 10v", 255 .matches = { 256 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 257 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1011"), 258 }, 259 .callback = dmi_check_cb 260 }, 261 { 262 .ident = "Dell Inspiron 11z", 263 .matches = { 264 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 265 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1110"), 266 }, 267 .callback = dmi_check_cb 268 }, 269 { 270 .ident = "Dell Mini 12", 271 .matches = { 272 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 273 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1210"), 274 }, 275 .callback = dmi_check_cb 276 }, 277 278 { } 279 }; 280 281 static int __init compal_init(void) 282 { 283 int ret; 284 285 if (acpi_disabled) 286 return -ENODEV; 287 288 if (!force && !dmi_check_system(compal_dmi_table)) 289 return -ENODEV; 290 291 /* Register backlight stuff */ 292 293 if (!acpi_video_backlight_support()) { 294 struct backlight_properties props; 295 memset(&props, 0, sizeof(struct backlight_properties)); 296 props.max_brightness = COMPAL_LCD_LEVEL_MAX - 1; 297 compalbl_device = backlight_device_register("compal-laptop", 298 NULL, NULL, 299 &compalbl_ops, 300 &props); 301 if (IS_ERR(compalbl_device)) 302 return PTR_ERR(compalbl_device); 303 } 304 305 ret = platform_driver_register(&compal_driver); 306 if (ret) 307 goto fail_backlight; 308 309 /* Register platform stuff */ 310 311 compal_device = platform_device_alloc("compal-laptop", -1); 312 if (!compal_device) { 313 ret = -ENOMEM; 314 goto fail_platform_driver; 315 } 316 317 ret = platform_device_add(compal_device); 318 if (ret) 319 goto fail_platform_device; 320 321 ret = setup_rfkill(); 322 if (ret) 323 goto fail_rfkill; 324 325 printk(KERN_INFO "compal-laptop: driver "COMPAL_DRIVER_VERSION 326 " successfully loaded.\n"); 327 328 return 0; 329 330 fail_rfkill: 331 platform_device_del(compal_device); 332 333 fail_platform_device: 334 335 platform_device_put(compal_device); 336 337 fail_platform_driver: 338 339 platform_driver_unregister(&compal_driver); 340 341 fail_backlight: 342 343 backlight_device_unregister(compalbl_device); 344 345 return ret; 346 } 347 348 static void __exit compal_cleanup(void) 349 { 350 351 platform_device_unregister(compal_device); 352 platform_driver_unregister(&compal_driver); 353 backlight_device_unregister(compalbl_device); 354 rfkill_unregister(wifi_rfkill); 355 rfkill_destroy(wifi_rfkill); 356 rfkill_unregister(bt_rfkill); 357 rfkill_destroy(bt_rfkill); 358 359 printk(KERN_INFO "compal-laptop: driver unloaded.\n"); 360 } 361 362 module_init(compal_init); 363 module_exit(compal_cleanup); 364 365 MODULE_AUTHOR("Cezary Jackiewicz"); 366 MODULE_DESCRIPTION("Compal Laptop Support"); 367 MODULE_VERSION(COMPAL_DRIVER_VERSION); 368 MODULE_LICENSE("GPL"); 369 370 MODULE_ALIAS("dmi:*:rnIFL90:rvrIFT00:*"); 371 MODULE_ALIAS("dmi:*:rnIFL90:rvrREFERENCE:*"); 372 MODULE_ALIAS("dmi:*:rnIFL91:rvrIFT00:*"); 373 MODULE_ALIAS("dmi:*:rnJFL92:rvrIFT00:*"); 374 MODULE_ALIAS("dmi:*:rnIFT00:rvrIFT00:*"); 375 MODULE_ALIAS("dmi:*:svnDellInc.:pnInspiron910:*"); 376 MODULE_ALIAS("dmi:*:svnDellInc.:pnInspiron1010:*"); 377 MODULE_ALIAS("dmi:*:svnDellInc.:pnInspiron1011:*"); 378 MODULE_ALIAS("dmi:*:svnDellInc.:pnInspiron1110:*"); 379 MODULE_ALIAS("dmi:*:svnDellInc.:pnInspiron1210:*"); 380