blob: 920d9d9f1f9caceb42d34dfca37f4e00b0126cd7 [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
Alan Jenkins854c7832009-12-03 07:45:09 +0000131static const struct key_entry eeepc_keymap[] = {
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000132 /* 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 },
Corentin Charyb39b85e742009-12-03 07:45:13 +0000139 {KE_KEY, 0x16, KEY_DISPLAY_OFF },
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000140 {KE_KEY, 0x1a, KEY_COFFEE },
141 {KE_KEY, 0x1b, KEY_ZOOM },
142 {KE_KEY, 0x1c, KEY_PROG2 },
143 {KE_KEY, 0x1d, KEY_PROG3 },
144 {KE_KEY, NOTIFY_BRN_MIN, KEY_BRIGHTNESSDOWN },
145 {KE_KEY, NOTIFY_BRN_MAX, KEY_BRIGHTNESSUP },
146 {KE_KEY, 0x30, KEY_SWITCHVIDEOMODE },
147 {KE_KEY, 0x31, KEY_SWITCHVIDEOMODE },
148 {KE_KEY, 0x32, KEY_SWITCHVIDEOMODE },
Corentin Charyb39b85e742009-12-03 07:45:13 +0000149 {KE_KEY, 0x37, KEY_F13 }, /* Disable Touchpad */
150 {KE_KEY, 0x38, KEY_F14 },
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000151 {KE_END, 0},
152};
Alan Jenkins463b4e42009-12-03 07:45:03 +0000153
Corentin Charye1faa9d2008-03-13 12:57:18 +0100154
Eric Coopere59f8792008-03-13 12:55:46 +0100155/*
156 * This is the main structure, we can use it to store useful information
Eric Coopere59f8792008-03-13 12:55:46 +0100157 */
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000158struct eeepc_laptop {
Alan Jenkins854c7832009-12-03 07:45:09 +0000159 acpi_handle handle; /* the handle of the acpi device */
Eric Coopere59f8792008-03-13 12:55:46 +0100160 u32 cm_supported; /* the control methods supported
161 by this BIOS */
Eric Coopere59f8792008-03-13 12:55:46 +0100162 u16 event_count[128]; /* count for each event */
Alan Jenkins854c7832009-12-03 07:45:09 +0000163
164 struct platform_device *platform_device;
165 struct device *hwmon_device;
166 struct backlight_device *backlight_device;
167
Matthew Garretta195dcd2008-08-19 12:13:20 +0100168 struct input_dev *inputdev;
Alan Jenkins854c7832009-12-03 07:45:09 +0000169 struct key_entry *keymap;
170
Corentin Chary7de39382009-06-25 13:25:38 +0200171 struct rfkill *wlan_rfkill;
172 struct rfkill *bluetooth_rfkill;
Corentin Chary3cd530b2009-06-25 13:25:42 +0200173 struct rfkill *wwan3g_rfkill;
Corentin Charyd1ec9c32009-08-28 12:56:41 +0000174 struct rfkill *wimax_rfkill;
Alan Jenkins854c7832009-12-03 07:45:09 +0000175
Corentin Chary2b121bc2009-06-25 13:25:36 +0200176 struct hotplug_slot *hotplug_slot;
Alan Jenkinsdcf443b2009-08-28 12:56:33 +0000177 struct mutex hotplug_lock;
Alan Jenkins854c7832009-12-03 07:45:09 +0000178
179 struct led_classdev tpd_led;
180 int tpd_led_wk;
181 struct workqueue_struct *led_workqueue;
182 struct work_struct tpd_led_work;
Eric Coopere59f8792008-03-13 12:55:46 +0100183};
184
Eric Coopere59f8792008-03-13 12:55:46 +0100185/*
186 * ACPI Helpers
187 */
Alan Jenkins6b188a72009-12-03 07:45:02 +0000188static int write_acpi_int(acpi_handle handle, const char *method, int val)
Eric Coopere59f8792008-03-13 12:55:46 +0100189{
190 struct acpi_object_list params;
191 union acpi_object in_obj;
192 acpi_status status;
193
194 params.count = 1;
195 params.pointer = &in_obj;
196 in_obj.type = ACPI_TYPE_INTEGER;
197 in_obj.integer.value = val;
198
Alan Jenkins6b188a72009-12-03 07:45:02 +0000199 status = acpi_evaluate_object(handle, (char *)method, &params, NULL);
Eric Coopere59f8792008-03-13 12:55:46 +0100200 return (status == AE_OK ? 0 : -1);
201}
202
203static int read_acpi_int(acpi_handle handle, const char *method, int *val)
204{
205 acpi_status status;
Matthew Wilcox27663c52008-10-10 02:22:59 -0400206 unsigned long long result;
Eric Coopere59f8792008-03-13 12:55:46 +0100207
208 status = acpi_evaluate_integer(handle, (char *)method, NULL, &result);
209 if (ACPI_FAILURE(status)) {
210 *val = -1;
211 return -1;
212 } else {
213 *val = result;
214 return 0;
215 }
216}
217
Alan Jenkins854c7832009-12-03 07:45:09 +0000218static int set_acpi(struct eeepc_laptop *eeepc, int cm, int value)
Eric Coopere59f8792008-03-13 12:55:46 +0100219{
Alan Jenkins13f70022009-12-03 07:44:59 +0000220 const char *method = cm_setv[cm];
221
222 if (method == NULL)
223 return -ENODEV;
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000224 if ((eeepc->cm_supported & (0x1 << cm)) == 0)
Alan Jenkins13f70022009-12-03 07:44:59 +0000225 return -ENODEV;
226
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000227 if (write_acpi_int(eeepc->handle, method, value))
Alan Jenkins13f70022009-12-03 07:44:59 +0000228 pr_warning("Error writing %s\n", method);
Eric Coopere59f8792008-03-13 12:55:46 +0100229 return 0;
230}
231
Alan Jenkins854c7832009-12-03 07:45:09 +0000232static int get_acpi(struct eeepc_laptop *eeepc, int cm)
Eric Coopere59f8792008-03-13 12:55:46 +0100233{
Alan Jenkins13f70022009-12-03 07:44:59 +0000234 const char *method = cm_getv[cm];
235 int value;
236
237 if (method == NULL)
238 return -ENODEV;
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000239 if ((eeepc->cm_supported & (0x1 << cm)) == 0)
Alan Jenkins13f70022009-12-03 07:44:59 +0000240 return -ENODEV;
241
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000242 if (read_acpi_int(eeepc->handle, method, &value))
Alan Jenkins13f70022009-12-03 07:44:59 +0000243 pr_warning("Error reading %s\n", method);
Eric Coopere59f8792008-03-13 12:55:46 +0100244 return value;
245}
246
Alan Jenkins854c7832009-12-03 07:45:09 +0000247static int acpi_setter_handle(struct eeepc_laptop *eeepc, int cm, acpi_handle *handle)
248{
249 const char *method = cm_setv[cm];
250 acpi_status status;
251
252 if (method == NULL)
253 return -ENODEV;
254 if ((eeepc->cm_supported & (0x1 << cm)) == 0)
255 return -ENODEV;
256
257 status = acpi_get_handle(eeepc->handle, (char *)method,
258 handle);
259 if (status != AE_OK) {
260 pr_warning("Error finding %s\n", method);
261 return -ENODEV;
262 }
263 return 0;
264}
265
266
Eric Coopere59f8792008-03-13 12:55:46 +0100267/*
268 * Sys helpers
269 */
270static int parse_arg(const char *buf, unsigned long count, int *val)
271{
272 if (!count)
273 return 0;
274 if (sscanf(buf, "%i", val) != 1)
275 return -EINVAL;
276 return count;
277}
278
Alan Jenkins854c7832009-12-03 07:45:09 +0000279static ssize_t store_sys_acpi(struct device *dev, int cm,
280 const char *buf, size_t count)
Eric Coopere59f8792008-03-13 12:55:46 +0100281{
Alan Jenkins854c7832009-12-03 07:45:09 +0000282 struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
Eric Coopere59f8792008-03-13 12:55:46 +0100283 int rv, value;
284
285 rv = parse_arg(buf, count, &value);
286 if (rv > 0)
Alan Jenkins854c7832009-12-03 07:45:09 +0000287 value = set_acpi(eeepc, cm, value);
Corentin Charyf36509e2009-06-25 13:25:40 +0200288 if (value < 0)
Alan Jenkins6dff29b2009-12-03 07:44:45 +0000289 return -EIO;
Eric Coopere59f8792008-03-13 12:55:46 +0100290 return rv;
291}
292
Alan Jenkins854c7832009-12-03 07:45:09 +0000293static ssize_t show_sys_acpi(struct device *dev, int cm, char *buf)
Eric Coopere59f8792008-03-13 12:55:46 +0100294{
Alan Jenkins854c7832009-12-03 07:45:09 +0000295 struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
296 int value = get_acpi(eeepc, cm);
Corentin Charyf36509e2009-06-25 13:25:40 +0200297
298 if (value < 0)
Alan Jenkins6dff29b2009-12-03 07:44:45 +0000299 return -EIO;
Corentin Charyf36509e2009-06-25 13:25:40 +0200300 return sprintf(buf, "%d\n", value);
Eric Coopere59f8792008-03-13 12:55:46 +0100301}
302
Alan Jenkins6dff29b2009-12-03 07:44:45 +0000303#define EEEPC_CREATE_DEVICE_ATTR(_name, _mode, _cm) \
Eric Coopere59f8792008-03-13 12:55:46 +0100304 static ssize_t show_##_name(struct device *dev, \
305 struct device_attribute *attr, \
306 char *buf) \
307 { \
Alan Jenkins854c7832009-12-03 07:45:09 +0000308 return show_sys_acpi(dev, _cm, buf); \
Eric Coopere59f8792008-03-13 12:55:46 +0100309 } \
310 static ssize_t store_##_name(struct device *dev, \
311 struct device_attribute *attr, \
312 const char *buf, size_t count) \
313 { \
Alan Jenkins854c7832009-12-03 07:45:09 +0000314 return store_sys_acpi(dev, _cm, buf, count); \
Eric Coopere59f8792008-03-13 12:55:46 +0100315 } \
316 static struct device_attribute dev_attr_##_name = { \
317 .attr = { \
318 .name = __stringify(_name), \
Alan Jenkins6dff29b2009-12-03 07:44:45 +0000319 .mode = _mode }, \
Eric Coopere59f8792008-03-13 12:55:46 +0100320 .show = show_##_name, \
321 .store = store_##_name, \
322 }
323
Alan Jenkins6dff29b2009-12-03 07:44:45 +0000324EEEPC_CREATE_DEVICE_ATTR(camera, 0644, CM_ASL_CAMERA);
325EEEPC_CREATE_DEVICE_ATTR(cardr, 0644, CM_ASL_CARDREADER);
326EEEPC_CREATE_DEVICE_ATTR(disp, 0200, CM_ASL_DISPLAYSWITCH);
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000327
328struct eeepc_cpufv {
329 int num;
330 int cur;
331};
332
Alan Jenkins854c7832009-12-03 07:45:09 +0000333static int get_cpufv(struct eeepc_laptop *eeepc, struct eeepc_cpufv *c)
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000334{
Alan Jenkins854c7832009-12-03 07:45:09 +0000335 c->cur = get_acpi(eeepc, CM_ASL_CPUFV);
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000336 c->num = (c->cur >> 8) & 0xff;
337 c->cur &= 0xff;
338 if (c->cur < 0 || c->num <= 0 || c->num > 12)
339 return -ENODEV;
340 return 0;
341}
342
343static ssize_t show_available_cpufv(struct device *dev,
344 struct device_attribute *attr,
345 char *buf)
346{
Alan Jenkins854c7832009-12-03 07:45:09 +0000347 struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000348 struct eeepc_cpufv c;
349 int i;
350 ssize_t len = 0;
351
Alan Jenkins854c7832009-12-03 07:45:09 +0000352 if (get_cpufv(eeepc, &c))
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000353 return -ENODEV;
354 for (i = 0; i < c.num; i++)
355 len += sprintf(buf + len, "%d ", i);
356 len += sprintf(buf + len, "\n");
357 return len;
358}
359
360static ssize_t show_cpufv(struct device *dev,
361 struct device_attribute *attr,
362 char *buf)
363{
Alan Jenkins854c7832009-12-03 07:45:09 +0000364 struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000365 struct eeepc_cpufv c;
366
Alan Jenkins854c7832009-12-03 07:45:09 +0000367 if (get_cpufv(eeepc, &c))
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000368 return -ENODEV;
369 return sprintf(buf, "%#x\n", (c.num << 8) | c.cur);
370}
371
372static ssize_t store_cpufv(struct device *dev,
373 struct device_attribute *attr,
374 const char *buf, size_t count)
375{
Alan Jenkins854c7832009-12-03 07:45:09 +0000376 struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000377 struct eeepc_cpufv c;
378 int rv, value;
379
Alan Jenkins854c7832009-12-03 07:45:09 +0000380 if (get_cpufv(eeepc, &c))
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000381 return -ENODEV;
382 rv = parse_arg(buf, count, &value);
383 if (rv < 0)
384 return rv;
385 if (!rv || value < 0 || value >= c.num)
386 return -EINVAL;
Alan Jenkins854c7832009-12-03 07:45:09 +0000387 set_acpi(eeepc, CM_ASL_CPUFV, value);
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000388 return rv;
389}
390
391static struct device_attribute dev_attr_cpufv = {
392 .attr = {
393 .name = "cpufv",
394 .mode = 0644 },
395 .show = show_cpufv,
396 .store = store_cpufv
397};
398
399static struct device_attribute dev_attr_available_cpufv = {
400 .attr = {
401 .name = "available_cpufv",
402 .mode = 0444 },
403 .show = show_available_cpufv
404};
Eric Coopere59f8792008-03-13 12:55:46 +0100405
406static struct attribute *platform_attributes[] = {
407 &dev_attr_camera.attr,
408 &dev_attr_cardr.attr,
409 &dev_attr_disp.attr,
Grigori Goronzy158ca1d2009-04-27 09:23:40 +0200410 &dev_attr_cpufv.attr,
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000411 &dev_attr_available_cpufv.attr,
Eric Coopere59f8792008-03-13 12:55:46 +0100412 NULL
413};
414
415static struct attribute_group platform_attribute_group = {
416 .attrs = platform_attributes
417};
418
Alan Jenkins854c7832009-12-03 07:45:09 +0000419static int eeepc_platform_init(struct eeepc_laptop *eeepc)
Alan Jenkins9db106b2009-12-03 07:45:06 +0000420{
421 int result;
422
Alan Jenkins854c7832009-12-03 07:45:09 +0000423 eeepc->platform_device = platform_device_alloc(EEEPC_LAPTOP_FILE, -1);
424 if (!eeepc->platform_device)
Alan Jenkins9db106b2009-12-03 07:45:06 +0000425 return -ENOMEM;
Alan Jenkins854c7832009-12-03 07:45:09 +0000426 platform_set_drvdata(eeepc->platform_device, eeepc);
Alan Jenkins9db106b2009-12-03 07:45:06 +0000427
Alan Jenkins854c7832009-12-03 07:45:09 +0000428 result = platform_device_add(eeepc->platform_device);
Alan Jenkins9db106b2009-12-03 07:45:06 +0000429 if (result)
430 goto fail_platform_device;
431
Alan Jenkins854c7832009-12-03 07:45:09 +0000432 result = sysfs_create_group(&eeepc->platform_device->dev.kobj,
Alan Jenkins9db106b2009-12-03 07:45:06 +0000433 &platform_attribute_group);
434 if (result)
435 goto fail_sysfs;
436 return 0;
437
438fail_sysfs:
Alan Jenkins854c7832009-12-03 07:45:09 +0000439 platform_device_del(eeepc->platform_device);
Alan Jenkins9db106b2009-12-03 07:45:06 +0000440fail_platform_device:
Alan Jenkins854c7832009-12-03 07:45:09 +0000441 platform_device_put(eeepc->platform_device);
Alan Jenkins9db106b2009-12-03 07:45:06 +0000442 return result;
443}
444
Alan Jenkins854c7832009-12-03 07:45:09 +0000445static void eeepc_platform_exit(struct eeepc_laptop *eeepc)
Alan Jenkins9db106b2009-12-03 07:45:06 +0000446{
Alan Jenkins854c7832009-12-03 07:45:09 +0000447 sysfs_remove_group(&eeepc->platform_device->dev.kobj,
Alan Jenkins9db106b2009-12-03 07:45:06 +0000448 &platform_attribute_group);
Alan Jenkins854c7832009-12-03 07:45:09 +0000449 platform_device_unregister(eeepc->platform_device);
Alan Jenkins9db106b2009-12-03 07:45:06 +0000450}
451
Eric Coopere59f8792008-03-13 12:55:46 +0100452/*
Corentin Chary3c0eb512009-12-03 07:44:52 +0000453 * LEDs
454 */
455/*
456 * These functions actually update the LED's, and are called from a
457 * workqueue. By doing this as separate work rather than when the LED
458 * subsystem asks, we avoid messing with the Asus ACPI stuff during a
459 * potentially bad time, such as a timer interrupt.
460 */
Alan Jenkins854c7832009-12-03 07:45:09 +0000461static void tpd_led_update(struct work_struct *work)
462 {
463 struct eeepc_laptop *eeepc;
Corentin Chary3c0eb512009-12-03 07:44:52 +0000464
Alan Jenkins854c7832009-12-03 07:45:09 +0000465 eeepc = container_of(work, struct eeepc_laptop, tpd_led_work);
466
467 set_acpi(eeepc, CM_ASL_TPD, eeepc->tpd_led_wk);
Corentin Chary3c0eb512009-12-03 07:44:52 +0000468}
469
Corentin Chary3c0eb512009-12-03 07:44:52 +0000470static void tpd_led_set(struct led_classdev *led_cdev,
471 enum led_brightness value)
472{
Alan Jenkins854c7832009-12-03 07:45:09 +0000473 struct eeepc_laptop *eeepc;
474
475 eeepc = container_of(led_cdev, struct eeepc_laptop, tpd_led);
476
477 eeepc->tpd_led_wk = (value > 0) ? 1 : 0;
478 queue_work(eeepc->led_workqueue, &eeepc->tpd_led_work);
Corentin Chary3c0eb512009-12-03 07:44:52 +0000479}
480
Alan Jenkins854c7832009-12-03 07:45:09 +0000481static int eeepc_led_init(struct eeepc_laptop *eeepc)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000482{
483 int rv;
484
Alan Jenkins854c7832009-12-03 07:45:09 +0000485 if (get_acpi(eeepc, CM_ASL_TPD) == -ENODEV)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000486 return 0;
487
Alan Jenkins854c7832009-12-03 07:45:09 +0000488 eeepc->led_workqueue = create_singlethread_workqueue("led_workqueue");
489 if (!eeepc->led_workqueue)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000490 return -ENOMEM;
Alan Jenkins854c7832009-12-03 07:45:09 +0000491 INIT_WORK(&eeepc->tpd_led_work, tpd_led_update);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000492
Alan Jenkins854c7832009-12-03 07:45:09 +0000493 eeepc->tpd_led.name = "eeepc::touchpad";
494 eeepc->tpd_led.brightness_set = tpd_led_set;
495 eeepc->tpd_led.max_brightness = 1;
496
497 rv = led_classdev_register(&eeepc->platform_device->dev,
498 &eeepc->tpd_led);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000499 if (rv) {
Alan Jenkins854c7832009-12-03 07:45:09 +0000500 destroy_workqueue(eeepc->led_workqueue);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000501 return rv;
502 }
503
504 return 0;
505}
506
Alan Jenkins854c7832009-12-03 07:45:09 +0000507static void eeepc_led_exit(struct eeepc_laptop *eeepc)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000508{
Alan Jenkins854c7832009-12-03 07:45:09 +0000509 if (eeepc->tpd_led.dev)
510 led_classdev_unregister(&eeepc->tpd_led);
511 if (eeepc->led_workqueue)
512 destroy_workqueue(eeepc->led_workqueue);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000513}
514
515
Corentin Chary3c0eb512009-12-03 07:44:52 +0000516/*
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000517 * PCI hotplug (for wlan rfkill)
Eric Coopere59f8792008-03-13 12:55:46 +0100518 */
Alan Jenkins854c7832009-12-03 07:45:09 +0000519static bool eeepc_wlan_rfkill_blocked(struct eeepc_laptop *eeepc)
Matthew Garretta195dcd2008-08-19 12:13:20 +0100520{
Alan Jenkins854c7832009-12-03 07:45:09 +0000521 if (get_acpi(eeepc, CM_ASL_WLAN) == 1)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000522 return false;
523 return true;
Corentin Chary2b121bc2009-06-25 13:25:36 +0200524}
525
Alan Jenkins854c7832009-12-03 07:45:09 +0000526static void eeepc_rfkill_hotplug(struct eeepc_laptop *eeepc)
Matthew Garrett57402942009-01-20 16:17:48 +0100527{
528 struct pci_dev *dev;
Alan Jenkins6d418392009-08-28 12:56:32 +0000529 struct pci_bus *bus;
Alan Jenkins854c7832009-12-03 07:45:09 +0000530 bool blocked = eeepc_wlan_rfkill_blocked(eeepc);
Matthew Garrett57402942009-01-20 16:17:48 +0100531
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000532 if (eeepc->wlan_rfkill)
533 rfkill_set_sw_state(eeepc->wlan_rfkill, blocked);
Alan Jenkins6d418392009-08-28 12:56:32 +0000534
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000535 mutex_lock(&eeepc->hotplug_lock);
Alan Jenkinsdcf443b2009-08-28 12:56:33 +0000536
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000537 if (eeepc->hotplug_slot) {
Alan Jenkins07e84aa2009-08-28 12:56:34 +0000538 bus = pci_find_bus(0, 1);
539 if (!bus) {
540 pr_warning("Unable to find PCI bus 1?\n");
Alan Jenkinsdcf443b2009-08-28 12:56:33 +0000541 goto out_unlock;
Matthew Garrett57402942009-01-20 16:17:48 +0100542 }
Alan Jenkins07e84aa2009-08-28 12:56:34 +0000543
544 if (!blocked) {
545 dev = pci_get_slot(bus, 0);
546 if (dev) {
547 /* Device already present */
548 pci_dev_put(dev);
549 goto out_unlock;
550 }
551 dev = pci_scan_single_device(bus, 0);
552 if (dev) {
553 pci_bus_assign_resources(bus);
554 if (pci_bus_add_device(dev))
555 pr_err("Unable to hotplug wifi\n");
556 }
557 } else {
558 dev = pci_get_slot(bus, 0);
559 if (dev) {
560 pci_remove_bus_device(dev);
561 pci_dev_put(dev);
562 }
Matthew Garrett57402942009-01-20 16:17:48 +0100563 }
564 }
Alan Jenkinsdcf443b2009-08-28 12:56:33 +0000565
566out_unlock:
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000567 mutex_unlock(&eeepc->hotplug_lock);
Matthew Garrett57402942009-01-20 16:17:48 +0100568}
569
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100570static void eeepc_rfkill_notify(acpi_handle handle, u32 event, void *data)
571{
Alan Jenkins854c7832009-12-03 07:45:09 +0000572 struct eeepc_laptop *eeepc = data;
573
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100574 if (event != ACPI_NOTIFY_BUS_CHECK)
575 return;
576
Alan Jenkins854c7832009-12-03 07:45:09 +0000577 eeepc_rfkill_hotplug(eeepc);
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100578}
579
Alan Jenkins854c7832009-12-03 07:45:09 +0000580static int eeepc_register_rfkill_notifier(struct eeepc_laptop *eeepc,
581 char *node)
Matthew Garrett57402942009-01-20 16:17:48 +0100582{
Alan Jenkins854c7832009-12-03 07:45:09 +0000583 acpi_status status;
Matthew Garrett57402942009-01-20 16:17:48 +0100584 acpi_handle handle;
585
586 status = acpi_get_handle(NULL, node, &handle);
587
588 if (ACPI_SUCCESS(status)) {
589 status = acpi_install_notify_handler(handle,
590 ACPI_SYSTEM_NOTIFY,
591 eeepc_rfkill_notify,
Alan Jenkins854c7832009-12-03 07:45:09 +0000592 eeepc);
Matthew Garrett57402942009-01-20 16:17:48 +0100593 if (ACPI_FAILURE(status))
Joe Perches19b53282009-06-25 13:25:37 +0200594 pr_warning("Failed to register notify on %s\n", node);
Matthew Garrett57402942009-01-20 16:17:48 +0100595 } else
596 return -ENODEV;
597
598 return 0;
599}
600
Alan Jenkins854c7832009-12-03 07:45:09 +0000601static void eeepc_unregister_rfkill_notifier(struct eeepc_laptop *eeepc,
602 char *node)
Matthew Garrett57402942009-01-20 16:17:48 +0100603{
604 acpi_status status = AE_OK;
605 acpi_handle handle;
606
607 status = acpi_get_handle(NULL, node, &handle);
608
609 if (ACPI_SUCCESS(status)) {
610 status = acpi_remove_notify_handler(handle,
611 ACPI_SYSTEM_NOTIFY,
612 eeepc_rfkill_notify);
613 if (ACPI_FAILURE(status))
Joe Perches19b53282009-06-25 13:25:37 +0200614 pr_err("Error removing rfkill notify handler %s\n",
Matthew Garrett57402942009-01-20 16:17:48 +0100615 node);
616 }
617}
618
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000619static int eeepc_get_adapter_status(struct hotplug_slot *hotplug_slot,
620 u8 *value)
621{
Alan Jenkins854c7832009-12-03 07:45:09 +0000622 struct eeepc_laptop *eeepc = hotplug_slot->private;
623 int val = get_acpi(eeepc, CM_ASL_WLAN);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000624
625 if (val == 1 || val == 0)
626 *value = val;
627 else
628 return -EINVAL;
629
630 return 0;
631}
632
Corentin Chary2b121bc2009-06-25 13:25:36 +0200633static void eeepc_cleanup_pci_hotplug(struct hotplug_slot *hotplug_slot)
634{
635 kfree(hotplug_slot->info);
636 kfree(hotplug_slot);
637}
638
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000639static struct hotplug_slot_ops eeepc_hotplug_slot_ops = {
640 .owner = THIS_MODULE,
641 .get_adapter_status = eeepc_get_adapter_status,
642 .get_power_status = eeepc_get_adapter_status,
643};
644
Alan Jenkins854c7832009-12-03 07:45:09 +0000645static int eeepc_setup_pci_hotplug(struct eeepc_laptop *eeepc)
Corentin Chary2b121bc2009-06-25 13:25:36 +0200646{
647 int ret = -ENOMEM;
648 struct pci_bus *bus = pci_find_bus(0, 1);
649
650 if (!bus) {
Joe Perches19b53282009-06-25 13:25:37 +0200651 pr_err("Unable to find wifi PCI bus\n");
Corentin Chary2b121bc2009-06-25 13:25:36 +0200652 return -ENODEV;
653 }
654
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000655 eeepc->hotplug_slot = kzalloc(sizeof(struct hotplug_slot), GFP_KERNEL);
656 if (!eeepc->hotplug_slot)
Corentin Chary2b121bc2009-06-25 13:25:36 +0200657 goto error_slot;
658
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000659 eeepc->hotplug_slot->info = kzalloc(sizeof(struct hotplug_slot_info),
Corentin Chary2b121bc2009-06-25 13:25:36 +0200660 GFP_KERNEL);
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000661 if (!eeepc->hotplug_slot->info)
Corentin Chary2b121bc2009-06-25 13:25:36 +0200662 goto error_info;
663
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000664 eeepc->hotplug_slot->private = eeepc;
665 eeepc->hotplug_slot->release = &eeepc_cleanup_pci_hotplug;
666 eeepc->hotplug_slot->ops = &eeepc_hotplug_slot_ops;
667 eeepc_get_adapter_status(eeepc->hotplug_slot,
668 &eeepc->hotplug_slot->info->adapter_status);
Corentin Chary2b121bc2009-06-25 13:25:36 +0200669
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000670 ret = pci_hp_register(eeepc->hotplug_slot, bus, 0, "eeepc-wifi");
Corentin Chary2b121bc2009-06-25 13:25:36 +0200671 if (ret) {
Joe Perches19b53282009-06-25 13:25:37 +0200672 pr_err("Unable to register hotplug slot - %d\n", ret);
Corentin Chary2b121bc2009-06-25 13:25:36 +0200673 goto error_register;
674 }
675
676 return 0;
677
678error_register:
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000679 kfree(eeepc->hotplug_slot->info);
Corentin Chary2b121bc2009-06-25 13:25:36 +0200680error_info:
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000681 kfree(eeepc->hotplug_slot);
682 eeepc->hotplug_slot = NULL;
Corentin Chary2b121bc2009-06-25 13:25:36 +0200683error_slot:
684 return ret;
685}
686
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000687/*
688 * Rfkill devices
689 */
690static int eeepc_rfkill_set(void *data, bool blocked)
691{
Alan Jenkins854c7832009-12-03 07:45:09 +0000692 acpi_handle handle = data;
693
694 return write_acpi_int(handle, NULL, !blocked);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000695}
696
697static const struct rfkill_ops eeepc_rfkill_ops = {
698 .set_block = eeepc_rfkill_set,
699};
700
Alan Jenkins854c7832009-12-03 07:45:09 +0000701static int eeepc_new_rfkill(struct eeepc_laptop *eeepc,
702 struct rfkill **rfkill,
703 const char *name,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000704 enum rfkill_type type, int cm)
705{
Alan Jenkins854c7832009-12-03 07:45:09 +0000706 acpi_handle handle;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000707 int result;
708
Alan Jenkins854c7832009-12-03 07:45:09 +0000709 result = acpi_setter_handle(eeepc, cm, &handle);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000710 if (result < 0)
711 return result;
712
Alan Jenkins854c7832009-12-03 07:45:09 +0000713 *rfkill = rfkill_alloc(name, &eeepc->platform_device->dev, type,
714 &eeepc_rfkill_ops, handle);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000715
716 if (!*rfkill)
717 return -EINVAL;
718
Alan Jenkins854c7832009-12-03 07:45:09 +0000719 rfkill_init_sw_state(*rfkill, get_acpi(eeepc, cm) != 1);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000720 result = rfkill_register(*rfkill);
721 if (result) {
722 rfkill_destroy(*rfkill);
723 *rfkill = NULL;
724 return result;
725 }
726 return 0;
727}
728
Alan Jenkins854c7832009-12-03 07:45:09 +0000729static void eeepc_rfkill_exit(struct eeepc_laptop *eeepc)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000730{
Alan Jenkins854c7832009-12-03 07:45:09 +0000731 eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P5");
732 eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P6");
733 eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P7");
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000734 if (eeepc->wlan_rfkill) {
735 rfkill_unregister(eeepc->wlan_rfkill);
736 rfkill_destroy(eeepc->wlan_rfkill);
737 eeepc->wlan_rfkill = NULL;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000738 }
739 /*
740 * Refresh pci hotplug in case the rfkill state was changed after
741 * eeepc_unregister_rfkill_notifier()
742 */
Alan Jenkins854c7832009-12-03 07:45:09 +0000743 eeepc_rfkill_hotplug(eeepc);
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000744 if (eeepc->hotplug_slot)
745 pci_hp_deregister(eeepc->hotplug_slot);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000746
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000747 if (eeepc->bluetooth_rfkill) {
748 rfkill_unregister(eeepc->bluetooth_rfkill);
749 rfkill_destroy(eeepc->bluetooth_rfkill);
750 eeepc->bluetooth_rfkill = NULL;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000751 }
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000752 if (eeepc->wwan3g_rfkill) {
753 rfkill_unregister(eeepc->wwan3g_rfkill);
754 rfkill_destroy(eeepc->wwan3g_rfkill);
755 eeepc->wwan3g_rfkill = NULL;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000756 }
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000757 if (eeepc->wimax_rfkill) {
758 rfkill_unregister(eeepc->wimax_rfkill);
759 rfkill_destroy(eeepc->wimax_rfkill);
760 eeepc->wimax_rfkill = NULL;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000761 }
762}
763
Alan Jenkins854c7832009-12-03 07:45:09 +0000764static int eeepc_rfkill_init(struct eeepc_laptop *eeepc)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000765{
766 int result = 0;
767
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000768 mutex_init(&eeepc->hotplug_lock);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000769
Alan Jenkins854c7832009-12-03 07:45:09 +0000770 result = eeepc_new_rfkill(eeepc, &eeepc->wlan_rfkill,
771 "eeepc-wlan", RFKILL_TYPE_WLAN,
772 CM_ASL_WLAN);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000773
774 if (result && result != -ENODEV)
775 goto exit;
776
Alan Jenkins854c7832009-12-03 07:45:09 +0000777 result = eeepc_new_rfkill(eeepc, &eeepc->bluetooth_rfkill,
778 "eeepc-bluetooth", RFKILL_TYPE_BLUETOOTH,
779 CM_ASL_BLUETOOTH);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000780
781 if (result && result != -ENODEV)
782 goto exit;
783
Alan Jenkins854c7832009-12-03 07:45:09 +0000784 result = eeepc_new_rfkill(eeepc, &eeepc->wwan3g_rfkill,
785 "eeepc-wwan3g", RFKILL_TYPE_WWAN,
786 CM_ASL_3G);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000787
788 if (result && result != -ENODEV)
789 goto exit;
790
Alan Jenkins854c7832009-12-03 07:45:09 +0000791 result = eeepc_new_rfkill(eeepc, &eeepc->wimax_rfkill,
792 "eeepc-wimax", RFKILL_TYPE_WIMAX,
793 CM_ASL_WIMAX);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000794
795 if (result && result != -ENODEV)
796 goto exit;
797
Alan Jenkins854c7832009-12-03 07:45:09 +0000798 result = eeepc_setup_pci_hotplug(eeepc);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000799 /*
800 * If we get -EBUSY then something else is handling the PCI hotplug -
801 * don't fail in this case
802 */
803 if (result == -EBUSY)
804 result = 0;
805
Alan Jenkins854c7832009-12-03 07:45:09 +0000806 eeepc_register_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P5");
807 eeepc_register_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P6");
808 eeepc_register_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P7");
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000809 /*
810 * Refresh pci hotplug in case the rfkill state was changed during
811 * setup.
812 */
Alan Jenkins854c7832009-12-03 07:45:09 +0000813 eeepc_rfkill_hotplug(eeepc);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000814
815exit:
816 if (result && result != -ENODEV)
Alan Jenkins854c7832009-12-03 07:45:09 +0000817 eeepc_rfkill_exit(eeepc);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000818 return result;
819}
820
821/*
822 * Platform driver - hibernate/resume callbacks
823 */
Alan Jenkins854c7832009-12-03 07:45:09 +0000824static int eeepc_hotk_thaw(struct device *device)
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100825{
Alan Jenkins854c7832009-12-03 07:45:09 +0000826 struct eeepc_laptop *eeepc = dev_get_drvdata(device);
827
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000828 if (eeepc->wlan_rfkill) {
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100829 bool wlan;
830
Alan Jenkinsc1edd992009-08-28 12:56:39 +0000831 /*
832 * Work around bios bug - acpi _PTS turns off the wireless led
833 * during suspend. Normally it restores it on resume, but
Alan Jenkinsc200da52009-08-28 12:56:40 +0000834 * we should kick it ourselves in case hibernation is aborted.
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100835 */
Alan Jenkins854c7832009-12-03 07:45:09 +0000836 wlan = get_acpi(eeepc, CM_ASL_WLAN);
837 set_acpi(eeepc, CM_ASL_WLAN, wlan);
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100838 }
839
Alan Jenkinsc200da52009-08-28 12:56:40 +0000840 return 0;
841}
842
Alan Jenkins854c7832009-12-03 07:45:09 +0000843static int eeepc_hotk_restore(struct device *device)
Alan Jenkinsc200da52009-08-28 12:56:40 +0000844{
Alan Jenkins854c7832009-12-03 07:45:09 +0000845 struct eeepc_laptop *eeepc = dev_get_drvdata(device);
846
Alan Jenkinsc200da52009-08-28 12:56:40 +0000847 /* Refresh both wlan rfkill state and pci hotplug */
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000848 if (eeepc->wlan_rfkill)
Alan Jenkins854c7832009-12-03 07:45:09 +0000849 eeepc_rfkill_hotplug(eeepc);
Alan Jenkinsc200da52009-08-28 12:56:40 +0000850
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000851 if (eeepc->bluetooth_rfkill)
852 rfkill_set_sw_state(eeepc->bluetooth_rfkill,
Alan Jenkins854c7832009-12-03 07:45:09 +0000853 get_acpi(eeepc, CM_ASL_BLUETOOTH) != 1);
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000854 if (eeepc->wwan3g_rfkill)
855 rfkill_set_sw_state(eeepc->wwan3g_rfkill,
Alan Jenkins854c7832009-12-03 07:45:09 +0000856 get_acpi(eeepc, CM_ASL_3G) != 1);
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000857 if (eeepc->wimax_rfkill)
858 rfkill_set_sw_state(eeepc->wimax_rfkill,
Alan Jenkins854c7832009-12-03 07:45:09 +0000859 get_acpi(eeepc, CM_ASL_WIMAX) != 1);
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100860
861 return 0;
862}
863
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000864static struct dev_pm_ops eeepc_pm_ops = {
Alan Jenkins854c7832009-12-03 07:45:09 +0000865 .thaw = eeepc_hotk_thaw,
866 .restore = eeepc_hotk_restore,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000867};
868
869static struct platform_driver platform_driver = {
870 .driver = {
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000871 .name = EEEPC_LAPTOP_FILE,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000872 .owner = THIS_MODULE,
873 .pm = &eeepc_pm_ops,
874 }
875};
876
Eric Coopere59f8792008-03-13 12:55:46 +0100877/*
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000878 * Hwmon device
Corentin Charye1faa9d2008-03-13 12:57:18 +0100879 */
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000880
881#define EEEPC_EC_SC00 0x61
882#define EEEPC_EC_FAN_PWM (EEEPC_EC_SC00 + 2) /* Fan PWM duty cycle (%) */
883#define EEEPC_EC_FAN_HRPM (EEEPC_EC_SC00 + 5) /* High byte, fan speed (RPM) */
884#define EEEPC_EC_FAN_LRPM (EEEPC_EC_SC00 + 6) /* Low byte, fan speed (RPM) */
885
886#define EEEPC_EC_SFB0 0xD0
887#define EEEPC_EC_FAN_CTRL (EEEPC_EC_SFB0 + 3) /* Byte containing SF25 */
888
Corentin Charye1faa9d2008-03-13 12:57:18 +0100889static int eeepc_get_fan_pwm(void)
890{
Alan Jenkins463b4e42009-12-03 07:45:03 +0000891 u8 value = 0;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100892
Alan Jenkins463b4e42009-12-03 07:45:03 +0000893 ec_read(EEEPC_EC_FAN_PWM, &value);
894 return value * 255 / 100;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100895}
896
897static void eeepc_set_fan_pwm(int value)
898{
Corentin Chary04dcd842008-10-09 15:33:57 +0200899 value = SENSORS_LIMIT(value, 0, 255);
900 value = value * 100 / 255;
Alan Jenkins463b4e42009-12-03 07:45:03 +0000901 ec_write(EEEPC_EC_FAN_PWM, value);
Corentin Charye1faa9d2008-03-13 12:57:18 +0100902}
903
904static int eeepc_get_fan_rpm(void)
905{
Alan Jenkins463b4e42009-12-03 07:45:03 +0000906 u8 high = 0;
907 u8 low = 0;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100908
Alan Jenkins463b4e42009-12-03 07:45:03 +0000909 ec_read(EEEPC_EC_FAN_HRPM, &high);
910 ec_read(EEEPC_EC_FAN_LRPM, &low);
911 return high << 8 | low;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100912}
913
914static int eeepc_get_fan_ctrl(void)
915{
Alan Jenkins463b4e42009-12-03 07:45:03 +0000916 u8 value = 0;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100917
Alan Jenkins463b4e42009-12-03 07:45:03 +0000918 ec_read(EEEPC_EC_FAN_CTRL, &value);
Alan Jenkins48718682009-12-03 07:44:56 +0000919 if (value & 0x02)
920 return 1; /* manual */
921 else
922 return 2; /* automatic */
Corentin Charye1faa9d2008-03-13 12:57:18 +0100923}
924
925static void eeepc_set_fan_ctrl(int manual)
926{
Alan Jenkins463b4e42009-12-03 07:45:03 +0000927 u8 value = 0;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100928
Alan Jenkins463b4e42009-12-03 07:45:03 +0000929 ec_read(EEEPC_EC_FAN_CTRL, &value);
Alan Jenkins48718682009-12-03 07:44:56 +0000930 if (manual == 1)
Corentin Charye1faa9d2008-03-13 12:57:18 +0100931 value |= 0x02;
932 else
933 value &= ~0x02;
Alan Jenkins463b4e42009-12-03 07:45:03 +0000934 ec_write(EEEPC_EC_FAN_CTRL, value);
Corentin Charye1faa9d2008-03-13 12:57:18 +0100935}
936
937static ssize_t store_sys_hwmon(void (*set)(int), const char *buf, size_t count)
938{
939 int rv, value;
940
941 rv = parse_arg(buf, count, &value);
942 if (rv > 0)
943 set(value);
944 return rv;
945}
946
947static ssize_t show_sys_hwmon(int (*get)(void), char *buf)
948{
949 return sprintf(buf, "%d\n", get());
950}
951
952#define EEEPC_CREATE_SENSOR_ATTR(_name, _mode, _set, _get) \
953 static ssize_t show_##_name(struct device *dev, \
954 struct device_attribute *attr, \
955 char *buf) \
956 { \
957 return show_sys_hwmon(_set, buf); \
958 } \
959 static ssize_t store_##_name(struct device *dev, \
960 struct device_attribute *attr, \
961 const char *buf, size_t count) \
962 { \
963 return store_sys_hwmon(_get, buf, count); \
964 } \
965 static SENSOR_DEVICE_ATTR(_name, _mode, show_##_name, store_##_name, 0);
966
967EEEPC_CREATE_SENSOR_ATTR(fan1_input, S_IRUGO, eeepc_get_fan_rpm, NULL);
Corentin Chary04dcd842008-10-09 15:33:57 +0200968EEEPC_CREATE_SENSOR_ATTR(pwm1, S_IRUGO | S_IWUSR,
Corentin Charye1faa9d2008-03-13 12:57:18 +0100969 eeepc_get_fan_pwm, eeepc_set_fan_pwm);
970EEEPC_CREATE_SENSOR_ATTR(pwm1_enable, S_IRUGO | S_IWUSR,
971 eeepc_get_fan_ctrl, eeepc_set_fan_ctrl);
972
Corentin Chary04dcd842008-10-09 15:33:57 +0200973static ssize_t
974show_name(struct device *dev, struct device_attribute *attr, char *buf)
975{
976 return sprintf(buf, "eeepc\n");
977}
978static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0);
979
Corentin Charye1faa9d2008-03-13 12:57:18 +0100980static struct attribute *hwmon_attributes[] = {
Corentin Chary04dcd842008-10-09 15:33:57 +0200981 &sensor_dev_attr_pwm1.dev_attr.attr,
Corentin Charye1faa9d2008-03-13 12:57:18 +0100982 &sensor_dev_attr_fan1_input.dev_attr.attr,
983 &sensor_dev_attr_pwm1_enable.dev_attr.attr,
Corentin Chary04dcd842008-10-09 15:33:57 +0200984 &sensor_dev_attr_name.dev_attr.attr,
Corentin Charye1faa9d2008-03-13 12:57:18 +0100985 NULL
986};
987
988static struct attribute_group hwmon_attribute_group = {
989 .attrs = hwmon_attributes
990};
991
Alan Jenkins854c7832009-12-03 07:45:09 +0000992static void eeepc_hwmon_exit(struct eeepc_laptop *eeepc)
Corentin Charye1faa9d2008-03-13 12:57:18 +0100993{
994 struct device *hwmon;
995
Alan Jenkins854c7832009-12-03 07:45:09 +0000996 hwmon = eeepc->hwmon_device;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100997 if (!hwmon)
Alan Jenkins854c7832009-12-03 07:45:09 +0000998 return;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100999 sysfs_remove_group(&hwmon->kobj,
1000 &hwmon_attribute_group);
Matthew Garrettf1441312008-08-20 14:08:57 -07001001 hwmon_device_unregister(hwmon);
Alan Jenkins854c7832009-12-03 07:45:09 +00001002 eeepc->hwmon_device = NULL;
Corentin Charye1faa9d2008-03-13 12:57:18 +01001003}
1004
Alan Jenkins854c7832009-12-03 07:45:09 +00001005static int eeepc_hwmon_init(struct eeepc_laptop *eeepc)
Corentin Chary3c0eb512009-12-03 07:44:52 +00001006{
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001007 struct device *hwmon;
Corentin Chary7de39382009-06-25 13:25:38 +02001008 int result;
1009
Alan Jenkins854c7832009-12-03 07:45:09 +00001010 hwmon = hwmon_device_register(&eeepc->platform_device->dev);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001011 if (IS_ERR(hwmon)) {
1012 pr_err("Could not register eeepc hwmon device\n");
Alan Jenkins854c7832009-12-03 07:45:09 +00001013 eeepc->hwmon_device = NULL;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001014 return PTR_ERR(hwmon);
Corentin Chary7de39382009-06-25 13:25:38 +02001015 }
Alan Jenkins854c7832009-12-03 07:45:09 +00001016 eeepc->hwmon_device = hwmon;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001017 result = sysfs_create_group(&hwmon->kobj,
1018 &hwmon_attribute_group);
1019 if (result)
Alan Jenkins854c7832009-12-03 07:45:09 +00001020 eeepc_hwmon_exit(eeepc);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001021 return result;
Corentin Chary7de39382009-06-25 13:25:38 +02001022}
1023
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001024/*
1025 * Backlight device
1026 */
1027static int read_brightness(struct backlight_device *bd)
Corentin Chary7de39382009-06-25 13:25:38 +02001028{
Alan Jenkins854c7832009-12-03 07:45:09 +00001029 struct eeepc_laptop *eeepc = bl_get_data(bd);
1030
1031 return get_acpi(eeepc, CM_ASL_PANELBRIGHT);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001032}
Corentin Chary7de39382009-06-25 13:25:38 +02001033
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001034static int set_brightness(struct backlight_device *bd, int value)
1035{
Alan Jenkins854c7832009-12-03 07:45:09 +00001036 struct eeepc_laptop *eeepc = bl_get_data(bd);
1037
1038 return set_acpi(eeepc, CM_ASL_PANELBRIGHT, value);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001039}
Alan Jenkins73345462009-06-29 09:40:07 +01001040
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001041static int update_bl_status(struct backlight_device *bd)
1042{
1043 return set_brightness(bd, bd->props.brightness);
1044}
Corentin Chary7de39382009-06-25 13:25:38 +02001045
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001046static struct backlight_ops eeepcbl_ops = {
1047 .get_brightness = read_brightness,
1048 .update_status = update_bl_status,
1049};
Corentin Chary7de39382009-06-25 13:25:38 +02001050
Alan Jenkins854c7832009-12-03 07:45:09 +00001051static int eeepc_backlight_notify(struct eeepc_laptop *eeepc)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001052{
Alan Jenkins854c7832009-12-03 07:45:09 +00001053 struct backlight_device *bd = eeepc->backlight_device;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001054 int old = bd->props.brightness;
Corentin Chary7de39382009-06-25 13:25:38 +02001055
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001056 backlight_force_update(bd, BACKLIGHT_UPDATE_HOTKEY);
Corentin Chary7de39382009-06-25 13:25:38 +02001057
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001058 return old;
Corentin Chary7de39382009-06-25 13:25:38 +02001059}
1060
Alan Jenkins854c7832009-12-03 07:45:09 +00001061static int eeepc_backlight_init(struct eeepc_laptop *eeepc)
Corentin Charya5fa4292008-03-13 12:56:37 +01001062{
1063 struct backlight_device *bd;
1064
Alan Jenkins854c7832009-12-03 07:45:09 +00001065 bd = backlight_device_register(EEEPC_LAPTOP_FILE,
1066 &eeepc->platform_device->dev,
1067 eeepc, &eeepcbl_ops);
Corentin Charya5fa4292008-03-13 12:56:37 +01001068 if (IS_ERR(bd)) {
Joe Perches19b53282009-06-25 13:25:37 +02001069 pr_err("Could not register eeepc backlight device\n");
Alan Jenkins854c7832009-12-03 07:45:09 +00001070 eeepc->backlight_device = NULL;
Corentin Charya5fa4292008-03-13 12:56:37 +01001071 return PTR_ERR(bd);
1072 }
Alan Jenkins854c7832009-12-03 07:45:09 +00001073 eeepc->backlight_device = bd;
Corentin Charya5fa4292008-03-13 12:56:37 +01001074 bd->props.max_brightness = 15;
Alan Jenkins854c7832009-12-03 07:45:09 +00001075 bd->props.brightness = read_brightness(bd);
Corentin Charya5fa4292008-03-13 12:56:37 +01001076 bd->props.power = FB_BLANK_UNBLANK;
1077 backlight_update_status(bd);
1078 return 0;
1079}
1080
Alan Jenkins854c7832009-12-03 07:45:09 +00001081static void eeepc_backlight_exit(struct eeepc_laptop *eeepc)
Corentin Charye1faa9d2008-03-13 12:57:18 +01001082{
Alan Jenkins854c7832009-12-03 07:45:09 +00001083 if (eeepc->backlight_device)
1084 backlight_device_unregister(eeepc->backlight_device);
1085 eeepc->backlight_device = NULL;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001086}
Corentin Charye1faa9d2008-03-13 12:57:18 +01001087
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001088
1089/*
1090 * Input device (i.e. hotkeys)
1091 */
Alan Jenkins854c7832009-12-03 07:45:09 +00001092static struct key_entry *eeepc_get_entry_by_scancode(
1093 struct eeepc_laptop *eeepc,
1094 int code)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001095{
1096 struct key_entry *key;
1097
Alan Jenkins854c7832009-12-03 07:45:09 +00001098 for (key = eeepc->keymap; key->type != KE_END; key++)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001099 if (code == key->code)
1100 return key;
1101
1102 return NULL;
1103}
1104
Alan Jenkins854c7832009-12-03 07:45:09 +00001105static void eeepc_input_notify(struct eeepc_laptop *eeepc, int event)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001106{
1107 static struct key_entry *key;
1108
Alan Jenkins854c7832009-12-03 07:45:09 +00001109 key = eeepc_get_entry_by_scancode(eeepc, event);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001110 if (key) {
1111 switch (key->type) {
1112 case KE_KEY:
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001113 input_report_key(eeepc->inputdev, key->keycode,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001114 1);
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001115 input_sync(eeepc->inputdev);
1116 input_report_key(eeepc->inputdev, key->keycode,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001117 0);
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001118 input_sync(eeepc->inputdev);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001119 break;
1120 }
Corentin Charye1faa9d2008-03-13 12:57:18 +01001121 }
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001122}
1123
Alan Jenkins854c7832009-12-03 07:45:09 +00001124static struct key_entry *eeepc_get_entry_by_keycode(
1125 struct eeepc_laptop *eeepc, int code)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001126{
1127 struct key_entry *key;
1128
Alan Jenkins854c7832009-12-03 07:45:09 +00001129 for (key = eeepc->keymap; key->type != KE_END; key++)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001130 if (code == key->keycode && key->type == KE_KEY)
1131 return key;
1132
1133 return NULL;
1134}
1135
1136static int eeepc_getkeycode(struct input_dev *dev, int scancode, int *keycode)
1137{
Alan Jenkins854c7832009-12-03 07:45:09 +00001138 struct eeepc_laptop *eeepc = input_get_drvdata(dev);
1139 struct key_entry *key = eeepc_get_entry_by_scancode(eeepc, scancode);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001140
1141 if (key && key->type == KE_KEY) {
1142 *keycode = key->keycode;
1143 return 0;
1144 }
1145
1146 return -EINVAL;
1147}
1148
1149static int eeepc_setkeycode(struct input_dev *dev, int scancode, int keycode)
1150{
Alan Jenkins854c7832009-12-03 07:45:09 +00001151 struct eeepc_laptop *eeepc = input_get_drvdata(dev);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001152 struct key_entry *key;
1153 int old_keycode;
1154
1155 if (keycode < 0 || keycode > KEY_MAX)
1156 return -EINVAL;
1157
Alan Jenkins854c7832009-12-03 07:45:09 +00001158 key = eeepc_get_entry_by_scancode(eeepc, scancode);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001159 if (key && key->type == KE_KEY) {
1160 old_keycode = key->keycode;
1161 key->keycode = keycode;
1162 set_bit(keycode, dev->keybit);
Alan Jenkins854c7832009-12-03 07:45:09 +00001163 if (!eeepc_get_entry_by_keycode(eeepc, old_keycode))
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001164 clear_bit(old_keycode, dev->keybit);
1165 return 0;
1166 }
1167
1168 return -EINVAL;
Corentin Charye1faa9d2008-03-13 12:57:18 +01001169}
1170
Alan Jenkins854c7832009-12-03 07:45:09 +00001171static int eeepc_input_init(struct eeepc_laptop *eeepc)
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001172{
1173 const struct key_entry *key;
1174 int result;
1175
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001176 eeepc->inputdev = input_allocate_device();
1177 if (!eeepc->inputdev) {
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001178 pr_info("Unable to allocate input device\n");
1179 return -ENOMEM;
1180 }
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001181 eeepc->inputdev->name = "Asus EeePC extra buttons";
Alan Jenkins854c7832009-12-03 07:45:09 +00001182 eeepc->inputdev->dev.parent = &eeepc->platform_device->dev;
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001183 eeepc->inputdev->phys = EEEPC_LAPTOP_FILE "/input0";
1184 eeepc->inputdev->id.bustype = BUS_HOST;
1185 eeepc->inputdev->getkeycode = eeepc_getkeycode;
1186 eeepc->inputdev->setkeycode = eeepc_setkeycode;
Alan Jenkins854c7832009-12-03 07:45:09 +00001187 input_set_drvdata(eeepc->inputdev, eeepc);
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001188
Alan Jenkins854c7832009-12-03 07:45:09 +00001189 eeepc->keymap = kmemdup(eeepc_keymap, sizeof(eeepc_keymap),
1190 GFP_KERNEL);
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001191 for (key = eeepc_keymap; key->type != KE_END; key++) {
1192 switch (key->type) {
1193 case KE_KEY:
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001194 set_bit(EV_KEY, eeepc->inputdev->evbit);
1195 set_bit(key->keycode, eeepc->inputdev->keybit);
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001196 break;
1197 }
1198 }
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001199 result = input_register_device(eeepc->inputdev);
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001200 if (result) {
1201 pr_info("Unable to register input device\n");
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001202 input_free_device(eeepc->inputdev);
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001203 return result;
1204 }
1205 return 0;
1206}
1207
Alan Jenkins854c7832009-12-03 07:45:09 +00001208static void eeepc_input_exit(struct eeepc_laptop *eeepc)
Corentin Chary3c0eb512009-12-03 07:44:52 +00001209{
Alan Jenkins854c7832009-12-03 07:45:09 +00001210 if (eeepc->inputdev) {
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001211 input_unregister_device(eeepc->inputdev);
Alan Jenkins854c7832009-12-03 07:45:09 +00001212 kfree(eeepc->keymap);
1213 }
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001214}
Corentin Chary3c0eb512009-12-03 07:44:52 +00001215
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001216/*
1217 * ACPI driver
1218 */
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001219static void eeepc_acpi_notify(struct acpi_device *device, u32 event)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001220{
Alan Jenkins854c7832009-12-03 07:45:09 +00001221 struct eeepc_laptop *eeepc = acpi_driver_data(device);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001222 u16 count;
Corentin Chary3c0eb512009-12-03 07:44:52 +00001223
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001224 if (event > ACPI_MAX_SYS_NOTIFY)
1225 return;
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001226 count = eeepc->event_count[event % 128]++;
Alan Jenkins854c7832009-12-03 07:45:09 +00001227 acpi_bus_generate_proc_event(device, event, count);
1228 acpi_bus_generate_netlink_event(device->pnp.device_class,
1229 dev_name(&device->dev), event,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001230 count);
Corentin Chary3c0eb512009-12-03 07:44:52 +00001231
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001232 if (event >= NOTIFY_BRN_MIN && event <= NOTIFY_BRN_MAX) {
1233 int old_brightness, new_brightness;
1234
1235 /* Update backlight device. */
Alan Jenkins854c7832009-12-03 07:45:09 +00001236 old_brightness = eeepc_backlight_notify(eeepc);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001237
1238 /* Convert brightness event to keypress (obsolescent hack). */
1239 new_brightness = event - NOTIFY_BRN_MIN;
1240
1241 if (new_brightness < old_brightness) {
1242 event = NOTIFY_BRN_MIN; /* brightness down */
1243 } else if (new_brightness > old_brightness) {
1244 event = NOTIFY_BRN_MAX; /* brightness up */
1245 } else {
1246 /*
1247 * no change in brightness - already at min/max,
1248 * event will be desired value (or else ignored).
1249 */
1250 }
1251 }
Alan Jenkins854c7832009-12-03 07:45:09 +00001252 eeepc_input_notify(eeepc, event);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001253}
1254
Alan Jenkins854c7832009-12-03 07:45:09 +00001255static void cmsg_quirk(struct eeepc_laptop *eeepc, int cm, const char *name)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001256{
1257 int dummy;
1258
1259 /* Some BIOSes do not report cm although it is avaliable.
1260 Check if cm_getv[cm] works and, if yes, assume cm should be set. */
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001261 if (!(eeepc->cm_supported & (1 << cm))
1262 && !read_acpi_int(eeepc->handle, cm_getv[cm], &dummy)) {
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001263 pr_info("%s (%x) not reported by BIOS,"
1264 " enabling anyway\n", name, 1 << cm);
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001265 eeepc->cm_supported |= 1 << cm;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001266 }
1267}
1268
Alan Jenkins854c7832009-12-03 07:45:09 +00001269static void cmsg_quirks(struct eeepc_laptop *eeepc)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001270{
Alan Jenkins854c7832009-12-03 07:45:09 +00001271 cmsg_quirk(eeepc, CM_ASL_LID, "LID");
1272 cmsg_quirk(eeepc, CM_ASL_TYPE, "TYPE");
1273 cmsg_quirk(eeepc, CM_ASL_PANELPOWER, "PANELPOWER");
1274 cmsg_quirk(eeepc, CM_ASL_TPD, "TPD");
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001275}
1276
Alan Jenkins854c7832009-12-03 07:45:09 +00001277static int eeepc_acpi_init(struct eeepc_laptop *eeepc, struct acpi_device *device)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001278{
1279 unsigned int init_flags;
1280 int result;
1281
Alan Jenkins854c7832009-12-03 07:45:09 +00001282 result = acpi_bus_get_status(device);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001283 if (result)
1284 return result;
Alan Jenkins854c7832009-12-03 07:45:09 +00001285 if (!device->status.present) {
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001286 pr_err("Hotkey device not present, aborting\n");
1287 return -ENODEV;
Alan Jenkinsdc56ad92009-12-03 07:44:58 +00001288 }
Alan Jenkins2b56f1c2009-12-03 07:44:57 +00001289
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001290 init_flags = DISABLE_ASL_WLAN | DISABLE_ASL_DISPLAYSWITCH;
1291 pr_notice("Hotkey init flags 0x%x\n", init_flags);
1292
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001293 if (write_acpi_int(eeepc->handle, "INIT", init_flags)) {
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001294 pr_err("Hotkey initialization failed\n");
1295 return -ENODEV;
1296 }
1297
1298 /* get control methods supported */
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001299 if (read_acpi_int(eeepc->handle, "CMSG", &eeepc->cm_supported)) {
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001300 pr_err("Get control methods supported failed\n");
1301 return -ENODEV;
1302 }
Alan Jenkins854c7832009-12-03 07:45:09 +00001303 cmsg_quirks(eeepc);
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001304 pr_info("Get control methods supported: 0x%x\n", eeepc->cm_supported);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001305
Corentin Chary3c0eb512009-12-03 07:44:52 +00001306 return 0;
1307}
1308
Alan Jenkins854c7832009-12-03 07:45:09 +00001309static void __devinit eeepc_enable_camera(struct eeepc_laptop *eeepc)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001310{
1311 /*
1312 * If the following call to set_acpi() fails, it's because there's no
1313 * camera so we can ignore the error.
1314 */
Alan Jenkins854c7832009-12-03 07:45:09 +00001315 if (get_acpi(eeepc, CM_ASL_CAMERA) == 0)
1316 set_acpi(eeepc, CM_ASL_CAMERA, 1);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001317}
1318
Alan Jenkins854c7832009-12-03 07:45:09 +00001319static bool eeepc_device_present;
1320
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001321static int __devinit eeepc_acpi_add(struct acpi_device *device)
Eric Coopere59f8792008-03-13 12:55:46 +01001322{
Alan Jenkins854c7832009-12-03 07:45:09 +00001323 struct eeepc_laptop *eeepc;
Eric Coopere59f8792008-03-13 12:55:46 +01001324 int result;
1325
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001326 pr_notice(EEEPC_LAPTOP_NAME "\n");
1327 eeepc = kzalloc(sizeof(struct eeepc_laptop), GFP_KERNEL);
1328 if (!eeepc)
Alan Jenkins1e779852009-08-28 12:56:35 +00001329 return -ENOMEM;
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001330 eeepc->handle = device->handle;
1331 strcpy(acpi_device_name(device), EEEPC_ACPI_DEVICE_NAME);
1332 strcpy(acpi_device_class(device), EEEPC_ACPI_CLASS);
1333 device->driver_data = eeepc;
Pekka Enbergcede2cb2009-06-16 19:28:45 +00001334
Alan Jenkins854c7832009-12-03 07:45:09 +00001335 result = eeepc_acpi_init(eeepc, device);
Alan Jenkins1e779852009-08-28 12:56:35 +00001336 if (result)
Alan Jenkins9db106b2009-12-03 07:45:06 +00001337 goto fail_platform;
Alan Jenkins854c7832009-12-03 07:45:09 +00001338 eeepc_enable_camera(eeepc);
Pekka Enbergcede2cb2009-06-16 19:28:45 +00001339
Alan Jenkins854c7832009-12-03 07:45:09 +00001340 /*
1341 * Register the platform device first. It is used as a parent for the
1342 * sub-devices below.
1343 *
1344 * Note that if there are multiple instances of this ACPI device it
1345 * will bail out, because the platform device is registered with a
1346 * fixed name. Of course it doesn't make sense to have more than one,
1347 * and machine-specific scripts find the fixed name convenient. But
1348 * It's also good for us to exclude multiple instances because both
1349 * our hwmon and our wlan rfkill subdevice use global ACPI objects
1350 * (the EC and the wlan PCI slot respectively).
1351 */
1352 result = eeepc_platform_init(eeepc);
Eric Coopere59f8792008-03-13 12:55:46 +01001353 if (result)
Alan Jenkins9db106b2009-12-03 07:45:06 +00001354 goto fail_platform;
Corentin Chary1ddec2f2009-06-25 13:25:39 +02001355
1356 if (!acpi_video_backlight_support()) {
Alan Jenkins854c7832009-12-03 07:45:09 +00001357 result = eeepc_backlight_init(eeepc);
Corentin Chary1ddec2f2009-06-25 13:25:39 +02001358 if (result)
1359 goto fail_backlight;
1360 } else
Alan Jenkins9db106b2009-12-03 07:45:06 +00001361 pr_info("Backlight controlled by ACPI video driver\n");
Corentin Chary1ddec2f2009-06-25 13:25:39 +02001362
Alan Jenkins854c7832009-12-03 07:45:09 +00001363 result = eeepc_input_init(eeepc);
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001364 if (result)
1365 goto fail_input;
1366
Alan Jenkins854c7832009-12-03 07:45:09 +00001367 result = eeepc_hwmon_init(eeepc);
Corentin Chary1ddec2f2009-06-25 13:25:39 +02001368 if (result)
1369 goto fail_hwmon;
1370
Alan Jenkins854c7832009-12-03 07:45:09 +00001371 result = eeepc_led_init(eeepc);
Corentin Chary3c0eb512009-12-03 07:44:52 +00001372 if (result)
1373 goto fail_led;
1374
Alan Jenkins854c7832009-12-03 07:45:09 +00001375 result = eeepc_rfkill_init(eeepc);
Corentin Chary7de39382009-06-25 13:25:38 +02001376 if (result)
1377 goto fail_rfkill;
1378
Alan Jenkins854c7832009-12-03 07:45:09 +00001379 eeepc_device_present = true;
Eric Coopere59f8792008-03-13 12:55:46 +01001380 return 0;
Alan Jenkins1e779852009-08-28 12:56:35 +00001381
Corentin Chary7de39382009-06-25 13:25:38 +02001382fail_rfkill:
Alan Jenkins854c7832009-12-03 07:45:09 +00001383 eeepc_led_exit(eeepc);
Corentin Chary3c0eb512009-12-03 07:44:52 +00001384fail_led:
Alan Jenkins854c7832009-12-03 07:45:09 +00001385 eeepc_hwmon_exit(eeepc);
Corentin Chary1ddec2f2009-06-25 13:25:39 +02001386fail_hwmon:
Alan Jenkins854c7832009-12-03 07:45:09 +00001387 eeepc_input_exit(eeepc);
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001388fail_input:
Alan Jenkins854c7832009-12-03 07:45:09 +00001389 eeepc_backlight_exit(eeepc);
Corentin Chary1ddec2f2009-06-25 13:25:39 +02001390fail_backlight:
Alan Jenkins854c7832009-12-03 07:45:09 +00001391 eeepc_platform_exit(eeepc);
Alan Jenkins9db106b2009-12-03 07:45:06 +00001392fail_platform:
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001393 kfree(eeepc);
Alan Jenkins1e779852009-08-28 12:56:35 +00001394
Eric Coopere59f8792008-03-13 12:55:46 +01001395 return result;
1396}
1397
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001398static int eeepc_acpi_remove(struct acpi_device *device, int type)
Alan Jenkins1e779852009-08-28 12:56:35 +00001399{
Alan Jenkins854c7832009-12-03 07:45:09 +00001400 struct eeepc_laptop *eeepc = acpi_driver_data(device);
1401
1402 eeepc_backlight_exit(eeepc);
1403 eeepc_rfkill_exit(eeepc);
1404 eeepc_input_exit(eeepc);
1405 eeepc_hwmon_exit(eeepc);
1406 eeepc_led_exit(eeepc);
1407 eeepc_platform_exit(eeepc);
Alan Jenkins1e779852009-08-28 12:56:35 +00001408
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001409 kfree(eeepc);
Alan Jenkins1e779852009-08-28 12:56:35 +00001410 return 0;
1411}
1412
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001413
1414static const struct acpi_device_id eeepc_device_ids[] = {
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001415 {EEEPC_ACPI_HID, 0},
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001416 {"", 0},
1417};
1418MODULE_DEVICE_TABLE(acpi, eeepc_device_ids);
1419
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001420static struct acpi_driver eeepc_acpi_driver = {
1421 .name = EEEPC_LAPTOP_NAME,
1422 .class = EEEPC_ACPI_CLASS,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001423 .owner = THIS_MODULE,
1424 .ids = eeepc_device_ids,
1425 .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS,
1426 .ops = {
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001427 .add = eeepc_acpi_add,
1428 .remove = eeepc_acpi_remove,
1429 .notify = eeepc_acpi_notify,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001430 },
1431};
1432
1433
Alan Jenkins1e779852009-08-28 12:56:35 +00001434static int __init eeepc_laptop_init(void)
1435{
1436 int result;
1437
Alan Jenkins22072e92009-12-03 07:45:05 +00001438 result = platform_driver_register(&platform_driver);
Alan Jenkins1e779852009-08-28 12:56:35 +00001439 if (result < 0)
1440 return result;
Alan Jenkins22072e92009-12-03 07:45:05 +00001441
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001442 result = acpi_bus_register_driver(&eeepc_acpi_driver);
Alan Jenkins22072e92009-12-03 07:45:05 +00001443 if (result < 0)
1444 goto fail_acpi_driver;
Alan Jenkins854c7832009-12-03 07:45:09 +00001445 if (!eeepc_device_present) {
Alan Jenkins22072e92009-12-03 07:45:05 +00001446 result = -ENODEV;
1447 goto fail_no_device;
Alan Jenkins1e779852009-08-28 12:56:35 +00001448 }
1449 return 0;
Alan Jenkins22072e92009-12-03 07:45:05 +00001450
1451fail_no_device:
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001452 acpi_bus_unregister_driver(&eeepc_acpi_driver);
Alan Jenkins22072e92009-12-03 07:45:05 +00001453fail_acpi_driver:
1454 platform_driver_unregister(&platform_driver);
1455 return result;
Alan Jenkins1e779852009-08-28 12:56:35 +00001456}
1457
1458static void __exit eeepc_laptop_exit(void)
1459{
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001460 acpi_bus_unregister_driver(&eeepc_acpi_driver);
Alan Jenkins22072e92009-12-03 07:45:05 +00001461 platform_driver_unregister(&platform_driver);
Alan Jenkins1e779852009-08-28 12:56:35 +00001462}
1463
Eric Coopere59f8792008-03-13 12:55:46 +01001464module_init(eeepc_laptop_init);
1465module_exit(eeepc_laptop_exit);