Commit | Line | Data |
---|---|---|
5411552c CJ |
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:*"); |