blob: 935ec4404f08a8ca8c52df5ad4f6b6c23df4be57 [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 },
139 {KE_KEY, 0x1a, KEY_COFFEE },
140 {KE_KEY, 0x1b, KEY_ZOOM },
141 {KE_KEY, 0x1c, KEY_PROG2 },
142 {KE_KEY, 0x1d, KEY_PROG3 },
143 {KE_KEY, NOTIFY_BRN_MIN, KEY_BRIGHTNESSDOWN },
144 {KE_KEY, NOTIFY_BRN_MAX, KEY_BRIGHTNESSUP },
145 {KE_KEY, 0x30, KEY_SWITCHVIDEOMODE },
146 {KE_KEY, 0x31, KEY_SWITCHVIDEOMODE },
147 {KE_KEY, 0x32, KEY_SWITCHVIDEOMODE },
148 {KE_END, 0},
149};
Alan Jenkins463b4e42009-12-03 07:45:03 +0000150
Corentin Charye1faa9d2008-03-13 12:57:18 +0100151
Eric Coopere59f8792008-03-13 12:55:46 +0100152/*
153 * This is the main structure, we can use it to store useful information
Eric Coopere59f8792008-03-13 12:55:46 +0100154 */
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000155struct eeepc_laptop {
Alan Jenkins854c7832009-12-03 07:45:09 +0000156 acpi_handle handle; /* the handle of the acpi device */
Eric Coopere59f8792008-03-13 12:55:46 +0100157 u32 cm_supported; /* the control methods supported
158 by this BIOS */
Eric Coopere59f8792008-03-13 12:55:46 +0100159 u16 event_count[128]; /* count for each event */
Alan Jenkins854c7832009-12-03 07:45:09 +0000160
161 struct platform_device *platform_device;
162 struct device *hwmon_device;
163 struct backlight_device *backlight_device;
164
Matthew Garretta195dcd2008-08-19 12:13:20 +0100165 struct input_dev *inputdev;
Alan Jenkins854c7832009-12-03 07:45:09 +0000166 struct key_entry *keymap;
167
Corentin Chary7de39382009-06-25 13:25:38 +0200168 struct rfkill *wlan_rfkill;
169 struct rfkill *bluetooth_rfkill;
Corentin Chary3cd530b2009-06-25 13:25:42 +0200170 struct rfkill *wwan3g_rfkill;
Corentin Charyd1ec9c32009-08-28 12:56:41 +0000171 struct rfkill *wimax_rfkill;
Alan Jenkins854c7832009-12-03 07:45:09 +0000172
Corentin Chary2b121bc2009-06-25 13:25:36 +0200173 struct hotplug_slot *hotplug_slot;
Alan Jenkinsdcf443b2009-08-28 12:56:33 +0000174 struct mutex hotplug_lock;
Alan Jenkins854c7832009-12-03 07:45:09 +0000175
176 struct led_classdev tpd_led;
177 int tpd_led_wk;
178 struct workqueue_struct *led_workqueue;
179 struct work_struct tpd_led_work;
Eric Coopere59f8792008-03-13 12:55:46 +0100180};
181
Eric Coopere59f8792008-03-13 12:55:46 +0100182/*
183 * ACPI Helpers
184 */
Alan Jenkins6b188a72009-12-03 07:45:02 +0000185static int write_acpi_int(acpi_handle handle, const char *method, int val)
Eric Coopere59f8792008-03-13 12:55:46 +0100186{
187 struct acpi_object_list params;
188 union acpi_object in_obj;
189 acpi_status status;
190
191 params.count = 1;
192 params.pointer = &in_obj;
193 in_obj.type = ACPI_TYPE_INTEGER;
194 in_obj.integer.value = val;
195
Alan Jenkins6b188a72009-12-03 07:45:02 +0000196 status = acpi_evaluate_object(handle, (char *)method, &params, NULL);
Eric Coopere59f8792008-03-13 12:55:46 +0100197 return (status == AE_OK ? 0 : -1);
198}
199
200static int read_acpi_int(acpi_handle handle, const char *method, int *val)
201{
202 acpi_status status;
Matthew Wilcox27663c52008-10-10 02:22:59 -0400203 unsigned long long result;
Eric Coopere59f8792008-03-13 12:55:46 +0100204
205 status = acpi_evaluate_integer(handle, (char *)method, NULL, &result);
206 if (ACPI_FAILURE(status)) {
207 *val = -1;
208 return -1;
209 } else {
210 *val = result;
211 return 0;
212 }
213}
214
Alan Jenkins854c7832009-12-03 07:45:09 +0000215static int set_acpi(struct eeepc_laptop *eeepc, int cm, int value)
Eric Coopere59f8792008-03-13 12:55:46 +0100216{
Alan Jenkins13f70022009-12-03 07:44:59 +0000217 const char *method = cm_setv[cm];
218
219 if (method == NULL)
220 return -ENODEV;
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000221 if ((eeepc->cm_supported & (0x1 << cm)) == 0)
Alan Jenkins13f70022009-12-03 07:44:59 +0000222 return -ENODEV;
223
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000224 if (write_acpi_int(eeepc->handle, method, value))
Alan Jenkins13f70022009-12-03 07:44:59 +0000225 pr_warning("Error writing %s\n", method);
Eric Coopere59f8792008-03-13 12:55:46 +0100226 return 0;
227}
228
Alan Jenkins854c7832009-12-03 07:45:09 +0000229static int get_acpi(struct eeepc_laptop *eeepc, int cm)
Eric Coopere59f8792008-03-13 12:55:46 +0100230{
Alan Jenkins13f70022009-12-03 07:44:59 +0000231 const char *method = cm_getv[cm];
232 int value;
233
234 if (method == NULL)
235 return -ENODEV;
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000236 if ((eeepc->cm_supported & (0x1 << cm)) == 0)
Alan Jenkins13f70022009-12-03 07:44:59 +0000237 return -ENODEV;
238
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000239 if (read_acpi_int(eeepc->handle, method, &value))
Alan Jenkins13f70022009-12-03 07:44:59 +0000240 pr_warning("Error reading %s\n", method);
Eric Coopere59f8792008-03-13 12:55:46 +0100241 return value;
242}
243
Alan Jenkins854c7832009-12-03 07:45:09 +0000244static int acpi_setter_handle(struct eeepc_laptop *eeepc, int cm, acpi_handle *handle)
245{
246 const char *method = cm_setv[cm];
247 acpi_status status;
248
249 if (method == NULL)
250 return -ENODEV;
251 if ((eeepc->cm_supported & (0x1 << cm)) == 0)
252 return -ENODEV;
253
254 status = acpi_get_handle(eeepc->handle, (char *)method,
255 handle);
256 if (status != AE_OK) {
257 pr_warning("Error finding %s\n", method);
258 return -ENODEV;
259 }
260 return 0;
261}
262
263
Eric Coopere59f8792008-03-13 12:55:46 +0100264/*
265 * Sys helpers
266 */
267static int parse_arg(const char *buf, unsigned long count, int *val)
268{
269 if (!count)
270 return 0;
271 if (sscanf(buf, "%i", val) != 1)
272 return -EINVAL;
273 return count;
274}
275
Alan Jenkins854c7832009-12-03 07:45:09 +0000276static ssize_t store_sys_acpi(struct device *dev, int cm,
277 const char *buf, size_t count)
Eric Coopere59f8792008-03-13 12:55:46 +0100278{
Alan Jenkins854c7832009-12-03 07:45:09 +0000279 struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
Eric Coopere59f8792008-03-13 12:55:46 +0100280 int rv, value;
281
282 rv = parse_arg(buf, count, &value);
283 if (rv > 0)
Alan Jenkins854c7832009-12-03 07:45:09 +0000284 value = set_acpi(eeepc, cm, value);
Corentin Charyf36509e2009-06-25 13:25:40 +0200285 if (value < 0)
Alan Jenkins6dff29b2009-12-03 07:44:45 +0000286 return -EIO;
Eric Coopere59f8792008-03-13 12:55:46 +0100287 return rv;
288}
289
Alan Jenkins854c7832009-12-03 07:45:09 +0000290static ssize_t show_sys_acpi(struct device *dev, int cm, char *buf)
Eric Coopere59f8792008-03-13 12:55:46 +0100291{
Alan Jenkins854c7832009-12-03 07:45:09 +0000292 struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
293 int value = get_acpi(eeepc, cm);
Corentin Charyf36509e2009-06-25 13:25:40 +0200294
295 if (value < 0)
Alan Jenkins6dff29b2009-12-03 07:44:45 +0000296 return -EIO;
Corentin Charyf36509e2009-06-25 13:25:40 +0200297 return sprintf(buf, "%d\n", value);
Eric Coopere59f8792008-03-13 12:55:46 +0100298}
299
Alan Jenkins6dff29b2009-12-03 07:44:45 +0000300#define EEEPC_CREATE_DEVICE_ATTR(_name, _mode, _cm) \
Eric Coopere59f8792008-03-13 12:55:46 +0100301 static ssize_t show_##_name(struct device *dev, \
302 struct device_attribute *attr, \
303 char *buf) \
304 { \
Alan Jenkins854c7832009-12-03 07:45:09 +0000305 return show_sys_acpi(dev, _cm, buf); \
Eric Coopere59f8792008-03-13 12:55:46 +0100306 } \
307 static ssize_t store_##_name(struct device *dev, \
308 struct device_attribute *attr, \
309 const char *buf, size_t count) \
310 { \
Alan Jenkins854c7832009-12-03 07:45:09 +0000311 return store_sys_acpi(dev, _cm, buf, count); \
Eric Coopere59f8792008-03-13 12:55:46 +0100312 } \
313 static struct device_attribute dev_attr_##_name = { \
314 .attr = { \
315 .name = __stringify(_name), \
Alan Jenkins6dff29b2009-12-03 07:44:45 +0000316 .mode = _mode }, \
Eric Coopere59f8792008-03-13 12:55:46 +0100317 .show = show_##_name, \
318 .store = store_##_name, \
319 }
320
Alan Jenkins6dff29b2009-12-03 07:44:45 +0000321EEEPC_CREATE_DEVICE_ATTR(camera, 0644, CM_ASL_CAMERA);
322EEEPC_CREATE_DEVICE_ATTR(cardr, 0644, CM_ASL_CARDREADER);
323EEEPC_CREATE_DEVICE_ATTR(disp, 0200, CM_ASL_DISPLAYSWITCH);
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000324
325struct eeepc_cpufv {
326 int num;
327 int cur;
328};
329
Alan Jenkins854c7832009-12-03 07:45:09 +0000330static int get_cpufv(struct eeepc_laptop *eeepc, struct eeepc_cpufv *c)
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000331{
Alan Jenkins854c7832009-12-03 07:45:09 +0000332 c->cur = get_acpi(eeepc, CM_ASL_CPUFV);
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000333 c->num = (c->cur >> 8) & 0xff;
334 c->cur &= 0xff;
335 if (c->cur < 0 || c->num <= 0 || c->num > 12)
336 return -ENODEV;
337 return 0;
338}
339
340static ssize_t show_available_cpufv(struct device *dev,
341 struct device_attribute *attr,
342 char *buf)
343{
Alan Jenkins854c7832009-12-03 07:45:09 +0000344 struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000345 struct eeepc_cpufv c;
346 int i;
347 ssize_t len = 0;
348
Alan Jenkins854c7832009-12-03 07:45:09 +0000349 if (get_cpufv(eeepc, &c))
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000350 return -ENODEV;
351 for (i = 0; i < c.num; i++)
352 len += sprintf(buf + len, "%d ", i);
353 len += sprintf(buf + len, "\n");
354 return len;
355}
356
357static ssize_t show_cpufv(struct device *dev,
358 struct device_attribute *attr,
359 char *buf)
360{
Alan Jenkins854c7832009-12-03 07:45:09 +0000361 struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000362 struct eeepc_cpufv c;
363
Alan Jenkins854c7832009-12-03 07:45:09 +0000364 if (get_cpufv(eeepc, &c))
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000365 return -ENODEV;
366 return sprintf(buf, "%#x\n", (c.num << 8) | c.cur);
367}
368
369static ssize_t store_cpufv(struct device *dev,
370 struct device_attribute *attr,
371 const char *buf, size_t count)
372{
Alan Jenkins854c7832009-12-03 07:45:09 +0000373 struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000374 struct eeepc_cpufv c;
375 int rv, value;
376
Alan Jenkins854c7832009-12-03 07:45:09 +0000377 if (get_cpufv(eeepc, &c))
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000378 return -ENODEV;
379 rv = parse_arg(buf, count, &value);
380 if (rv < 0)
381 return rv;
382 if (!rv || value < 0 || value >= c.num)
383 return -EINVAL;
Alan Jenkins854c7832009-12-03 07:45:09 +0000384 set_acpi(eeepc, CM_ASL_CPUFV, value);
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000385 return rv;
386}
387
388static struct device_attribute dev_attr_cpufv = {
389 .attr = {
390 .name = "cpufv",
391 .mode = 0644 },
392 .show = show_cpufv,
393 .store = store_cpufv
394};
395
396static struct device_attribute dev_attr_available_cpufv = {
397 .attr = {
398 .name = "available_cpufv",
399 .mode = 0444 },
400 .show = show_available_cpufv
401};
Eric Coopere59f8792008-03-13 12:55:46 +0100402
403static struct attribute *platform_attributes[] = {
404 &dev_attr_camera.attr,
405 &dev_attr_cardr.attr,
406 &dev_attr_disp.attr,
Grigori Goronzy158ca1d2009-04-27 09:23:40 +0200407 &dev_attr_cpufv.attr,
Corentin Charyb31d0fd2009-06-16 19:28:56 +0000408 &dev_attr_available_cpufv.attr,
Eric Coopere59f8792008-03-13 12:55:46 +0100409 NULL
410};
411
412static struct attribute_group platform_attribute_group = {
413 .attrs = platform_attributes
414};
415
Alan Jenkins854c7832009-12-03 07:45:09 +0000416static int eeepc_platform_init(struct eeepc_laptop *eeepc)
Alan Jenkins9db106b2009-12-03 07:45:06 +0000417{
418 int result;
419
Alan Jenkins854c7832009-12-03 07:45:09 +0000420 eeepc->platform_device = platform_device_alloc(EEEPC_LAPTOP_FILE, -1);
421 if (!eeepc->platform_device)
Alan Jenkins9db106b2009-12-03 07:45:06 +0000422 return -ENOMEM;
Alan Jenkins854c7832009-12-03 07:45:09 +0000423 platform_set_drvdata(eeepc->platform_device, eeepc);
Alan Jenkins9db106b2009-12-03 07:45:06 +0000424
Alan Jenkins854c7832009-12-03 07:45:09 +0000425 result = platform_device_add(eeepc->platform_device);
Alan Jenkins9db106b2009-12-03 07:45:06 +0000426 if (result)
427 goto fail_platform_device;
428
Alan Jenkins854c7832009-12-03 07:45:09 +0000429 result = sysfs_create_group(&eeepc->platform_device->dev.kobj,
Alan Jenkins9db106b2009-12-03 07:45:06 +0000430 &platform_attribute_group);
431 if (result)
432 goto fail_sysfs;
433 return 0;
434
435fail_sysfs:
Alan Jenkins854c7832009-12-03 07:45:09 +0000436 platform_device_del(eeepc->platform_device);
Alan Jenkins9db106b2009-12-03 07:45:06 +0000437fail_platform_device:
Alan Jenkins854c7832009-12-03 07:45:09 +0000438 platform_device_put(eeepc->platform_device);
Alan Jenkins9db106b2009-12-03 07:45:06 +0000439 return result;
440}
441
Alan Jenkins854c7832009-12-03 07:45:09 +0000442static void eeepc_platform_exit(struct eeepc_laptop *eeepc)
Alan Jenkins9db106b2009-12-03 07:45:06 +0000443{
Alan Jenkins854c7832009-12-03 07:45:09 +0000444 sysfs_remove_group(&eeepc->platform_device->dev.kobj,
Alan Jenkins9db106b2009-12-03 07:45:06 +0000445 &platform_attribute_group);
Alan Jenkins854c7832009-12-03 07:45:09 +0000446 platform_device_unregister(eeepc->platform_device);
Alan Jenkins9db106b2009-12-03 07:45:06 +0000447}
448
Eric Coopere59f8792008-03-13 12:55:46 +0100449/*
Corentin Chary3c0eb512009-12-03 07:44:52 +0000450 * LEDs
451 */
452/*
453 * These functions actually update the LED's, and are called from a
454 * workqueue. By doing this as separate work rather than when the LED
455 * subsystem asks, we avoid messing with the Asus ACPI stuff during a
456 * potentially bad time, such as a timer interrupt.
457 */
Alan Jenkins854c7832009-12-03 07:45:09 +0000458static void tpd_led_update(struct work_struct *work)
459 {
460 struct eeepc_laptop *eeepc;
Corentin Chary3c0eb512009-12-03 07:44:52 +0000461
Alan Jenkins854c7832009-12-03 07:45:09 +0000462 eeepc = container_of(work, struct eeepc_laptop, tpd_led_work);
463
464 set_acpi(eeepc, CM_ASL_TPD, eeepc->tpd_led_wk);
Corentin Chary3c0eb512009-12-03 07:44:52 +0000465}
466
Corentin Chary3c0eb512009-12-03 07:44:52 +0000467static void tpd_led_set(struct led_classdev *led_cdev,
468 enum led_brightness value)
469{
Alan Jenkins854c7832009-12-03 07:45:09 +0000470 struct eeepc_laptop *eeepc;
471
472 eeepc = container_of(led_cdev, struct eeepc_laptop, tpd_led);
473
474 eeepc->tpd_led_wk = (value > 0) ? 1 : 0;
475 queue_work(eeepc->led_workqueue, &eeepc->tpd_led_work);
Corentin Chary3c0eb512009-12-03 07:44:52 +0000476}
477
Alan Jenkins854c7832009-12-03 07:45:09 +0000478static int eeepc_led_init(struct eeepc_laptop *eeepc)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000479{
480 int rv;
481
Alan Jenkins854c7832009-12-03 07:45:09 +0000482 if (get_acpi(eeepc, CM_ASL_TPD) == -ENODEV)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000483 return 0;
484
Alan Jenkins854c7832009-12-03 07:45:09 +0000485 eeepc->led_workqueue = create_singlethread_workqueue("led_workqueue");
486 if (!eeepc->led_workqueue)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000487 return -ENOMEM;
Alan Jenkins854c7832009-12-03 07:45:09 +0000488 INIT_WORK(&eeepc->tpd_led_work, tpd_led_update);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000489
Alan Jenkins854c7832009-12-03 07:45:09 +0000490 eeepc->tpd_led.name = "eeepc::touchpad";
491 eeepc->tpd_led.brightness_set = tpd_led_set;
492 eeepc->tpd_led.max_brightness = 1;
493
494 rv = led_classdev_register(&eeepc->platform_device->dev,
495 &eeepc->tpd_led);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000496 if (rv) {
Alan Jenkins854c7832009-12-03 07:45:09 +0000497 destroy_workqueue(eeepc->led_workqueue);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000498 return rv;
499 }
500
501 return 0;
502}
503
Alan Jenkins854c7832009-12-03 07:45:09 +0000504static void eeepc_led_exit(struct eeepc_laptop *eeepc)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000505{
Alan Jenkins854c7832009-12-03 07:45:09 +0000506 if (eeepc->tpd_led.dev)
507 led_classdev_unregister(&eeepc->tpd_led);
508 if (eeepc->led_workqueue)
509 destroy_workqueue(eeepc->led_workqueue);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000510}
511
512
Corentin Chary3c0eb512009-12-03 07:44:52 +0000513/*
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000514 * PCI hotplug (for wlan rfkill)
Eric Coopere59f8792008-03-13 12:55:46 +0100515 */
Alan Jenkins854c7832009-12-03 07:45:09 +0000516static bool eeepc_wlan_rfkill_blocked(struct eeepc_laptop *eeepc)
Matthew Garretta195dcd2008-08-19 12:13:20 +0100517{
Alan Jenkins854c7832009-12-03 07:45:09 +0000518 if (get_acpi(eeepc, CM_ASL_WLAN) == 1)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000519 return false;
520 return true;
Corentin Chary2b121bc2009-06-25 13:25:36 +0200521}
522
Alan Jenkins854c7832009-12-03 07:45:09 +0000523static void eeepc_rfkill_hotplug(struct eeepc_laptop *eeepc)
Matthew Garrett57402942009-01-20 16:17:48 +0100524{
525 struct pci_dev *dev;
Alan Jenkins6d418392009-08-28 12:56:32 +0000526 struct pci_bus *bus;
Alan Jenkins854c7832009-12-03 07:45:09 +0000527 bool blocked = eeepc_wlan_rfkill_blocked(eeepc);
Matthew Garrett57402942009-01-20 16:17:48 +0100528
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000529 if (eeepc->wlan_rfkill)
530 rfkill_set_sw_state(eeepc->wlan_rfkill, blocked);
Alan Jenkins6d418392009-08-28 12:56:32 +0000531
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000532 mutex_lock(&eeepc->hotplug_lock);
Alan Jenkinsdcf443b2009-08-28 12:56:33 +0000533
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000534 if (eeepc->hotplug_slot) {
Alan Jenkins07e84aa2009-08-28 12:56:34 +0000535 bus = pci_find_bus(0, 1);
536 if (!bus) {
537 pr_warning("Unable to find PCI bus 1?\n");
Alan Jenkinsdcf443b2009-08-28 12:56:33 +0000538 goto out_unlock;
Matthew Garrett57402942009-01-20 16:17:48 +0100539 }
Alan Jenkins07e84aa2009-08-28 12:56:34 +0000540
541 if (!blocked) {
542 dev = pci_get_slot(bus, 0);
543 if (dev) {
544 /* Device already present */
545 pci_dev_put(dev);
546 goto out_unlock;
547 }
548 dev = pci_scan_single_device(bus, 0);
549 if (dev) {
550 pci_bus_assign_resources(bus);
551 if (pci_bus_add_device(dev))
552 pr_err("Unable to hotplug wifi\n");
553 }
554 } else {
555 dev = pci_get_slot(bus, 0);
556 if (dev) {
557 pci_remove_bus_device(dev);
558 pci_dev_put(dev);
559 }
Matthew Garrett57402942009-01-20 16:17:48 +0100560 }
561 }
Alan Jenkinsdcf443b2009-08-28 12:56:33 +0000562
563out_unlock:
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000564 mutex_unlock(&eeepc->hotplug_lock);
Matthew Garrett57402942009-01-20 16:17:48 +0100565}
566
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100567static void eeepc_rfkill_notify(acpi_handle handle, u32 event, void *data)
568{
Alan Jenkins854c7832009-12-03 07:45:09 +0000569 struct eeepc_laptop *eeepc = data;
570
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100571 if (event != ACPI_NOTIFY_BUS_CHECK)
572 return;
573
Alan Jenkins854c7832009-12-03 07:45:09 +0000574 eeepc_rfkill_hotplug(eeepc);
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100575}
576
Alan Jenkins854c7832009-12-03 07:45:09 +0000577static int eeepc_register_rfkill_notifier(struct eeepc_laptop *eeepc,
578 char *node)
Matthew Garrett57402942009-01-20 16:17:48 +0100579{
Alan Jenkins854c7832009-12-03 07:45:09 +0000580 acpi_status status;
Matthew Garrett57402942009-01-20 16:17:48 +0100581 acpi_handle handle;
582
583 status = acpi_get_handle(NULL, node, &handle);
584
585 if (ACPI_SUCCESS(status)) {
586 status = acpi_install_notify_handler(handle,
587 ACPI_SYSTEM_NOTIFY,
588 eeepc_rfkill_notify,
Alan Jenkins854c7832009-12-03 07:45:09 +0000589 eeepc);
Matthew Garrett57402942009-01-20 16:17:48 +0100590 if (ACPI_FAILURE(status))
Joe Perches19b53282009-06-25 13:25:37 +0200591 pr_warning("Failed to register notify on %s\n", node);
Matthew Garrett57402942009-01-20 16:17:48 +0100592 } else
593 return -ENODEV;
594
595 return 0;
596}
597
Alan Jenkins854c7832009-12-03 07:45:09 +0000598static void eeepc_unregister_rfkill_notifier(struct eeepc_laptop *eeepc,
599 char *node)
Matthew Garrett57402942009-01-20 16:17:48 +0100600{
601 acpi_status status = AE_OK;
602 acpi_handle handle;
603
604 status = acpi_get_handle(NULL, node, &handle);
605
606 if (ACPI_SUCCESS(status)) {
607 status = acpi_remove_notify_handler(handle,
608 ACPI_SYSTEM_NOTIFY,
609 eeepc_rfkill_notify);
610 if (ACPI_FAILURE(status))
Joe Perches19b53282009-06-25 13:25:37 +0200611 pr_err("Error removing rfkill notify handler %s\n",
Matthew Garrett57402942009-01-20 16:17:48 +0100612 node);
613 }
614}
615
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000616static int eeepc_get_adapter_status(struct hotplug_slot *hotplug_slot,
617 u8 *value)
618{
Alan Jenkins854c7832009-12-03 07:45:09 +0000619 struct eeepc_laptop *eeepc = hotplug_slot->private;
620 int val = get_acpi(eeepc, CM_ASL_WLAN);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000621
622 if (val == 1 || val == 0)
623 *value = val;
624 else
625 return -EINVAL;
626
627 return 0;
628}
629
Corentin Chary2b121bc2009-06-25 13:25:36 +0200630static void eeepc_cleanup_pci_hotplug(struct hotplug_slot *hotplug_slot)
631{
632 kfree(hotplug_slot->info);
633 kfree(hotplug_slot);
634}
635
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000636static struct hotplug_slot_ops eeepc_hotplug_slot_ops = {
637 .owner = THIS_MODULE,
638 .get_adapter_status = eeepc_get_adapter_status,
639 .get_power_status = eeepc_get_adapter_status,
640};
641
Alan Jenkins854c7832009-12-03 07:45:09 +0000642static int eeepc_setup_pci_hotplug(struct eeepc_laptop *eeepc)
Corentin Chary2b121bc2009-06-25 13:25:36 +0200643{
644 int ret = -ENOMEM;
645 struct pci_bus *bus = pci_find_bus(0, 1);
646
647 if (!bus) {
Joe Perches19b53282009-06-25 13:25:37 +0200648 pr_err("Unable to find wifi PCI bus\n");
Corentin Chary2b121bc2009-06-25 13:25:36 +0200649 return -ENODEV;
650 }
651
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000652 eeepc->hotplug_slot = kzalloc(sizeof(struct hotplug_slot), GFP_KERNEL);
653 if (!eeepc->hotplug_slot)
Corentin Chary2b121bc2009-06-25 13:25:36 +0200654 goto error_slot;
655
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000656 eeepc->hotplug_slot->info = kzalloc(sizeof(struct hotplug_slot_info),
Corentin Chary2b121bc2009-06-25 13:25:36 +0200657 GFP_KERNEL);
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000658 if (!eeepc->hotplug_slot->info)
Corentin Chary2b121bc2009-06-25 13:25:36 +0200659 goto error_info;
660
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000661 eeepc->hotplug_slot->private = eeepc;
662 eeepc->hotplug_slot->release = &eeepc_cleanup_pci_hotplug;
663 eeepc->hotplug_slot->ops = &eeepc_hotplug_slot_ops;
664 eeepc_get_adapter_status(eeepc->hotplug_slot,
665 &eeepc->hotplug_slot->info->adapter_status);
Corentin Chary2b121bc2009-06-25 13:25:36 +0200666
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000667 ret = pci_hp_register(eeepc->hotplug_slot, bus, 0, "eeepc-wifi");
Corentin Chary2b121bc2009-06-25 13:25:36 +0200668 if (ret) {
Joe Perches19b53282009-06-25 13:25:37 +0200669 pr_err("Unable to register hotplug slot - %d\n", ret);
Corentin Chary2b121bc2009-06-25 13:25:36 +0200670 goto error_register;
671 }
672
673 return 0;
674
675error_register:
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000676 kfree(eeepc->hotplug_slot->info);
Corentin Chary2b121bc2009-06-25 13:25:36 +0200677error_info:
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000678 kfree(eeepc->hotplug_slot);
679 eeepc->hotplug_slot = NULL;
Corentin Chary2b121bc2009-06-25 13:25:36 +0200680error_slot:
681 return ret;
682}
683
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000684/*
685 * Rfkill devices
686 */
687static int eeepc_rfkill_set(void *data, bool blocked)
688{
Alan Jenkins854c7832009-12-03 07:45:09 +0000689 acpi_handle handle = data;
690
691 return write_acpi_int(handle, NULL, !blocked);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000692}
693
694static const struct rfkill_ops eeepc_rfkill_ops = {
695 .set_block = eeepc_rfkill_set,
696};
697
Alan Jenkins854c7832009-12-03 07:45:09 +0000698static int eeepc_new_rfkill(struct eeepc_laptop *eeepc,
699 struct rfkill **rfkill,
700 const char *name,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000701 enum rfkill_type type, int cm)
702{
Alan Jenkins854c7832009-12-03 07:45:09 +0000703 acpi_handle handle;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000704 int result;
705
Alan Jenkins854c7832009-12-03 07:45:09 +0000706 result = acpi_setter_handle(eeepc, cm, &handle);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000707 if (result < 0)
708 return result;
709
Alan Jenkins854c7832009-12-03 07:45:09 +0000710 *rfkill = rfkill_alloc(name, &eeepc->platform_device->dev, type,
711 &eeepc_rfkill_ops, handle);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000712
713 if (!*rfkill)
714 return -EINVAL;
715
Alan Jenkins854c7832009-12-03 07:45:09 +0000716 rfkill_init_sw_state(*rfkill, get_acpi(eeepc, cm) != 1);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000717 result = rfkill_register(*rfkill);
718 if (result) {
719 rfkill_destroy(*rfkill);
720 *rfkill = NULL;
721 return result;
722 }
723 return 0;
724}
725
Alan Jenkins854c7832009-12-03 07:45:09 +0000726static void eeepc_rfkill_exit(struct eeepc_laptop *eeepc)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000727{
Alan Jenkins854c7832009-12-03 07:45:09 +0000728 eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P5");
729 eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P6");
730 eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P7");
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000731 if (eeepc->wlan_rfkill) {
732 rfkill_unregister(eeepc->wlan_rfkill);
733 rfkill_destroy(eeepc->wlan_rfkill);
734 eeepc->wlan_rfkill = NULL;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000735 }
736 /*
737 * Refresh pci hotplug in case the rfkill state was changed after
738 * eeepc_unregister_rfkill_notifier()
739 */
Alan Jenkins854c7832009-12-03 07:45:09 +0000740 eeepc_rfkill_hotplug(eeepc);
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000741 if (eeepc->hotplug_slot)
742 pci_hp_deregister(eeepc->hotplug_slot);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000743
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000744 if (eeepc->bluetooth_rfkill) {
745 rfkill_unregister(eeepc->bluetooth_rfkill);
746 rfkill_destroy(eeepc->bluetooth_rfkill);
747 eeepc->bluetooth_rfkill = NULL;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000748 }
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000749 if (eeepc->wwan3g_rfkill) {
750 rfkill_unregister(eeepc->wwan3g_rfkill);
751 rfkill_destroy(eeepc->wwan3g_rfkill);
752 eeepc->wwan3g_rfkill = NULL;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000753 }
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000754 if (eeepc->wimax_rfkill) {
755 rfkill_unregister(eeepc->wimax_rfkill);
756 rfkill_destroy(eeepc->wimax_rfkill);
757 eeepc->wimax_rfkill = NULL;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000758 }
759}
760
Alan Jenkins854c7832009-12-03 07:45:09 +0000761static int eeepc_rfkill_init(struct eeepc_laptop *eeepc)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000762{
763 int result = 0;
764
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000765 mutex_init(&eeepc->hotplug_lock);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000766
Alan Jenkins854c7832009-12-03 07:45:09 +0000767 result = eeepc_new_rfkill(eeepc, &eeepc->wlan_rfkill,
768 "eeepc-wlan", RFKILL_TYPE_WLAN,
769 CM_ASL_WLAN);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000770
771 if (result && result != -ENODEV)
772 goto exit;
773
Alan Jenkins854c7832009-12-03 07:45:09 +0000774 result = eeepc_new_rfkill(eeepc, &eeepc->bluetooth_rfkill,
775 "eeepc-bluetooth", RFKILL_TYPE_BLUETOOTH,
776 CM_ASL_BLUETOOTH);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000777
778 if (result && result != -ENODEV)
779 goto exit;
780
Alan Jenkins854c7832009-12-03 07:45:09 +0000781 result = eeepc_new_rfkill(eeepc, &eeepc->wwan3g_rfkill,
782 "eeepc-wwan3g", RFKILL_TYPE_WWAN,
783 CM_ASL_3G);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000784
785 if (result && result != -ENODEV)
786 goto exit;
787
Alan Jenkins854c7832009-12-03 07:45:09 +0000788 result = eeepc_new_rfkill(eeepc, &eeepc->wimax_rfkill,
789 "eeepc-wimax", RFKILL_TYPE_WIMAX,
790 CM_ASL_WIMAX);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000791
792 if (result && result != -ENODEV)
793 goto exit;
794
Alan Jenkins854c7832009-12-03 07:45:09 +0000795 result = eeepc_setup_pci_hotplug(eeepc);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000796 /*
797 * If we get -EBUSY then something else is handling the PCI hotplug -
798 * don't fail in this case
799 */
800 if (result == -EBUSY)
801 result = 0;
802
Alan Jenkins854c7832009-12-03 07:45:09 +0000803 eeepc_register_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P5");
804 eeepc_register_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P6");
805 eeepc_register_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P7");
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000806 /*
807 * Refresh pci hotplug in case the rfkill state was changed during
808 * setup.
809 */
Alan Jenkins854c7832009-12-03 07:45:09 +0000810 eeepc_rfkill_hotplug(eeepc);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000811
812exit:
813 if (result && result != -ENODEV)
Alan Jenkins854c7832009-12-03 07:45:09 +0000814 eeepc_rfkill_exit(eeepc);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000815 return result;
816}
817
818/*
819 * Platform driver - hibernate/resume callbacks
820 */
Alan Jenkins854c7832009-12-03 07:45:09 +0000821static int eeepc_hotk_thaw(struct device *device)
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100822{
Alan Jenkins854c7832009-12-03 07:45:09 +0000823 struct eeepc_laptop *eeepc = dev_get_drvdata(device);
824
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000825 if (eeepc->wlan_rfkill) {
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100826 bool wlan;
827
Alan Jenkinsc1edd992009-08-28 12:56:39 +0000828 /*
829 * Work around bios bug - acpi _PTS turns off the wireless led
830 * during suspend. Normally it restores it on resume, but
Alan Jenkinsc200da52009-08-28 12:56:40 +0000831 * we should kick it ourselves in case hibernation is aborted.
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100832 */
Alan Jenkins854c7832009-12-03 07:45:09 +0000833 wlan = get_acpi(eeepc, CM_ASL_WLAN);
834 set_acpi(eeepc, CM_ASL_WLAN, wlan);
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100835 }
836
Alan Jenkinsc200da52009-08-28 12:56:40 +0000837 return 0;
838}
839
Alan Jenkins854c7832009-12-03 07:45:09 +0000840static int eeepc_hotk_restore(struct device *device)
Alan Jenkinsc200da52009-08-28 12:56:40 +0000841{
Alan Jenkins854c7832009-12-03 07:45:09 +0000842 struct eeepc_laptop *eeepc = dev_get_drvdata(device);
843
Alan Jenkinsc200da52009-08-28 12:56:40 +0000844 /* Refresh both wlan rfkill state and pci hotplug */
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000845 if (eeepc->wlan_rfkill)
Alan Jenkins854c7832009-12-03 07:45:09 +0000846 eeepc_rfkill_hotplug(eeepc);
Alan Jenkinsc200da52009-08-28 12:56:40 +0000847
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000848 if (eeepc->bluetooth_rfkill)
849 rfkill_set_sw_state(eeepc->bluetooth_rfkill,
Alan Jenkins854c7832009-12-03 07:45:09 +0000850 get_acpi(eeepc, CM_ASL_BLUETOOTH) != 1);
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000851 if (eeepc->wwan3g_rfkill)
852 rfkill_set_sw_state(eeepc->wwan3g_rfkill,
Alan Jenkins854c7832009-12-03 07:45:09 +0000853 get_acpi(eeepc, CM_ASL_3G) != 1);
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000854 if (eeepc->wimax_rfkill)
855 rfkill_set_sw_state(eeepc->wimax_rfkill,
Alan Jenkins854c7832009-12-03 07:45:09 +0000856 get_acpi(eeepc, CM_ASL_WIMAX) != 1);
Alan Jenkins96e9cfe2009-06-16 14:53:52 +0100857
858 return 0;
859}
860
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000861static struct dev_pm_ops eeepc_pm_ops = {
Alan Jenkins854c7832009-12-03 07:45:09 +0000862 .thaw = eeepc_hotk_thaw,
863 .restore = eeepc_hotk_restore,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000864};
865
866static struct platform_driver platform_driver = {
867 .driver = {
Alan Jenkinsa7624b62009-12-03 07:45:08 +0000868 .name = EEEPC_LAPTOP_FILE,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000869 .owner = THIS_MODULE,
870 .pm = &eeepc_pm_ops,
871 }
872};
873
Eric Coopere59f8792008-03-13 12:55:46 +0100874/*
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000875 * Hwmon device
Corentin Charye1faa9d2008-03-13 12:57:18 +0100876 */
Alan Jenkins52bbe3c2009-12-03 07:45:07 +0000877
878#define EEEPC_EC_SC00 0x61
879#define EEEPC_EC_FAN_PWM (EEEPC_EC_SC00 + 2) /* Fan PWM duty cycle (%) */
880#define EEEPC_EC_FAN_HRPM (EEEPC_EC_SC00 + 5) /* High byte, fan speed (RPM) */
881#define EEEPC_EC_FAN_LRPM (EEEPC_EC_SC00 + 6) /* Low byte, fan speed (RPM) */
882
883#define EEEPC_EC_SFB0 0xD0
884#define EEEPC_EC_FAN_CTRL (EEEPC_EC_SFB0 + 3) /* Byte containing SF25 */
885
Corentin Charye1faa9d2008-03-13 12:57:18 +0100886static int eeepc_get_fan_pwm(void)
887{
Alan Jenkins463b4e42009-12-03 07:45:03 +0000888 u8 value = 0;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100889
Alan Jenkins463b4e42009-12-03 07:45:03 +0000890 ec_read(EEEPC_EC_FAN_PWM, &value);
891 return value * 255 / 100;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100892}
893
894static void eeepc_set_fan_pwm(int value)
895{
Corentin Chary04dcd842008-10-09 15:33:57 +0200896 value = SENSORS_LIMIT(value, 0, 255);
897 value = value * 100 / 255;
Alan Jenkins463b4e42009-12-03 07:45:03 +0000898 ec_write(EEEPC_EC_FAN_PWM, value);
Corentin Charye1faa9d2008-03-13 12:57:18 +0100899}
900
901static int eeepc_get_fan_rpm(void)
902{
Alan Jenkins463b4e42009-12-03 07:45:03 +0000903 u8 high = 0;
904 u8 low = 0;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100905
Alan Jenkins463b4e42009-12-03 07:45:03 +0000906 ec_read(EEEPC_EC_FAN_HRPM, &high);
907 ec_read(EEEPC_EC_FAN_LRPM, &low);
908 return high << 8 | low;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100909}
910
911static int eeepc_get_fan_ctrl(void)
912{
Alan Jenkins463b4e42009-12-03 07:45:03 +0000913 u8 value = 0;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100914
Alan Jenkins463b4e42009-12-03 07:45:03 +0000915 ec_read(EEEPC_EC_FAN_CTRL, &value);
Alan Jenkins48718682009-12-03 07:44:56 +0000916 if (value & 0x02)
917 return 1; /* manual */
918 else
919 return 2; /* automatic */
Corentin Charye1faa9d2008-03-13 12:57:18 +0100920}
921
922static void eeepc_set_fan_ctrl(int manual)
923{
Alan Jenkins463b4e42009-12-03 07:45:03 +0000924 u8 value = 0;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100925
Alan Jenkins463b4e42009-12-03 07:45:03 +0000926 ec_read(EEEPC_EC_FAN_CTRL, &value);
Alan Jenkins48718682009-12-03 07:44:56 +0000927 if (manual == 1)
Corentin Charye1faa9d2008-03-13 12:57:18 +0100928 value |= 0x02;
929 else
930 value &= ~0x02;
Alan Jenkins463b4e42009-12-03 07:45:03 +0000931 ec_write(EEEPC_EC_FAN_CTRL, value);
Corentin Charye1faa9d2008-03-13 12:57:18 +0100932}
933
934static ssize_t store_sys_hwmon(void (*set)(int), const char *buf, size_t count)
935{
936 int rv, value;
937
938 rv = parse_arg(buf, count, &value);
939 if (rv > 0)
940 set(value);
941 return rv;
942}
943
944static ssize_t show_sys_hwmon(int (*get)(void), char *buf)
945{
946 return sprintf(buf, "%d\n", get());
947}
948
949#define EEEPC_CREATE_SENSOR_ATTR(_name, _mode, _set, _get) \
950 static ssize_t show_##_name(struct device *dev, \
951 struct device_attribute *attr, \
952 char *buf) \
953 { \
954 return show_sys_hwmon(_set, buf); \
955 } \
956 static ssize_t store_##_name(struct device *dev, \
957 struct device_attribute *attr, \
958 const char *buf, size_t count) \
959 { \
960 return store_sys_hwmon(_get, buf, count); \
961 } \
962 static SENSOR_DEVICE_ATTR(_name, _mode, show_##_name, store_##_name, 0);
963
964EEEPC_CREATE_SENSOR_ATTR(fan1_input, S_IRUGO, eeepc_get_fan_rpm, NULL);
Corentin Chary04dcd842008-10-09 15:33:57 +0200965EEEPC_CREATE_SENSOR_ATTR(pwm1, S_IRUGO | S_IWUSR,
Corentin Charye1faa9d2008-03-13 12:57:18 +0100966 eeepc_get_fan_pwm, eeepc_set_fan_pwm);
967EEEPC_CREATE_SENSOR_ATTR(pwm1_enable, S_IRUGO | S_IWUSR,
968 eeepc_get_fan_ctrl, eeepc_set_fan_ctrl);
969
Corentin Chary04dcd842008-10-09 15:33:57 +0200970static ssize_t
971show_name(struct device *dev, struct device_attribute *attr, char *buf)
972{
973 return sprintf(buf, "eeepc\n");
974}
975static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0);
976
Corentin Charye1faa9d2008-03-13 12:57:18 +0100977static struct attribute *hwmon_attributes[] = {
Corentin Chary04dcd842008-10-09 15:33:57 +0200978 &sensor_dev_attr_pwm1.dev_attr.attr,
Corentin Charye1faa9d2008-03-13 12:57:18 +0100979 &sensor_dev_attr_fan1_input.dev_attr.attr,
980 &sensor_dev_attr_pwm1_enable.dev_attr.attr,
Corentin Chary04dcd842008-10-09 15:33:57 +0200981 &sensor_dev_attr_name.dev_attr.attr,
Corentin Charye1faa9d2008-03-13 12:57:18 +0100982 NULL
983};
984
985static struct attribute_group hwmon_attribute_group = {
986 .attrs = hwmon_attributes
987};
988
Alan Jenkins854c7832009-12-03 07:45:09 +0000989static void eeepc_hwmon_exit(struct eeepc_laptop *eeepc)
Corentin Charye1faa9d2008-03-13 12:57:18 +0100990{
991 struct device *hwmon;
992
Alan Jenkins854c7832009-12-03 07:45:09 +0000993 hwmon = eeepc->hwmon_device;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100994 if (!hwmon)
Alan Jenkins854c7832009-12-03 07:45:09 +0000995 return;
Corentin Charye1faa9d2008-03-13 12:57:18 +0100996 sysfs_remove_group(&hwmon->kobj,
997 &hwmon_attribute_group);
Matthew Garrettf1441312008-08-20 14:08:57 -0700998 hwmon_device_unregister(hwmon);
Alan Jenkins854c7832009-12-03 07:45:09 +0000999 eeepc->hwmon_device = NULL;
Corentin Charye1faa9d2008-03-13 12:57:18 +01001000}
1001
Alan Jenkins854c7832009-12-03 07:45:09 +00001002static int eeepc_hwmon_init(struct eeepc_laptop *eeepc)
Corentin Chary3c0eb512009-12-03 07:44:52 +00001003{
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001004 struct device *hwmon;
Corentin Chary7de39382009-06-25 13:25:38 +02001005 int result;
1006
Alan Jenkins854c7832009-12-03 07:45:09 +00001007 hwmon = hwmon_device_register(&eeepc->platform_device->dev);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001008 if (IS_ERR(hwmon)) {
1009 pr_err("Could not register eeepc hwmon device\n");
Alan Jenkins854c7832009-12-03 07:45:09 +00001010 eeepc->hwmon_device = NULL;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001011 return PTR_ERR(hwmon);
Corentin Chary7de39382009-06-25 13:25:38 +02001012 }
Alan Jenkins854c7832009-12-03 07:45:09 +00001013 eeepc->hwmon_device = hwmon;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001014 result = sysfs_create_group(&hwmon->kobj,
1015 &hwmon_attribute_group);
1016 if (result)
Alan Jenkins854c7832009-12-03 07:45:09 +00001017 eeepc_hwmon_exit(eeepc);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001018 return result;
Corentin Chary7de39382009-06-25 13:25:38 +02001019}
1020
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001021/*
1022 * Backlight device
1023 */
1024static int read_brightness(struct backlight_device *bd)
Corentin Chary7de39382009-06-25 13:25:38 +02001025{
Alan Jenkins854c7832009-12-03 07:45:09 +00001026 struct eeepc_laptop *eeepc = bl_get_data(bd);
1027
1028 return get_acpi(eeepc, CM_ASL_PANELBRIGHT);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001029}
Corentin Chary7de39382009-06-25 13:25:38 +02001030
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001031static int set_brightness(struct backlight_device *bd, int value)
1032{
Alan Jenkins854c7832009-12-03 07:45:09 +00001033 struct eeepc_laptop *eeepc = bl_get_data(bd);
1034
1035 return set_acpi(eeepc, CM_ASL_PANELBRIGHT, value);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001036}
Alan Jenkins73345462009-06-29 09:40:07 +01001037
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001038static int update_bl_status(struct backlight_device *bd)
1039{
1040 return set_brightness(bd, bd->props.brightness);
1041}
Corentin Chary7de39382009-06-25 13:25:38 +02001042
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001043static struct backlight_ops eeepcbl_ops = {
1044 .get_brightness = read_brightness,
1045 .update_status = update_bl_status,
1046};
Corentin Chary7de39382009-06-25 13:25:38 +02001047
Alan Jenkins854c7832009-12-03 07:45:09 +00001048static int eeepc_backlight_notify(struct eeepc_laptop *eeepc)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001049{
Alan Jenkins854c7832009-12-03 07:45:09 +00001050 struct backlight_device *bd = eeepc->backlight_device;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001051 int old = bd->props.brightness;
Corentin Chary7de39382009-06-25 13:25:38 +02001052
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001053 backlight_force_update(bd, BACKLIGHT_UPDATE_HOTKEY);
Corentin Chary7de39382009-06-25 13:25:38 +02001054
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001055 return old;
Corentin Chary7de39382009-06-25 13:25:38 +02001056}
1057
Alan Jenkins854c7832009-12-03 07:45:09 +00001058static int eeepc_backlight_init(struct eeepc_laptop *eeepc)
Corentin Charya5fa4292008-03-13 12:56:37 +01001059{
1060 struct backlight_device *bd;
1061
Alan Jenkins854c7832009-12-03 07:45:09 +00001062 bd = backlight_device_register(EEEPC_LAPTOP_FILE,
1063 &eeepc->platform_device->dev,
1064 eeepc, &eeepcbl_ops);
Corentin Charya5fa4292008-03-13 12:56:37 +01001065 if (IS_ERR(bd)) {
Joe Perches19b53282009-06-25 13:25:37 +02001066 pr_err("Could not register eeepc backlight device\n");
Alan Jenkins854c7832009-12-03 07:45:09 +00001067 eeepc->backlight_device = NULL;
Corentin Charya5fa4292008-03-13 12:56:37 +01001068 return PTR_ERR(bd);
1069 }
Alan Jenkins854c7832009-12-03 07:45:09 +00001070 eeepc->backlight_device = bd;
Corentin Charya5fa4292008-03-13 12:56:37 +01001071 bd->props.max_brightness = 15;
Alan Jenkins854c7832009-12-03 07:45:09 +00001072 bd->props.brightness = read_brightness(bd);
Corentin Charya5fa4292008-03-13 12:56:37 +01001073 bd->props.power = FB_BLANK_UNBLANK;
1074 backlight_update_status(bd);
1075 return 0;
1076}
1077
Alan Jenkins854c7832009-12-03 07:45:09 +00001078static void eeepc_backlight_exit(struct eeepc_laptop *eeepc)
Corentin Charye1faa9d2008-03-13 12:57:18 +01001079{
Alan Jenkins854c7832009-12-03 07:45:09 +00001080 if (eeepc->backlight_device)
1081 backlight_device_unregister(eeepc->backlight_device);
1082 eeepc->backlight_device = NULL;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001083}
Corentin Charye1faa9d2008-03-13 12:57:18 +01001084
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001085
1086/*
1087 * Input device (i.e. hotkeys)
1088 */
Alan Jenkins854c7832009-12-03 07:45:09 +00001089static struct key_entry *eeepc_get_entry_by_scancode(
1090 struct eeepc_laptop *eeepc,
1091 int code)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001092{
1093 struct key_entry *key;
1094
Alan Jenkins854c7832009-12-03 07:45:09 +00001095 for (key = eeepc->keymap; key->type != KE_END; key++)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001096 if (code == key->code)
1097 return key;
1098
1099 return NULL;
1100}
1101
Alan Jenkins854c7832009-12-03 07:45:09 +00001102static void eeepc_input_notify(struct eeepc_laptop *eeepc, int event)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001103{
1104 static struct key_entry *key;
1105
Alan Jenkins854c7832009-12-03 07:45:09 +00001106 key = eeepc_get_entry_by_scancode(eeepc, event);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001107 if (key) {
1108 switch (key->type) {
1109 case KE_KEY:
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001110 input_report_key(eeepc->inputdev, key->keycode,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001111 1);
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001112 input_sync(eeepc->inputdev);
1113 input_report_key(eeepc->inputdev, key->keycode,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001114 0);
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001115 input_sync(eeepc->inputdev);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001116 break;
1117 }
Corentin Charye1faa9d2008-03-13 12:57:18 +01001118 }
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001119}
1120
Alan Jenkins854c7832009-12-03 07:45:09 +00001121static struct key_entry *eeepc_get_entry_by_keycode(
1122 struct eeepc_laptop *eeepc, int code)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001123{
1124 struct key_entry *key;
1125
Alan Jenkins854c7832009-12-03 07:45:09 +00001126 for (key = eeepc->keymap; key->type != KE_END; key++)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001127 if (code == key->keycode && key->type == KE_KEY)
1128 return key;
1129
1130 return NULL;
1131}
1132
1133static int eeepc_getkeycode(struct input_dev *dev, int scancode, int *keycode)
1134{
Alan Jenkins854c7832009-12-03 07:45:09 +00001135 struct eeepc_laptop *eeepc = input_get_drvdata(dev);
1136 struct key_entry *key = eeepc_get_entry_by_scancode(eeepc, scancode);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001137
1138 if (key && key->type == KE_KEY) {
1139 *keycode = key->keycode;
1140 return 0;
1141 }
1142
1143 return -EINVAL;
1144}
1145
1146static int eeepc_setkeycode(struct input_dev *dev, int scancode, int keycode)
1147{
Alan Jenkins854c7832009-12-03 07:45:09 +00001148 struct eeepc_laptop *eeepc = input_get_drvdata(dev);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001149 struct key_entry *key;
1150 int old_keycode;
1151
1152 if (keycode < 0 || keycode > KEY_MAX)
1153 return -EINVAL;
1154
Alan Jenkins854c7832009-12-03 07:45:09 +00001155 key = eeepc_get_entry_by_scancode(eeepc, scancode);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001156 if (key && key->type == KE_KEY) {
1157 old_keycode = key->keycode;
1158 key->keycode = keycode;
1159 set_bit(keycode, dev->keybit);
Alan Jenkins854c7832009-12-03 07:45:09 +00001160 if (!eeepc_get_entry_by_keycode(eeepc, old_keycode))
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001161 clear_bit(old_keycode, dev->keybit);
1162 return 0;
1163 }
1164
1165 return -EINVAL;
Corentin Charye1faa9d2008-03-13 12:57:18 +01001166}
1167
Alan Jenkins854c7832009-12-03 07:45:09 +00001168static int eeepc_input_init(struct eeepc_laptop *eeepc)
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001169{
1170 const struct key_entry *key;
1171 int result;
1172
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001173 eeepc->inputdev = input_allocate_device();
1174 if (!eeepc->inputdev) {
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001175 pr_info("Unable to allocate input device\n");
1176 return -ENOMEM;
1177 }
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001178 eeepc->inputdev->name = "Asus EeePC extra buttons";
Alan Jenkins854c7832009-12-03 07:45:09 +00001179 eeepc->inputdev->dev.parent = &eeepc->platform_device->dev;
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001180 eeepc->inputdev->phys = EEEPC_LAPTOP_FILE "/input0";
1181 eeepc->inputdev->id.bustype = BUS_HOST;
1182 eeepc->inputdev->getkeycode = eeepc_getkeycode;
1183 eeepc->inputdev->setkeycode = eeepc_setkeycode;
Alan Jenkins854c7832009-12-03 07:45:09 +00001184 input_set_drvdata(eeepc->inputdev, eeepc);
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001185
Alan Jenkins854c7832009-12-03 07:45:09 +00001186 eeepc->keymap = kmemdup(eeepc_keymap, sizeof(eeepc_keymap),
1187 GFP_KERNEL);
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001188 for (key = eeepc_keymap; key->type != KE_END; key++) {
1189 switch (key->type) {
1190 case KE_KEY:
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001191 set_bit(EV_KEY, eeepc->inputdev->evbit);
1192 set_bit(key->keycode, eeepc->inputdev->keybit);
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001193 break;
1194 }
1195 }
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001196 result = input_register_device(eeepc->inputdev);
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001197 if (result) {
1198 pr_info("Unable to register input device\n");
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001199 input_free_device(eeepc->inputdev);
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001200 return result;
1201 }
1202 return 0;
1203}
1204
Alan Jenkins854c7832009-12-03 07:45:09 +00001205static void eeepc_input_exit(struct eeepc_laptop *eeepc)
Corentin Chary3c0eb512009-12-03 07:44:52 +00001206{
Alan Jenkins854c7832009-12-03 07:45:09 +00001207 if (eeepc->inputdev) {
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001208 input_unregister_device(eeepc->inputdev);
Alan Jenkins854c7832009-12-03 07:45:09 +00001209 kfree(eeepc->keymap);
1210 }
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001211}
Corentin Chary3c0eb512009-12-03 07:44:52 +00001212
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001213/*
1214 * ACPI driver
1215 */
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001216static void eeepc_acpi_notify(struct acpi_device *device, u32 event)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001217{
Alan Jenkins854c7832009-12-03 07:45:09 +00001218 struct eeepc_laptop *eeepc = acpi_driver_data(device);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001219 u16 count;
Corentin Chary3c0eb512009-12-03 07:44:52 +00001220
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001221 if (event > ACPI_MAX_SYS_NOTIFY)
1222 return;
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001223 count = eeepc->event_count[event % 128]++;
Alan Jenkins854c7832009-12-03 07:45:09 +00001224 acpi_bus_generate_proc_event(device, event, count);
1225 acpi_bus_generate_netlink_event(device->pnp.device_class,
1226 dev_name(&device->dev), event,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001227 count);
Corentin Chary3c0eb512009-12-03 07:44:52 +00001228
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001229 if (event >= NOTIFY_BRN_MIN && event <= NOTIFY_BRN_MAX) {
1230 int old_brightness, new_brightness;
1231
1232 /* Update backlight device. */
Alan Jenkins854c7832009-12-03 07:45:09 +00001233 old_brightness = eeepc_backlight_notify(eeepc);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001234
1235 /* Convert brightness event to keypress (obsolescent hack). */
1236 new_brightness = event - NOTIFY_BRN_MIN;
1237
1238 if (new_brightness < old_brightness) {
1239 event = NOTIFY_BRN_MIN; /* brightness down */
1240 } else if (new_brightness > old_brightness) {
1241 event = NOTIFY_BRN_MAX; /* brightness up */
1242 } else {
1243 /*
1244 * no change in brightness - already at min/max,
1245 * event will be desired value (or else ignored).
1246 */
1247 }
1248 }
Alan Jenkins854c7832009-12-03 07:45:09 +00001249 eeepc_input_notify(eeepc, event);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001250}
1251
Alan Jenkins854c7832009-12-03 07:45:09 +00001252static void cmsg_quirk(struct eeepc_laptop *eeepc, int cm, const char *name)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001253{
1254 int dummy;
1255
1256 /* Some BIOSes do not report cm although it is avaliable.
1257 Check if cm_getv[cm] works and, if yes, assume cm should be set. */
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001258 if (!(eeepc->cm_supported & (1 << cm))
1259 && !read_acpi_int(eeepc->handle, cm_getv[cm], &dummy)) {
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001260 pr_info("%s (%x) not reported by BIOS,"
1261 " enabling anyway\n", name, 1 << cm);
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001262 eeepc->cm_supported |= 1 << cm;
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001263 }
1264}
1265
Alan Jenkins854c7832009-12-03 07:45:09 +00001266static void cmsg_quirks(struct eeepc_laptop *eeepc)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001267{
Alan Jenkins854c7832009-12-03 07:45:09 +00001268 cmsg_quirk(eeepc, CM_ASL_LID, "LID");
1269 cmsg_quirk(eeepc, CM_ASL_TYPE, "TYPE");
1270 cmsg_quirk(eeepc, CM_ASL_PANELPOWER, "PANELPOWER");
1271 cmsg_quirk(eeepc, CM_ASL_TPD, "TPD");
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001272}
1273
Alan Jenkins854c7832009-12-03 07:45:09 +00001274static int eeepc_acpi_init(struct eeepc_laptop *eeepc, struct acpi_device *device)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001275{
1276 unsigned int init_flags;
1277 int result;
1278
Alan Jenkins854c7832009-12-03 07:45:09 +00001279 result = acpi_bus_get_status(device);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001280 if (result)
1281 return result;
Alan Jenkins854c7832009-12-03 07:45:09 +00001282 if (!device->status.present) {
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001283 pr_err("Hotkey device not present, aborting\n");
1284 return -ENODEV;
Alan Jenkinsdc56ad92009-12-03 07:44:58 +00001285 }
Alan Jenkins2b56f1c2009-12-03 07:44:57 +00001286
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001287 init_flags = DISABLE_ASL_WLAN | DISABLE_ASL_DISPLAYSWITCH;
1288 pr_notice("Hotkey init flags 0x%x\n", init_flags);
1289
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001290 if (write_acpi_int(eeepc->handle, "INIT", init_flags)) {
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001291 pr_err("Hotkey initialization failed\n");
1292 return -ENODEV;
1293 }
1294
1295 /* get control methods supported */
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001296 if (read_acpi_int(eeepc->handle, "CMSG", &eeepc->cm_supported)) {
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001297 pr_err("Get control methods supported failed\n");
1298 return -ENODEV;
1299 }
Alan Jenkins854c7832009-12-03 07:45:09 +00001300 cmsg_quirks(eeepc);
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001301 pr_info("Get control methods supported: 0x%x\n", eeepc->cm_supported);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001302
Corentin Chary3c0eb512009-12-03 07:44:52 +00001303 return 0;
1304}
1305
Alan Jenkins854c7832009-12-03 07:45:09 +00001306static void __devinit eeepc_enable_camera(struct eeepc_laptop *eeepc)
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001307{
1308 /*
1309 * If the following call to set_acpi() fails, it's because there's no
1310 * camera so we can ignore the error.
1311 */
Alan Jenkins854c7832009-12-03 07:45:09 +00001312 if (get_acpi(eeepc, CM_ASL_CAMERA) == 0)
1313 set_acpi(eeepc, CM_ASL_CAMERA, 1);
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001314}
1315
Alan Jenkins854c7832009-12-03 07:45:09 +00001316static bool eeepc_device_present;
1317
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001318static int __devinit eeepc_acpi_add(struct acpi_device *device)
Eric Coopere59f8792008-03-13 12:55:46 +01001319{
Alan Jenkins854c7832009-12-03 07:45:09 +00001320 struct eeepc_laptop *eeepc;
Eric Coopere59f8792008-03-13 12:55:46 +01001321 int result;
1322
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001323 pr_notice(EEEPC_LAPTOP_NAME "\n");
1324 eeepc = kzalloc(sizeof(struct eeepc_laptop), GFP_KERNEL);
1325 if (!eeepc)
Alan Jenkins1e779852009-08-28 12:56:35 +00001326 return -ENOMEM;
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001327 eeepc->handle = device->handle;
1328 strcpy(acpi_device_name(device), EEEPC_ACPI_DEVICE_NAME);
1329 strcpy(acpi_device_class(device), EEEPC_ACPI_CLASS);
1330 device->driver_data = eeepc;
Pekka Enbergcede2cb2009-06-16 19:28:45 +00001331
Alan Jenkins854c7832009-12-03 07:45:09 +00001332 result = eeepc_acpi_init(eeepc, device);
Alan Jenkins1e779852009-08-28 12:56:35 +00001333 if (result)
Alan Jenkins9db106b2009-12-03 07:45:06 +00001334 goto fail_platform;
Alan Jenkins854c7832009-12-03 07:45:09 +00001335 eeepc_enable_camera(eeepc);
Pekka Enbergcede2cb2009-06-16 19:28:45 +00001336
Alan Jenkins854c7832009-12-03 07:45:09 +00001337 /*
1338 * Register the platform device first. It is used as a parent for the
1339 * sub-devices below.
1340 *
1341 * Note that if there are multiple instances of this ACPI device it
1342 * will bail out, because the platform device is registered with a
1343 * fixed name. Of course it doesn't make sense to have more than one,
1344 * and machine-specific scripts find the fixed name convenient. But
1345 * It's also good for us to exclude multiple instances because both
1346 * our hwmon and our wlan rfkill subdevice use global ACPI objects
1347 * (the EC and the wlan PCI slot respectively).
1348 */
1349 result = eeepc_platform_init(eeepc);
Eric Coopere59f8792008-03-13 12:55:46 +01001350 if (result)
Alan Jenkins9db106b2009-12-03 07:45:06 +00001351 goto fail_platform;
Corentin Chary1ddec2f2009-06-25 13:25:39 +02001352
1353 if (!acpi_video_backlight_support()) {
Alan Jenkins854c7832009-12-03 07:45:09 +00001354 result = eeepc_backlight_init(eeepc);
Corentin Chary1ddec2f2009-06-25 13:25:39 +02001355 if (result)
1356 goto fail_backlight;
1357 } else
Alan Jenkins9db106b2009-12-03 07:45:06 +00001358 pr_info("Backlight controlled by ACPI video driver\n");
Corentin Chary1ddec2f2009-06-25 13:25:39 +02001359
Alan Jenkins854c7832009-12-03 07:45:09 +00001360 result = eeepc_input_init(eeepc);
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001361 if (result)
1362 goto fail_input;
1363
Alan Jenkins854c7832009-12-03 07:45:09 +00001364 result = eeepc_hwmon_init(eeepc);
Corentin Chary1ddec2f2009-06-25 13:25:39 +02001365 if (result)
1366 goto fail_hwmon;
1367
Alan Jenkins854c7832009-12-03 07:45:09 +00001368 result = eeepc_led_init(eeepc);
Corentin Chary3c0eb512009-12-03 07:44:52 +00001369 if (result)
1370 goto fail_led;
1371
Alan Jenkins854c7832009-12-03 07:45:09 +00001372 result = eeepc_rfkill_init(eeepc);
Corentin Chary7de39382009-06-25 13:25:38 +02001373 if (result)
1374 goto fail_rfkill;
1375
Alan Jenkins854c7832009-12-03 07:45:09 +00001376 eeepc_device_present = true;
Eric Coopere59f8792008-03-13 12:55:46 +01001377 return 0;
Alan Jenkins1e779852009-08-28 12:56:35 +00001378
Corentin Chary7de39382009-06-25 13:25:38 +02001379fail_rfkill:
Alan Jenkins854c7832009-12-03 07:45:09 +00001380 eeepc_led_exit(eeepc);
Corentin Chary3c0eb512009-12-03 07:44:52 +00001381fail_led:
Alan Jenkins854c7832009-12-03 07:45:09 +00001382 eeepc_hwmon_exit(eeepc);
Corentin Chary1ddec2f2009-06-25 13:25:39 +02001383fail_hwmon:
Alan Jenkins854c7832009-12-03 07:45:09 +00001384 eeepc_input_exit(eeepc);
Alan Jenkinsf2a9d5e2009-08-28 12:56:36 +00001385fail_input:
Alan Jenkins854c7832009-12-03 07:45:09 +00001386 eeepc_backlight_exit(eeepc);
Corentin Chary1ddec2f2009-06-25 13:25:39 +02001387fail_backlight:
Alan Jenkins854c7832009-12-03 07:45:09 +00001388 eeepc_platform_exit(eeepc);
Alan Jenkins9db106b2009-12-03 07:45:06 +00001389fail_platform:
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001390 kfree(eeepc);
Alan Jenkins1e779852009-08-28 12:56:35 +00001391
Eric Coopere59f8792008-03-13 12:55:46 +01001392 return result;
1393}
1394
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001395static int eeepc_acpi_remove(struct acpi_device *device, int type)
Alan Jenkins1e779852009-08-28 12:56:35 +00001396{
Alan Jenkins854c7832009-12-03 07:45:09 +00001397 struct eeepc_laptop *eeepc = acpi_driver_data(device);
1398
1399 eeepc_backlight_exit(eeepc);
1400 eeepc_rfkill_exit(eeepc);
1401 eeepc_input_exit(eeepc);
1402 eeepc_hwmon_exit(eeepc);
1403 eeepc_led_exit(eeepc);
1404 eeepc_platform_exit(eeepc);
Alan Jenkins1e779852009-08-28 12:56:35 +00001405
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001406 kfree(eeepc);
Alan Jenkins1e779852009-08-28 12:56:35 +00001407 return 0;
1408}
1409
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001410
1411static const struct acpi_device_id eeepc_device_ids[] = {
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001412 {EEEPC_ACPI_HID, 0},
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001413 {"", 0},
1414};
1415MODULE_DEVICE_TABLE(acpi, eeepc_device_ids);
1416
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001417static struct acpi_driver eeepc_acpi_driver = {
1418 .name = EEEPC_LAPTOP_NAME,
1419 .class = EEEPC_ACPI_CLASS,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001420 .owner = THIS_MODULE,
1421 .ids = eeepc_device_ids,
1422 .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS,
1423 .ops = {
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001424 .add = eeepc_acpi_add,
1425 .remove = eeepc_acpi_remove,
1426 .notify = eeepc_acpi_notify,
Alan Jenkins52bbe3c2009-12-03 07:45:07 +00001427 },
1428};
1429
1430
Alan Jenkins1e779852009-08-28 12:56:35 +00001431static int __init eeepc_laptop_init(void)
1432{
1433 int result;
1434
Alan Jenkins22072e92009-12-03 07:45:05 +00001435 result = platform_driver_register(&platform_driver);
Alan Jenkins1e779852009-08-28 12:56:35 +00001436 if (result < 0)
1437 return result;
Alan Jenkins22072e92009-12-03 07:45:05 +00001438
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001439 result = acpi_bus_register_driver(&eeepc_acpi_driver);
Alan Jenkins22072e92009-12-03 07:45:05 +00001440 if (result < 0)
1441 goto fail_acpi_driver;
Alan Jenkins854c7832009-12-03 07:45:09 +00001442 if (!eeepc_device_present) {
Alan Jenkins22072e92009-12-03 07:45:05 +00001443 result = -ENODEV;
1444 goto fail_no_device;
Alan Jenkins1e779852009-08-28 12:56:35 +00001445 }
1446 return 0;
Alan Jenkins22072e92009-12-03 07:45:05 +00001447
1448fail_no_device:
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001449 acpi_bus_unregister_driver(&eeepc_acpi_driver);
Alan Jenkins22072e92009-12-03 07:45:05 +00001450fail_acpi_driver:
1451 platform_driver_unregister(&platform_driver);
1452 return result;
Alan Jenkins1e779852009-08-28 12:56:35 +00001453}
1454
1455static void __exit eeepc_laptop_exit(void)
1456{
Alan Jenkinsa7624b62009-12-03 07:45:08 +00001457 acpi_bus_unregister_driver(&eeepc_acpi_driver);
Alan Jenkins22072e92009-12-03 07:45:05 +00001458 platform_driver_unregister(&platform_driver);
Alan Jenkins1e779852009-08-28 12:56:35 +00001459}
1460
Eric Coopere59f8792008-03-13 12:55:46 +01001461module_init(eeepc_laptop_init);
1462module_exit(eeepc_laptop_exit);