blob: b9b5aebbd5b056695014365db72503047ac70734 [file] [log] [blame]
Eric Coopere59f8792008-03-13 12:55:46 +01001/*
Alan Jenkinsa7624b62009-12-03 07:45:08 +00002 * eeepc-laptop.c - Asus Eee PC extras
Eric Coopere59f8792008-03-13 12:55:46 +01003 *
4 * Based on asus_acpi.c as patched for the Eee PC by Asus:
5 * ftp://ftp.asus.com/pub/ASUS/EeePC/701/ASUS_ACPI_071126.rar
6 * Based on eee.c from eeepc-linux
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 */
18
Joe Perches19b53282009-06-25 13:25:37 +020019#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
20
Eric Coopere59f8792008-03-13 12:55:46 +010021#include <linux/kernel.h>
22#include <linux/module.h>
23#include <linux/init.h>
24#include <linux/types.h>
25#include <linux/platform_device.h>
Corentin Charya5fa4292008-03-13 12:56:37 +010026#include <linux/backlight.h>
27#include <linux/fb.h>
Corentin Charye1faa9d2008-03-13 12:57:18 +010028#include <linux/hwmon.h>
29#include <linux/hwmon-sysfs.h>
Eric Coopere59f8792008-03-13 12:55:46 +010030#include <acpi/acpi_drivers.h>
31#include <acpi/acpi_bus.h>
32#include <linux/uaccess.h>
Matthew Garretta195dcd2008-08-19 12:13:20 +010033#include <linux/input.h>
34#include <linux/rfkill.h>
Matthew Garrett57402942009-01-20 16:17:48 +010035#include <linux/pci.h>
Corentin Chary2b121bc2009-06-25 13:25:36 +020036#include <linux/pci_hotplug.h>
Corentin Chary3c0eb512009-12-03 07:44:52 +000037#include <linux/leds.h>
Eric Coopere59f8792008-03-13 12:55:46 +010038
39#define EEEPC_LAPTOP_VERSION "0.1"
Alan Jenkinsa7624b62009-12-03 07:45:08 +000040#define EEEPC_LAPTOP_NAME "Eee PC Hotkey Driver"
41#define EEEPC_LAPTOP_FILE "eeepc"
Eric Coopere59f8792008-03-13 12:55:46 +010042
Alan Jenkinsa7624b62009-12-03 07:45:08 +000043#define EEEPC_ACPI_CLASS "hotkey"
44#define EEEPC_ACPI_DEVICE_NAME "Hotkey"
45#define EEEPC_ACPI_HID "ASUS010"
Eric Coopere59f8792008-03-13 12:55:46 +010046
Alan Jenkins52bbe3c2009-12-03 07:45:07 +000047MODULE_AUTHOR("Corentin Chary, Eric Cooper");
Alan Jenkinsa7624b62009-12-03 07:45:08 +000048MODULE_DESCRIPTION(EEEPC_LAPTOP_NAME);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +000049MODULE_LICENSE("GPL");
Eric Coopere59f8792008-03-13 12:55:46 +010050
51/*
52 * Definitions for Asus EeePC
53 */
Corentin Charya5fa4292008-03-13 12:56:37 +010054#define NOTIFY_BRN_MIN 0x20
55#define NOTIFY_BRN_MAX 0x2f
Eric Coopere59f8792008-03-13 12:55:46 +010056
57enum {
58 DISABLE_ASL_WLAN = 0x0001,
59 DISABLE_ASL_BLUETOOTH = 0x0002,
60 DISABLE_ASL_IRDA = 0x0004,
61 DISABLE_ASL_CAMERA = 0x0008,
62 DISABLE_ASL_TV = 0x0010,
63 DISABLE_ASL_GPS = 0x0020,
64 DISABLE_ASL_DISPLAYSWITCH = 0x0040,
65 DISABLE_ASL_MODEM = 0x0080,
Corentin Charyb7b700d2009-06-16 19:28:52 +000066 DISABLE_ASL_CARDREADER = 0x0100,
67 DISABLE_ASL_3G = 0x0200,
68 DISABLE_ASL_WIMAX = 0x0400,
69 DISABLE_ASL_HWCF = 0x0800
Eric Coopere59f8792008-03-13 12:55:46 +010070};
71
72enum {
73 CM_ASL_WLAN = 0,
74 CM_ASL_BLUETOOTH,
75 CM_ASL_IRDA,
76 CM_ASL_1394,
77 CM_ASL_CAMERA,
78 CM_ASL_TV,
79 CM_ASL_GPS,
80 CM_ASL_DVDROM,
81 CM_ASL_DISPLAYSWITCH,
82 CM_ASL_PANELBRIGHT,
83 CM_ASL_BIOSFLASH,
84 CM_ASL_ACPIFLASH,
85 CM_ASL_CPUFV,
86 CM_ASL_CPUTEMPERATURE,
87 CM_ASL_FANCPU,
88 CM_ASL_FANCHASSIS,
89 CM_ASL_USBPORT1,
90 CM_ASL_USBPORT2,
91 CM_ASL_USBPORT3,
92 CM_ASL_MODEM,
93 CM_ASL_CARDREADER,
Corentin Charyb7b700d2009-06-16 19:28:52 +000094 CM_ASL_3G,
95 CM_ASL_WIMAX,
96 CM_ASL_HWCF,
97 CM_ASL_LID,
98 CM_ASL_TYPE,
99 CM_ASL_PANELPOWER, /*P901*/
100 CM_ASL_TPD
Eric Coopere59f8792008-03-13 12:55:46 +0100101};
102
Adrian Bunk14109462008-06-25 19:25:47 +0300103static const char *cm_getv[] = {
Jonathan McDowell3af9bfc2008-12-03 20:31:11 +0000104 "WLDG", "BTHG", NULL, NULL,
Eric Coopere59f8792008-03-13 12:55:46 +0100105 "CAMG", NULL, NULL, NULL,
106 NULL, "PBLG", NULL, NULL,
107 "CFVG", NULL, NULL, NULL,
108 "USBG", NULL, NULL, "MODG",
Corentin Charyb7b700d2009-06-16 19:28:52 +0000109 "CRDG", "M3GG", "WIMG", "HWCF",
110 "LIDG", "TYPE", "PBPG", "TPDG"
Eric Coopere59f8792008-03-13 12:55:46 +0100111};
112
Adrian Bunk14109462008-06-25 19:25:47 +0300113static const char *cm_setv[] = {
Jonathan McDowell3af9bfc2008-12-03 20:31:11 +0000114 "WLDS", "BTHS", NULL, NULL,
Eric Coopere59f8792008-03-13 12:55:46 +0100115 "CAMS", NULL, NULL, NULL,
116 "SDSP", "PBLS", "HDPS", NULL,
117 "CFVS", NULL, NULL, NULL,
118 "USBG", NULL, NULL, "MODS",
Corentin Charyb7b700d2009-06-16 19:28:52 +0000119 "CRDS", "M3GS", "WIMS", NULL,
120 NULL, NULL, "PBPS", "TPDS"
Eric Coopere59f8792008-03-13 12:55:46 +0100121};
122
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000123struct key_entry {
124 char type;
125 u8 code;
126 u16 keycode;
127};
Corentin Charye1faa9d2008-03-13 12:57:18 +0100128
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000129enum { KE_KEY, KE_END };
130
131static struct key_entry eeepc_keymap[] = {
132 /* Sleep already handled via generic ACPI code */
133 {KE_KEY, 0x10, KEY_WLAN },
134 {KE_KEY, 0x11, KEY_WLAN },
135 {KE_KEY, 0x12, KEY_PROG1 },
136 {KE_KEY, 0x13, KEY_MUTE },
137 {KE_KEY, 0x14, KEY_VOLUMEDOWN },
138 {KE_KEY, 0x15, KEY_VOLUMEUP },
139 {KE_KEY, 0x1a, KEY_COFFEE },
140 {KE_KEY, 0x1b, KEY_ZOOM },
141 {KE_KEY, 0x1c, KEY_PROG2 },
142 {KE_KEY, 0x1d, KEY_PROG3 },
143 {KE_KEY, NOTIFY_BRN_MIN, KEY_BRIGHTNESSDOWN },
144 {KE_KEY, NOTIFY_BRN_MAX, KEY_BRIGHTNESSUP },
145 {KE_KEY, 0x30, KEY_SWITCHVIDEOMODE },
146 {KE_KEY, 0x31, KEY_SWITCHVIDEOMODE },
147 {KE_KEY, 0x32, KEY_SWITCHVIDEOMODE },
148 {KE_END, 0},
149};
Alan Jenkins463b4e42009-12-03 07:45:03 +0000150
Corentin Charye1faa9d2008-03-13 12:57:18 +0100151
Eric Coopere59f8792008-03-13 12:55:46 +0100152/*
153 * This is the main structure, we can use it to store useful information
Eric Coopere59f8792008-03-13 12:55:46 +0100154 */
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000155struct eeepc_laptop {
Eric Coopere59f8792008-03-13 12:55:46 +0100156 struct acpi_device *device; /* the device we are in */
157 acpi_handle handle; /* the handle of the hotk device */
158 u32 cm_supported; /* the control methods supported
159 by this BIOS */
Eric Coopere59f8792008-03-13 12:55:46 +0100160 u16 event_count[128]; /* count for each event */
Matthew Garretta195dcd2008-08-19 12:13:20 +0100161 struct input_dev *inputdev;
162 u16 *keycode_map;
Corentin Chary7de39382009-06-25 13:25:38 +0200163 struct rfkill *wlan_rfkill;
164 struct rfkill *bluetooth_rfkill;
Corentin Chary3cd530b2009-06-25 13:25:42 +0200165 struct rfkill *wwan3g_rfkill;
Corentin Charyd1ec9c32009-08-28 12:56:41 +0000166 struct rfkill *wimax_rfkill;
Corentin Chary2b121bc2009-06-25 13:25:36 +0200167 struct hotplug_slot *hotplug_slot;
Alan Jenkinsdcf443b2009-08-28 12:56:33 +0000168 struct mutex hotplug_lock;
Eric Coopere59f8792008-03-13 12:55:46 +0100169};
170
171/* The actual device the driver binds to */
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000172static struct eeepc_laptop *eeepc;
Eric Coopere59f8792008-03-13 12:55:46 +0100173
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000174/* The platform device */
Eric Coopere59f8792008-03-13 12:55:46 +0100175static struct platform_device *platform_device;
176
Corentin Charya5fa4292008-03-13 12:56:37 +0100177/* The backlight device /sys/class/backlight */
178static struct backlight_device *eeepc_backlight_device;
179
Corentin Charye1faa9d2008-03-13 12:57:18 +0100180/* The hwmon device */
181static struct device *eeepc_hwmon_device;
182
Eric Coopere59f8792008-03-13 12:55:46 +0100183
184/*
185 * ACPI Helpers
186 */
Alan Jenkins6b188a72009-12-03 07:45:02 +0000187static int write_acpi_int(acpi_handle handle, const char *method, int val)
Eric Coopere59f8792008-03-13 12:55:46 +0100188{
189 struct acpi_object_list params;
190 union acpi_object in_obj;
191 acpi_status status;
192
193 params.count = 1;
194 params.pointer = &in_obj;
195 in_obj.type = ACPI_TYPE_INTEGER;
196 in_obj.integer.value = val;
197
Alan Jenkins6b188a72009-12-03 07:45:02 +0000198 status = acpi_evaluate_object(handle, (char *)method, &params, NULL);
Eric Coopere59f8792008-03-13 12:55:46 +0100199 return (status == AE_OK ? 0 : -1);
200}
201
202static int read_acpi_int(acpi_handle handle, const char *method, int *val)
203{
204 acpi_status status;
Matthew Wilcox27663c52008-10-10 02:22:59 -0400205 unsigned long long result;
Eric Coopere59f8792008-03-13 12:55:46 +0100206
207 status = acpi_evaluate_integer(handle, (char *)method, NULL, &result);
208 if (ACPI_FAILURE(status)) {
209 *val = -1;
210 return -1;
211 } else {
212 *val = result;
213 return 0;
214 }
215}
216
217static int set_acpi(int cm, int value)
218{
Alan Jenkins13f70022009-12-03 07:44:59 +0000219 const char *method = cm_setv[cm];
220
221 if (method == NULL)
222 return -ENODEV;
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000223 if ((eeepc->cm_supported & (0x1 << cm)) == 0)
Alan Jenkins13f70022009-12-03 07:44:59 +0000224 return -ENODEV;
225
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000226 if (write_acpi_int(eeepc->handle, method, value))
Alan Jenkins13f70022009-12-03 07:44:59 +0000227 pr_warning("Error writing %s\n", method);
Eric Coopere59f8792008-03-13 12:55:46 +0100228 return 0;
229}
230
231static int get_acpi(int cm)
232{
Alan Jenkins13f70022009-12-03 07:44:59 +0000233 const char *method = cm_getv[cm];
234 int value;
235
236 if (method == NULL)
237 return -ENODEV;
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000238 if ((eeepc->cm_supported & (0x1 << cm)) == 0)
Alan Jenkins13f70022009-12-03 07:44:59 +0000239 return -ENODEV;
240
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000241 if (read_acpi_int(eeepc->handle, method, &value))
Alan Jenkins13f70022009-12-03 07:44:59 +0000242 pr_warning("Error reading %s\n", method);
Eric Coopere59f8792008-03-13 12:55:46 +0100243 return value;
244}
245
246/*
247 * Sys helpers
248 */
249static int parse_arg(const char *buf, unsigned long count, int *val)
250{
251 if (!count)
252 return 0;
253 if (sscanf(buf, "%i", val) != 1)
254 return -EINVAL;
255 return count;
256}
257
258static ssize_t store_sys_acpi(int cm, const char *buf, size_t count)
259{
260 int rv, value;
261
262 rv = parse_arg(buf, count, &value);
263 if (rv > 0)
Corentin Charyf36509e2009-06-25 13:25:40 +0200264 value = set_acpi(cm, value);
265 if (value < 0)
Alan Jenkins6dff29b2009-12-03 07:44:45 +0000266 return -EIO;
Eric Coopere59f8792008-03-13 12:55:46 +0100267 return rv;
268}
269
270static ssize_t show_sys_acpi(int cm, char *buf)
271{
Corentin Charyf36509e2009-06-25 13:25:40 +0200272 int value = get_acpi(cm);
273
274 if (value < 0)
Alan Jenkins6dff29b2009-12-03 07:44:45 +0000275 return -EIO;
Corentin Charyf36509e2009-06-25 13:25:40 +0200276 return sprintf(buf, "%d\n", value);
Eric Coopere59f8792008-03-13 12:55:46 +0100277}
278
Alan Jenkins6dff29b2009-12-03 07:44:45 +0000279#define EEEPC_CREATE_DEVICE_ATTR(_name, _mode, _cm) \
Eric Coopere59f8792008-03-13 12:55:46 +0100280 static ssize_t show_##_name(struct device *dev, \
281 struct device_attribute *attr, \
282 char *buf) \
283 { \
284 return show_sys_acpi(_cm, buf); \
285 } \
286 static ssize_t store_##_name(struct device *dev, \
287 struct device_attribute *attr, \
288 const char *buf, size_t count) \
289 { \
290 return store_sys_acpi(_cm, buf, count); \
291 } \
292 static struct device_attribute dev_attr_##_name = { \
293 .attr = { \
294 .name = __stringify(_name), \
Alan Jenkins6dff29b2009-12-03 07:44:45 +0000295 .mode = _mode }, \
Eric Coopere59f8792008-03-13 12:55:46 +0100296 .show = show_##_name, \
297 .store = store_##_name, \
298 }
299
Alan Jenkins6dff29b2009-12-03 07:44:45 +0000300EEEPC_CREATE_DEVICE_ATTR(camera, 0644, CM_ASL_CAMERA);
301EEEPC_CREATE_DEVICE_ATTR(cardr, 0644, CM_ASL_CARDREADER);
302EEEPC_CREATE_DEVICE_ATTR(disp, 0200, CM_ASL_DISPLAYSWITCH);
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000303
304struct eeepc_cpufv {
305 int num;
306 int cur;
307};
308
309static int get_cpufv(struct eeepc_cpufv *c)
310{
311 c->cur = get_acpi(CM_ASL_CPUFV);
312 c->num = (c->cur >> 8) & 0xff;
313 c->cur &= 0xff;
314 if (c->cur < 0 || c->num <= 0 || c->num > 12)
315 return -ENODEV;
316 return 0;
317}
318
319static ssize_t show_available_cpufv(struct device *dev,
320 struct device_attribute *attr,
321 char *buf)
322{
323 struct eeepc_cpufv c;
324 int i;
325 ssize_t len = 0;
326
327 if (get_cpufv(&c))
328 return -ENODEV;
329 for (i = 0; i < c.num; i++)
330 len += sprintf(buf + len, "%d ", i);
331 len += sprintf(buf + len, "\n");
332 return len;
333}
334
335static ssize_t show_cpufv(struct device *dev,
336 struct device_attribute *attr,
337 char *buf)
338{
339 struct eeepc_cpufv c;
340
341 if (get_cpufv(&c))
342 return -ENODEV;
343 return sprintf(buf, "%#x\n", (c.num << 8) | c.cur);
344}
345
346static ssize_t store_cpufv(struct device *dev,
347 struct device_attribute *attr,
348 const char *buf, size_t count)
349{
350 struct eeepc_cpufv c;
351 int rv, value;
352
353 if (get_cpufv(&c))
354 return -ENODEV;
355 rv = parse_arg(buf, count, &value);
356 if (rv < 0)
357 return rv;
358 if (!rv || value < 0 || value >= c.num)
359 return -EINVAL;
360 set_acpi(CM_ASL_CPUFV, value);
361 return rv;
362}
363
364static struct device_attribute dev_attr_cpufv = {
365 .attr = {
366 .name = "cpufv",
367 .mode = 0644 },
368 .show = show_cpufv,
369 .store = store_cpufv
370};
371
372static struct device_attribute dev_attr_available_cpufv = {
373 .attr = {
374 .name = "available_cpufv",
375 .mode = 0444 },
376 .show = show_available_cpufv
377};
Eric Coopere59f8792008-03-13 12:55:46 +0100378
379static struct attribute *platform_attributes[] = {
380 &dev_attr_camera.attr,
381 &dev_attr_cardr.attr,
382 &dev_attr_disp.attr,
Grigori Goronzy158ca1d2009-04-27 09:23:40 +0200383 &dev_attr_cpufv.attr,
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000384 &dev_attr_available_cpufv.attr,
Eric Coopere59f8792008-03-13 12:55:46 +0100385 NULL
386};
387
388static struct attribute_group platform_attribute_group = {
389 .attrs = platform_attributes
390};
391
Alan Jenkins9db106b2009-12-03 07:45:06 +0000392static int eeepc_platform_init(void)
393{
394 int result;
395
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000396 platform_device = platform_device_alloc(EEEPC_LAPTOP_FILE, -1);
Alan Jenkins9db106b2009-12-03 07:45:06 +0000397 if (!platform_device)
398 return -ENOMEM;
399
400 result = platform_device_add(platform_device);
401 if (result)
402 goto fail_platform_device;
403
404 result = sysfs_create_group(&platform_device->dev.kobj,
405 &platform_attribute_group);
406 if (result)
407 goto fail_sysfs;
408 return 0;
409
410fail_sysfs:
411 platform_device_del(platform_device);
412fail_platform_device:
413 platform_device_put(platform_device);
414 return result;
415}
416
417static void eeepc_platform_exit(void)
418{
419 sysfs_remove_group(&platform_device->dev.kobj,
420 &platform_attribute_group);
421 platform_device_unregister(platform_device);
422}
423
Eric Coopere59f8792008-03-13 12:55:46 +0100424/*
Corentin Chary3c0eb512009-12-03 07:44:52 +0000425 * LEDs
426 */
427/*
428 * These functions actually update the LED's, and are called from a
429 * workqueue. By doing this as separate work rather than when the LED
430 * subsystem asks, we avoid messing with the Asus ACPI stuff during a
431 * potentially bad time, such as a timer interrupt.
432 */
433static int tpd_led_wk;
434
435static void tpd_led_update(struct work_struct *ignored)
436{
437 int value = tpd_led_wk;
438 set_acpi(CM_ASL_TPD, value);
439}
440
441static struct workqueue_struct *led_workqueue;
442static DECLARE_WORK(tpd_led_work, tpd_led_update);
443
444static void tpd_led_set(struct led_classdev *led_cdev,
445 enum led_brightness value)
446{
447 tpd_led_wk = (value > 0) ? 1 : 0;
448 queue_work(led_workqueue, &tpd_led_work);
449}
450
451static struct led_classdev tpd_led = {
452 .name = "eeepc::touchpad",
453 .brightness_set = tpd_led_set,
454 .max_brightness = 1
455};
456
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000457static int eeepc_led_init(struct device *dev)
458{
459 int rv;
460
461 if (get_acpi(CM_ASL_TPD) == -ENODEV)
462 return 0;
463
464 led_workqueue = create_singlethread_workqueue("led_workqueue");
465 if (!led_workqueue)
466 return -ENOMEM;
467
468 rv = led_classdev_register(dev, &tpd_led);
469 if (rv) {
470 destroy_workqueue(led_workqueue);
471 return rv;
472 }
473
474 return 0;
475}
476
477static void eeepc_led_exit(void)
478{
479 if (tpd_led.dev)
480 led_classdev_unregister(&tpd_led);
481 if (led_workqueue)
482 destroy_workqueue(led_workqueue);
483}
484
485
Corentin Chary3c0eb512009-12-03 07:44:52 +0000486/*
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000487 * PCI hotplug (for wlan rfkill)
Eric Coopere59f8792008-03-13 12:55:46 +0100488 */
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000489static bool eeepc_wlan_rfkill_blocked(void)
Matthew Garretta195dcd2008-08-19 12:13:20 +0100490{
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000491 if (get_acpi(CM_ASL_WLAN) == 1)
492 return false;
493 return true;
Corentin Chary2b121bc2009-06-25 13:25:36 +0200494}
495
Corentin Chary58ce48a2009-10-16 22:22:46 +0200496static void eeepc_rfkill_hotplug(void)
Matthew Garrett57402942009-01-20 16:17:48 +0100497{
498 struct pci_dev *dev;
Alan Jenkins6d418392009-08-28 12:56:32 +0000499 struct pci_bus *bus;
Corentin Chary58ce48a2009-10-16 22:22:46 +0200500 bool blocked = eeepc_wlan_rfkill_blocked();
Matthew Garrett57402942009-01-20 16:17:48 +0100501
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000502 if (eeepc->wlan_rfkill)
503 rfkill_set_sw_state(eeepc->wlan_rfkill, blocked);
Alan Jenkins6d418392009-08-28 12:56:32 +0000504
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000505 mutex_lock(&eeepc->hotplug_lock);
Alan Jenkinsdcf443b2009-08-28 12:56:33 +0000506
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000507 if (eeepc->hotplug_slot) {
Alan Jenkins07e84aa2009-08-28 12:56:34 +0000508 bus = pci_find_bus(0, 1);
509 if (!bus) {
510 pr_warning("Unable to find PCI bus 1?\n");
Alan Jenkinsdcf443b2009-08-28 12:56:33 +0000511 goto out_unlock;
Matthew Garrett57402942009-01-20 16:17:48 +0100512 }
Alan Jenkins07e84aa2009-08-28 12:56:34 +0000513
514 if (!blocked) {
515 dev = pci_get_slot(bus, 0);
516 if (dev) {
517 /* Device already present */
518 pci_dev_put(dev);
519 goto out_unlock;
520 }
521 dev = pci_scan_single_device(bus, 0);
522 if (dev) {
523 pci_bus_assign_resources(bus);
524 if (pci_bus_add_device(dev))
525 pr_err("Unable to hotplug wifi\n");
526 }
527 } else {
528 dev = pci_get_slot(bus, 0);
529 if (dev) {
530 pci_remove_bus_device(dev);
531 pci_dev_put(dev);
532 }
Matthew Garrett57402942009-01-20 16:17:48 +0100533 }
534 }
Alan Jenkinsdcf443b2009-08-28 12:56:33 +0000535
536out_unlock:
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000537 mutex_unlock(&eeepc->hotplug_lock);
Matthew Garrett57402942009-01-20 16:17:48 +0100538}
539
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100540static void eeepc_rfkill_notify(acpi_handle handle, u32 event, void *data)
541{
542 if (event != ACPI_NOTIFY_BUS_CHECK)
543 return;
544
Corentin Chary58ce48a2009-10-16 22:22:46 +0200545 eeepc_rfkill_hotplug();
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100546}
547
Matthew Garrett57402942009-01-20 16:17:48 +0100548static int eeepc_register_rfkill_notifier(char *node)
549{
550 acpi_status status = AE_OK;
551 acpi_handle handle;
552
553 status = acpi_get_handle(NULL, node, &handle);
554
555 if (ACPI_SUCCESS(status)) {
556 status = acpi_install_notify_handler(handle,
557 ACPI_SYSTEM_NOTIFY,
558 eeepc_rfkill_notify,
559 NULL);
560 if (ACPI_FAILURE(status))
Joe Perches19b53282009-06-25 13:25:37 +0200561 pr_warning("Failed to register notify on %s\n", node);
Matthew Garrett57402942009-01-20 16:17:48 +0100562 } else
563 return -ENODEV;
564
565 return 0;
566}
567
568static void eeepc_unregister_rfkill_notifier(char *node)
569{
570 acpi_status status = AE_OK;
571 acpi_handle handle;
572
573 status = acpi_get_handle(NULL, node, &handle);
574
575 if (ACPI_SUCCESS(status)) {
576 status = acpi_remove_notify_handler(handle,
577 ACPI_SYSTEM_NOTIFY,
578 eeepc_rfkill_notify);
579 if (ACPI_FAILURE(status))
Joe Perches19b53282009-06-25 13:25:37 +0200580 pr_err("Error removing rfkill notify handler %s\n",
Matthew Garrett57402942009-01-20 16:17:48 +0100581 node);
582 }
583}
584
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000585static int eeepc_get_adapter_status(struct hotplug_slot *hotplug_slot,
586 u8 *value)
587{
588 int val = get_acpi(CM_ASL_WLAN);
589
590 if (val == 1 || val == 0)
591 *value = val;
592 else
593 return -EINVAL;
594
595 return 0;
596}
597
Corentin Chary2b121bc2009-06-25 13:25:36 +0200598static void eeepc_cleanup_pci_hotplug(struct hotplug_slot *hotplug_slot)
599{
600 kfree(hotplug_slot->info);
601 kfree(hotplug_slot);
602}
603
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000604static struct hotplug_slot_ops eeepc_hotplug_slot_ops = {
605 .owner = THIS_MODULE,
606 .get_adapter_status = eeepc_get_adapter_status,
607 .get_power_status = eeepc_get_adapter_status,
608};
609
Corentin Chary2b121bc2009-06-25 13:25:36 +0200610static int eeepc_setup_pci_hotplug(void)
611{
612 int ret = -ENOMEM;
613 struct pci_bus *bus = pci_find_bus(0, 1);
614
615 if (!bus) {
Joe Perches19b53282009-06-25 13:25:37 +0200616 pr_err("Unable to find wifi PCI bus\n");
Corentin Chary2b121bc2009-06-25 13:25:36 +0200617 return -ENODEV;
618 }
619
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000620 eeepc->hotplug_slot = kzalloc(sizeof(struct hotplug_slot), GFP_KERNEL);
621 if (!eeepc->hotplug_slot)
Corentin Chary2b121bc2009-06-25 13:25:36 +0200622 goto error_slot;
623
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000624 eeepc->hotplug_slot->info = kzalloc(sizeof(struct hotplug_slot_info),
Corentin Chary2b121bc2009-06-25 13:25:36 +0200625 GFP_KERNEL);
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000626 if (!eeepc->hotplug_slot->info)
Corentin Chary2b121bc2009-06-25 13:25:36 +0200627 goto error_info;
628
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000629 eeepc->hotplug_slot->private = eeepc;
630 eeepc->hotplug_slot->release = &eeepc_cleanup_pci_hotplug;
631 eeepc->hotplug_slot->ops = &eeepc_hotplug_slot_ops;
632 eeepc_get_adapter_status(eeepc->hotplug_slot,
633 &eeepc->hotplug_slot->info->adapter_status);
Corentin Chary2b121bc2009-06-25 13:25:36 +0200634
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000635 ret = pci_hp_register(eeepc->hotplug_slot, bus, 0, "eeepc-wifi");
Corentin Chary2b121bc2009-06-25 13:25:36 +0200636 if (ret) {
Joe Perches19b53282009-06-25 13:25:37 +0200637 pr_err("Unable to register hotplug slot - %d\n", ret);
Corentin Chary2b121bc2009-06-25 13:25:36 +0200638 goto error_register;
639 }
640
641 return 0;
642
643error_register:
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000644 kfree(eeepc->hotplug_slot->info);
Corentin Chary2b121bc2009-06-25 13:25:36 +0200645error_info:
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000646 kfree(eeepc->hotplug_slot);
647 eeepc->hotplug_slot = NULL;
Corentin Chary2b121bc2009-06-25 13:25:36 +0200648error_slot:
649 return ret;
650}
651
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000652/*
653 * Rfkill devices
654 */
655static int eeepc_rfkill_set(void *data, bool blocked)
656{
657 unsigned long asl = (unsigned long)data;
658 return set_acpi(asl, !blocked);
659}
660
661static const struct rfkill_ops eeepc_rfkill_ops = {
662 .set_block = eeepc_rfkill_set,
663};
664
665static int eeepc_new_rfkill(struct rfkill **rfkill,
666 const char *name, struct device *dev,
667 enum rfkill_type type, int cm)
668{
669 int result;
670
671 result = get_acpi(cm);
672 if (result < 0)
673 return result;
674
675 *rfkill = rfkill_alloc(name, dev, type,
676 &eeepc_rfkill_ops, (void *)(unsigned long)cm);
677
678 if (!*rfkill)
679 return -EINVAL;
680
681 rfkill_init_sw_state(*rfkill, get_acpi(cm) != 1);
682 result = rfkill_register(*rfkill);
683 if (result) {
684 rfkill_destroy(*rfkill);
685 *rfkill = NULL;
686 return result;
687 }
688 return 0;
689}
690
691static void eeepc_rfkill_exit(void)
692{
693 eeepc_unregister_rfkill_notifier("\\_SB.PCI0.P0P5");
694 eeepc_unregister_rfkill_notifier("\\_SB.PCI0.P0P6");
695 eeepc_unregister_rfkill_notifier("\\_SB.PCI0.P0P7");
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000696 if (eeepc->wlan_rfkill) {
697 rfkill_unregister(eeepc->wlan_rfkill);
698 rfkill_destroy(eeepc->wlan_rfkill);
699 eeepc->wlan_rfkill = NULL;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000700 }
701 /*
702 * Refresh pci hotplug in case the rfkill state was changed after
703 * eeepc_unregister_rfkill_notifier()
704 */
705 eeepc_rfkill_hotplug();
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000706 if (eeepc->hotplug_slot)
707 pci_hp_deregister(eeepc->hotplug_slot);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000708
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000709 if (eeepc->bluetooth_rfkill) {
710 rfkill_unregister(eeepc->bluetooth_rfkill);
711 rfkill_destroy(eeepc->bluetooth_rfkill);
712 eeepc->bluetooth_rfkill = NULL;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000713 }
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000714 if (eeepc->wwan3g_rfkill) {
715 rfkill_unregister(eeepc->wwan3g_rfkill);
716 rfkill_destroy(eeepc->wwan3g_rfkill);
717 eeepc->wwan3g_rfkill = NULL;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000718 }
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000719 if (eeepc->wimax_rfkill) {
720 rfkill_unregister(eeepc->wimax_rfkill);
721 rfkill_destroy(eeepc->wimax_rfkill);
722 eeepc->wimax_rfkill = NULL;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000723 }
724}
725
726static int eeepc_rfkill_init(struct device *dev)
727{
728 int result = 0;
729
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000730 mutex_init(&eeepc->hotplug_lock);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000731
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000732 result = eeepc_new_rfkill(&eeepc->wlan_rfkill,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000733 "eeepc-wlan", dev,
734 RFKILL_TYPE_WLAN, CM_ASL_WLAN);
735
736 if (result && result != -ENODEV)
737 goto exit;
738
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000739 result = eeepc_new_rfkill(&eeepc->bluetooth_rfkill,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000740 "eeepc-bluetooth", dev,
741 RFKILL_TYPE_BLUETOOTH, CM_ASL_BLUETOOTH);
742
743 if (result && result != -ENODEV)
744 goto exit;
745
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000746 result = eeepc_new_rfkill(&eeepc->wwan3g_rfkill,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000747 "eeepc-wwan3g", dev,
748 RFKILL_TYPE_WWAN, CM_ASL_3G);
749
750 if (result && result != -ENODEV)
751 goto exit;
752
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000753 result = eeepc_new_rfkill(&eeepc->wimax_rfkill,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000754 "eeepc-wimax", dev,
755 RFKILL_TYPE_WIMAX, CM_ASL_WIMAX);
756
757 if (result && result != -ENODEV)
758 goto exit;
759
760 result = eeepc_setup_pci_hotplug();
761 /*
762 * If we get -EBUSY then something else is handling the PCI hotplug -
763 * don't fail in this case
764 */
765 if (result == -EBUSY)
766 result = 0;
767
768 eeepc_register_rfkill_notifier("\\_SB.PCI0.P0P5");
769 eeepc_register_rfkill_notifier("\\_SB.PCI0.P0P6");
770 eeepc_register_rfkill_notifier("\\_SB.PCI0.P0P7");
771 /*
772 * Refresh pci hotplug in case the rfkill state was changed during
773 * setup.
774 */
775 eeepc_rfkill_hotplug();
776
777exit:
778 if (result && result != -ENODEV)
779 eeepc_rfkill_exit();
780 return result;
781}
782
783/*
784 * Platform driver - hibernate/resume callbacks
785 */
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000786static int eeepc_thaw(struct device *device)
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100787{
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000788 if (eeepc->wlan_rfkill) {
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100789 bool wlan;
790
Alan Jenkinsc1edd992009-08-28 12:56:39 +0000791 /*
792 * Work around bios bug - acpi _PTS turns off the wireless led
793 * during suspend. Normally it restores it on resume, but
Alan Jenkinsc200da52009-08-28 12:56:40 +0000794 * we should kick it ourselves in case hibernation is aborted.
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100795 */
796 wlan = get_acpi(CM_ASL_WLAN);
797 set_acpi(CM_ASL_WLAN, wlan);
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100798 }
799
Alan Jenkinsc200da52009-08-28 12:56:40 +0000800 return 0;
801}
802
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000803static int eeepc_restore(struct device *device)
Alan Jenkinsc200da52009-08-28 12:56:40 +0000804{
805 /* Refresh both wlan rfkill state and pci hotplug */
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000806 if (eeepc->wlan_rfkill)
Corentin Chary58ce48a2009-10-16 22:22:46 +0200807 eeepc_rfkill_hotplug();
Alan Jenkinsc200da52009-08-28 12:56:40 +0000808
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000809 if (eeepc->bluetooth_rfkill)
810 rfkill_set_sw_state(eeepc->bluetooth_rfkill,
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100811 get_acpi(CM_ASL_BLUETOOTH) != 1);
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000812 if (eeepc->wwan3g_rfkill)
813 rfkill_set_sw_state(eeepc->wwan3g_rfkill,
Alan Jenkinsa4746102009-08-28 12:56:38 +0000814 get_acpi(CM_ASL_3G) != 1);
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000815 if (eeepc->wimax_rfkill)
816 rfkill_set_sw_state(eeepc->wimax_rfkill,
Corentin Charyd1ec9c32009-08-28 12:56:41 +0000817 get_acpi(CM_ASL_WIMAX) != 1);
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100818
819 return 0;
820}
821
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000822static struct dev_pm_ops eeepc_pm_ops = {
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000823 .thaw = eeepc_thaw,
824 .restore = eeepc_restore,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000825};
826
827static struct platform_driver platform_driver = {
828 .driver = {
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000829 .name = EEEPC_LAPTOP_FILE,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000830 .owner = THIS_MODULE,
831 .pm = &eeepc_pm_ops,
832 }
833};
834
Eric Coopere59f8792008-03-13 12:55:46 +0100835/*
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000836 * Hwmon device
Corentin Charye1faa9d2008-03-13 12:57:18 +0100837 */
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000838
839#define EEEPC_EC_SC00 0x61
840#define EEEPC_EC_FAN_PWM (EEEPC_EC_SC00 + 2) /* Fan PWM duty cycle (%) */
841#define EEEPC_EC_FAN_HRPM (EEEPC_EC_SC00 + 5) /* High byte, fan speed (RPM) */
842#define EEEPC_EC_FAN_LRPM (EEEPC_EC_SC00 + 6) /* Low byte, fan speed (RPM) */
843
844#define EEEPC_EC_SFB0 0xD0
845#define EEEPC_EC_FAN_CTRL (EEEPC_EC_SFB0 + 3) /* Byte containing SF25 */
846
Corentin Charye1faa9d2008-03-13 12:57:18 +0100847static int eeepc_get_fan_pwm(void)
848{
Alan Jenkins463b4e42009-12-03 07:45:03 +0000849 u8 value = 0;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100850
Alan Jenkins463b4e42009-12-03 07:45:03 +0000851 ec_read(EEEPC_EC_FAN_PWM, &value);
852 return value * 255 / 100;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100853}
854
855static void eeepc_set_fan_pwm(int value)
856{
Corentin Chary04dcd842008-10-09 15:33:57 +0200857 value = SENSORS_LIMIT(value, 0, 255);
858 value = value * 100 / 255;
Alan Jenkins463b4e42009-12-03 07:45:03 +0000859 ec_write(EEEPC_EC_FAN_PWM, value);
Corentin Charye1faa9d2008-03-13 12:57:18 +0100860}
861
862static int eeepc_get_fan_rpm(void)
863{
Alan Jenkins463b4e42009-12-03 07:45:03 +0000864 u8 high = 0;
865 u8 low = 0;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100866
Alan Jenkins463b4e42009-12-03 07:45:03 +0000867 ec_read(EEEPC_EC_FAN_HRPM, &high);
868 ec_read(EEEPC_EC_FAN_LRPM, &low);
869 return high << 8 | low;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100870}
871
872static int eeepc_get_fan_ctrl(void)
873{
Alan Jenkins463b4e42009-12-03 07:45:03 +0000874 u8 value = 0;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100875
Alan Jenkins463b4e42009-12-03 07:45:03 +0000876 ec_read(EEEPC_EC_FAN_CTRL, &value);
Alan Jenkins48718682009-12-03 07:44:56 +0000877 if (value & 0x02)
878 return 1; /* manual */
879 else
880 return 2; /* automatic */
Corentin Charye1faa9d2008-03-13 12:57:18 +0100881}
882
883static void eeepc_set_fan_ctrl(int manual)
884{
Alan Jenkins463b4e42009-12-03 07:45:03 +0000885 u8 value = 0;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100886
Alan Jenkins463b4e42009-12-03 07:45:03 +0000887 ec_read(EEEPC_EC_FAN_CTRL, &value);
Alan Jenkins48718682009-12-03 07:44:56 +0000888 if (manual == 1)
Corentin Charye1faa9d2008-03-13 12:57:18 +0100889 value |= 0x02;
890 else
891 value &= ~0x02;
Alan Jenkins463b4e42009-12-03 07:45:03 +0000892 ec_write(EEEPC_EC_FAN_CTRL, value);
Corentin Charye1faa9d2008-03-13 12:57:18 +0100893}
894
895static ssize_t store_sys_hwmon(void (*set)(int), const char *buf, size_t count)
896{
897 int rv, value;
898
899 rv = parse_arg(buf, count, &value);
900 if (rv > 0)
901 set(value);
902 return rv;
903}
904
905static ssize_t show_sys_hwmon(int (*get)(void), char *buf)
906{
907 return sprintf(buf, "%d\n", get());
908}
909
910#define EEEPC_CREATE_SENSOR_ATTR(_name, _mode, _set, _get) \
911 static ssize_t show_##_name(struct device *dev, \
912 struct device_attribute *attr, \
913 char *buf) \
914 { \
915 return show_sys_hwmon(_set, buf); \
916 } \
917 static ssize_t store_##_name(struct device *dev, \
918 struct device_attribute *attr, \
919 const char *buf, size_t count) \
920 { \
921 return store_sys_hwmon(_get, buf, count); \
922 } \
923 static SENSOR_DEVICE_ATTR(_name, _mode, show_##_name, store_##_name, 0);
924
925EEEPC_CREATE_SENSOR_ATTR(fan1_input, S_IRUGO, eeepc_get_fan_rpm, NULL);
Corentin Chary04dcd842008-10-09 15:33:57 +0200926EEEPC_CREATE_SENSOR_ATTR(pwm1, S_IRUGO | S_IWUSR,
Corentin Charye1faa9d2008-03-13 12:57:18 +0100927 eeepc_get_fan_pwm, eeepc_set_fan_pwm);
928EEEPC_CREATE_SENSOR_ATTR(pwm1_enable, S_IRUGO | S_IWUSR,
929 eeepc_get_fan_ctrl, eeepc_set_fan_ctrl);
930
Corentin Chary04dcd842008-10-09 15:33:57 +0200931static ssize_t
932show_name(struct device *dev, struct device_attribute *attr, char *buf)
933{
934 return sprintf(buf, "eeepc\n");
935}
936static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0);
937
Corentin Charye1faa9d2008-03-13 12:57:18 +0100938static struct attribute *hwmon_attributes[] = {
Corentin Chary04dcd842008-10-09 15:33:57 +0200939 &sensor_dev_attr_pwm1.dev_attr.attr,
Corentin Charye1faa9d2008-03-13 12:57:18 +0100940 &sensor_dev_attr_fan1_input.dev_attr.attr,
941 &sensor_dev_attr_pwm1_enable.dev_attr.attr,
Corentin Chary04dcd842008-10-09 15:33:57 +0200942 &sensor_dev_attr_name.dev_attr.attr,
Corentin Charye1faa9d2008-03-13 12:57:18 +0100943 NULL
944};
945
946static struct attribute_group hwmon_attribute_group = {
947 .attrs = hwmon_attributes
948};
949
Corentin Charye1faa9d2008-03-13 12:57:18 +0100950static void eeepc_hwmon_exit(void)
951{
952 struct device *hwmon;
953
954 hwmon = eeepc_hwmon_device;
955 if (!hwmon)
956 return ;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100957 sysfs_remove_group(&hwmon->kobj,
958 &hwmon_attribute_group);
Matthew Garrettf1441312008-08-20 14:08:57 -0700959 hwmon_device_unregister(hwmon);
Corentin Charye1faa9d2008-03-13 12:57:18 +0100960 eeepc_hwmon_device = NULL;
961}
962
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000963static int eeepc_hwmon_init(struct device *dev)
Corentin Chary3c0eb512009-12-03 07:44:52 +0000964{
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000965 struct device *hwmon;
Corentin Chary7de39382009-06-25 13:25:38 +0200966 int result;
967
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000968 hwmon = hwmon_device_register(dev);
969 if (IS_ERR(hwmon)) {
970 pr_err("Could not register eeepc hwmon device\n");
971 eeepc_hwmon_device = NULL;
972 return PTR_ERR(hwmon);
Corentin Chary7de39382009-06-25 13:25:38 +0200973 }
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000974 eeepc_hwmon_device = hwmon;
975 result = sysfs_create_group(&hwmon->kobj,
976 &hwmon_attribute_group);
977 if (result)
978 eeepc_hwmon_exit();
979 return result;
Corentin Chary7de39382009-06-25 13:25:38 +0200980}
981
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000982/*
983 * Backlight device
984 */
985static int read_brightness(struct backlight_device *bd)
Corentin Chary7de39382009-06-25 13:25:38 +0200986{
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000987 return get_acpi(CM_ASL_PANELBRIGHT);
988}
Corentin Chary7de39382009-06-25 13:25:38 +0200989
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000990static int set_brightness(struct backlight_device *bd, int value)
991{
992 return set_acpi(CM_ASL_PANELBRIGHT, value);
993}
Alan Jenkins73345462009-06-29 09:40:07 +0100994
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000995static int update_bl_status(struct backlight_device *bd)
996{
997 return set_brightness(bd, bd->props.brightness);
998}
Corentin Chary7de39382009-06-25 13:25:38 +0200999
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001000static struct backlight_ops eeepcbl_ops = {
1001 .get_brightness = read_brightness,
1002 .update_status = update_bl_status,
1003};
Corentin Chary7de39382009-06-25 13:25:38 +02001004
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001005static int eeepc_backlight_notify(void)
1006{
1007 struct backlight_device *bd = eeepc_backlight_device;
1008 int old = bd->props.brightness;
Corentin Chary7de39382009-06-25 13:25:38 +02001009
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001010 backlight_force_update(bd, BACKLIGHT_UPDATE_HOTKEY);
Corentin Chary7de39382009-06-25 13:25:38 +02001011
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001012 return old;
Corentin Chary7de39382009-06-25 13:25:38 +02001013}
1014
Corentin Charya5fa4292008-03-13 12:56:37 +01001015static int eeepc_backlight_init(struct device *dev)
1016{
1017 struct backlight_device *bd;
1018
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001019 bd = backlight_device_register(EEEPC_LAPTOP_FILE, dev,
Corentin Charya5fa4292008-03-13 12:56:37 +01001020 NULL, &eeepcbl_ops);
1021 if (IS_ERR(bd)) {
Joe Perches19b53282009-06-25 13:25:37 +02001022 pr_err("Could not register eeepc backlight device\n");
Corentin Charya5fa4292008-03-13 12:56:37 +01001023 eeepc_backlight_device = NULL;
1024 return PTR_ERR(bd);
1025 }
1026 eeepc_backlight_device = bd;
1027 bd->props.max_brightness = 15;
1028 bd->props.brightness = read_brightness(NULL);
1029 bd->props.power = FB_BLANK_UNBLANK;
1030 backlight_update_status(bd);
1031 return 0;
1032}
1033
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001034static void eeepc_backlight_exit(void)
Corentin Charye1faa9d2008-03-13 12:57:18 +01001035{
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001036 if (eeepc_backlight_device)
1037 backlight_device_unregister(eeepc_backlight_device);
1038 eeepc_backlight_device = NULL;
1039}
Corentin Charye1faa9d2008-03-13 12:57:18 +01001040
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001041
1042/*
1043 * Input device (i.e. hotkeys)
1044 */
1045static struct key_entry *eeepc_get_entry_by_scancode(int code)
1046{
1047 struct key_entry *key;
1048
1049 for (key = eeepc_keymap; key->type != KE_END; key++)
1050 if (code == key->code)
1051 return key;
1052
1053 return NULL;
1054}
1055
1056static void eeepc_input_notify(int event)
1057{
1058 static struct key_entry *key;
1059
1060 key = eeepc_get_entry_by_scancode(event);
1061 if (key) {
1062 switch (key->type) {
1063 case KE_KEY:
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001064 input_report_key(eeepc->inputdev, key->keycode,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001065 1);
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001066 input_sync(eeepc->inputdev);
1067 input_report_key(eeepc->inputdev, key->keycode,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001068 0);
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001069 input_sync(eeepc->inputdev);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001070 break;
1071 }
Corentin Charye1faa9d2008-03-13 12:57:18 +01001072 }
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001073}
1074
1075static struct key_entry *eepc_get_entry_by_keycode(int code)
1076{
1077 struct key_entry *key;
1078
1079 for (key = eeepc_keymap; key->type != KE_END; key++)
1080 if (code == key->keycode && key->type == KE_KEY)
1081 return key;
1082
1083 return NULL;
1084}
1085
1086static int eeepc_getkeycode(struct input_dev *dev, int scancode, int *keycode)
1087{
1088 struct key_entry *key = eeepc_get_entry_by_scancode(scancode);
1089
1090 if (key && key->type == KE_KEY) {
1091 *keycode = key->keycode;
1092 return 0;
1093 }
1094
1095 return -EINVAL;
1096}
1097
1098static int eeepc_setkeycode(struct input_dev *dev, int scancode, int keycode)
1099{
1100 struct key_entry *key;
1101 int old_keycode;
1102
1103 if (keycode < 0 || keycode > KEY_MAX)
1104 return -EINVAL;
1105
1106 key = eeepc_get_entry_by_scancode(scancode);
1107 if (key && key->type == KE_KEY) {
1108 old_keycode = key->keycode;
1109 key->keycode = keycode;
1110 set_bit(keycode, dev->keybit);
1111 if (!eepc_get_entry_by_keycode(old_keycode))
1112 clear_bit(old_keycode, dev->keybit);
1113 return 0;
1114 }
1115
1116 return -EINVAL;
Corentin Charye1faa9d2008-03-13 12:57:18 +01001117}
1118
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001119static int eeepc_input_init(struct device *dev)
1120{
1121 const struct key_entry *key;
1122 int result;
1123
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001124 eeepc->inputdev = input_allocate_device();
1125 if (!eeepc->inputdev) {
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001126 pr_info("Unable to allocate input device\n");
1127 return -ENOMEM;
1128 }
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001129 eeepc->inputdev->name = "Asus EeePC extra buttons";
1130 eeepc->inputdev->dev.parent = dev;
1131 eeepc->inputdev->phys = EEEPC_LAPTOP_FILE "/input0";
1132 eeepc->inputdev->id.bustype = BUS_HOST;
1133 eeepc->inputdev->getkeycode = eeepc_getkeycode;
1134 eeepc->inputdev->setkeycode = eeepc_setkeycode;
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001135
1136 for (key = eeepc_keymap; key->type != KE_END; key++) {
1137 switch (key->type) {
1138 case KE_KEY:
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001139 set_bit(EV_KEY, eeepc->inputdev->evbit);
1140 set_bit(key->keycode, eeepc->inputdev->keybit);
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001141 break;
1142 }
1143 }
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001144 result = input_register_device(eeepc->inputdev);
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001145 if (result) {
1146 pr_info("Unable to register input device\n");
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001147 input_free_device(eeepc->inputdev);
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001148 return result;
1149 }
1150 return 0;
1151}
1152
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001153static void eeepc_input_exit(void)
Corentin Chary3c0eb512009-12-03 07:44:52 +00001154{
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001155 if (eeepc->inputdev)
1156 input_unregister_device(eeepc->inputdev);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001157}
Corentin Chary3c0eb512009-12-03 07:44:52 +00001158
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001159/*
1160 * ACPI driver
1161 */
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001162static void eeepc_acpi_notify(struct acpi_device *device, u32 event)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001163{
1164 u16 count;
Corentin Chary3c0eb512009-12-03 07:44:52 +00001165
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001166 if (event > ACPI_MAX_SYS_NOTIFY)
1167 return;
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001168 count = eeepc->event_count[event % 128]++;
1169 acpi_bus_generate_proc_event(eeepc->device, event, count);
1170 acpi_bus_generate_netlink_event(eeepc->device->pnp.device_class,
1171 dev_name(&eeepc->device->dev), event,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001172 count);
Corentin Chary3c0eb512009-12-03 07:44:52 +00001173
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001174 if (event >= NOTIFY_BRN_MIN && event <= NOTIFY_BRN_MAX) {
1175 int old_brightness, new_brightness;
1176
1177 /* Update backlight device. */
1178 old_brightness = eeepc_backlight_notify();
1179
1180 /* Convert brightness event to keypress (obsolescent hack). */
1181 new_brightness = event - NOTIFY_BRN_MIN;
1182
1183 if (new_brightness < old_brightness) {
1184 event = NOTIFY_BRN_MIN; /* brightness down */
1185 } else if (new_brightness > old_brightness) {
1186 event = NOTIFY_BRN_MAX; /* brightness up */
1187 } else {
1188 /*
1189 * no change in brightness - already at min/max,
1190 * event will be desired value (or else ignored).
1191 */
1192 }
1193 }
1194 eeepc_input_notify(event);
1195}
1196
1197static void cmsg_quirk(int cm, const char *name)
1198{
1199 int dummy;
1200
1201 /* Some BIOSes do not report cm although it is avaliable.
1202 Check if cm_getv[cm] works and, if yes, assume cm should be set. */
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001203 if (!(eeepc->cm_supported & (1 << cm))
1204 && !read_acpi_int(eeepc->handle, cm_getv[cm], &dummy)) {
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001205 pr_info("%s (%x) not reported by BIOS,"
1206 " enabling anyway\n", name, 1 << cm);
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001207 eeepc->cm_supported |= 1 << cm;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001208 }
1209}
1210
1211static void cmsg_quirks(void)
1212{
1213 cmsg_quirk(CM_ASL_LID, "LID");
1214 cmsg_quirk(CM_ASL_TYPE, "TYPE");
1215 cmsg_quirk(CM_ASL_PANELPOWER, "PANELPOWER");
1216 cmsg_quirk(CM_ASL_TPD, "TPD");
1217}
1218
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001219static int eeepc_acpi_init(void)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001220{
1221 unsigned int init_flags;
1222 int result;
1223
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001224 result = acpi_bus_get_status(eeepc->device);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001225 if (result)
1226 return result;
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001227 if (!eeepc->device->status.present) {
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001228 pr_err("Hotkey device not present, aborting\n");
1229 return -ENODEV;
Alan Jenkinsdc56ad92009-12-03 07:44:58 +00001230 }
Alan Jenkins2b56f1c2009-12-03 07:44:57 +00001231
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001232 init_flags = DISABLE_ASL_WLAN | DISABLE_ASL_DISPLAYSWITCH;
1233 pr_notice("Hotkey init flags 0x%x\n", init_flags);
1234
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001235 if (write_acpi_int(eeepc->handle, "INIT", init_flags)) {
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001236 pr_err("Hotkey initialization failed\n");
1237 return -ENODEV;
1238 }
1239
1240 /* get control methods supported */
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001241 if (read_acpi_int(eeepc->handle, "CMSG", &eeepc->cm_supported)) {
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001242 pr_err("Get control methods supported failed\n");
1243 return -ENODEV;
1244 }
1245 cmsg_quirks();
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001246 pr_info("Get control methods supported: 0x%x\n", eeepc->cm_supported);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001247
Corentin Chary3c0eb512009-12-03 07:44:52 +00001248 return 0;
1249}
1250
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001251static void __devinit eeepc_enable_camera(void)
1252{
1253 /*
1254 * If the following call to set_acpi() fails, it's because there's no
1255 * camera so we can ignore the error.
1256 */
1257 if (get_acpi(CM_ASL_CAMERA) == 0)
1258 set_acpi(CM_ASL_CAMERA, 1);
1259}
1260
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001261static int __devinit eeepc_acpi_add(struct acpi_device *device)
Eric Coopere59f8792008-03-13 12:55:46 +01001262{
1263 struct device *dev;
1264 int result;
1265
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001266 pr_notice(EEEPC_LAPTOP_NAME "\n");
1267 eeepc = kzalloc(sizeof(struct eeepc_laptop), GFP_KERNEL);
1268 if (!eeepc)
Alan Jenkins1e779852009-08-28 12:56:35 +00001269 return -ENOMEM;
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001270 eeepc->handle = device->handle;
1271 strcpy(acpi_device_name(device), EEEPC_ACPI_DEVICE_NAME);
1272 strcpy(acpi_device_class(device), EEEPC_ACPI_CLASS);
1273 device->driver_data = eeepc;
1274 eeepc->device = device;
Pekka Enbergcede2cb2009-06-16 19:28:45 +00001275
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001276 result = eeepc_acpi_init();
Alan Jenkins1e779852009-08-28 12:56:35 +00001277 if (result)
Alan Jenkins9db106b2009-12-03 07:45:06 +00001278 goto fail_platform;
Pekka Enbergcede2cb2009-06-16 19:28:45 +00001279 eeepc_enable_camera();
1280
Alan Jenkins9db106b2009-12-03 07:45:06 +00001281 result = eeepc_platform_init();
Eric Coopere59f8792008-03-13 12:55:46 +01001282 if (result)
Alan Jenkins9db106b2009-12-03 07:45:06 +00001283 goto fail_platform;
Corentin Chary1ddec2f2009-06-25 13:25:39 +02001284 dev = &platform_device->dev;
1285
1286 if (!acpi_video_backlight_support()) {
1287 result = eeepc_backlight_init(dev);
1288 if (result)
1289 goto fail_backlight;
1290 } else
Alan Jenkins9db106b2009-12-03 07:45:06 +00001291 pr_info("Backlight controlled by ACPI video driver\n");
Corentin Chary1ddec2f2009-06-25 13:25:39 +02001292
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001293 result = eeepc_input_init(dev);
1294 if (result)
1295 goto fail_input;
1296
Corentin Chary1ddec2f2009-06-25 13:25:39 +02001297 result = eeepc_hwmon_init(dev);
1298 if (result)
1299 goto fail_hwmon;
1300
Corentin Chary3c0eb512009-12-03 07:44:52 +00001301 result = eeepc_led_init(dev);
1302 if (result)
1303 goto fail_led;
1304
Corentin Chary7de39382009-06-25 13:25:38 +02001305 result = eeepc_rfkill_init(dev);
1306 if (result)
1307 goto fail_rfkill;
1308
Eric Coopere59f8792008-03-13 12:55:46 +01001309 return 0;
Alan Jenkins1e779852009-08-28 12:56:35 +00001310
Corentin Chary7de39382009-06-25 13:25:38 +02001311fail_rfkill:
Corentin Chary3c0eb512009-12-03 07:44:52 +00001312 eeepc_led_exit();
1313fail_led:
Corentin Chary1ddec2f2009-06-25 13:25:39 +02001314 eeepc_hwmon_exit();
1315fail_hwmon:
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001316 eeepc_input_exit();
1317fail_input:
Corentin Chary1ddec2f2009-06-25 13:25:39 +02001318 eeepc_backlight_exit();
1319fail_backlight:
Alan Jenkins9db106b2009-12-03 07:45:06 +00001320 eeepc_platform_exit();
1321fail_platform:
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001322 kfree(eeepc);
Alan Jenkins1e779852009-08-28 12:56:35 +00001323
Eric Coopere59f8792008-03-13 12:55:46 +01001324 return result;
1325}
1326
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001327static int eeepc_acpi_remove(struct acpi_device *device, int type)
Alan Jenkins1e779852009-08-28 12:56:35 +00001328{
Alan Jenkins1e779852009-08-28 12:56:35 +00001329 eeepc_backlight_exit();
1330 eeepc_rfkill_exit();
1331 eeepc_input_exit();
1332 eeepc_hwmon_exit();
Corentin Chary3c0eb512009-12-03 07:44:52 +00001333 eeepc_led_exit();
Alan Jenkins9db106b2009-12-03 07:45:06 +00001334 eeepc_platform_exit();
Alan Jenkins1e779852009-08-28 12:56:35 +00001335
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001336 kfree(eeepc);
Alan Jenkins1e779852009-08-28 12:56:35 +00001337 return 0;
1338}
1339
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001340
1341static const struct acpi_device_id eeepc_device_ids[] = {
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001342 {EEEPC_ACPI_HID, 0},
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001343 {"", 0},
1344};
1345MODULE_DEVICE_TABLE(acpi, eeepc_device_ids);
1346
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001347static struct acpi_driver eeepc_acpi_driver = {
1348 .name = EEEPC_LAPTOP_NAME,
1349 .class = EEEPC_ACPI_CLASS,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001350 .owner = THIS_MODULE,
1351 .ids = eeepc_device_ids,
1352 .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS,
1353 .ops = {
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001354 .add = eeepc_acpi_add,
1355 .remove = eeepc_acpi_remove,
1356 .notify = eeepc_acpi_notify,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001357 },
1358};
1359
1360
Alan Jenkins1e779852009-08-28 12:56:35 +00001361static int __init eeepc_laptop_init(void)
1362{
1363 int result;
1364
Alan Jenkins22072e92009-12-03 07:45:05 +00001365 result = platform_driver_register(&platform_driver);
Alan Jenkins1e779852009-08-28 12:56:35 +00001366 if (result < 0)
1367 return result;
Alan Jenkins22072e92009-12-03 07:45:05 +00001368
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001369 result = acpi_bus_register_driver(&eeepc_acpi_driver);
Alan Jenkins22072e92009-12-03 07:45:05 +00001370 if (result < 0)
1371 goto fail_acpi_driver;
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001372 if (!eeepc) {
Alan Jenkins22072e92009-12-03 07:45:05 +00001373 result = -ENODEV;
1374 goto fail_no_device;
Alan Jenkins1e779852009-08-28 12:56:35 +00001375 }
1376 return 0;
Alan Jenkins22072e92009-12-03 07:45:05 +00001377
1378fail_no_device:
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001379 acpi_bus_unregister_driver(&eeepc_acpi_driver);
Alan Jenkins22072e92009-12-03 07:45:05 +00001380fail_acpi_driver:
1381 platform_driver_unregister(&platform_driver);
1382 return result;
Alan Jenkins1e779852009-08-28 12:56:35 +00001383}
1384
1385static void __exit eeepc_laptop_exit(void)
1386{
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001387 acpi_bus_unregister_driver(&eeepc_acpi_driver);
Alan Jenkins22072e92009-12-03 07:45:05 +00001388 platform_driver_unregister(&platform_driver);
Alan Jenkins1e779852009-08-28 12:56:35 +00001389}
1390
Eric Coopere59f8792008-03-13 12:55:46 +01001391module_init(eeepc_laptop_init);
1392module_exit(eeepc_laptop_exit);