blob: 73f3cb0fd76ca5ceafff129b296fd3c7847591c7 [file] [log] [blame]
Eric Coopere59f8792008-03-13 12:55:46 +01001/*
2 * eepc-laptop.c - Asus Eee PC extras
3 *
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
19#include <linux/kernel.h>
20#include <linux/module.h>
21#include <linux/init.h>
22#include <linux/types.h>
23#include <linux/platform_device.h>
Corentin Charya5fa4292008-03-13 12:56:37 +010024#include <linux/backlight.h>
25#include <linux/fb.h>
Corentin Charye1faa9d2008-03-13 12:57:18 +010026#include <linux/hwmon.h>
27#include <linux/hwmon-sysfs.h>
Eric Coopere59f8792008-03-13 12:55:46 +010028#include <acpi/acpi_drivers.h>
29#include <acpi/acpi_bus.h>
30#include <linux/uaccess.h>
Matthew Garretta195dcd2008-08-19 12:13:20 +010031#include <linux/input.h>
32#include <linux/rfkill.h>
Matthew Garrett57402942009-01-20 16:17:48 +010033#include <linux/pci.h>
Eric Coopere59f8792008-03-13 12:55:46 +010034
35#define EEEPC_LAPTOP_VERSION "0.1"
36
37#define EEEPC_HOTK_NAME "Eee PC Hotkey Driver"
38#define EEEPC_HOTK_FILE "eeepc"
39#define EEEPC_HOTK_CLASS "hotkey"
40#define EEEPC_HOTK_DEVICE_NAME "Hotkey"
41#define EEEPC_HOTK_HID "ASUS010"
42
43#define EEEPC_LOG EEEPC_HOTK_FILE ": "
44#define EEEPC_ERR KERN_ERR EEEPC_LOG
45#define EEEPC_WARNING KERN_WARNING EEEPC_LOG
46#define EEEPC_NOTICE KERN_NOTICE EEEPC_LOG
47#define EEEPC_INFO KERN_INFO EEEPC_LOG
48
49/*
50 * Definitions for Asus EeePC
51 */
52#define NOTIFY_WLAN_ON 0x10
Corentin Charya5fa4292008-03-13 12:56:37 +010053#define NOTIFY_BRN_MIN 0x20
54#define NOTIFY_BRN_MAX 0x2f
Eric Coopere59f8792008-03-13 12:55:46 +010055
56enum {
57 DISABLE_ASL_WLAN = 0x0001,
58 DISABLE_ASL_BLUETOOTH = 0x0002,
59 DISABLE_ASL_IRDA = 0x0004,
60 DISABLE_ASL_CAMERA = 0x0008,
61 DISABLE_ASL_TV = 0x0010,
62 DISABLE_ASL_GPS = 0x0020,
63 DISABLE_ASL_DISPLAYSWITCH = 0x0040,
64 DISABLE_ASL_MODEM = 0x0080,
Corentin Charyb7b700d2009-06-16 19:28:52 +000065 DISABLE_ASL_CARDREADER = 0x0100,
66 DISABLE_ASL_3G = 0x0200,
67 DISABLE_ASL_WIMAX = 0x0400,
68 DISABLE_ASL_HWCF = 0x0800
Eric Coopere59f8792008-03-13 12:55:46 +010069};
70
71enum {
72 CM_ASL_WLAN = 0,
73 CM_ASL_BLUETOOTH,
74 CM_ASL_IRDA,
75 CM_ASL_1394,
76 CM_ASL_CAMERA,
77 CM_ASL_TV,
78 CM_ASL_GPS,
79 CM_ASL_DVDROM,
80 CM_ASL_DISPLAYSWITCH,
81 CM_ASL_PANELBRIGHT,
82 CM_ASL_BIOSFLASH,
83 CM_ASL_ACPIFLASH,
84 CM_ASL_CPUFV,
85 CM_ASL_CPUTEMPERATURE,
86 CM_ASL_FANCPU,
87 CM_ASL_FANCHASSIS,
88 CM_ASL_USBPORT1,
89 CM_ASL_USBPORT2,
90 CM_ASL_USBPORT3,
91 CM_ASL_MODEM,
92 CM_ASL_CARDREADER,
Corentin Charyb7b700d2009-06-16 19:28:52 +000093 CM_ASL_3G,
94 CM_ASL_WIMAX,
95 CM_ASL_HWCF,
96 CM_ASL_LID,
97 CM_ASL_TYPE,
98 CM_ASL_PANELPOWER, /*P901*/
99 CM_ASL_TPD
Eric Coopere59f8792008-03-13 12:55:46 +0100100};
101
Adrian Bunk14109462008-06-25 19:25:47 +0300102static const char *cm_getv[] = {
Jonathan McDowell3af9bfc2008-12-03 20:31:11 +0000103 "WLDG", "BTHG", NULL, NULL,
Eric Coopere59f8792008-03-13 12:55:46 +0100104 "CAMG", NULL, NULL, NULL,
105 NULL, "PBLG", NULL, NULL,
106 "CFVG", NULL, NULL, NULL,
107 "USBG", NULL, NULL, "MODG",
Corentin Charyb7b700d2009-06-16 19:28:52 +0000108 "CRDG", "M3GG", "WIMG", "HWCF",
109 "LIDG", "TYPE", "PBPG", "TPDG"
Eric Coopere59f8792008-03-13 12:55:46 +0100110};
111
Adrian Bunk14109462008-06-25 19:25:47 +0300112static const char *cm_setv[] = {
Jonathan McDowell3af9bfc2008-12-03 20:31:11 +0000113 "WLDS", "BTHS", NULL, NULL,
Eric Coopere59f8792008-03-13 12:55:46 +0100114 "CAMS", NULL, NULL, NULL,
115 "SDSP", "PBLS", "HDPS", NULL,
116 "CFVS", NULL, NULL, NULL,
117 "USBG", NULL, NULL, "MODS",
Corentin Charyb7b700d2009-06-16 19:28:52 +0000118 "CRDS", "M3GS", "WIMS", NULL,
119 NULL, NULL, "PBPS", "TPDS"
Eric Coopere59f8792008-03-13 12:55:46 +0100120};
121
Corentin Charye1faa9d2008-03-13 12:57:18 +0100122#define EEEPC_EC "\\_SB.PCI0.SBRG.EC0."
123
124#define EEEPC_EC_FAN_PWM EEEPC_EC "SC02" /* Fan PWM duty cycle (%) */
125#define EEEPC_EC_SC02 0x63
126#define EEEPC_EC_FAN_HRPM EEEPC_EC "SC05" /* High byte, fan speed (RPM) */
127#define EEEPC_EC_FAN_LRPM EEEPC_EC "SC06" /* Low byte, fan speed (RPM) */
128#define EEEPC_EC_FAN_CTRL EEEPC_EC "SFB3" /* Byte containing SF25 */
129#define EEEPC_EC_SFB3 0xD3
130
Eric Coopere59f8792008-03-13 12:55:46 +0100131/*
132 * This is the main structure, we can use it to store useful information
133 * about the hotk device
134 */
135struct eeepc_hotk {
136 struct acpi_device *device; /* the device we are in */
137 acpi_handle handle; /* the handle of the hotk device */
138 u32 cm_supported; /* the control methods supported
139 by this BIOS */
140 uint init_flag; /* Init flags */
141 u16 event_count[128]; /* count for each event */
Matthew Garretta195dcd2008-08-19 12:13:20 +0100142 struct input_dev *inputdev;
143 u16 *keycode_map;
144 struct rfkill *eeepc_wlan_rfkill;
145 struct rfkill *eeepc_bluetooth_rfkill;
Eric Coopere59f8792008-03-13 12:55:46 +0100146};
147
148/* The actual device the driver binds to */
149static struct eeepc_hotk *ehotk;
150
151/* Platform device/driver */
152static struct platform_driver platform_driver = {
153 .driver = {
154 .name = EEEPC_HOTK_FILE,
155 .owner = THIS_MODULE,
156 }
157};
158
159static struct platform_device *platform_device;
160
Matthew Garretta195dcd2008-08-19 12:13:20 +0100161struct key_entry {
162 char type;
163 u8 code;
164 u16 keycode;
165};
166
167enum { KE_KEY, KE_END };
168
169static struct key_entry eeepc_keymap[] = {
170 /* Sleep already handled via generic ACPI code */
171 {KE_KEY, 0x10, KEY_WLAN },
Alan Jenkins978605c2009-04-27 09:23:39 +0200172 {KE_KEY, 0x11, KEY_WLAN },
Matthew Garretta195dcd2008-08-19 12:13:20 +0100173 {KE_KEY, 0x12, KEY_PROG1 },
174 {KE_KEY, 0x13, KEY_MUTE },
175 {KE_KEY, 0x14, KEY_VOLUMEDOWN },
176 {KE_KEY, 0x15, KEY_VOLUMEUP },
Matthew Garrettb5f6f262009-01-20 16:17:46 +0100177 {KE_KEY, 0x1a, KEY_COFFEE },
178 {KE_KEY, 0x1b, KEY_ZOOM },
179 {KE_KEY, 0x1c, KEY_PROG2 },
180 {KE_KEY, 0x1d, KEY_PROG3 },
Darren Salt64b86b62009-04-27 09:23:38 +0200181 {KE_KEY, NOTIFY_BRN_MIN, KEY_BRIGHTNESSDOWN },
182 {KE_KEY, NOTIFY_BRN_MIN + 2, KEY_BRIGHTNESSUP },
Matthew Garretta195dcd2008-08-19 12:13:20 +0100183 {KE_KEY, 0x30, KEY_SWITCHVIDEOMODE },
184 {KE_KEY, 0x31, KEY_SWITCHVIDEOMODE },
185 {KE_KEY, 0x32, KEY_SWITCHVIDEOMODE },
186 {KE_END, 0},
187};
188
Eric Coopere59f8792008-03-13 12:55:46 +0100189/*
190 * The hotkey driver declaration
191 */
192static int eeepc_hotk_add(struct acpi_device *device);
193static int eeepc_hotk_remove(struct acpi_device *device, int type);
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100194static int eeepc_hotk_resume(struct acpi_device *device);
Bjorn Helgaasd9b9bd72009-04-30 09:36:03 -0600195static void eeepc_hotk_notify(struct acpi_device *device, u32 event);
Eric Coopere59f8792008-03-13 12:55:46 +0100196
197static const struct acpi_device_id eeepc_device_ids[] = {
198 {EEEPC_HOTK_HID, 0},
199 {"", 0},
200};
201MODULE_DEVICE_TABLE(acpi, eeepc_device_ids);
202
203static struct acpi_driver eeepc_hotk_driver = {
204 .name = EEEPC_HOTK_NAME,
205 .class = EEEPC_HOTK_CLASS,
206 .ids = eeepc_device_ids,
Bjorn Helgaasd9b9bd72009-04-30 09:36:03 -0600207 .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS,
Eric Coopere59f8792008-03-13 12:55:46 +0100208 .ops = {
209 .add = eeepc_hotk_add,
210 .remove = eeepc_hotk_remove,
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100211 .resume = eeepc_hotk_resume,
Bjorn Helgaasd9b9bd72009-04-30 09:36:03 -0600212 .notify = eeepc_hotk_notify,
Eric Coopere59f8792008-03-13 12:55:46 +0100213 },
214};
215
Corentin Charya5fa4292008-03-13 12:56:37 +0100216/* The backlight device /sys/class/backlight */
217static struct backlight_device *eeepc_backlight_device;
218
Corentin Charye1faa9d2008-03-13 12:57:18 +0100219/* The hwmon device */
220static struct device *eeepc_hwmon_device;
221
Corentin Charya5fa4292008-03-13 12:56:37 +0100222/*
223 * The backlight class declaration
224 */
225static int read_brightness(struct backlight_device *bd);
226static int update_bl_status(struct backlight_device *bd);
227static struct backlight_ops eeepcbl_ops = {
228 .get_brightness = read_brightness,
229 .update_status = update_bl_status,
230};
231
Eric Coopere59f8792008-03-13 12:55:46 +0100232MODULE_AUTHOR("Corentin Chary, Eric Cooper");
233MODULE_DESCRIPTION(EEEPC_HOTK_NAME);
234MODULE_LICENSE("GPL");
235
236/*
237 * ACPI Helpers
238 */
239static int write_acpi_int(acpi_handle handle, const char *method, int val,
240 struct acpi_buffer *output)
241{
242 struct acpi_object_list params;
243 union acpi_object in_obj;
244 acpi_status status;
245
246 params.count = 1;
247 params.pointer = &in_obj;
248 in_obj.type = ACPI_TYPE_INTEGER;
249 in_obj.integer.value = val;
250
251 status = acpi_evaluate_object(handle, (char *)method, &params, output);
252 return (status == AE_OK ? 0 : -1);
253}
254
255static int read_acpi_int(acpi_handle handle, const char *method, int *val)
256{
257 acpi_status status;
Matthew Wilcox27663c52008-10-10 02:22:59 -0400258 unsigned long long result;
Eric Coopere59f8792008-03-13 12:55:46 +0100259
260 status = acpi_evaluate_integer(handle, (char *)method, NULL, &result);
261 if (ACPI_FAILURE(status)) {
262 *val = -1;
263 return -1;
264 } else {
265 *val = result;
266 return 0;
267 }
268}
269
270static int set_acpi(int cm, int value)
271{
272 if (ehotk->cm_supported & (0x1 << cm)) {
273 const char *method = cm_setv[cm];
274 if (method == NULL)
275 return -ENODEV;
276 if (write_acpi_int(ehotk->handle, method, value, NULL))
277 printk(EEEPC_WARNING "Error writing %s\n", method);
278 }
279 return 0;
280}
281
282static int get_acpi(int cm)
283{
284 int value = -1;
285 if ((ehotk->cm_supported & (0x1 << cm))) {
286 const char *method = cm_getv[cm];
287 if (method == NULL)
288 return -ENODEV;
289 if (read_acpi_int(ehotk->handle, method, &value))
290 printk(EEEPC_WARNING "Error reading %s\n", method);
291 }
292 return value;
293}
294
295/*
Corentin Charya5fa4292008-03-13 12:56:37 +0100296 * Backlight
297 */
298static int read_brightness(struct backlight_device *bd)
299{
300 return get_acpi(CM_ASL_PANELBRIGHT);
301}
302
303static int set_brightness(struct backlight_device *bd, int value)
304{
305 value = max(0, min(15, value));
306 return set_acpi(CM_ASL_PANELBRIGHT, value);
307}
308
309static int update_bl_status(struct backlight_device *bd)
310{
311 return set_brightness(bd, bd->props.brightness);
312}
313
314/*
Matthew Garretta195dcd2008-08-19 12:13:20 +0100315 * Rfkill helpers
316 */
317
Johannes Berg19d337d2009-06-02 13:01:37 +0200318static bool eeepc_wlan_rfkill_blocked(void)
Matthew Garretta195dcd2008-08-19 12:13:20 +0100319{
320 if (get_acpi(CM_ASL_WLAN) == 1)
Johannes Berg19d337d2009-06-02 13:01:37 +0200321 return false;
322 return true;
Matthew Garretta195dcd2008-08-19 12:13:20 +0100323}
324
Johannes Berg19d337d2009-06-02 13:01:37 +0200325static int eeepc_rfkill_set(void *data, bool blocked)
Matthew Garretta195dcd2008-08-19 12:13:20 +0100326{
Johannes Berg19d337d2009-06-02 13:01:37 +0200327 unsigned long asl = (unsigned long)data;
328 return set_acpi(asl, !blocked);
Matthew Garretta195dcd2008-08-19 12:13:20 +0100329}
330
Johannes Berg19d337d2009-06-02 13:01:37 +0200331static const struct rfkill_ops eeepc_rfkill_ops = {
332 .set_block = eeepc_rfkill_set,
333};
Matthew Garretta195dcd2008-08-19 12:13:20 +0100334
Pekka Enbergcede2cb2009-06-16 19:28:45 +0000335static void __init eeepc_enable_camera(void)
336{
337 /*
338 * If the following call to set_acpi() fails, it's because there's no
339 * camera so we can ignore the error.
340 */
341 set_acpi(CM_ASL_CAMERA, 1);
342}
343
Matthew Garretta195dcd2008-08-19 12:13:20 +0100344/*
Eric Coopere59f8792008-03-13 12:55:46 +0100345 * Sys helpers
346 */
347static int parse_arg(const char *buf, unsigned long count, int *val)
348{
349 if (!count)
350 return 0;
351 if (sscanf(buf, "%i", val) != 1)
352 return -EINVAL;
353 return count;
354}
355
356static ssize_t store_sys_acpi(int cm, const char *buf, size_t count)
357{
358 int rv, value;
359
360 rv = parse_arg(buf, count, &value);
361 if (rv > 0)
362 set_acpi(cm, value);
363 return rv;
364}
365
366static ssize_t show_sys_acpi(int cm, char *buf)
367{
368 return sprintf(buf, "%d\n", get_acpi(cm));
369}
370
371#define EEEPC_CREATE_DEVICE_ATTR(_name, _cm) \
372 static ssize_t show_##_name(struct device *dev, \
373 struct device_attribute *attr, \
374 char *buf) \
375 { \
376 return show_sys_acpi(_cm, buf); \
377 } \
378 static ssize_t store_##_name(struct device *dev, \
379 struct device_attribute *attr, \
380 const char *buf, size_t count) \
381 { \
382 return store_sys_acpi(_cm, buf, count); \
383 } \
384 static struct device_attribute dev_attr_##_name = { \
385 .attr = { \
386 .name = __stringify(_name), \
387 .mode = 0644 }, \
388 .show = show_##_name, \
389 .store = store_##_name, \
390 }
391
392EEEPC_CREATE_DEVICE_ATTR(camera, CM_ASL_CAMERA);
393EEEPC_CREATE_DEVICE_ATTR(cardr, CM_ASL_CARDREADER);
394EEEPC_CREATE_DEVICE_ATTR(disp, CM_ASL_DISPLAYSWITCH);
Grigori Goronzy158ca1d2009-04-27 09:23:40 +0200395EEEPC_CREATE_DEVICE_ATTR(cpufv, CM_ASL_CPUFV);
Eric Coopere59f8792008-03-13 12:55:46 +0100396
397static struct attribute *platform_attributes[] = {
398 &dev_attr_camera.attr,
399 &dev_attr_cardr.attr,
400 &dev_attr_disp.attr,
Grigori Goronzy158ca1d2009-04-27 09:23:40 +0200401 &dev_attr_cpufv.attr,
Eric Coopere59f8792008-03-13 12:55:46 +0100402 NULL
403};
404
405static struct attribute_group platform_attribute_group = {
406 .attrs = platform_attributes
407};
408
409/*
410 * Hotkey functions
411 */
Matthew Garretta195dcd2008-08-19 12:13:20 +0100412static struct key_entry *eepc_get_entry_by_scancode(int code)
413{
414 struct key_entry *key;
415
416 for (key = eeepc_keymap; key->type != KE_END; key++)
417 if (code == key->code)
418 return key;
419
420 return NULL;
421}
422
423static struct key_entry *eepc_get_entry_by_keycode(int code)
424{
425 struct key_entry *key;
426
427 for (key = eeepc_keymap; key->type != KE_END; key++)
428 if (code == key->keycode && key->type == KE_KEY)
429 return key;
430
431 return NULL;
432}
433
434static int eeepc_getkeycode(struct input_dev *dev, int scancode, int *keycode)
435{
436 struct key_entry *key = eepc_get_entry_by_scancode(scancode);
437
438 if (key && key->type == KE_KEY) {
439 *keycode = key->keycode;
440 return 0;
441 }
442
443 return -EINVAL;
444}
445
446static int eeepc_setkeycode(struct input_dev *dev, int scancode, int keycode)
447{
448 struct key_entry *key;
449 int old_keycode;
450
451 if (keycode < 0 || keycode > KEY_MAX)
452 return -EINVAL;
453
454 key = eepc_get_entry_by_scancode(scancode);
455 if (key && key->type == KE_KEY) {
456 old_keycode = key->keycode;
457 key->keycode = keycode;
458 set_bit(keycode, dev->keybit);
459 if (!eepc_get_entry_by_keycode(old_keycode))
460 clear_bit(old_keycode, dev->keybit);
461 return 0;
462 }
463
464 return -EINVAL;
465}
466
Eric Coopere59f8792008-03-13 12:55:46 +0100467static int eeepc_hotk_check(void)
468{
Matthew Garretta195dcd2008-08-19 12:13:20 +0100469 const struct key_entry *key;
Eric Coopere59f8792008-03-13 12:55:46 +0100470 struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
471 int result;
472
473 result = acpi_bus_get_status(ehotk->device);
474 if (result)
475 return result;
476 if (ehotk->device->status.present) {
477 if (write_acpi_int(ehotk->handle, "INIT", ehotk->init_flag,
478 &buffer)) {
479 printk(EEEPC_ERR "Hotkey initialization failed\n");
480 return -ENODEV;
481 } else {
482 printk(EEEPC_NOTICE "Hotkey init flags 0x%x\n",
483 ehotk->init_flag);
484 }
485 /* get control methods supported */
486 if (read_acpi_int(ehotk->handle, "CMSG"
487 , &ehotk->cm_supported)) {
488 printk(EEEPC_ERR
489 "Get control methods supported failed\n");
490 return -ENODEV;
491 } else {
492 printk(EEEPC_INFO
493 "Get control methods supported: 0x%x\n",
494 ehotk->cm_supported);
495 }
Matthew Garretta195dcd2008-08-19 12:13:20 +0100496 ehotk->inputdev = input_allocate_device();
497 if (!ehotk->inputdev) {
498 printk(EEEPC_INFO "Unable to allocate input device\n");
499 return 0;
500 }
501 ehotk->inputdev->name = "Asus EeePC extra buttons";
502 ehotk->inputdev->phys = EEEPC_HOTK_FILE "/input0";
503 ehotk->inputdev->id.bustype = BUS_HOST;
504 ehotk->inputdev->getkeycode = eeepc_getkeycode;
505 ehotk->inputdev->setkeycode = eeepc_setkeycode;
506
507 for (key = eeepc_keymap; key->type != KE_END; key++) {
508 switch (key->type) {
509 case KE_KEY:
510 set_bit(EV_KEY, ehotk->inputdev->evbit);
511 set_bit(key->keycode, ehotk->inputdev->keybit);
512 break;
513 }
514 }
515 result = input_register_device(ehotk->inputdev);
516 if (result) {
517 printk(EEEPC_INFO "Unable to register input device\n");
518 input_free_device(ehotk->inputdev);
519 return 0;
520 }
Eric Coopere59f8792008-03-13 12:55:46 +0100521 } else {
522 printk(EEEPC_ERR "Hotkey device not present, aborting\n");
523 return -EINVAL;
524 }
525 return 0;
526}
527
Darren Salt64b86b62009-04-27 09:23:38 +0200528static int notify_brn(void)
Corentin Charya5fa4292008-03-13 12:56:37 +0100529{
Darren Salt64b86b62009-04-27 09:23:38 +0200530 /* returns the *previous* brightness, or -1 */
Corentin Charya5fa4292008-03-13 12:56:37 +0100531 struct backlight_device *bd = eeepc_backlight_device;
Darren Salt64b86b62009-04-27 09:23:38 +0200532 if (bd) {
533 int old = bd->props.brightness;
Darren Salt7695fb02009-02-07 01:02:07 -0500534 bd->props.brightness = read_brightness(bd);
Darren Salt64b86b62009-04-27 09:23:38 +0200535 return old;
536 }
537 return -1;
Corentin Charya5fa4292008-03-13 12:56:37 +0100538}
539
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100540static void eeepc_rfkill_hotplug(void)
Matthew Garrett57402942009-01-20 16:17:48 +0100541{
542 struct pci_dev *dev;
543 struct pci_bus *bus = pci_find_bus(0, 1);
Johannes Berg19d337d2009-06-02 13:01:37 +0200544 bool blocked;
Matthew Garrett57402942009-01-20 16:17:48 +0100545
Matthew Garrett57402942009-01-20 16:17:48 +0100546 if (!bus) {
547 printk(EEEPC_WARNING "Unable to find PCI bus 1?\n");
548 return;
549 }
550
Johannes Berg19d337d2009-06-02 13:01:37 +0200551 blocked = eeepc_wlan_rfkill_blocked();
552 if (!blocked) {
Matthew Garrett57402942009-01-20 16:17:48 +0100553 dev = pci_get_slot(bus, 0);
554 if (dev) {
555 /* Device already present */
556 pci_dev_put(dev);
557 return;
558 }
559 dev = pci_scan_single_device(bus, 0);
560 if (dev) {
561 pci_bus_assign_resources(bus);
562 if (pci_bus_add_device(dev))
563 printk(EEEPC_ERR "Unable to hotplug wifi\n");
564 }
565 } else {
566 dev = pci_get_slot(bus, 0);
567 if (dev) {
568 pci_remove_bus_device(dev);
569 pci_dev_put(dev);
570 }
571 }
Alan Jenkins978605c2009-04-27 09:23:39 +0200572
Johannes Berg19d337d2009-06-02 13:01:37 +0200573 rfkill_set_sw_state(ehotk->eeepc_wlan_rfkill, blocked);
Matthew Garrett57402942009-01-20 16:17:48 +0100574}
575
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100576static void eeepc_rfkill_notify(acpi_handle handle, u32 event, void *data)
577{
578 if (event != ACPI_NOTIFY_BUS_CHECK)
579 return;
580
581 eeepc_rfkill_hotplug();
582}
583
Bjorn Helgaasd9b9bd72009-04-30 09:36:03 -0600584static void eeepc_hotk_notify(struct acpi_device *device, u32 event)
Eric Coopere59f8792008-03-13 12:55:46 +0100585{
Matthew Garretta195dcd2008-08-19 12:13:20 +0100586 static struct key_entry *key;
Corentin Chary7950b712009-02-15 19:30:20 +0100587 u16 count;
Darren Salt64b86b62009-04-27 09:23:38 +0200588 int brn = -ENODEV;
Corentin Chary7950b712009-02-15 19:30:20 +0100589
Eric Coopere59f8792008-03-13 12:55:46 +0100590 if (!ehotk)
591 return;
Bjorn Helgaasd9b9bd72009-04-30 09:36:03 -0600592 if (event > ACPI_MAX_SYS_NOTIFY)
593 return;
Corentin Charya5fa4292008-03-13 12:56:37 +0100594 if (event >= NOTIFY_BRN_MIN && event <= NOTIFY_BRN_MAX)
Darren Salt64b86b62009-04-27 09:23:38 +0200595 brn = notify_brn();
Corentin Chary7950b712009-02-15 19:30:20 +0100596 count = ehotk->event_count[event % 128]++;
597 acpi_bus_generate_proc_event(ehotk->device, event, count);
Corentin Chary2b25c9f2009-01-20 16:17:49 +0100598 acpi_bus_generate_netlink_event(ehotk->device->pnp.device_class,
599 dev_name(&ehotk->device->dev), event,
Corentin Chary7950b712009-02-15 19:30:20 +0100600 count);
Matthew Garretta195dcd2008-08-19 12:13:20 +0100601 if (ehotk->inputdev) {
Darren Salt64b86b62009-04-27 09:23:38 +0200602 if (brn != -ENODEV) {
603 /* brightness-change events need special
604 * handling for conversion to key events
605 */
606 if (brn < 0)
607 brn = event;
608 else
609 brn += NOTIFY_BRN_MIN;
610 if (event < brn)
611 event = NOTIFY_BRN_MIN; /* brightness down */
612 else if (event > brn)
613 event = NOTIFY_BRN_MIN + 2; /* ... up */
614 else
615 event = NOTIFY_BRN_MIN + 1; /* ... unchanged */
616 }
Matthew Garretta195dcd2008-08-19 12:13:20 +0100617 key = eepc_get_entry_by_scancode(event);
618 if (key) {
619 switch (key->type) {
620 case KE_KEY:
621 input_report_key(ehotk->inputdev, key->keycode,
622 1);
623 input_sync(ehotk->inputdev);
624 input_report_key(ehotk->inputdev, key->keycode,
625 0);
626 input_sync(ehotk->inputdev);
627 break;
628 }
629 }
630 }
Eric Coopere59f8792008-03-13 12:55:46 +0100631}
632
Matthew Garrett57402942009-01-20 16:17:48 +0100633static int eeepc_register_rfkill_notifier(char *node)
634{
635 acpi_status status = AE_OK;
636 acpi_handle handle;
637
638 status = acpi_get_handle(NULL, node, &handle);
639
640 if (ACPI_SUCCESS(status)) {
641 status = acpi_install_notify_handler(handle,
642 ACPI_SYSTEM_NOTIFY,
643 eeepc_rfkill_notify,
644 NULL);
645 if (ACPI_FAILURE(status))
646 printk(EEEPC_WARNING
647 "Failed to register notify on %s\n", node);
648 } else
649 return -ENODEV;
650
651 return 0;
652}
653
654static void eeepc_unregister_rfkill_notifier(char *node)
655{
656 acpi_status status = AE_OK;
657 acpi_handle handle;
658
659 status = acpi_get_handle(NULL, node, &handle);
660
661 if (ACPI_SUCCESS(status)) {
662 status = acpi_remove_notify_handler(handle,
663 ACPI_SYSTEM_NOTIFY,
664 eeepc_rfkill_notify);
665 if (ACPI_FAILURE(status))
666 printk(EEEPC_ERR
667 "Error removing rfkill notify handler %s\n",
668 node);
669 }
670}
671
Eric Coopere59f8792008-03-13 12:55:46 +0100672static int eeepc_hotk_add(struct acpi_device *device)
673{
Eric Coopere59f8792008-03-13 12:55:46 +0100674 int result;
675
676 if (!device)
677 return -EINVAL;
678 printk(EEEPC_NOTICE EEEPC_HOTK_NAME "\n");
679 ehotk = kzalloc(sizeof(struct eeepc_hotk), GFP_KERNEL);
680 if (!ehotk)
681 return -ENOMEM;
682 ehotk->init_flag = DISABLE_ASL_WLAN | DISABLE_ASL_DISPLAYSWITCH;
683 ehotk->handle = device->handle;
684 strcpy(acpi_device_name(device), EEEPC_HOTK_DEVICE_NAME);
685 strcpy(acpi_device_class(device), EEEPC_HOTK_CLASS);
Pavel Machekdb89b4f2008-09-22 14:37:34 -0700686 device->driver_data = ehotk;
Eric Coopere59f8792008-03-13 12:55:46 +0100687 ehotk->device = device;
688 result = eeepc_hotk_check();
689 if (result)
Matthew Garrettc9ddf8f2009-01-20 16:17:47 +0100690 goto ehotk_fail;
Matthew Garretta195dcd2008-08-19 12:13:20 +0100691
Alan Jenkinsfbc97e42009-04-27 09:23:37 +0200692 eeepc_register_rfkill_notifier("\\_SB.PCI0.P0P6");
693 eeepc_register_rfkill_notifier("\\_SB.PCI0.P0P7");
694
Matthew Garretta195dcd2008-08-19 12:13:20 +0100695 if (get_acpi(CM_ASL_WLAN) != -1) {
Johannes Berg19d337d2009-06-02 13:01:37 +0200696 ehotk->eeepc_wlan_rfkill = rfkill_alloc("eeepc-wlan",
697 &device->dev,
698 RFKILL_TYPE_WLAN,
699 &eeepc_rfkill_ops,
700 (void *)CM_ASL_WLAN);
Matthew Garretta195dcd2008-08-19 12:13:20 +0100701
702 if (!ehotk->eeepc_wlan_rfkill)
Matthew Garrettc9ddf8f2009-01-20 16:17:47 +0100703 goto wlan_fail;
Matthew Garretta195dcd2008-08-19 12:13:20 +0100704
Alan Jenkins06d5caf2009-06-16 15:39:51 +0100705 rfkill_init_sw_state(ehotk->eeepc_wlan_rfkill,
706 get_acpi(CM_ASL_WLAN) != 1);
Matthew Garrettc9ddf8f2009-01-20 16:17:47 +0100707 result = rfkill_register(ehotk->eeepc_wlan_rfkill);
708 if (result)
709 goto wlan_fail;
Matthew Garretta195dcd2008-08-19 12:13:20 +0100710 }
711
712 if (get_acpi(CM_ASL_BLUETOOTH) != -1) {
713 ehotk->eeepc_bluetooth_rfkill =
Johannes Berg19d337d2009-06-02 13:01:37 +0200714 rfkill_alloc("eeepc-bluetooth",
715 &device->dev,
716 RFKILL_TYPE_BLUETOOTH,
717 &eeepc_rfkill_ops,
718 (void *)CM_ASL_BLUETOOTH);
Matthew Garretta195dcd2008-08-19 12:13:20 +0100719
720 if (!ehotk->eeepc_bluetooth_rfkill)
Matthew Garrettc9ddf8f2009-01-20 16:17:47 +0100721 goto bluetooth_fail;
Matthew Garretta195dcd2008-08-19 12:13:20 +0100722
Alan Jenkins06d5caf2009-06-16 15:39:51 +0100723 rfkill_init_sw_state(ehotk->eeepc_bluetooth_rfkill,
724 get_acpi(CM_ASL_BLUETOOTH) != 1);
Matthew Garrettc9ddf8f2009-01-20 16:17:47 +0100725 result = rfkill_register(ehotk->eeepc_bluetooth_rfkill);
726 if (result)
727 goto bluetooth_fail;
Eric Coopere59f8792008-03-13 12:55:46 +0100728 }
Matthew Garrett57402942009-01-20 16:17:48 +0100729
Matthew Garrettc9ddf8f2009-01-20 16:17:47 +0100730 return 0;
731
732 bluetooth_fail:
Johannes Berg19d337d2009-06-02 13:01:37 +0200733 rfkill_destroy(ehotk->eeepc_bluetooth_rfkill);
Matthew Garrettc9ddf8f2009-01-20 16:17:47 +0100734 rfkill_unregister(ehotk->eeepc_wlan_rfkill);
Matthew Garrettc9ddf8f2009-01-20 16:17:47 +0100735 wlan_fail:
Johannes Berg19d337d2009-06-02 13:01:37 +0200736 rfkill_destroy(ehotk->eeepc_wlan_rfkill);
Corentin Charybd320052009-04-27 09:23:43 +0200737 eeepc_unregister_rfkill_notifier("\\_SB.PCI0.P0P6");
738 eeepc_unregister_rfkill_notifier("\\_SB.PCI0.P0P7");
Matthew Garrettc9ddf8f2009-01-20 16:17:47 +0100739 ehotk_fail:
740 kfree(ehotk);
741 ehotk = NULL;
742
Eric Coopere59f8792008-03-13 12:55:46 +0100743 return result;
744}
745
746static int eeepc_hotk_remove(struct acpi_device *device, int type)
747{
Eric Coopere59f8792008-03-13 12:55:46 +0100748 if (!device || !acpi_driver_data(device))
749 return -EINVAL;
Matthew Garrett57402942009-01-20 16:17:48 +0100750
751 eeepc_unregister_rfkill_notifier("\\_SB.PCI0.P0P6");
752 eeepc_unregister_rfkill_notifier("\\_SB.PCI0.P0P7");
753
Eric Coopere59f8792008-03-13 12:55:46 +0100754 kfree(ehotk);
755 return 0;
756}
757
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100758static int eeepc_hotk_resume(struct acpi_device *device)
759{
760 if (ehotk->eeepc_wlan_rfkill) {
761 bool wlan;
762
763 /* Workaround - it seems that _PTS disables the wireless
764 without notification or changing the value read by WLAN.
765 Normally this is fine because the correct value is restored
766 from the non-volatile storage on resume, but we need to do
767 it ourself if case suspend is aborted, or we lose wireless.
768 */
769 wlan = get_acpi(CM_ASL_WLAN);
770 set_acpi(CM_ASL_WLAN, wlan);
771
772 rfkill_set_sw_state(ehotk->eeepc_wlan_rfkill,
773 wlan != 1);
774
775 eeepc_rfkill_hotplug();
776 }
777
778 if (ehotk->eeepc_bluetooth_rfkill)
779 rfkill_set_sw_state(ehotk->eeepc_bluetooth_rfkill,
780 get_acpi(CM_ASL_BLUETOOTH) != 1);
781
782 return 0;
783}
784
Eric Coopere59f8792008-03-13 12:55:46 +0100785/*
Corentin Charye1faa9d2008-03-13 12:57:18 +0100786 * Hwmon
787 */
788static int eeepc_get_fan_pwm(void)
789{
790 int value = 0;
791
792 read_acpi_int(NULL, EEEPC_EC_FAN_PWM, &value);
Corentin Chary04dcd842008-10-09 15:33:57 +0200793 value = value * 255 / 100;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100794 return (value);
795}
796
797static void eeepc_set_fan_pwm(int value)
798{
Corentin Chary04dcd842008-10-09 15:33:57 +0200799 value = SENSORS_LIMIT(value, 0, 255);
800 value = value * 100 / 255;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100801 ec_write(EEEPC_EC_SC02, value);
802}
803
804static int eeepc_get_fan_rpm(void)
805{
806 int high = 0;
807 int low = 0;
808
809 read_acpi_int(NULL, EEEPC_EC_FAN_HRPM, &high);
810 read_acpi_int(NULL, EEEPC_EC_FAN_LRPM, &low);
811 return (high << 8 | low);
812}
813
814static int eeepc_get_fan_ctrl(void)
815{
816 int value = 0;
817
818 read_acpi_int(NULL, EEEPC_EC_FAN_CTRL, &value);
819 return ((value & 0x02 ? 1 : 0));
820}
821
822static void eeepc_set_fan_ctrl(int manual)
823{
824 int value = 0;
825
826 read_acpi_int(NULL, EEEPC_EC_FAN_CTRL, &value);
827 if (manual)
828 value |= 0x02;
829 else
830 value &= ~0x02;
831 ec_write(EEEPC_EC_SFB3, value);
832}
833
834static ssize_t store_sys_hwmon(void (*set)(int), const char *buf, size_t count)
835{
836 int rv, value;
837
838 rv = parse_arg(buf, count, &value);
839 if (rv > 0)
840 set(value);
841 return rv;
842}
843
844static ssize_t show_sys_hwmon(int (*get)(void), char *buf)
845{
846 return sprintf(buf, "%d\n", get());
847}
848
849#define EEEPC_CREATE_SENSOR_ATTR(_name, _mode, _set, _get) \
850 static ssize_t show_##_name(struct device *dev, \
851 struct device_attribute *attr, \
852 char *buf) \
853 { \
854 return show_sys_hwmon(_set, buf); \
855 } \
856 static ssize_t store_##_name(struct device *dev, \
857 struct device_attribute *attr, \
858 const char *buf, size_t count) \
859 { \
860 return store_sys_hwmon(_get, buf, count); \
861 } \
862 static SENSOR_DEVICE_ATTR(_name, _mode, show_##_name, store_##_name, 0);
863
864EEEPC_CREATE_SENSOR_ATTR(fan1_input, S_IRUGO, eeepc_get_fan_rpm, NULL);
Corentin Chary04dcd842008-10-09 15:33:57 +0200865EEEPC_CREATE_SENSOR_ATTR(pwm1, S_IRUGO | S_IWUSR,
Corentin Charye1faa9d2008-03-13 12:57:18 +0100866 eeepc_get_fan_pwm, eeepc_set_fan_pwm);
867EEEPC_CREATE_SENSOR_ATTR(pwm1_enable, S_IRUGO | S_IWUSR,
868 eeepc_get_fan_ctrl, eeepc_set_fan_ctrl);
869
Corentin Chary04dcd842008-10-09 15:33:57 +0200870static ssize_t
871show_name(struct device *dev, struct device_attribute *attr, char *buf)
872{
873 return sprintf(buf, "eeepc\n");
874}
875static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0);
876
Corentin Charye1faa9d2008-03-13 12:57:18 +0100877static struct attribute *hwmon_attributes[] = {
Corentin Chary04dcd842008-10-09 15:33:57 +0200878 &sensor_dev_attr_pwm1.dev_attr.attr,
Corentin Charye1faa9d2008-03-13 12:57:18 +0100879 &sensor_dev_attr_fan1_input.dev_attr.attr,
880 &sensor_dev_attr_pwm1_enable.dev_attr.attr,
Corentin Chary04dcd842008-10-09 15:33:57 +0200881 &sensor_dev_attr_name.dev_attr.attr,
Corentin Charye1faa9d2008-03-13 12:57:18 +0100882 NULL
883};
884
885static struct attribute_group hwmon_attribute_group = {
886 .attrs = hwmon_attributes
887};
888
889/*
Eric Coopere59f8792008-03-13 12:55:46 +0100890 * exit/init
891 */
Corentin Charya5fa4292008-03-13 12:56:37 +0100892static void eeepc_backlight_exit(void)
893{
894 if (eeepc_backlight_device)
895 backlight_device_unregister(eeepc_backlight_device);
Corentin Charya9df80c2009-01-20 16:17:40 +0100896 eeepc_backlight_device = NULL;
897}
898
899static void eeepc_rfkill_exit(void)
900{
Matthew Garretta195dcd2008-08-19 12:13:20 +0100901 if (ehotk->eeepc_wlan_rfkill)
902 rfkill_unregister(ehotk->eeepc_wlan_rfkill);
903 if (ehotk->eeepc_bluetooth_rfkill)
904 rfkill_unregister(ehotk->eeepc_bluetooth_rfkill);
Corentin Charya9df80c2009-01-20 16:17:40 +0100905}
906
907static void eeepc_input_exit(void)
908{
909 if (ehotk->inputdev)
910 input_unregister_device(ehotk->inputdev);
Corentin Charya5fa4292008-03-13 12:56:37 +0100911}
912
Corentin Charye1faa9d2008-03-13 12:57:18 +0100913static void eeepc_hwmon_exit(void)
914{
915 struct device *hwmon;
916
917 hwmon = eeepc_hwmon_device;
918 if (!hwmon)
919 return ;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100920 sysfs_remove_group(&hwmon->kobj,
921 &hwmon_attribute_group);
Matthew Garrettf1441312008-08-20 14:08:57 -0700922 hwmon_device_unregister(hwmon);
Corentin Charye1faa9d2008-03-13 12:57:18 +0100923 eeepc_hwmon_device = NULL;
924}
925
Eric Coopere59f8792008-03-13 12:55:46 +0100926static void __exit eeepc_laptop_exit(void)
927{
Corentin Charya5fa4292008-03-13 12:56:37 +0100928 eeepc_backlight_exit();
Corentin Charya9df80c2009-01-20 16:17:40 +0100929 eeepc_rfkill_exit();
930 eeepc_input_exit();
Corentin Charye1faa9d2008-03-13 12:57:18 +0100931 eeepc_hwmon_exit();
Eric Coopere59f8792008-03-13 12:55:46 +0100932 acpi_bus_unregister_driver(&eeepc_hotk_driver);
933 sysfs_remove_group(&platform_device->dev.kobj,
934 &platform_attribute_group);
935 platform_device_unregister(platform_device);
936 platform_driver_unregister(&platform_driver);
937}
938
Corentin Charya5fa4292008-03-13 12:56:37 +0100939static int eeepc_backlight_init(struct device *dev)
940{
941 struct backlight_device *bd;
942
943 bd = backlight_device_register(EEEPC_HOTK_FILE, dev,
944 NULL, &eeepcbl_ops);
945 if (IS_ERR(bd)) {
946 printk(EEEPC_ERR
947 "Could not register eeepc backlight device\n");
948 eeepc_backlight_device = NULL;
949 return PTR_ERR(bd);
950 }
951 eeepc_backlight_device = bd;
952 bd->props.max_brightness = 15;
953 bd->props.brightness = read_brightness(NULL);
954 bd->props.power = FB_BLANK_UNBLANK;
955 backlight_update_status(bd);
956 return 0;
957}
958
Corentin Charye1faa9d2008-03-13 12:57:18 +0100959static int eeepc_hwmon_init(struct device *dev)
960{
961 struct device *hwmon;
962 int result;
963
964 hwmon = hwmon_device_register(dev);
965 if (IS_ERR(hwmon)) {
966 printk(EEEPC_ERR
967 "Could not register eeepc hwmon device\n");
968 eeepc_hwmon_device = NULL;
969 return PTR_ERR(hwmon);
970 }
971 eeepc_hwmon_device = hwmon;
972 result = sysfs_create_group(&hwmon->kobj,
973 &hwmon_attribute_group);
974 if (result)
975 eeepc_hwmon_exit();
976 return result;
977}
978
Eric Coopere59f8792008-03-13 12:55:46 +0100979static int __init eeepc_laptop_init(void)
980{
981 struct device *dev;
982 int result;
983
984 if (acpi_disabled)
985 return -ENODEV;
986 result = acpi_bus_register_driver(&eeepc_hotk_driver);
987 if (result < 0)
988 return result;
989 if (!ehotk) {
990 acpi_bus_unregister_driver(&eeepc_hotk_driver);
991 return -ENODEV;
992 }
993 dev = acpi_get_physical_device(ehotk->device->handle);
Thomas Renningera2bf8c02008-08-01 17:37:59 +0200994
995 if (!acpi_video_backlight_support()) {
996 result = eeepc_backlight_init(dev);
997 if (result)
998 goto fail_backlight;
999 } else
1000 printk(EEEPC_INFO "Backlight controlled by ACPI video "
1001 "driver\n");
1002
Corentin Charye1faa9d2008-03-13 12:57:18 +01001003 result = eeepc_hwmon_init(dev);
1004 if (result)
1005 goto fail_hwmon;
Pekka Enbergcede2cb2009-06-16 19:28:45 +00001006
1007 eeepc_enable_camera();
1008
Eric Coopere59f8792008-03-13 12:55:46 +01001009 /* Register platform stuff */
1010 result = platform_driver_register(&platform_driver);
1011 if (result)
1012 goto fail_platform_driver;
1013 platform_device = platform_device_alloc(EEEPC_HOTK_FILE, -1);
1014 if (!platform_device) {
1015 result = -ENOMEM;
1016 goto fail_platform_device1;
1017 }
1018 result = platform_device_add(platform_device);
1019 if (result)
1020 goto fail_platform_device2;
1021 result = sysfs_create_group(&platform_device->dev.kobj,
1022 &platform_attribute_group);
1023 if (result)
1024 goto fail_sysfs;
1025 return 0;
1026fail_sysfs:
1027 platform_device_del(platform_device);
1028fail_platform_device2:
1029 platform_device_put(platform_device);
1030fail_platform_device1:
1031 platform_driver_unregister(&platform_driver);
1032fail_platform_driver:
Corentin Charye1faa9d2008-03-13 12:57:18 +01001033 eeepc_hwmon_exit();
1034fail_hwmon:
Corentin Charya5fa4292008-03-13 12:56:37 +01001035 eeepc_backlight_exit();
1036fail_backlight:
Corentin Charya9df80c2009-01-20 16:17:40 +01001037 eeepc_input_exit();
1038 eeepc_rfkill_exit();
Eric Coopere59f8792008-03-13 12:55:46 +01001039 return result;
1040}
1041
1042module_init(eeepc_laptop_init);
1043module_exit(eeepc_laptop_exit);