Merge git://git.kernel.org/pub/scm/linux/kernel/git/mason/btrfs-unstable
[platform/adaptation/renesas_rcar/renesas_kernel.git] / drivers / platform / x86 / compal-laptop.c
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:*");