diff options
Diffstat (limited to 'drivers/misc/compal-laptop.c')
-rw-r--r-- | drivers/misc/compal-laptop.c | 437 |
1 files changed, 437 insertions, 0 deletions
diff --git a/drivers/misc/compal-laptop.c b/drivers/misc/compal-laptop.c new file mode 100644 index 000000000000..0c1f5875fbb9 --- /dev/null +++ b/drivers/misc/compal-laptop.c | |||
@@ -0,0 +1,437 @@ | |||
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 | * lcd_level - Screen brightness: contains a single integer in the | ||
32 | * range 0..7. (rw) | ||
33 | * | ||
34 | * wlan - wlan subsystem state: contains 0 or 1 (rw) | ||
35 | * | ||
36 | * bluetooth - Bluetooth subsystem state: contains 0 or 1 (rw) | ||
37 | * | ||
38 | * raw - raw value taken from embedded controller register (ro) | ||
39 | * | ||
40 | * In addition to these platform device attributes the driver | ||
41 | * registers itself in the Linux backlight control subsystem and is | ||
42 | * available to userspace under /sys/class/backlight/compal-laptop/. | ||
43 | * | ||
44 | * This driver might work on other laptops produced by Compal. If you | ||
45 | * want to try it you can pass force=1 as argument to the module which | ||
46 | * will force it to load even when the DMI data doesn't identify the | ||
47 | * laptop as IFL90. | ||
48 | */ | ||
49 | |||
50 | #include <linux/module.h> | ||
51 | #include <linux/kernel.h> | ||
52 | #include <linux/init.h> | ||
53 | #include <linux/acpi.h> | ||
54 | #include <linux/dmi.h> | ||
55 | #include <linux/backlight.h> | ||
56 | #include <linux/platform_device.h> | ||
57 | #include <linux/autoconf.h> | ||
58 | |||
59 | #define COMPAL_DRIVER_VERSION "0.2.5" | ||
60 | |||
61 | #define COMPAL_LCD_LEVEL_MAX 8 | ||
62 | |||
63 | #define COMPAL_EC_COMMAND_WIRELESS 0xBB | ||
64 | #define COMPAL_EC_COMMAND_LCD_LEVEL 0xB9 | ||
65 | |||
66 | #define KILLSWITCH_MASK 0x10 | ||
67 | #define WLAN_MASK 0x01 | ||
68 | #define BT_MASK 0x02 | ||
69 | |||
70 | static int force; | ||
71 | module_param(force, bool, 0); | ||
72 | MODULE_PARM_DESC(force, "Force driver load, ignore DMI data"); | ||
73 | |||
74 | /* Hardware access */ | ||
75 | |||
76 | static int set_lcd_level(int level) | ||
77 | { | ||
78 | if (level < 0 || level >= COMPAL_LCD_LEVEL_MAX) | ||
79 | return -EINVAL; | ||
80 | |||
81 | ec_write(COMPAL_EC_COMMAND_LCD_LEVEL, level); | ||
82 | |||
83 | return 0; | ||
84 | } | ||
85 | |||
86 | static int get_lcd_level(void) | ||
87 | { | ||
88 | u8 result; | ||
89 | |||
90 | ec_read(COMPAL_EC_COMMAND_LCD_LEVEL, &result); | ||
91 | |||
92 | return (int) result; | ||
93 | } | ||
94 | |||
95 | static int set_wlan_state(int state) | ||
96 | { | ||
97 | u8 result, value; | ||
98 | |||
99 | ec_read(COMPAL_EC_COMMAND_WIRELESS, &result); | ||
100 | |||
101 | if ((result & KILLSWITCH_MASK) == 0) | ||
102 | return -EINVAL; | ||
103 | else { | ||
104 | if (state) | ||
105 | value = (u8) (result | WLAN_MASK); | ||
106 | else | ||
107 | value = (u8) (result & ~WLAN_MASK); | ||
108 | ec_write(COMPAL_EC_COMMAND_WIRELESS, value); | ||
109 | } | ||
110 | |||
111 | return 0; | ||
112 | } | ||
113 | |||
114 | static int set_bluetooth_state(int state) | ||
115 | { | ||
116 | u8 result, value; | ||
117 | |||
118 | ec_read(COMPAL_EC_COMMAND_WIRELESS, &result); | ||
119 | |||
120 | if ((result & KILLSWITCH_MASK) == 0) | ||
121 | return -EINVAL; | ||
122 | else { | ||
123 | if (state) | ||
124 | value = (u8) (result | BT_MASK); | ||
125 | else | ||
126 | value = (u8) (result & ~BT_MASK); | ||
127 | ec_write(COMPAL_EC_COMMAND_WIRELESS, value); | ||
128 | } | ||
129 | |||
130 | return 0; | ||
131 | } | ||
132 | |||
133 | static int get_wireless_state(int *wlan, int *bluetooth) | ||
134 | { | ||
135 | u8 result; | ||
136 | |||
137 | ec_read(COMPAL_EC_COMMAND_WIRELESS, &result); | ||
138 | |||
139 | if (wlan) { | ||
140 | if ((result & KILLSWITCH_MASK) == 0) | ||
141 | *wlan = 0; | ||
142 | else | ||
143 | *wlan = result & WLAN_MASK; | ||
144 | } | ||
145 | |||
146 | if (bluetooth) { | ||
147 | if ((result & KILLSWITCH_MASK) == 0) | ||
148 | *bluetooth = 0; | ||
149 | else | ||
150 | *bluetooth = (result & BT_MASK) >> 1; | ||
151 | } | ||
152 | |||
153 | return 0; | ||
154 | } | ||
155 | |||
156 | /* Backlight device stuff */ | ||
157 | |||
158 | static int bl_get_brightness(struct backlight_device *b) | ||
159 | { | ||
160 | return get_lcd_level(); | ||
161 | } | ||
162 | |||
163 | |||
164 | static int bl_update_status(struct backlight_device *b) | ||
165 | { | ||
166 | return set_lcd_level(b->props.brightness); | ||
167 | } | ||
168 | |||
169 | static struct backlight_ops compalbl_ops = { | ||
170 | .get_brightness = bl_get_brightness, | ||
171 | .update_status = bl_update_status, | ||
172 | }; | ||
173 | |||
174 | static struct backlight_device *compalbl_device; | ||
175 | |||
176 | /* Platform device */ | ||
177 | |||
178 | static ssize_t show_wlan(struct device *dev, | ||
179 | struct device_attribute *attr, char *buf) | ||
180 | { | ||
181 | int ret, enabled; | ||
182 | |||
183 | ret = get_wireless_state(&enabled, NULL); | ||
184 | if (ret < 0) | ||
185 | return ret; | ||
186 | |||
187 | return sprintf(buf, "%i\n", enabled); | ||
188 | } | ||
189 | |||
190 | static ssize_t show_raw(struct device *dev, | ||
191 | struct device_attribute *attr, char *buf) | ||
192 | { | ||
193 | u8 result; | ||
194 | |||
195 | ec_read(COMPAL_EC_COMMAND_WIRELESS, &result); | ||
196 | |||
197 | return sprintf(buf, "%i\n", result); | ||
198 | } | ||
199 | |||
200 | static ssize_t show_bluetooth(struct device *dev, | ||
201 | struct device_attribute *attr, char *buf) | ||
202 | { | ||
203 | int ret, enabled; | ||
204 | |||
205 | ret = get_wireless_state(NULL, &enabled); | ||
206 | if (ret < 0) | ||
207 | return ret; | ||
208 | |||
209 | return sprintf(buf, "%i\n", enabled); | ||
210 | } | ||
211 | |||
212 | static ssize_t show_lcd_level(struct device *dev, | ||
213 | struct device_attribute *attr, char *buf) | ||
214 | { | ||
215 | int ret; | ||
216 | |||
217 | ret = get_lcd_level(); | ||
218 | if (ret < 0) | ||
219 | return ret; | ||
220 | |||
221 | return sprintf(buf, "%i\n", ret); | ||
222 | } | ||
223 | |||
224 | static ssize_t store_lcd_level(struct device *dev, | ||
225 | struct device_attribute *attr, const char *buf, size_t count) | ||
226 | { | ||
227 | int level, ret; | ||
228 | |||
229 | if (sscanf(buf, "%i", &level) != 1 || | ||
230 | (level < 0 || level >= COMPAL_LCD_LEVEL_MAX)) | ||
231 | return -EINVAL; | ||
232 | |||
233 | ret = set_lcd_level(level); | ||
234 | if (ret < 0) | ||
235 | return ret; | ||
236 | |||
237 | return count; | ||
238 | } | ||
239 | |||
240 | static ssize_t store_wlan_state(struct device *dev, | ||
241 | struct device_attribute *attr, const char *buf, size_t count) | ||
242 | { | ||
243 | int state, ret; | ||
244 | |||
245 | if (sscanf(buf, "%i", &state) != 1 || (state < 0 || state > 1)) | ||
246 | return -EINVAL; | ||
247 | |||
248 | ret = set_wlan_state(state); | ||
249 | if (ret < 0) | ||
250 | return ret; | ||
251 | |||
252 | return count; | ||
253 | } | ||
254 | |||
255 | static ssize_t store_bluetooth_state(struct device *dev, | ||
256 | struct device_attribute *attr, const char *buf, size_t count) | ||
257 | { | ||
258 | int state, ret; | ||
259 | |||
260 | if (sscanf(buf, "%i", &state) != 1 || (state < 0 || state > 1)) | ||
261 | return -EINVAL; | ||
262 | |||
263 | ret = set_bluetooth_state(state); | ||
264 | if (ret < 0) | ||
265 | return ret; | ||
266 | |||
267 | return count; | ||
268 | } | ||
269 | |||
270 | static DEVICE_ATTR(lcd_level, 0644, show_lcd_level, store_lcd_level); | ||
271 | static DEVICE_ATTR(bluetooth, 0644, show_bluetooth, store_bluetooth_state); | ||
272 | static DEVICE_ATTR(wlan, 0644, show_wlan, store_wlan_state); | ||
273 | static DEVICE_ATTR(raw, 0444, show_raw, NULL); | ||
274 | |||
275 | static struct attribute *compal_attributes[] = { | ||
276 | &dev_attr_lcd_level.attr, | ||
277 | &dev_attr_bluetooth.attr, | ||
278 | &dev_attr_wlan.attr, | ||
279 | &dev_attr_raw.attr, | ||
280 | NULL | ||
281 | }; | ||
282 | |||
283 | static struct attribute_group compal_attribute_group = { | ||
284 | .attrs = compal_attributes | ||
285 | }; | ||
286 | |||
287 | static struct platform_driver compal_driver = { | ||
288 | .driver = { | ||
289 | .name = "compal-laptop", | ||
290 | .owner = THIS_MODULE, | ||
291 | } | ||
292 | }; | ||
293 | |||
294 | static struct platform_device *compal_device; | ||
295 | |||
296 | /* Initialization */ | ||
297 | |||
298 | static int dmi_check_cb(const struct dmi_system_id *id) | ||
299 | { | ||
300 | printk(KERN_INFO "compal-laptop: Identified laptop model '%s'.\n", | ||
301 | id->ident); | ||
302 | |||
303 | return 0; | ||
304 | } | ||
305 | |||
306 | static struct dmi_system_id __initdata compal_dmi_table[] = { | ||
307 | { | ||
308 | .ident = "FL90/IFL90", | ||
309 | .matches = { | ||
310 | DMI_MATCH(DMI_BOARD_NAME, "IFL90"), | ||
311 | DMI_MATCH(DMI_BOARD_VERSION, "IFT00"), | ||
312 | }, | ||
313 | .callback = dmi_check_cb | ||
314 | }, | ||
315 | { | ||
316 | .ident = "FL90/IFL90", | ||
317 | .matches = { | ||
318 | DMI_MATCH(DMI_BOARD_NAME, "IFL90"), | ||
319 | DMI_MATCH(DMI_BOARD_VERSION, "REFERENCE"), | ||
320 | }, | ||
321 | .callback = dmi_check_cb | ||
322 | }, | ||
323 | { | ||
324 | .ident = "FL91/IFL91", | ||
325 | .matches = { | ||
326 | DMI_MATCH(DMI_BOARD_NAME, "IFL91"), | ||
327 | DMI_MATCH(DMI_BOARD_VERSION, "IFT00"), | ||
328 | }, | ||
329 | .callback = dmi_check_cb | ||
330 | }, | ||
331 | { | ||
332 | .ident = "FL92/JFL92", | ||
333 | .matches = { | ||
334 | DMI_MATCH(DMI_BOARD_NAME, "JFL92"), | ||
335 | DMI_MATCH(DMI_BOARD_VERSION, "IFT00"), | ||
336 | }, | ||
337 | .callback = dmi_check_cb | ||
338 | }, | ||
339 | { | ||
340 | .ident = "FT00/IFT00", | ||
341 | .matches = { | ||
342 | DMI_MATCH(DMI_BOARD_NAME, "IFT00"), | ||
343 | DMI_MATCH(DMI_BOARD_VERSION, "IFT00"), | ||
344 | }, | ||
345 | .callback = dmi_check_cb | ||
346 | }, | ||
347 | { } | ||
348 | }; | ||
349 | |||
350 | static int __init compal_init(void) | ||
351 | { | ||
352 | int ret; | ||
353 | |||
354 | if (acpi_disabled) | ||
355 | return -ENODEV; | ||
356 | |||
357 | if (!force && !dmi_check_system(compal_dmi_table)) | ||
358 | return -ENODEV; | ||
359 | |||
360 | /* Register backlight stuff */ | ||
361 | |||
362 | compalbl_device = backlight_device_register("compal-laptop", NULL, NULL, | ||
363 | &compalbl_ops); | ||
364 | if (IS_ERR(compalbl_device)) | ||
365 | return PTR_ERR(compalbl_device); | ||
366 | |||
367 | compalbl_device->props.max_brightness = COMPAL_LCD_LEVEL_MAX-1; | ||
368 | |||
369 | ret = platform_driver_register(&compal_driver); | ||
370 | if (ret) | ||
371 | goto fail_backlight; | ||
372 | |||
373 | /* Register platform stuff */ | ||
374 | |||
375 | compal_device = platform_device_alloc("compal-laptop", -1); | ||
376 | if (!compal_device) { | ||
377 | ret = -ENOMEM; | ||
378 | goto fail_platform_driver; | ||
379 | } | ||
380 | |||
381 | ret = platform_device_add(compal_device); | ||
382 | if (ret) | ||
383 | goto fail_platform_device1; | ||
384 | |||
385 | ret = sysfs_create_group(&compal_device->dev.kobj, | ||
386 | &compal_attribute_group); | ||
387 | if (ret) | ||
388 | goto fail_platform_device2; | ||
389 | |||
390 | printk(KERN_INFO "compal-laptop: driver "COMPAL_DRIVER_VERSION | ||
391 | " successfully loaded.\n"); | ||
392 | |||
393 | return 0; | ||
394 | |||
395 | fail_platform_device2: | ||
396 | |||
397 | platform_device_del(compal_device); | ||
398 | |||
399 | fail_platform_device1: | ||
400 | |||
401 | platform_device_put(compal_device); | ||
402 | |||
403 | fail_platform_driver: | ||
404 | |||
405 | platform_driver_unregister(&compal_driver); | ||
406 | |||
407 | fail_backlight: | ||
408 | |||
409 | backlight_device_unregister(compalbl_device); | ||
410 | |||
411 | return ret; | ||
412 | } | ||
413 | |||
414 | static void __exit compal_cleanup(void) | ||
415 | { | ||
416 | |||
417 | sysfs_remove_group(&compal_device->dev.kobj, &compal_attribute_group); | ||
418 | platform_device_unregister(compal_device); | ||
419 | platform_driver_unregister(&compal_driver); | ||
420 | backlight_device_unregister(compalbl_device); | ||
421 | |||
422 | printk(KERN_INFO "compal-laptop: driver unloaded.\n"); | ||
423 | } | ||
424 | |||
425 | module_init(compal_init); | ||
426 | module_exit(compal_cleanup); | ||
427 | |||
428 | MODULE_AUTHOR("Cezary Jackiewicz"); | ||
429 | MODULE_DESCRIPTION("Compal Laptop Support"); | ||
430 | MODULE_VERSION(COMPAL_DRIVER_VERSION); | ||
431 | MODULE_LICENSE("GPL"); | ||
432 | |||
433 | MODULE_ALIAS("dmi:*:rnIFL90:rvrIFT00:*"); | ||
434 | MODULE_ALIAS("dmi:*:rnIFL90:rvrREFERENCE:*"); | ||
435 | MODULE_ALIAS("dmi:*:rnIFL91:rvrIFT00:*"); | ||
436 | MODULE_ALIAS("dmi:*:rnJFL92:rvrIFT00:*"); | ||
437 | MODULE_ALIAS("dmi:*:rnIFT00:rvrIFT00:*"); | ||