blob: a086ce8ed4eba36888dcb424ae57220bc584f9e7 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
Henrique de Moraes Holschuh643f12d2007-03-29 01:58:43 -03002 * thinkpad_acpi.c - ThinkPad ACPI Extras
Linus Torvalds1da177e2005-04-16 15:20:36 -07003 *
4 *
Borislav Deianov78f81cc2005-08-17 00:00:00 -04005 * Copyright (C) 2004-2005 Borislav Deianov <borislav@users.sf.net>
Henrique de Moraes Holschuh6a2e2932008-01-08 13:02:56 -02006 * Copyright (C) 2006-2008 Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Linus Torvalds1da177e2005-04-16 15:20:36 -07007 *
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 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
Henrique de Moraes Holschuha62bc912007-03-23 17:33:58 -030020 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 * 02110-1301, USA.
Borislav Deianov78f81cc2005-08-17 00:00:00 -040022 */
23
Henrique de Moraes Holschuh490673d2008-07-21 09:15:51 -030024#define TPACPI_VERSION "0.21"
Henrique de Moraes Holschuh50ebec02008-01-08 13:02:55 -020025#define TPACPI_SYSFS_VERSION 0x020200
Borislav Deianov78f81cc2005-08-17 00:00:00 -040026
27/*
Linus Torvalds1da177e2005-04-16 15:20:36 -070028 * Changelog:
Henrique de Moraes Holschuh4b45cc02008-01-08 13:02:46 -020029 * 2007-10-20 changelog trimmed down
30 *
Henrique de Moraes Holschuh643f12d2007-03-29 01:58:43 -030031 * 2007-03-27 0.14 renamed to thinkpad_acpi and moved to
32 * drivers/misc.
Henrique de Moraes Holschuhf9ff43a2006-11-25 16:37:38 -020033 *
34 * 2006-11-22 0.13 new maintainer
35 * changelog now lives in git commit history, and will
36 * not be updated further in-file.
Henrique de Moraes Holschuh837ca6d2007-03-23 17:33:54 -030037 *
Borislav Deianov78f81cc2005-08-17 00:00:00 -040038 * 2005-03-17 0.11 support for 600e, 770x
39 * thanks to Jamie Lentin <lentinj@dial.pipex.com>
Henrique de Moraes Holschuh4b45cc02008-01-08 13:02:46 -020040 *
41 * 2005-01-16 0.9 use MODULE_VERSION
Borislav Deianov78f81cc2005-08-17 00:00:00 -040042 * thanks to Henrik Brix Andersen <brix@gentoo.org>
43 * fix parameter passing on module loading
44 * thanks to Rusty Russell <rusty@rustcorp.com.au>
45 * thanks to Jim Radford <radford@blackbean.org>
46 * 2004-11-08 0.8 fix init error case, don't return from a macro
47 * thanks to Chris Wright <chrisw@osdl.org>
Linus Torvalds1da177e2005-04-16 15:20:36 -070048 */
49
Henrique de Moraes Holschuh0c780392008-01-08 13:02:43 -020050#include <linux/kernel.h>
51#include <linux/module.h>
52#include <linux/init.h>
53#include <linux/types.h>
54#include <linux/string.h>
55#include <linux/list.h>
56#include <linux/mutex.h>
57#include <linux/kthread.h>
58#include <linux/freezer.h>
59#include <linux/delay.h>
60
61#include <linux/nvram.h>
62#include <linux/proc_fs.h>
63#include <linux/sysfs.h>
64#include <linux/backlight.h>
65#include <linux/fb.h>
66#include <linux/platform_device.h>
67#include <linux/hwmon.h>
68#include <linux/hwmon-sysfs.h>
69#include <linux/input.h>
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -030070#include <linux/leds.h>
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -030071#include <linux/rfkill.h>
Henrique de Moraes Holschuh0c780392008-01-08 13:02:43 -020072#include <asm/uaccess.h>
73
74#include <linux/dmi.h>
75#include <linux/jiffies.h>
76#include <linux/workqueue.h>
77
78#include <acpi/acpi_drivers.h>
Henrique de Moraes Holschuh0c780392008-01-08 13:02:43 -020079
80#include <linux/pci_ids.h>
81
Henrique de Moraes Holschuh0c780392008-01-08 13:02:43 -020082
83/* ThinkPad CMOS commands */
84#define TP_CMOS_VOLUME_DOWN 0
85#define TP_CMOS_VOLUME_UP 1
86#define TP_CMOS_VOLUME_MUTE 2
87#define TP_CMOS_BRIGHTNESS_UP 4
88#define TP_CMOS_BRIGHTNESS_DOWN 5
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -030089#define TP_CMOS_THINKLIGHT_ON 12
90#define TP_CMOS_THINKLIGHT_OFF 13
Henrique de Moraes Holschuh0c780392008-01-08 13:02:43 -020091
92/* NVRAM Addresses */
93enum tp_nvram_addr {
94 TP_NVRAM_ADDR_HK2 = 0x57,
95 TP_NVRAM_ADDR_THINKLIGHT = 0x58,
96 TP_NVRAM_ADDR_VIDEO = 0x59,
97 TP_NVRAM_ADDR_BRIGHTNESS = 0x5e,
98 TP_NVRAM_ADDR_MIXER = 0x60,
99};
100
101/* NVRAM bit masks */
102enum {
103 TP_NVRAM_MASK_HKT_THINKPAD = 0x08,
104 TP_NVRAM_MASK_HKT_ZOOM = 0x20,
105 TP_NVRAM_MASK_HKT_DISPLAY = 0x40,
106 TP_NVRAM_MASK_HKT_HIBERNATE = 0x80,
107 TP_NVRAM_MASK_THINKLIGHT = 0x10,
108 TP_NVRAM_MASK_HKT_DISPEXPND = 0x30,
109 TP_NVRAM_MASK_HKT_BRIGHTNESS = 0x20,
110 TP_NVRAM_MASK_LEVEL_BRIGHTNESS = 0x0f,
111 TP_NVRAM_POS_LEVEL_BRIGHTNESS = 0,
112 TP_NVRAM_MASK_MUTE = 0x40,
113 TP_NVRAM_MASK_HKT_VOLUME = 0x80,
114 TP_NVRAM_MASK_LEVEL_VOLUME = 0x0f,
115 TP_NVRAM_POS_LEVEL_VOLUME = 0,
116};
117
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -0200118/* ACPI HIDs */
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -0200119#define TPACPI_ACPI_HKEY_HID "IBM0068"
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -0200120
121/* Input IDs */
122#define TPACPI_HKEY_INPUT_PRODUCT 0x5054 /* "TP" */
123#define TPACPI_HKEY_INPUT_VERSION 0x4101
124
125
126/****************************************************************************
127 * Main driver
128 */
129
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -0200130#define TPACPI_NAME "thinkpad"
131#define TPACPI_DESC "ThinkPad ACPI Extras"
132#define TPACPI_FILE TPACPI_NAME "_acpi"
133#define TPACPI_URL "http://ibm-acpi.sf.net/"
134#define TPACPI_MAIL "ibm-acpi-devel@lists.sourceforge.net"
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -0200135
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -0200136#define TPACPI_PROC_DIR "ibm"
137#define TPACPI_ACPI_EVENT_PREFIX "ibm"
138#define TPACPI_DRVR_NAME TPACPI_FILE
Henrique de Moraes Holschuh95e57ab2008-04-26 01:02:22 -0300139#define TPACPI_DRVR_SHORTNAME "tpacpi"
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -0200140#define TPACPI_HWMON_DRVR_NAME TPACPI_NAME "_hwmon"
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -0200141
Henrique de Moraes Holschuh95e57ab2008-04-26 01:02:22 -0300142#define TPACPI_NVRAM_KTHREAD_NAME "ktpacpi_nvramd"
Henrique de Moraes Holschuhe0e3c062008-04-26 01:02:28 -0300143#define TPACPI_WORKQUEUE_NAME "ktpacpid"
Henrique de Moraes Holschuh95e57ab2008-04-26 01:02:22 -0300144
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -0200145#define TPACPI_MAX_ACPI_ARGS 3
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -0200146
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -0300147/* rfkill switches */
148enum {
149 TPACPI_RFK_BLUETOOTH_SW_ID = 0,
150 TPACPI_RFK_WWAN_SW_ID,
151};
152
Henrique de Moraes Holschuh0c780392008-01-08 13:02:43 -0200153/* Debugging */
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -0200154#define TPACPI_LOG TPACPI_FILE ": "
155#define TPACPI_ERR KERN_ERR TPACPI_LOG
156#define TPACPI_NOTICE KERN_NOTICE TPACPI_LOG
157#define TPACPI_INFO KERN_INFO TPACPI_LOG
158#define TPACPI_DEBUG KERN_DEBUG TPACPI_LOG
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -0200159
Henrique de Moraes Holschuh0c780392008-01-08 13:02:43 -0200160#define TPACPI_DBG_ALL 0xffff
Henrique de Moraes Holschuh0c780392008-01-08 13:02:43 -0200161#define TPACPI_DBG_INIT 0x0001
162#define TPACPI_DBG_EXIT 0x0002
163#define dbg_printk(a_dbg_level, format, arg...) \
164 do { if (dbg_level & a_dbg_level) \
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -0200165 printk(TPACPI_DEBUG "%s: " format, __func__ , ## arg); \
166 } while (0)
Henrique de Moraes Holschuh0c780392008-01-08 13:02:43 -0200167#ifdef CONFIG_THINKPAD_ACPI_DEBUG
168#define vdbg_printk(a_dbg_level, format, arg...) \
169 dbg_printk(a_dbg_level, format, ## arg)
170static const char *str_supported(int is_supported);
171#else
172#define vdbg_printk(a_dbg_level, format, arg...)
173#endif
174
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -0200175#define onoff(status, bit) ((status) & (1 << (bit)) ? "on" : "off")
176#define enabled(status, bit) ((status) & (1 << (bit)) ? "enabled" : "disabled")
177#define strlencmp(a, b) (strncmp((a), (b), strlen(b)))
Henrique de Moraes Holschuh0c780392008-01-08 13:02:43 -0200178
Henrique de Moraes Holschuh0c780392008-01-08 13:02:43 -0200179
180/****************************************************************************
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -0200181 * Driver-wide structs and misc. variables
Henrique de Moraes Holschuh0c780392008-01-08 13:02:43 -0200182 */
183
184struct ibm_struct;
185
186struct tp_acpi_drv_struct {
187 const struct acpi_device_id *hid;
188 struct acpi_driver *driver;
189
190 void (*notify) (struct ibm_struct *, u32);
191 acpi_handle *handle;
192 u32 type;
193 struct acpi_device *device;
194};
195
196struct ibm_struct {
197 char *name;
198
199 int (*read) (char *);
200 int (*write) (char *);
201 void (*exit) (void);
202 void (*resume) (void);
Henrique de Moraes Holschuh083f1762008-01-08 13:02:50 -0200203 void (*suspend) (pm_message_t state);
Henrique de Moraes Holschuh0c780392008-01-08 13:02:43 -0200204
205 struct list_head all_drivers;
206
207 struct tp_acpi_drv_struct *acpi;
208
209 struct {
210 u8 acpi_driver_registered:1;
211 u8 acpi_notify_installed:1;
212 u8 proc_created:1;
213 u8 init_called:1;
214 u8 experimental:1;
215 } flags;
216};
217
218struct ibm_init_struct {
219 char param[32];
220
221 int (*init) (struct ibm_init_struct *);
222 struct ibm_struct *data;
223};
224
225static struct {
226#ifdef CONFIG_THINKPAD_ACPI_BAY
227 u32 bay_status:1;
228 u32 bay_eject:1;
229 u32 bay_status2:1;
230 u32 bay_eject2:1;
231#endif
232 u32 bluetooth:1;
233 u32 hotkey:1;
234 u32 hotkey_mask:1;
235 u32 hotkey_wlsw:1;
Henrique de Moraes Holschuh6c231bd2008-02-16 02:17:58 -0200236 u32 hotkey_tablet:1;
Henrique de Moraes Holschuh0c780392008-01-08 13:02:43 -0200237 u32 light:1;
238 u32 light_status:1;
239 u32 bright_16levels:1;
Henrique de Moraes Holschuhb5972792008-04-26 01:02:17 -0300240 u32 bright_acpimode:1;
Henrique de Moraes Holschuh0c780392008-01-08 13:02:43 -0200241 u32 wan:1;
242 u32 fan_ctrl_status_undef:1;
243 u32 input_device_registered:1;
244 u32 platform_drv_registered:1;
245 u32 platform_drv_attrs_registered:1;
246 u32 sensors_pdrv_registered:1;
247 u32 sensors_pdrv_attrs_registered:1;
248 u32 sensors_pdev_attrs_registered:1;
249 u32 hotkey_poll_active:1;
250} tp_features;
251
Henrique de Moraes Holschuh92889022008-04-26 01:02:18 -0300252static struct {
253 u16 hotkey_mask_ff:1;
Henrique de Moraes Holschuh2d5e94d2008-04-26 01:02:20 -0300254 u16 bright_cmos_ec_unsync:1;
Henrique de Moraes Holschuh92889022008-04-26 01:02:18 -0300255} tp_warned;
256
Henrique de Moraes Holschuh0c780392008-01-08 13:02:43 -0200257struct thinkpad_id_data {
258 unsigned int vendor; /* ThinkPad vendor:
259 * PCI_VENDOR_ID_IBM/PCI_VENDOR_ID_LENOVO */
260
261 char *bios_version_str; /* Something like 1ZET51WW (1.03z) */
262 char *ec_version_str; /* Something like 1ZHT51WW-1.04a */
263
264 u16 bios_model; /* Big Endian, TP-1Y = 0x5931, 0 = unknown */
265 u16 ec_model;
266
Henrique de Moraes Holschuh8c74adb2008-04-26 01:02:19 -0300267 char *model_str; /* ThinkPad T43 */
268 char *nummodel_str; /* 9384A9C for a 9384-A9C model */
Henrique de Moraes Holschuh0c780392008-01-08 13:02:43 -0200269};
Henrique de Moraes Holschuh0c780392008-01-08 13:02:43 -0200270static struct thinkpad_id_data thinkpad_id;
271
Henrique de Moraes Holschuh8fef5022007-09-23 11:39:02 -0300272static enum {
273 TPACPI_LIFE_INIT = 0,
274 TPACPI_LIFE_RUNNING,
275 TPACPI_LIFE_EXITING,
276} tpacpi_lifecycle;
277
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -0200278static int experimental;
279static u32 dbg_level;
280
Henrique de Moraes Holschuhe0e3c062008-04-26 01:02:28 -0300281static struct workqueue_struct *tpacpi_wq;
282
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -0300283/* Special LED class that can defer work */
284struct tpacpi_led_classdev {
285 struct led_classdev led_classdev;
286 struct work_struct work;
287 enum led_brightness new_brightness;
Henrique de Moraes Holschuhaf116102008-04-26 01:02:25 -0300288 unsigned int led;
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -0300289};
290
Henrique de Moraes Holschuha73f3092009-01-11 03:01:00 -0200291#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
292static int dbg_wlswemul;
293static int tpacpi_wlsw_emulstate;
294static int dbg_bluetoothemul;
295static int tpacpi_bluetooth_emulstate;
296static int dbg_wwanemul;
297static int tpacpi_wwan_emulstate;
298#endif
299
300
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300301/****************************************************************************
302 ****************************************************************************
303 *
304 * ACPI Helpers and device model
305 *
306 ****************************************************************************
307 ****************************************************************************/
308
309/*************************************************************************
310 * ACPI basic handles
311 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700312
Henrique de Moraes Holschuh94954cc2007-07-18 23:45:27 -0300313static acpi_handle root_handle;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700314
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -0200315#define TPACPI_HANDLE(object, parent, paths...) \
Linus Torvalds1da177e2005-04-16 15:20:36 -0700316 static acpi_handle object##_handle; \
317 static acpi_handle *object##_parent = &parent##_handle; \
Borislav Deianov78f81cc2005-08-17 00:00:00 -0400318 static char *object##_path; \
Linus Torvalds1da177e2005-04-16 15:20:36 -0700319 static char *object##_paths[] = { paths }
320
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -0200321TPACPI_HANDLE(ec, root, "\\_SB.PCI0.ISA.EC0", /* 240, 240x */
Borislav Deianov78f81cc2005-08-17 00:00:00 -0400322 "\\_SB.PCI.ISA.EC", /* 570 */
323 "\\_SB.PCI0.ISA0.EC0", /* 600e/x, 770e, 770x */
324 "\\_SB.PCI0.ISA.EC", /* A21e, A2xm/p, T20-22, X20-21 */
325 "\\_SB.PCI0.AD4S.EC0", /* i1400, R30 */
326 "\\_SB.PCI0.ICH3.EC0", /* R31 */
327 "\\_SB.PCI0.LPC.EC", /* all others */
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300328 );
Linus Torvalds1da177e2005-04-16 15:20:36 -0700329
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -0200330TPACPI_HANDLE(ecrd, ec, "ECRD"); /* 570 */
331TPACPI_HANDLE(ecwr, ec, "ECWR"); /* 570 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700332
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -0200333TPACPI_HANDLE(cmos, root, "\\UCMS", /* R50, R50e, R50p, R51, */
334 /* T4x, X31, X40 */
Borislav Deianov78f81cc2005-08-17 00:00:00 -0400335 "\\CMOS", /* A3x, G4x, R32, T23, T30, X22-24, X30 */
336 "\\CMS", /* R40, R40e */
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300337 ); /* all others */
Borislav Deianov78f81cc2005-08-17 00:00:00 -0400338
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -0200339TPACPI_HANDLE(hkey, ec, "\\_SB.HKEY", /* 600e/x, 770e, 770x */
Borislav Deianov78f81cc2005-08-17 00:00:00 -0400340 "^HKEY", /* R30, R31 */
341 "HKEY", /* all others */
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300342 ); /* 570 */
Borislav Deianov78f81cc2005-08-17 00:00:00 -0400343
Henrique de Moraes Holschuhd7c1d172008-02-16 02:17:54 -0200344TPACPI_HANDLE(vid, root, "\\_SB.PCI.AGP.VGA", /* 570 */
345 "\\_SB.PCI0.AGP0.VID0", /* 600e/x, 770x */
346 "\\_SB.PCI0.VID0", /* 770e */
347 "\\_SB.PCI0.VID", /* A21e, G4x, R50e, X30, X40 */
348 "\\_SB.PCI0.AGP.VID", /* all others */
349 ); /* R30, R31 */
350
Borislav Deianov78f81cc2005-08-17 00:00:00 -0400351
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300352/*************************************************************************
353 * ACPI helpers
Henrique de Moraes Holschuha8b7a662006-11-24 11:47:11 -0200354 */
355
Linus Torvalds1da177e2005-04-16 15:20:36 -0700356static int acpi_evalf(acpi_handle handle,
357 void *res, char *method, char *fmt, ...)
358{
359 char *fmt0 = fmt;
Borislav Deianov78f81cc2005-08-17 00:00:00 -0400360 struct acpi_object_list params;
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -0200361 union acpi_object in_objs[TPACPI_MAX_ACPI_ARGS];
Borislav Deianov78f81cc2005-08-17 00:00:00 -0400362 struct acpi_buffer result, *resultp;
363 union acpi_object out_obj;
364 acpi_status status;
365 va_list ap;
366 char res_type;
367 int success;
368 int quiet;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700369
370 if (!*fmt) {
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -0200371 printk(TPACPI_ERR "acpi_evalf() called with empty format\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700372 return 0;
373 }
374
375 if (*fmt == 'q') {
376 quiet = 1;
377 fmt++;
378 } else
379 quiet = 0;
380
381 res_type = *(fmt++);
382
383 params.count = 0;
384 params.pointer = &in_objs[0];
385
386 va_start(ap, fmt);
387 while (*fmt) {
388 char c = *(fmt++);
389 switch (c) {
390 case 'd': /* int */
391 in_objs[params.count].integer.value = va_arg(ap, int);
392 in_objs[params.count++].type = ACPI_TYPE_INTEGER;
393 break;
Borislav Deianov78f81cc2005-08-17 00:00:00 -0400394 /* add more types as needed */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700395 default:
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -0200396 printk(TPACPI_ERR "acpi_evalf() called "
Linus Torvalds1da177e2005-04-16 15:20:36 -0700397 "with invalid format character '%c'\n", c);
398 return 0;
399 }
400 }
401 va_end(ap);
402
Borislav Deianov78f81cc2005-08-17 00:00:00 -0400403 if (res_type != 'v') {
404 result.length = sizeof(out_obj);
405 result.pointer = &out_obj;
406 resultp = &result;
407 } else
408 resultp = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700409
Borislav Deianov78f81cc2005-08-17 00:00:00 -0400410 status = acpi_evaluate_object(handle, method, &params, resultp);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700411
412 switch (res_type) {
Borislav Deianov78f81cc2005-08-17 00:00:00 -0400413 case 'd': /* int */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700414 if (res)
415 *(int *)res = out_obj.integer.value;
416 success = status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER;
417 break;
Borislav Deianov78f81cc2005-08-17 00:00:00 -0400418 case 'v': /* void */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700419 success = status == AE_OK;
420 break;
Borislav Deianov78f81cc2005-08-17 00:00:00 -0400421 /* add more types as needed */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700422 default:
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -0200423 printk(TPACPI_ERR "acpi_evalf() called "
Linus Torvalds1da177e2005-04-16 15:20:36 -0700424 "with invalid format character '%c'\n", res_type);
425 return 0;
426 }
427
428 if (!success && !quiet)
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -0200429 printk(TPACPI_ERR "acpi_evalf(%s, %s, ...) failed: %d\n",
Linus Torvalds1da177e2005-04-16 15:20:36 -0700430 method, fmt0, status);
431
432 return success;
433}
434
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -0200435static int acpi_ec_read(int i, u8 *p)
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300436{
437 int v;
438
439 if (ecrd_handle) {
440 if (!acpi_evalf(ecrd_handle, &v, NULL, "dd", i))
441 return 0;
442 *p = v;
443 } else {
444 if (ec_read(i, p) < 0)
445 return 0;
446 }
447
448 return 1;
449}
450
451static int acpi_ec_write(int i, u8 v)
452{
453 if (ecwr_handle) {
454 if (!acpi_evalf(ecwr_handle, NULL, NULL, "vdd", i, v))
455 return 0;
456 } else {
457 if (ec_write(i, v) < 0)
458 return 0;
459 }
460
461 return 1;
462}
463
Henrique de Moraes Holschuh013c40e2008-01-08 13:02:54 -0200464#if defined(CONFIG_THINKPAD_ACPI_DOCK) || defined(CONFIG_THINKPAD_ACPI_BAY)
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300465static int _sta(acpi_handle handle)
466{
467 int status;
468
469 if (!handle || !acpi_evalf(handle, &status, "_STA", "d"))
470 status = 0;
471
472 return status;
473}
Henrique de Moraes Holschuh013c40e2008-01-08 13:02:54 -0200474#endif
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300475
Henrique de Moraes Holschuhc9bea992007-04-21 11:08:42 -0300476static int issue_thinkpad_cmos_command(int cmos_cmd)
477{
478 if (!cmos_handle)
479 return -ENXIO;
480
481 if (!acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd))
482 return -EIO;
483
484 return 0;
485}
486
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300487/*************************************************************************
488 * ACPI device model
489 */
490
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -0200491#define TPACPI_ACPIHANDLE_INIT(object) \
492 drv_acpi_handle_init(#object, &object##_handle, *object##_parent, \
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -0200493 object##_paths, ARRAY_SIZE(object##_paths), &object##_path)
494
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -0300495static void drv_acpi_handle_init(char *name,
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -0300496 acpi_handle *handle, acpi_handle parent,
497 char **paths, int num_paths, char **path)
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300498{
499 int i;
500 acpi_status status;
501
Henrique de Moraes Holschuh5ae930e2007-04-27 22:00:14 -0300502 vdbg_printk(TPACPI_DBG_INIT, "trying to locate ACPI handle for %s\n",
503 name);
504
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300505 for (i = 0; i < num_paths; i++) {
506 status = acpi_get_handle(parent, paths[i], handle);
507 if (ACPI_SUCCESS(status)) {
508 *path = paths[i];
Henrique de Moraes Holschuh5ae930e2007-04-27 22:00:14 -0300509 dbg_printk(TPACPI_DBG_INIT,
510 "Found ACPI handle %s for %s\n",
511 *path, name);
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300512 return;
513 }
514 }
515
Henrique de Moraes Holschuh5ae930e2007-04-27 22:00:14 -0300516 vdbg_printk(TPACPI_DBG_INIT, "ACPI handle for %s not found\n",
517 name);
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300518 *handle = NULL;
519}
520
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -0300521static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data)
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300522{
523 struct ibm_struct *ibm = data;
524
Henrique de Moraes Holschuh8fef5022007-09-23 11:39:02 -0300525 if (tpacpi_lifecycle != TPACPI_LIFE_RUNNING)
526 return;
527
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -0300528 if (!ibm || !ibm->acpi || !ibm->acpi->notify)
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300529 return;
530
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -0300531 ibm->acpi->notify(ibm, event);
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300532}
533
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -0300534static int __init setup_acpi_notify(struct ibm_struct *ibm)
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300535{
536 acpi_status status;
Henrique de Moraes Holschuh5ae930e2007-04-27 22:00:14 -0300537 int rc;
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300538
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -0300539 BUG_ON(!ibm->acpi);
540
541 if (!*ibm->acpi->handle)
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300542 return 0;
543
Henrique de Moraes Holschuh5ae930e2007-04-27 22:00:14 -0300544 vdbg_printk(TPACPI_DBG_INIT,
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -0300545 "setting up ACPI notify for %s\n", ibm->name);
546
Henrique de Moraes Holschuh5ae930e2007-04-27 22:00:14 -0300547 rc = acpi_bus_get_device(*ibm->acpi->handle, &ibm->acpi->device);
548 if (rc < 0) {
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -0200549 printk(TPACPI_ERR "acpi_bus_get_device(%s) failed: %d\n",
Henrique de Moraes Holschuh5ae930e2007-04-27 22:00:14 -0300550 ibm->name, rc);
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300551 return -ENODEV;
552 }
553
Pavel Machekdb89b4f2008-09-22 14:37:34 -0700554 ibm->acpi->device->driver_data = ibm;
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -0300555 sprintf(acpi_device_class(ibm->acpi->device), "%s/%s",
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -0200556 TPACPI_ACPI_EVENT_PREFIX,
Henrique de Moraes Holschuh643f12d2007-03-29 01:58:43 -0300557 ibm->name);
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300558
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -0300559 status = acpi_install_notify_handler(*ibm->acpi->handle,
560 ibm->acpi->type, dispatch_acpi_notify, ibm);
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300561 if (ACPI_FAILURE(status)) {
562 if (status == AE_ALREADY_EXISTS) {
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -0200563 printk(TPACPI_NOTICE
564 "another device driver is already "
565 "handling %s events\n", ibm->name);
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300566 } else {
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -0200567 printk(TPACPI_ERR
568 "acpi_install_notify_handler(%s) failed: %d\n",
569 ibm->name, status);
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300570 }
571 return -ENODEV;
572 }
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -0300573 ibm->flags.acpi_notify_installed = 1;
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300574 return 0;
575}
576
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -0300577static int __init tpacpi_device_add(struct acpi_device *device)
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300578{
579 return 0;
580}
581
Henrique de Moraes Holschuh67001212007-04-21 11:08:25 -0300582static int __init register_tpacpi_subdriver(struct ibm_struct *ibm)
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300583{
Henrique de Moraes Holschuh5ae930e2007-04-27 22:00:14 -0300584 int rc;
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300585
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -0300586 dbg_printk(TPACPI_DBG_INIT,
587 "registering %s as an ACPI driver\n", ibm->name);
588
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -0300589 BUG_ON(!ibm->acpi);
590
591 ibm->acpi->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL);
592 if (!ibm->acpi->driver) {
Mariusz Kozlowski4f778b92008-10-18 14:23:53 -0300593 printk(TPACPI_ERR
594 "failed to allocate memory for ibm->acpi->driver\n");
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -0300595 return -ENOMEM;
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300596 }
597
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -0200598 sprintf(ibm->acpi->driver->name, "%s_%s", TPACPI_NAME, ibm->name);
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -0300599 ibm->acpi->driver->ids = ibm->acpi->hid;
Thomas Renninger1ba90e32007-07-23 14:44:41 +0200600
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -0300601 ibm->acpi->driver->ops.add = &tpacpi_device_add;
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300602
Henrique de Moraes Holschuh5ae930e2007-04-27 22:00:14 -0300603 rc = acpi_bus_register_driver(ibm->acpi->driver);
604 if (rc < 0) {
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -0200605 printk(TPACPI_ERR "acpi_bus_register_driver(%s) failed: %d\n",
Thomas Renninger1ba90e32007-07-23 14:44:41 +0200606 ibm->name, rc);
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -0300607 kfree(ibm->acpi->driver);
608 ibm->acpi->driver = NULL;
Henrique de Moraes Holschuh5ae930e2007-04-27 22:00:14 -0300609 } else if (!rc)
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -0300610 ibm->flags.acpi_driver_registered = 1;
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300611
Henrique de Moraes Holschuh5ae930e2007-04-27 22:00:14 -0300612 return rc;
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300613}
614
615
616/****************************************************************************
617 ****************************************************************************
618 *
619 * Procfs Helpers
620 *
621 ****************************************************************************
622 ****************************************************************************/
623
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -0300624static int dispatch_procfs_read(char *page, char **start, off_t off,
625 int count, int *eof, void *data)
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300626{
627 struct ibm_struct *ibm = data;
628 int len;
629
630 if (!ibm || !ibm->read)
631 return -EINVAL;
632
633 len = ibm->read(page);
634 if (len < 0)
635 return len;
636
637 if (len <= off + count)
638 *eof = 1;
639 *start = page + off;
640 len -= off;
641 if (len > count)
642 len = count;
643 if (len < 0)
644 len = 0;
645
646 return len;
647}
648
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -0300649static int dispatch_procfs_write(struct file *file,
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -0200650 const char __user *userbuf,
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -0300651 unsigned long count, void *data)
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300652{
653 struct ibm_struct *ibm = data;
654 char *kernbuf;
655 int ret;
656
657 if (!ibm || !ibm->write)
658 return -EINVAL;
659
660 kernbuf = kmalloc(count + 2, GFP_KERNEL);
661 if (!kernbuf)
662 return -ENOMEM;
663
664 if (copy_from_user(kernbuf, userbuf, count)) {
665 kfree(kernbuf);
666 return -EFAULT;
667 }
668
669 kernbuf[count] = 0;
670 strcat(kernbuf, ",");
671 ret = ibm->write(kernbuf);
672 if (ret == 0)
673 ret = count;
674
675 kfree(kernbuf);
676
677 return ret;
678}
679
Linus Torvalds1da177e2005-04-16 15:20:36 -0700680static char *next_cmd(char **cmds)
681{
682 char *start = *cmds;
683 char *end;
684
685 while ((end = strchr(start, ',')) && end == start)
686 start = end + 1;
687
688 if (!end)
689 return NULL;
690
691 *end = 0;
692 *cmds = end + 1;
693 return start;
694}
695
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -0300696
697/****************************************************************************
698 ****************************************************************************
699 *
Henrique de Moraes Holschuh7f5d1cd2007-07-18 23:45:34 -0300700 * Device model: input, hwmon and platform
Henrique de Moraes Holschuh54ae1502007-04-24 11:48:12 -0300701 *
702 ****************************************************************************
703 ****************************************************************************/
704
Henrique de Moraes Holschuh94954cc2007-07-18 23:45:27 -0300705static struct platform_device *tpacpi_pdev;
Henrique de Moraes Holschuh7fd40022007-09-25 06:38:03 -0300706static struct platform_device *tpacpi_sensors_pdev;
Tony Jones1beeffe2007-08-20 13:46:20 -0700707static struct device *tpacpi_hwmon;
Henrique de Moraes Holschuh7f5d1cd2007-07-18 23:45:34 -0300708static struct input_dev *tpacpi_inputdev;
Henrique de Moraes Holschuh8523ed62007-09-23 11:39:01 -0300709static struct mutex tpacpi_inputdev_send_mutex;
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -0200710static LIST_HEAD(tpacpi_all_drivers);
Henrique de Moraes Holschuhe295e852007-07-18 23:45:37 -0300711
Henrique de Moraes Holschuh083f1762008-01-08 13:02:50 -0200712static int tpacpi_suspend_handler(struct platform_device *pdev,
713 pm_message_t state)
714{
715 struct ibm_struct *ibm, *itmp;
716
717 list_for_each_entry_safe(ibm, itmp,
718 &tpacpi_all_drivers,
719 all_drivers) {
720 if (ibm->suspend)
721 (ibm->suspend)(state);
722 }
723
724 return 0;
725}
726
Henrique de Moraes Holschuhe295e852007-07-18 23:45:37 -0300727static int tpacpi_resume_handler(struct platform_device *pdev)
728{
729 struct ibm_struct *ibm, *itmp;
730
731 list_for_each_entry_safe(ibm, itmp,
732 &tpacpi_all_drivers,
733 all_drivers) {
734 if (ibm->resume)
735 (ibm->resume)();
736 }
737
738 return 0;
739}
740
Henrique de Moraes Holschuh54ae1502007-04-24 11:48:12 -0300741static struct platform_driver tpacpi_pdriver = {
742 .driver = {
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -0200743 .name = TPACPI_DRVR_NAME,
Henrique de Moraes Holschuh54ae1502007-04-24 11:48:12 -0300744 .owner = THIS_MODULE,
745 },
Henrique de Moraes Holschuh083f1762008-01-08 13:02:50 -0200746 .suspend = tpacpi_suspend_handler,
Henrique de Moraes Holschuhe295e852007-07-18 23:45:37 -0300747 .resume = tpacpi_resume_handler,
Henrique de Moraes Holschuh54ae1502007-04-24 11:48:12 -0300748};
749
Henrique de Moraes Holschuh7fd40022007-09-25 06:38:03 -0300750static struct platform_driver tpacpi_hwmon_pdriver = {
751 .driver = {
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -0200752 .name = TPACPI_HWMON_DRVR_NAME,
Henrique de Moraes Holschuh7fd40022007-09-25 06:38:03 -0300753 .owner = THIS_MODULE,
754 },
755};
Henrique de Moraes Holschuh54ae1502007-04-24 11:48:12 -0300756
Henrique de Moraes Holschuh176750d2007-04-24 11:48:13 -0300757/*************************************************************************
Henrique de Moraes Holschuh72523742007-04-24 11:48:14 -0300758 * sysfs support helpers
759 */
760
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -0200761struct attribute_set {
762 unsigned int members, max_members;
763 struct attribute_group group;
764};
765
Henrique de Moraes Holschuh72523742007-04-24 11:48:14 -0300766struct attribute_set_obj {
767 struct attribute_set s;
768 struct attribute *a;
769} __attribute__((packed));
770
771static struct attribute_set *create_attr_set(unsigned int max_members,
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -0200772 const char *name)
Henrique de Moraes Holschuh72523742007-04-24 11:48:14 -0300773{
774 struct attribute_set_obj *sobj;
775
776 if (max_members == 0)
777 return NULL;
778
779 /* Allocates space for implicit NULL at the end too */
780 sobj = kzalloc(sizeof(struct attribute_set_obj) +
781 max_members * sizeof(struct attribute *),
782 GFP_KERNEL);
783 if (!sobj)
784 return NULL;
785 sobj->s.max_members = max_members;
786 sobj->s.group.attrs = &sobj->a;
787 sobj->s.group.name = name;
788
789 return &sobj->s;
790}
791
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -0200792#define destroy_attr_set(_set) \
793 kfree(_set);
794
Henrique de Moraes Holschuh72523742007-04-24 11:48:14 -0300795/* not multi-threaded safe, use it in a single thread per set */
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -0200796static int add_to_attr_set(struct attribute_set *s, struct attribute *attr)
Henrique de Moraes Holschuh72523742007-04-24 11:48:14 -0300797{
798 if (!s || !attr)
799 return -EINVAL;
800
801 if (s->members >= s->max_members)
802 return -ENOMEM;
803
804 s->group.attrs[s->members] = attr;
805 s->members++;
806
807 return 0;
808}
809
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -0200810static int add_many_to_attr_set(struct attribute_set *s,
Henrique de Moraes Holschuh72523742007-04-24 11:48:14 -0300811 struct attribute **attr,
812 unsigned int count)
813{
814 int i, res;
815
816 for (i = 0; i < count; i++) {
817 res = add_to_attr_set(s, attr[i]);
818 if (res)
819 return res;
820 }
821
822 return 0;
823}
824
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -0200825static void delete_attr_set(struct attribute_set *s, struct kobject *kobj)
Henrique de Moraes Holschuh72523742007-04-24 11:48:14 -0300826{
827 sysfs_remove_group(kobj, &s->group);
828 destroy_attr_set(s);
829}
830
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -0200831#define register_attr_set_with_sysfs(_attr_set, _kobj) \
832 sysfs_create_group(_kobj, &_attr_set->group)
833
Henrique de Moraes Holschuh72523742007-04-24 11:48:14 -0300834static int parse_strtoul(const char *buf,
835 unsigned long max, unsigned long *value)
836{
837 char *endp;
838
Henrique de Moraes Holschuh32afbf02007-10-08 10:12:56 -0300839 while (*buf && isspace(*buf))
840 buf++;
Henrique de Moraes Holschuh72523742007-04-24 11:48:14 -0300841 *value = simple_strtoul(buf, &endp, 0);
842 while (*endp && isspace(*endp))
843 endp++;
844 if (*endp || *value > max)
845 return -EINVAL;
846
847 return 0;
848}
849
Henrique de Moraes Holschuhd64c81c2008-10-18 14:23:55 -0300850static void tpacpi_disable_brightness_delay(void)
851{
852 if (acpi_evalf(hkey_handle, NULL, "PWMS", "qvd", 0))
853 printk(TPACPI_NOTICE
854 "ACPI backlight control delay disabled\n");
855}
856
Henrique de Moraes Holschuhb5972792008-04-26 01:02:17 -0300857static int __init tpacpi_query_bcl_levels(acpi_handle handle)
858{
859 struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
860 union acpi_object *obj;
861 int rc;
862
863 if (ACPI_SUCCESS(acpi_evaluate_object(handle, NULL, NULL, &buffer))) {
864 obj = (union acpi_object *)buffer.pointer;
865 if (!obj || (obj->type != ACPI_TYPE_PACKAGE)) {
866 printk(TPACPI_ERR "Unknown _BCL data, "
867 "please report this to %s\n", TPACPI_MAIL);
868 rc = 0;
869 } else {
870 rc = obj->package.count;
871 }
872 } else {
873 return 0;
874 }
875
876 kfree(buffer.pointer);
877 return rc;
878}
879
880static acpi_status __init tpacpi_acpi_walk_find_bcl(acpi_handle handle,
881 u32 lvl, void *context, void **rv)
882{
883 char name[ACPI_PATH_SEGMENT_LENGTH];
884 struct acpi_buffer buffer = { sizeof(name), &name };
885
886 if (ACPI_SUCCESS(acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer)) &&
887 !strncmp("_BCL", name, sizeof(name) - 1)) {
888 BUG_ON(!rv || !*rv);
889 **(int **)rv = tpacpi_query_bcl_levels(handle);
890 return AE_CTRL_TERMINATE;
891 } else {
892 return AE_OK;
893 }
894}
895
896/*
897 * Returns 0 (no ACPI _BCL or _BCL invalid), or size of brightness map
898 */
899static int __init tpacpi_check_std_acpi_brightness_support(void)
900{
901 int status;
902 int bcl_levels = 0;
903 void *bcl_ptr = &bcl_levels;
904
905 if (!vid_handle) {
906 TPACPI_ACPIHANDLE_INIT(vid);
907 }
908 if (!vid_handle)
909 return 0;
910
911 /*
912 * Search for a _BCL method, and execute it. This is safe on all
913 * ThinkPads, and as a side-effect, _BCL will place a Lenovo Vista
914 * BIOS in ACPI backlight control mode. We do NOT have to care
915 * about calling the _BCL method in an enabled video device, any
916 * will do for our purposes.
917 */
918
919 status = acpi_walk_namespace(ACPI_TYPE_METHOD, vid_handle, 3,
920 tpacpi_acpi_walk_find_bcl, NULL,
921 &bcl_ptr);
922
923 if (ACPI_SUCCESS(status) && bcl_levels > 2) {
924 tp_features.bright_acpimode = 1;
925 return (bcl_levels - 2);
926 }
927
928 return 0;
929}
930
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -0300931static int __init tpacpi_new_rfkill(const unsigned int id,
932 struct rfkill **rfk,
933 const enum rfkill_type rfktype,
934 const char *name,
935 int (*toggle_radio)(void *, enum rfkill_state),
936 int (*get_state)(void *, enum rfkill_state *))
937{
938 int res;
939 enum rfkill_state initial_state;
940
941 *rfk = rfkill_allocate(&tpacpi_pdev->dev, rfktype);
942 if (!*rfk) {
943 printk(TPACPI_ERR
944 "failed to allocate memory for rfkill class\n");
945 return -ENOMEM;
946 }
947
948 (*rfk)->name = name;
949 (*rfk)->get_state = get_state;
950 (*rfk)->toggle_radio = toggle_radio;
951
952 if (!get_state(NULL, &initial_state))
953 (*rfk)->state = initial_state;
954
955 res = rfkill_register(*rfk);
956 if (res < 0) {
957 printk(TPACPI_ERR
958 "failed to register %s rfkill switch: %d\n",
959 name, res);
960 rfkill_free(*rfk);
961 *rfk = NULL;
962 return res;
963 }
964
965 return 0;
966}
967
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -0200968/*************************************************************************
969 * thinkpad-acpi driver attributes
970 */
971
972/* interface_version --------------------------------------------------- */
973static ssize_t tpacpi_driver_interface_version_show(
974 struct device_driver *drv,
975 char *buf)
976{
977 return snprintf(buf, PAGE_SIZE, "0x%08x\n", TPACPI_SYSFS_VERSION);
978}
979
980static DRIVER_ATTR(interface_version, S_IRUGO,
981 tpacpi_driver_interface_version_show, NULL);
982
983/* debug_level --------------------------------------------------------- */
984static ssize_t tpacpi_driver_debug_show(struct device_driver *drv,
985 char *buf)
986{
987 return snprintf(buf, PAGE_SIZE, "0x%04x\n", dbg_level);
988}
989
990static ssize_t tpacpi_driver_debug_store(struct device_driver *drv,
991 const char *buf, size_t count)
992{
993 unsigned long t;
994
995 if (parse_strtoul(buf, 0xffff, &t))
996 return -EINVAL;
997
998 dbg_level = t;
999
1000 return count;
1001}
1002
1003static DRIVER_ATTR(debug_level, S_IWUSR | S_IRUGO,
1004 tpacpi_driver_debug_show, tpacpi_driver_debug_store);
1005
1006/* version ------------------------------------------------------------- */
1007static ssize_t tpacpi_driver_version_show(struct device_driver *drv,
1008 char *buf)
1009{
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02001010 return snprintf(buf, PAGE_SIZE, "%s v%s\n",
1011 TPACPI_DESC, TPACPI_VERSION);
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02001012}
1013
1014static DRIVER_ATTR(version, S_IRUGO,
1015 tpacpi_driver_version_show, NULL);
1016
1017/* --------------------------------------------------------------------- */
1018
Henrique de Moraes Holschuha73f3092009-01-11 03:01:00 -02001019#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
1020
1021static void tpacpi_send_radiosw_update(void);
1022
1023/* wlsw_emulstate ------------------------------------------------------ */
1024static ssize_t tpacpi_driver_wlsw_emulstate_show(struct device_driver *drv,
1025 char *buf)
1026{
1027 return snprintf(buf, PAGE_SIZE, "%d\n", !!tpacpi_wlsw_emulstate);
1028}
1029
1030static ssize_t tpacpi_driver_wlsw_emulstate_store(struct device_driver *drv,
1031 const char *buf, size_t count)
1032{
1033 unsigned long t;
1034
1035 if (parse_strtoul(buf, 1, &t))
1036 return -EINVAL;
1037
1038 if (tpacpi_wlsw_emulstate != t) {
1039 tpacpi_wlsw_emulstate = !!t;
1040 tpacpi_send_radiosw_update();
1041 } else
1042 tpacpi_wlsw_emulstate = !!t;
1043
1044 return count;
1045}
1046
1047static DRIVER_ATTR(wlsw_emulstate, S_IWUSR | S_IRUGO,
1048 tpacpi_driver_wlsw_emulstate_show,
1049 tpacpi_driver_wlsw_emulstate_store);
1050
1051/* bluetooth_emulstate ------------------------------------------------- */
1052static ssize_t tpacpi_driver_bluetooth_emulstate_show(
1053 struct device_driver *drv,
1054 char *buf)
1055{
1056 return snprintf(buf, PAGE_SIZE, "%d\n", !!tpacpi_bluetooth_emulstate);
1057}
1058
1059static ssize_t tpacpi_driver_bluetooth_emulstate_store(
1060 struct device_driver *drv,
1061 const char *buf, size_t count)
1062{
1063 unsigned long t;
1064
1065 if (parse_strtoul(buf, 1, &t))
1066 return -EINVAL;
1067
1068 tpacpi_bluetooth_emulstate = !!t;
1069
1070 return count;
1071}
1072
1073static DRIVER_ATTR(bluetooth_emulstate, S_IWUSR | S_IRUGO,
1074 tpacpi_driver_bluetooth_emulstate_show,
1075 tpacpi_driver_bluetooth_emulstate_store);
1076
1077/* wwan_emulstate ------------------------------------------------- */
1078static ssize_t tpacpi_driver_wwan_emulstate_show(
1079 struct device_driver *drv,
1080 char *buf)
1081{
1082 return snprintf(buf, PAGE_SIZE, "%d\n", !!tpacpi_wwan_emulstate);
1083}
1084
1085static ssize_t tpacpi_driver_wwan_emulstate_store(
1086 struct device_driver *drv,
1087 const char *buf, size_t count)
1088{
1089 unsigned long t;
1090
1091 if (parse_strtoul(buf, 1, &t))
1092 return -EINVAL;
1093
1094 tpacpi_wwan_emulstate = !!t;
1095
1096 return count;
1097}
1098
1099static DRIVER_ATTR(wwan_emulstate, S_IWUSR | S_IRUGO,
1100 tpacpi_driver_wwan_emulstate_show,
1101 tpacpi_driver_wwan_emulstate_store);
1102
1103#endif
1104
1105/* --------------------------------------------------------------------- */
1106
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02001107static struct driver_attribute *tpacpi_driver_attributes[] = {
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02001108 &driver_attr_debug_level, &driver_attr_version,
1109 &driver_attr_interface_version,
1110};
1111
1112static int __init tpacpi_create_driver_attributes(struct device_driver *drv)
1113{
1114 int i, res;
1115
1116 i = 0;
1117 res = 0;
1118 while (!res && i < ARRAY_SIZE(tpacpi_driver_attributes)) {
1119 res = driver_create_file(drv, tpacpi_driver_attributes[i]);
1120 i++;
1121 }
1122
Henrique de Moraes Holschuha73f3092009-01-11 03:01:00 -02001123#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
1124 if (!res && dbg_wlswemul)
1125 res = driver_create_file(drv, &driver_attr_wlsw_emulstate);
1126 if (!res && dbg_bluetoothemul)
1127 res = driver_create_file(drv, &driver_attr_bluetooth_emulstate);
1128 if (!res && dbg_wwanemul)
1129 res = driver_create_file(drv, &driver_attr_wwan_emulstate);
1130#endif
1131
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02001132 return res;
1133}
1134
1135static void tpacpi_remove_driver_attributes(struct device_driver *drv)
1136{
1137 int i;
1138
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02001139 for (i = 0; i < ARRAY_SIZE(tpacpi_driver_attributes); i++)
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02001140 driver_remove_file(drv, tpacpi_driver_attributes[i]);
Henrique de Moraes Holschuha73f3092009-01-11 03:01:00 -02001141
1142#ifdef THINKPAD_ACPI_DEBUGFACILITIES
1143 driver_remove_file(drv, &driver_attr_wlsw_emulstate);
1144 driver_remove_file(drv, &driver_attr_bluetooth_emulstate);
1145 driver_remove_file(drv, &driver_attr_wwan_emulstate);
1146#endif
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02001147}
1148
Henrique de Moraes Holschuh54ae1502007-04-24 11:48:12 -03001149/****************************************************************************
1150 ****************************************************************************
1151 *
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03001152 * Subdrivers
1153 *
1154 ****************************************************************************
1155 ****************************************************************************/
1156
1157/*************************************************************************
Henrique de Moraes Holschuh142cfc92007-04-21 11:08:26 -03001158 * thinkpad-acpi init subdriver
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03001159 */
1160
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03001161static int __init thinkpad_acpi_driver_init(struct ibm_init_struct *iibm)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001162{
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02001163 printk(TPACPI_INFO "%s v%s\n", TPACPI_DESC, TPACPI_VERSION);
1164 printk(TPACPI_INFO "%s\n", TPACPI_URL);
Linus Torvalds1da177e2005-04-16 15:20:36 -07001165
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02001166 printk(TPACPI_INFO "ThinkPad BIOS %s, EC %s\n",
Henrique de Moraes Holschuhd5a2f2f2007-07-18 23:45:42 -03001167 (thinkpad_id.bios_version_str) ?
1168 thinkpad_id.bios_version_str : "unknown",
1169 (thinkpad_id.ec_version_str) ?
1170 thinkpad_id.ec_version_str : "unknown");
1171
1172 if (thinkpad_id.vendor && thinkpad_id.model_str)
Henrique de Moraes Holschuh8c74adb2008-04-26 01:02:19 -03001173 printk(TPACPI_INFO "%s %s, model %s\n",
Henrique de Moraes Holschuhd5a2f2f2007-07-18 23:45:42 -03001174 (thinkpad_id.vendor == PCI_VENDOR_ID_IBM) ?
1175 "IBM" : ((thinkpad_id.vendor ==
1176 PCI_VENDOR_ID_LENOVO) ?
1177 "Lenovo" : "Unknown vendor"),
Henrique de Moraes Holschuh8c74adb2008-04-26 01:02:19 -03001178 thinkpad_id.model_str,
1179 (thinkpad_id.nummodel_str) ?
1180 thinkpad_id.nummodel_str : "unknown");
Henrique de Moraes Holschuh3945ac32007-02-06 19:13:44 -02001181
Linus Torvalds1da177e2005-04-16 15:20:36 -07001182 return 0;
1183}
1184
Henrique de Moraes Holschuh643f12d2007-03-29 01:58:43 -03001185static int thinkpad_acpi_driver_read(char *p)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001186{
1187 int len = 0;
1188
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02001189 len += sprintf(p + len, "driver:\t\t%s\n", TPACPI_DESC);
1190 len += sprintf(p + len, "version:\t%s\n", TPACPI_VERSION);
Linus Torvalds1da177e2005-04-16 15:20:36 -07001191
1192 return len;
1193}
1194
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03001195static struct ibm_struct thinkpad_acpi_driver_data = {
1196 .name = "driver",
1197 .read = thinkpad_acpi_driver_read,
1198};
1199
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03001200/*************************************************************************
1201 * Hotkey subdriver
1202 */
1203
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -02001204enum { /* hot key scan codes (derived from ACPI DSDT) */
1205 TP_ACPI_HOTKEYSCAN_FNF1 = 0,
1206 TP_ACPI_HOTKEYSCAN_FNF2,
1207 TP_ACPI_HOTKEYSCAN_FNF3,
1208 TP_ACPI_HOTKEYSCAN_FNF4,
1209 TP_ACPI_HOTKEYSCAN_FNF5,
1210 TP_ACPI_HOTKEYSCAN_FNF6,
1211 TP_ACPI_HOTKEYSCAN_FNF7,
1212 TP_ACPI_HOTKEYSCAN_FNF8,
1213 TP_ACPI_HOTKEYSCAN_FNF9,
1214 TP_ACPI_HOTKEYSCAN_FNF10,
1215 TP_ACPI_HOTKEYSCAN_FNF11,
1216 TP_ACPI_HOTKEYSCAN_FNF12,
1217 TP_ACPI_HOTKEYSCAN_FNBACKSPACE,
1218 TP_ACPI_HOTKEYSCAN_FNINSERT,
1219 TP_ACPI_HOTKEYSCAN_FNDELETE,
1220 TP_ACPI_HOTKEYSCAN_FNHOME,
1221 TP_ACPI_HOTKEYSCAN_FNEND,
1222 TP_ACPI_HOTKEYSCAN_FNPAGEUP,
1223 TP_ACPI_HOTKEYSCAN_FNPAGEDOWN,
1224 TP_ACPI_HOTKEYSCAN_FNSPACE,
1225 TP_ACPI_HOTKEYSCAN_VOLUMEUP,
1226 TP_ACPI_HOTKEYSCAN_VOLUMEDOWN,
1227 TP_ACPI_HOTKEYSCAN_MUTE,
1228 TP_ACPI_HOTKEYSCAN_THINKPAD,
1229};
1230
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001231enum { /* Keys available through NVRAM polling */
1232 TPACPI_HKEY_NVRAM_KNOWN_MASK = 0x00fb88c0U,
1233 TPACPI_HKEY_NVRAM_GOOD_MASK = 0x00fb8000U,
1234};
1235
1236enum { /* Positions of some of the keys in hotkey masks */
1237 TP_ACPI_HKEY_DISPSWTCH_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNF7,
1238 TP_ACPI_HKEY_DISPXPAND_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNF8,
1239 TP_ACPI_HKEY_HIBERNATE_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNF12,
1240 TP_ACPI_HKEY_BRGHTUP_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNHOME,
1241 TP_ACPI_HKEY_BRGHTDWN_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNEND,
1242 TP_ACPI_HKEY_THNKLGHT_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNPAGEUP,
1243 TP_ACPI_HKEY_ZOOM_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNSPACE,
1244 TP_ACPI_HKEY_VOLUP_MASK = 1 << TP_ACPI_HOTKEYSCAN_VOLUMEUP,
1245 TP_ACPI_HKEY_VOLDWN_MASK = 1 << TP_ACPI_HOTKEYSCAN_VOLUMEDOWN,
1246 TP_ACPI_HKEY_MUTE_MASK = 1 << TP_ACPI_HOTKEYSCAN_MUTE,
1247 TP_ACPI_HKEY_THINKPAD_MASK = 1 << TP_ACPI_HOTKEYSCAN_THINKPAD,
1248};
1249
1250enum { /* NVRAM to ACPI HKEY group map */
1251 TP_NVRAM_HKEY_GROUP_HK2 = TP_ACPI_HKEY_THINKPAD_MASK |
1252 TP_ACPI_HKEY_ZOOM_MASK |
1253 TP_ACPI_HKEY_DISPSWTCH_MASK |
1254 TP_ACPI_HKEY_HIBERNATE_MASK,
1255 TP_NVRAM_HKEY_GROUP_BRIGHTNESS = TP_ACPI_HKEY_BRGHTUP_MASK |
1256 TP_ACPI_HKEY_BRGHTDWN_MASK,
1257 TP_NVRAM_HKEY_GROUP_VOLUME = TP_ACPI_HKEY_VOLUP_MASK |
1258 TP_ACPI_HKEY_VOLDWN_MASK |
1259 TP_ACPI_HKEY_MUTE_MASK,
1260};
1261
1262#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
1263struct tp_nvram_state {
1264 u16 thinkpad_toggle:1;
1265 u16 zoom_toggle:1;
1266 u16 display_toggle:1;
1267 u16 thinklight_toggle:1;
1268 u16 hibernate_toggle:1;
1269 u16 displayexp_toggle:1;
1270 u16 display_state:1;
1271 u16 brightness_toggle:1;
1272 u16 volume_toggle:1;
1273 u16 mute:1;
1274
1275 u8 brightness_level;
1276 u8 volume_level;
1277};
1278
1279static struct task_struct *tpacpi_hotkey_task;
1280static u32 hotkey_source_mask; /* bit mask 0=ACPI,1=NVRAM */
1281static int hotkey_poll_freq = 10; /* Hz */
1282static struct mutex hotkey_thread_mutex;
1283static struct mutex hotkey_thread_data_mutex;
1284static unsigned int hotkey_config_change;
1285
1286#else /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
1287
1288#define hotkey_source_mask 0U
1289
1290#endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
1291
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -02001292static struct mutex hotkey_mutex;
1293
Henrique de Moraes Holschuha713b4d2008-01-08 13:02:52 -02001294static enum { /* Reasons for waking up */
1295 TP_ACPI_WAKEUP_NONE = 0, /* None or unknown */
1296 TP_ACPI_WAKEUP_BAYEJ, /* Bay ejection request */
1297 TP_ACPI_WAKEUP_UNDOCK, /* Undock request */
1298} hotkey_wakeup_reason;
1299
1300static int hotkey_autosleep_ack;
1301
Borislav Deianov78f81cc2005-08-17 00:00:00 -04001302static int hotkey_orig_status;
Henrique de Moraes Holschuhae92bd12007-07-18 23:45:29 -03001303static u32 hotkey_orig_mask;
Henrique de Moraes Holschuh9b010de2007-07-18 23:45:30 -03001304static u32 hotkey_all_mask;
Henrique de Moraes Holschuh6a38abb2007-07-18 23:45:35 -03001305static u32 hotkey_reserved_mask;
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02001306static u32 hotkey_mask;
Henrique de Moraes Holschuh6a38abb2007-07-18 23:45:35 -03001307
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02001308static unsigned int hotkey_report_mode;
1309
Henrique de Moraes Holschuhedf0e0e2007-07-18 23:45:44 -03001310static u16 *hotkey_keycode_map;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04001311
Henrique de Moraes Holschuh94954cc2007-07-18 23:45:27 -03001312static struct attribute_set *hotkey_dev_attributes;
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03001313
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001314#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
1315#define HOTKEY_CONFIG_CRITICAL_START \
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02001316 do { \
1317 mutex_lock(&hotkey_thread_data_mutex); \
1318 hotkey_config_change++; \
1319 } while (0);
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001320#define HOTKEY_CONFIG_CRITICAL_END \
1321 mutex_unlock(&hotkey_thread_data_mutex);
1322#else
1323#define HOTKEY_CONFIG_CRITICAL_START
1324#define HOTKEY_CONFIG_CRITICAL_END
1325#endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
1326
Henrique de Moraes Holschuh6c231bd2008-02-16 02:17:58 -02001327/* HKEY.MHKG() return bits */
1328#define TP_HOTKEY_TABLET_MASK (1 << 3)
1329
Henrique de Moraes Holschuh74941a62007-07-18 23:45:31 -03001330static int hotkey_get_wlsw(int *status)
1331{
Henrique de Moraes Holschuha73f3092009-01-11 03:01:00 -02001332#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
1333 if (dbg_wlswemul) {
1334 *status = !!tpacpi_wlsw_emulstate;
1335 return 0;
1336 }
1337#endif
Henrique de Moraes Holschuh74941a62007-07-18 23:45:31 -03001338 if (!acpi_evalf(hkey_handle, status, "WLSW", "d"))
1339 return -EIO;
1340 return 0;
1341}
1342
Henrique de Moraes Holschuh6c231bd2008-02-16 02:17:58 -02001343static int hotkey_get_tablet_mode(int *status)
1344{
1345 int s;
1346
1347 if (!acpi_evalf(hkey_handle, &s, "MHKG", "d"))
1348 return -EIO;
1349
Henrique de Moraes Holschuhcee47f52008-03-04 14:29:21 -08001350 *status = ((s & TP_HOTKEY_TABLET_MASK) != 0);
1351 return 0;
Henrique de Moraes Holschuh6c231bd2008-02-16 02:17:58 -02001352}
1353
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02001354/*
1355 * Call with hotkey_mutex held
1356 */
1357static int hotkey_mask_get(void)
1358{
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001359 u32 m = 0;
1360
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02001361 if (tp_features.hotkey_mask) {
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001362 if (!acpi_evalf(hkey_handle, &m, "DHKN", "d"))
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02001363 return -EIO;
1364 }
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001365 hotkey_mask = m | (hotkey_source_mask & hotkey_mask);
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02001366
1367 return 0;
1368}
1369
1370/*
1371 * Call with hotkey_mutex held
1372 */
1373static int hotkey_mask_set(u32 mask)
1374{
1375 int i;
1376 int rc = 0;
1377
1378 if (tp_features.hotkey_mask) {
Henrique de Moraes Holschuh92889022008-04-26 01:02:18 -03001379 if (!tp_warned.hotkey_mask_ff &&
1380 (mask == 0xffff || mask == 0xffffff ||
1381 mask == 0xffffffff)) {
1382 tp_warned.hotkey_mask_ff = 1;
1383 printk(TPACPI_NOTICE
1384 "setting the hotkey mask to 0x%08x is likely "
1385 "not the best way to go about it\n", mask);
1386 printk(TPACPI_NOTICE
1387 "please consider using the driver defaults, "
1388 "and refer to up-to-date thinkpad-acpi "
1389 "documentation\n");
1390 }
1391
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001392 HOTKEY_CONFIG_CRITICAL_START
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02001393 for (i = 0; i < 32; i++) {
1394 u32 m = 1 << i;
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001395 /* enable in firmware mask only keys not in NVRAM
1396 * mode, but enable the key in the cached hotkey_mask
1397 * regardless of mode, or the key will end up
1398 * disabled by hotkey_mask_get() */
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02001399 if (!acpi_evalf(hkey_handle,
1400 NULL, "MHKM", "vdd", i + 1,
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001401 !!((mask & ~hotkey_source_mask) & m))) {
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02001402 rc = -EIO;
1403 break;
1404 } else {
1405 hotkey_mask = (hotkey_mask & ~m) | (mask & m);
1406 }
1407 }
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001408 HOTKEY_CONFIG_CRITICAL_END
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02001409
1410 /* hotkey_mask_get must be called unconditionally below */
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001411 if (!hotkey_mask_get() && !rc &&
1412 (hotkey_mask & ~hotkey_source_mask) !=
1413 (mask & ~hotkey_source_mask)) {
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02001414 printk(TPACPI_NOTICE
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02001415 "requested hot key mask 0x%08x, but "
1416 "firmware forced it to 0x%08x\n",
1417 mask, hotkey_mask);
1418 }
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001419 } else {
1420#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
1421 HOTKEY_CONFIG_CRITICAL_START
1422 hotkey_mask = mask & hotkey_source_mask;
1423 HOTKEY_CONFIG_CRITICAL_END
1424 hotkey_mask_get();
1425 if (hotkey_mask != mask) {
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02001426 printk(TPACPI_NOTICE
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001427 "requested hot key mask 0x%08x, "
1428 "forced to 0x%08x (NVRAM poll mask is "
1429 "0x%08x): no firmware mask support\n",
1430 mask, hotkey_mask, hotkey_source_mask);
1431 }
1432#else
1433 hotkey_mask_get();
1434 rc = -ENXIO;
1435#endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02001436 }
1437
1438 return rc;
1439}
1440
1441static int hotkey_status_get(int *status)
1442{
1443 if (!acpi_evalf(hkey_handle, status, "DHKC", "d"))
1444 return -EIO;
1445
1446 return 0;
1447}
1448
1449static int hotkey_status_set(int status)
1450{
1451 if (!acpi_evalf(hkey_handle, NULL, "MHKC", "vd", status))
1452 return -EIO;
1453
1454 return 0;
1455}
1456
Henrique de Moraes Holschuh6c231bd2008-02-16 02:17:58 -02001457static void tpacpi_input_send_tabletsw(void)
Henrique de Moraes Holschuhb3ec6f92008-02-16 02:17:55 -02001458{
Henrique de Moraes Holschuh6c231bd2008-02-16 02:17:58 -02001459 int state;
Henrique de Moraes Holschuhb3ec6f92008-02-16 02:17:55 -02001460
Henrique de Moraes Holschuh6c231bd2008-02-16 02:17:58 -02001461 if (tp_features.hotkey_tablet &&
1462 !hotkey_get_tablet_mode(&state)) {
1463 mutex_lock(&tpacpi_inputdev_send_mutex);
Henrique de Moraes Holschuhb3ec6f92008-02-16 02:17:55 -02001464
Henrique de Moraes Holschuh6c231bd2008-02-16 02:17:58 -02001465 input_report_switch(tpacpi_inputdev,
1466 SW_TABLET_MODE, !!state);
1467 input_sync(tpacpi_inputdev);
1468
1469 mutex_unlock(&tpacpi_inputdev_send_mutex);
1470 }
Henrique de Moraes Holschuhb3ec6f92008-02-16 02:17:55 -02001471}
1472
Henrique de Moraes Holschuhb7c8c202008-01-08 13:02:40 -02001473static void tpacpi_input_send_key(unsigned int scancode)
1474{
1475 unsigned int keycode;
1476
1477 keycode = hotkey_keycode_map[scancode];
1478
1479 if (keycode != KEY_RESERVED) {
1480 mutex_lock(&tpacpi_inputdev_send_mutex);
1481
1482 input_report_key(tpacpi_inputdev, keycode, 1);
1483 if (keycode == KEY_UNKNOWN)
1484 input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN,
1485 scancode);
1486 input_sync(tpacpi_inputdev);
1487
1488 input_report_key(tpacpi_inputdev, keycode, 0);
1489 if (keycode == KEY_UNKNOWN)
1490 input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN,
1491 scancode);
1492 input_sync(tpacpi_inputdev);
1493
1494 mutex_unlock(&tpacpi_inputdev_send_mutex);
1495 }
1496}
1497
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001498#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
1499static struct tp_acpi_drv_struct ibm_hotkey_acpidriver;
1500
1501static void tpacpi_hotkey_send_key(unsigned int scancode)
1502{
1503 tpacpi_input_send_key(scancode);
1504 if (hotkey_report_mode < 2) {
1505 acpi_bus_generate_proc_event(ibm_hotkey_acpidriver.device,
1506 0x80, 0x1001 + scancode);
1507 }
1508}
1509
1510static void hotkey_read_nvram(struct tp_nvram_state *n, u32 m)
1511{
1512 u8 d;
1513
1514 if (m & TP_NVRAM_HKEY_GROUP_HK2) {
1515 d = nvram_read_byte(TP_NVRAM_ADDR_HK2);
1516 n->thinkpad_toggle = !!(d & TP_NVRAM_MASK_HKT_THINKPAD);
1517 n->zoom_toggle = !!(d & TP_NVRAM_MASK_HKT_ZOOM);
1518 n->display_toggle = !!(d & TP_NVRAM_MASK_HKT_DISPLAY);
1519 n->hibernate_toggle = !!(d & TP_NVRAM_MASK_HKT_HIBERNATE);
1520 }
1521 if (m & TP_ACPI_HKEY_THNKLGHT_MASK) {
1522 d = nvram_read_byte(TP_NVRAM_ADDR_THINKLIGHT);
1523 n->thinklight_toggle = !!(d & TP_NVRAM_MASK_THINKLIGHT);
1524 }
1525 if (m & TP_ACPI_HKEY_DISPXPAND_MASK) {
1526 d = nvram_read_byte(TP_NVRAM_ADDR_VIDEO);
1527 n->displayexp_toggle =
1528 !!(d & TP_NVRAM_MASK_HKT_DISPEXPND);
1529 }
1530 if (m & TP_NVRAM_HKEY_GROUP_BRIGHTNESS) {
1531 d = nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS);
1532 n->brightness_level = (d & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
1533 >> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
1534 n->brightness_toggle =
1535 !!(d & TP_NVRAM_MASK_HKT_BRIGHTNESS);
1536 }
1537 if (m & TP_NVRAM_HKEY_GROUP_VOLUME) {
1538 d = nvram_read_byte(TP_NVRAM_ADDR_MIXER);
1539 n->volume_level = (d & TP_NVRAM_MASK_LEVEL_VOLUME)
1540 >> TP_NVRAM_POS_LEVEL_VOLUME;
1541 n->mute = !!(d & TP_NVRAM_MASK_MUTE);
1542 n->volume_toggle = !!(d & TP_NVRAM_MASK_HKT_VOLUME);
1543 }
1544}
1545
1546#define TPACPI_COMPARE_KEY(__scancode, __member) \
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02001547 do { \
1548 if ((mask & (1 << __scancode)) && \
1549 oldn->__member != newn->__member) \
1550 tpacpi_hotkey_send_key(__scancode); \
1551 } while (0)
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001552
1553#define TPACPI_MAY_SEND_KEY(__scancode) \
1554 do { if (mask & (1 << __scancode)) \
1555 tpacpi_hotkey_send_key(__scancode); } while (0)
1556
1557static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn,
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02001558 struct tp_nvram_state *newn,
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001559 u32 mask)
1560{
1561 TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_THINKPAD, thinkpad_toggle);
1562 TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNSPACE, zoom_toggle);
1563 TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF7, display_toggle);
1564 TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF12, hibernate_toggle);
1565
1566 TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNPAGEUP, thinklight_toggle);
1567
1568 TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF8, displayexp_toggle);
1569
1570 /* handle volume */
1571 if (oldn->volume_toggle != newn->volume_toggle) {
1572 if (oldn->mute != newn->mute) {
1573 TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE);
1574 }
1575 if (oldn->volume_level > newn->volume_level) {
1576 TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
1577 } else if (oldn->volume_level < newn->volume_level) {
1578 TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
1579 } else if (oldn->mute == newn->mute) {
1580 /* repeated key presses that didn't change state */
1581 if (newn->mute) {
1582 TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE);
1583 } else if (newn->volume_level != 0) {
1584 TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
1585 } else {
1586 TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
1587 }
1588 }
1589 }
1590
1591 /* handle brightness */
1592 if (oldn->brightness_toggle != newn->brightness_toggle) {
1593 if (oldn->brightness_level < newn->brightness_level) {
1594 TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
1595 } else if (oldn->brightness_level > newn->brightness_level) {
1596 TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND);
1597 } else {
1598 /* repeated key presses that didn't change state */
1599 if (newn->brightness_level != 0) {
1600 TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
1601 } else {
1602 TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND);
1603 }
1604 }
1605 }
1606}
1607
1608#undef TPACPI_COMPARE_KEY
1609#undef TPACPI_MAY_SEND_KEY
1610
1611static int hotkey_kthread(void *data)
1612{
1613 struct tp_nvram_state s[2];
1614 u32 mask;
1615 unsigned int si, so;
1616 unsigned long t;
1617 unsigned int change_detector, must_reset;
1618
1619 mutex_lock(&hotkey_thread_mutex);
1620
1621 if (tpacpi_lifecycle == TPACPI_LIFE_EXITING)
1622 goto exit;
1623
1624 set_freezable();
1625
1626 so = 0;
1627 si = 1;
1628 t = 0;
1629
1630 /* Initial state for compares */
1631 mutex_lock(&hotkey_thread_data_mutex);
1632 change_detector = hotkey_config_change;
1633 mask = hotkey_source_mask & hotkey_mask;
1634 mutex_unlock(&hotkey_thread_data_mutex);
1635 hotkey_read_nvram(&s[so], mask);
1636
1637 while (!kthread_should_stop() && hotkey_poll_freq) {
1638 if (t == 0)
1639 t = 1000/hotkey_poll_freq;
1640 t = msleep_interruptible(t);
1641 if (unlikely(kthread_should_stop()))
1642 break;
1643 must_reset = try_to_freeze();
1644 if (t > 0 && !must_reset)
1645 continue;
1646
1647 mutex_lock(&hotkey_thread_data_mutex);
1648 if (must_reset || hotkey_config_change != change_detector) {
1649 /* forget old state on thaw or config change */
1650 si = so;
1651 t = 0;
1652 change_detector = hotkey_config_change;
1653 }
1654 mask = hotkey_source_mask & hotkey_mask;
1655 mutex_unlock(&hotkey_thread_data_mutex);
1656
1657 if (likely(mask)) {
1658 hotkey_read_nvram(&s[si], mask);
1659 if (likely(si != so)) {
1660 hotkey_compare_and_issue_event(&s[so], &s[si],
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02001661 mask);
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001662 }
1663 }
1664
1665 so = si;
1666 si ^= 1;
1667 }
1668
1669exit:
1670 mutex_unlock(&hotkey_thread_mutex);
1671 return 0;
1672}
1673
1674static void hotkey_poll_stop_sync(void)
1675{
1676 if (tpacpi_hotkey_task) {
1677 if (frozen(tpacpi_hotkey_task) ||
1678 freezing(tpacpi_hotkey_task))
1679 thaw_process(tpacpi_hotkey_task);
1680
1681 kthread_stop(tpacpi_hotkey_task);
1682 tpacpi_hotkey_task = NULL;
1683 mutex_lock(&hotkey_thread_mutex);
1684 /* at this point, the thread did exit */
1685 mutex_unlock(&hotkey_thread_mutex);
1686 }
1687}
1688
1689/* call with hotkey_mutex held */
1690static void hotkey_poll_setup(int may_warn)
1691{
1692 if ((hotkey_source_mask & hotkey_mask) != 0 &&
1693 hotkey_poll_freq > 0 &&
1694 (tpacpi_inputdev->users > 0 || hotkey_report_mode < 2)) {
1695 if (!tpacpi_hotkey_task) {
1696 tpacpi_hotkey_task = kthread_run(hotkey_kthread,
Henrique de Moraes Holschuh95e57ab2008-04-26 01:02:22 -03001697 NULL, TPACPI_NVRAM_KTHREAD_NAME);
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001698 if (IS_ERR(tpacpi_hotkey_task)) {
1699 tpacpi_hotkey_task = NULL;
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02001700 printk(TPACPI_ERR
1701 "could not create kernel thread "
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001702 "for hotkey polling\n");
1703 }
1704 }
1705 } else {
1706 hotkey_poll_stop_sync();
1707 if (may_warn &&
1708 hotkey_source_mask != 0 && hotkey_poll_freq == 0) {
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02001709 printk(TPACPI_NOTICE
1710 "hot keys 0x%08x require polling, "
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001711 "which is currently disabled\n",
1712 hotkey_source_mask);
1713 }
1714 }
1715}
1716
1717static void hotkey_poll_setup_safe(int may_warn)
1718{
1719 mutex_lock(&hotkey_mutex);
1720 hotkey_poll_setup(may_warn);
1721 mutex_unlock(&hotkey_mutex);
1722}
1723
Henrique de Moraes Holschuh1bc6b9c2008-02-16 02:17:52 -02001724#else /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
1725
1726static void hotkey_poll_setup_safe(int __unused)
1727{
1728}
1729
1730#endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
1731
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001732static int hotkey_inputdev_open(struct input_dev *dev)
1733{
1734 switch (tpacpi_lifecycle) {
1735 case TPACPI_LIFE_INIT:
1736 /*
1737 * hotkey_init will call hotkey_poll_setup_safe
1738 * at the appropriate moment
1739 */
1740 return 0;
1741 case TPACPI_LIFE_EXITING:
1742 return -EBUSY;
1743 case TPACPI_LIFE_RUNNING:
1744 hotkey_poll_setup_safe(0);
1745 return 0;
1746 }
1747
1748 /* Should only happen if tpacpi_lifecycle is corrupt */
1749 BUG();
1750 return -EBUSY;
1751}
1752
1753static void hotkey_inputdev_close(struct input_dev *dev)
1754{
1755 /* disable hotkey polling when possible */
1756 if (tpacpi_lifecycle == TPACPI_LIFE_RUNNING)
1757 hotkey_poll_setup_safe(0);
1758}
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001759
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03001760/* sysfs hotkey enable ------------------------------------------------- */
1761static ssize_t hotkey_enable_show(struct device *dev,
1762 struct device_attribute *attr,
1763 char *buf)
1764{
Henrique de Moraes Holschuhae92bd12007-07-18 23:45:29 -03001765 int res, status;
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03001766
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02001767 res = hotkey_status_get(&status);
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03001768 if (res)
1769 return res;
1770
1771 return snprintf(buf, PAGE_SIZE, "%d\n", status);
1772}
1773
1774static ssize_t hotkey_enable_store(struct device *dev,
1775 struct device_attribute *attr,
1776 const char *buf, size_t count)
1777{
1778 unsigned long t;
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02001779 int res;
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03001780
1781 if (parse_strtoul(buf, 1, &t))
1782 return -EINVAL;
1783
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02001784 res = hotkey_status_set(t);
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03001785
1786 return (res) ? res : count;
1787}
1788
1789static struct device_attribute dev_attr_hotkey_enable =
Henrique de Moraes Holschuhcc4c24e2007-05-30 20:50:14 -03001790 __ATTR(hotkey_enable, S_IWUSR | S_IRUGO,
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03001791 hotkey_enable_show, hotkey_enable_store);
1792
1793/* sysfs hotkey mask --------------------------------------------------- */
1794static ssize_t hotkey_mask_show(struct device *dev,
1795 struct device_attribute *attr,
1796 char *buf)
1797{
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02001798 int res;
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03001799
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02001800 if (mutex_lock_interruptible(&hotkey_mutex))
1801 return -ERESTARTSYS;
1802 res = hotkey_mask_get();
1803 mutex_unlock(&hotkey_mutex);
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03001804
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02001805 return (res)?
1806 res : snprintf(buf, PAGE_SIZE, "0x%08x\n", hotkey_mask);
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03001807}
1808
1809static ssize_t hotkey_mask_store(struct device *dev,
1810 struct device_attribute *attr,
1811 const char *buf, size_t count)
1812{
1813 unsigned long t;
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02001814 int res;
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03001815
Henrique de Moraes Holschuhae92bd12007-07-18 23:45:29 -03001816 if (parse_strtoul(buf, 0xffffffffUL, &t))
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03001817 return -EINVAL;
1818
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02001819 if (mutex_lock_interruptible(&hotkey_mutex))
1820 return -ERESTARTSYS;
1821
1822 res = hotkey_mask_set(t);
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001823
1824#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
1825 hotkey_poll_setup(1);
1826#endif
1827
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02001828 mutex_unlock(&hotkey_mutex);
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03001829
1830 return (res) ? res : count;
1831}
1832
1833static struct device_attribute dev_attr_hotkey_mask =
Henrique de Moraes Holschuhcc4c24e2007-05-30 20:50:14 -03001834 __ATTR(hotkey_mask, S_IWUSR | S_IRUGO,
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03001835 hotkey_mask_show, hotkey_mask_store);
1836
1837/* sysfs hotkey bios_enabled ------------------------------------------- */
1838static ssize_t hotkey_bios_enabled_show(struct device *dev,
1839 struct device_attribute *attr,
1840 char *buf)
1841{
1842 return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_orig_status);
1843}
1844
1845static struct device_attribute dev_attr_hotkey_bios_enabled =
Henrique de Moraes Holschuhcc4c24e2007-05-30 20:50:14 -03001846 __ATTR(hotkey_bios_enabled, S_IRUGO, hotkey_bios_enabled_show, NULL);
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03001847
1848/* sysfs hotkey bios_mask ---------------------------------------------- */
1849static ssize_t hotkey_bios_mask_show(struct device *dev,
1850 struct device_attribute *attr,
1851 char *buf)
1852{
Henrique de Moraes Holschuhae92bd12007-07-18 23:45:29 -03001853 return snprintf(buf, PAGE_SIZE, "0x%08x\n", hotkey_orig_mask);
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03001854}
1855
1856static struct device_attribute dev_attr_hotkey_bios_mask =
Henrique de Moraes Holschuhcc4c24e2007-05-30 20:50:14 -03001857 __ATTR(hotkey_bios_mask, S_IRUGO, hotkey_bios_mask_show, NULL);
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03001858
Henrique de Moraes Holschuh9b010de2007-07-18 23:45:30 -03001859/* sysfs hotkey all_mask ----------------------------------------------- */
1860static ssize_t hotkey_all_mask_show(struct device *dev,
1861 struct device_attribute *attr,
1862 char *buf)
1863{
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001864 return snprintf(buf, PAGE_SIZE, "0x%08x\n",
1865 hotkey_all_mask | hotkey_source_mask);
Henrique de Moraes Holschuh9b010de2007-07-18 23:45:30 -03001866}
1867
1868static struct device_attribute dev_attr_hotkey_all_mask =
1869 __ATTR(hotkey_all_mask, S_IRUGO, hotkey_all_mask_show, NULL);
1870
1871/* sysfs hotkey recommended_mask --------------------------------------- */
1872static ssize_t hotkey_recommended_mask_show(struct device *dev,
1873 struct device_attribute *attr,
1874 char *buf)
1875{
1876 return snprintf(buf, PAGE_SIZE, "0x%08x\n",
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001877 (hotkey_all_mask | hotkey_source_mask)
1878 & ~hotkey_reserved_mask);
Henrique de Moraes Holschuh9b010de2007-07-18 23:45:30 -03001879}
1880
1881static struct device_attribute dev_attr_hotkey_recommended_mask =
1882 __ATTR(hotkey_recommended_mask, S_IRUGO,
1883 hotkey_recommended_mask_show, NULL);
1884
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02001885#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
1886
1887/* sysfs hotkey hotkey_source_mask ------------------------------------- */
1888static ssize_t hotkey_source_mask_show(struct device *dev,
1889 struct device_attribute *attr,
1890 char *buf)
1891{
1892 return snprintf(buf, PAGE_SIZE, "0x%08x\n", hotkey_source_mask);
1893}
1894
1895static ssize_t hotkey_source_mask_store(struct device *dev,
1896 struct device_attribute *attr,
1897 const char *buf, size_t count)
1898{
1899 unsigned long t;
1900
1901 if (parse_strtoul(buf, 0xffffffffUL, &t) ||
1902 ((t & ~TPACPI_HKEY_NVRAM_KNOWN_MASK) != 0))
1903 return -EINVAL;
1904
1905 if (mutex_lock_interruptible(&hotkey_mutex))
1906 return -ERESTARTSYS;
1907
1908 HOTKEY_CONFIG_CRITICAL_START
1909 hotkey_source_mask = t;
1910 HOTKEY_CONFIG_CRITICAL_END
1911
1912 hotkey_poll_setup(1);
1913
1914 mutex_unlock(&hotkey_mutex);
1915
1916 return count;
1917}
1918
1919static struct device_attribute dev_attr_hotkey_source_mask =
1920 __ATTR(hotkey_source_mask, S_IWUSR | S_IRUGO,
1921 hotkey_source_mask_show, hotkey_source_mask_store);
1922
1923/* sysfs hotkey hotkey_poll_freq --------------------------------------- */
1924static ssize_t hotkey_poll_freq_show(struct device *dev,
1925 struct device_attribute *attr,
1926 char *buf)
1927{
1928 return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_poll_freq);
1929}
1930
1931static ssize_t hotkey_poll_freq_store(struct device *dev,
1932 struct device_attribute *attr,
1933 const char *buf, size_t count)
1934{
1935 unsigned long t;
1936
1937 if (parse_strtoul(buf, 25, &t))
1938 return -EINVAL;
1939
1940 if (mutex_lock_interruptible(&hotkey_mutex))
1941 return -ERESTARTSYS;
1942
1943 hotkey_poll_freq = t;
1944
1945 hotkey_poll_setup(1);
1946 mutex_unlock(&hotkey_mutex);
1947
1948 return count;
1949}
1950
1951static struct device_attribute dev_attr_hotkey_poll_freq =
1952 __ATTR(hotkey_poll_freq, S_IWUSR | S_IRUGO,
1953 hotkey_poll_freq_show, hotkey_poll_freq_store);
1954
1955#endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
1956
Henrique de Moraes Holschuh50ebec02008-01-08 13:02:55 -02001957/* sysfs hotkey radio_sw (pollable) ------------------------------------ */
Henrique de Moraes Holschuh74941a62007-07-18 23:45:31 -03001958static ssize_t hotkey_radio_sw_show(struct device *dev,
1959 struct device_attribute *attr,
1960 char *buf)
1961{
1962 int res, s;
1963 res = hotkey_get_wlsw(&s);
1964 if (res < 0)
1965 return res;
1966
1967 return snprintf(buf, PAGE_SIZE, "%d\n", !!s);
1968}
1969
1970static struct device_attribute dev_attr_hotkey_radio_sw =
1971 __ATTR(hotkey_radio_sw, S_IRUGO, hotkey_radio_sw_show, NULL);
1972
Henrique de Moraes Holschuh50ebec02008-01-08 13:02:55 -02001973static void hotkey_radio_sw_notify_change(void)
1974{
1975 if (tp_features.hotkey_wlsw)
1976 sysfs_notify(&tpacpi_pdev->dev.kobj, NULL,
1977 "hotkey_radio_sw");
1978}
1979
Henrique de Moraes Holschuh6c231bd2008-02-16 02:17:58 -02001980/* sysfs hotkey tablet mode (pollable) --------------------------------- */
1981static ssize_t hotkey_tablet_mode_show(struct device *dev,
1982 struct device_attribute *attr,
1983 char *buf)
1984{
1985 int res, s;
1986 res = hotkey_get_tablet_mode(&s);
1987 if (res < 0)
1988 return res;
1989
1990 return snprintf(buf, PAGE_SIZE, "%d\n", !!s);
1991}
1992
1993static struct device_attribute dev_attr_hotkey_tablet_mode =
1994 __ATTR(hotkey_tablet_mode, S_IRUGO, hotkey_tablet_mode_show, NULL);
1995
1996static void hotkey_tablet_mode_notify_change(void)
1997{
1998 if (tp_features.hotkey_tablet)
1999 sysfs_notify(&tpacpi_pdev->dev.kobj, NULL,
2000 "hotkey_tablet_mode");
2001}
2002
Henrique de Moraes Holschuhff80f132007-09-04 11:13:15 -03002003/* sysfs hotkey report_mode -------------------------------------------- */
2004static ssize_t hotkey_report_mode_show(struct device *dev,
2005 struct device_attribute *attr,
2006 char *buf)
2007{
2008 return snprintf(buf, PAGE_SIZE, "%d\n",
2009 (hotkey_report_mode != 0) ? hotkey_report_mode : 1);
2010}
2011
2012static struct device_attribute dev_attr_hotkey_report_mode =
2013 __ATTR(hotkey_report_mode, S_IRUGO, hotkey_report_mode_show, NULL);
2014
Henrique de Moraes Holschuh50ebec02008-01-08 13:02:55 -02002015/* sysfs wakeup reason (pollable) -------------------------------------- */
Henrique de Moraes Holschuha713b4d2008-01-08 13:02:52 -02002016static ssize_t hotkey_wakeup_reason_show(struct device *dev,
2017 struct device_attribute *attr,
2018 char *buf)
2019{
2020 return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_wakeup_reason);
2021}
2022
2023static struct device_attribute dev_attr_hotkey_wakeup_reason =
2024 __ATTR(wakeup_reason, S_IRUGO, hotkey_wakeup_reason_show, NULL);
2025
Adrian Bunk1d5a2b52008-02-13 23:30:06 +02002026static void hotkey_wakeup_reason_notify_change(void)
Henrique de Moraes Holschuh50ebec02008-01-08 13:02:55 -02002027{
2028 if (tp_features.hotkey_mask)
2029 sysfs_notify(&tpacpi_pdev->dev.kobj, NULL,
2030 "wakeup_reason");
2031}
2032
2033/* sysfs wakeup hotunplug_complete (pollable) -------------------------- */
Henrique de Moraes Holschuha713b4d2008-01-08 13:02:52 -02002034static ssize_t hotkey_wakeup_hotunplug_complete_show(struct device *dev,
2035 struct device_attribute *attr,
2036 char *buf)
2037{
2038 return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_autosleep_ack);
2039}
2040
2041static struct device_attribute dev_attr_hotkey_wakeup_hotunplug_complete =
2042 __ATTR(wakeup_hotunplug_complete, S_IRUGO,
2043 hotkey_wakeup_hotunplug_complete_show, NULL);
2044
Adrian Bunk1d5a2b52008-02-13 23:30:06 +02002045static void hotkey_wakeup_hotunplug_complete_notify_change(void)
Henrique de Moraes Holschuh50ebec02008-01-08 13:02:55 -02002046{
2047 if (tp_features.hotkey_mask)
2048 sysfs_notify(&tpacpi_pdev->dev.kobj, NULL,
2049 "wakeup_hotunplug_complete");
2050}
2051
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03002052/* --------------------------------------------------------------------- */
2053
Henrique de Moraes Holschuhff80f132007-09-04 11:13:15 -03002054static struct attribute *hotkey_attributes[] __initdata = {
2055 &dev_attr_hotkey_enable.attr,
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02002056 &dev_attr_hotkey_bios_enabled.attr,
Henrique de Moraes Holschuhff80f132007-09-04 11:13:15 -03002057 &dev_attr_hotkey_report_mode.attr,
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02002058#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
2059 &dev_attr_hotkey_mask.attr,
2060 &dev_attr_hotkey_all_mask.attr,
2061 &dev_attr_hotkey_recommended_mask.attr,
2062 &dev_attr_hotkey_source_mask.attr,
2063 &dev_attr_hotkey_poll_freq.attr,
2064#endif
Henrique de Moraes Holschuhff80f132007-09-04 11:13:15 -03002065};
2066
2067static struct attribute *hotkey_mask_attributes[] __initdata = {
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03002068 &dev_attr_hotkey_bios_mask.attr,
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02002069#ifndef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
2070 &dev_attr_hotkey_mask.attr,
Henrique de Moraes Holschuh9b010de2007-07-18 23:45:30 -03002071 &dev_attr_hotkey_all_mask.attr,
2072 &dev_attr_hotkey_recommended_mask.attr,
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02002073#endif
Henrique de Moraes Holschuha713b4d2008-01-08 13:02:52 -02002074 &dev_attr_hotkey_wakeup_reason.attr,
2075 &dev_attr_hotkey_wakeup_hotunplug_complete.attr,
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03002076};
2077
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03002078static void bluetooth_update_rfk(void);
2079static void wan_update_rfk(void);
Henrique de Moraes Holschuh733e27c2008-07-21 09:15:49 -03002080static void tpacpi_send_radiosw_update(void)
2081{
2082 int wlsw;
2083
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03002084 /* Sync these BEFORE sending any rfkill events */
2085 if (tp_features.bluetooth)
2086 bluetooth_update_rfk();
2087 if (tp_features.wan)
2088 wan_update_rfk();
2089
Henrique de Moraes Holschuh733e27c2008-07-21 09:15:49 -03002090 if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&wlsw)) {
2091 mutex_lock(&tpacpi_inputdev_send_mutex);
2092
2093 input_report_switch(tpacpi_inputdev,
2094 SW_RFKILL_ALL, !!wlsw);
2095 input_sync(tpacpi_inputdev);
2096
2097 mutex_unlock(&tpacpi_inputdev_send_mutex);
2098 }
2099 hotkey_radio_sw_notify_change();
2100}
2101
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03002102static void hotkey_exit(void)
2103{
2104#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
2105 hotkey_poll_stop_sync();
2106#endif
2107
2108 if (hotkey_dev_attributes)
2109 delete_attr_set(hotkey_dev_attributes, &tpacpi_pdev->dev.kobj);
2110
2111 kfree(hotkey_keycode_map);
2112
2113 if (tp_features.hotkey) {
2114 dbg_printk(TPACPI_DBG_EXIT,
2115 "restoring original hot key mask\n");
2116 /* no short-circuit boolean operator below! */
2117 if ((hotkey_mask_set(hotkey_orig_mask) |
2118 hotkey_status_set(hotkey_orig_status)) != 0)
2119 printk(TPACPI_ERR
2120 "failed to restore hot key mask "
2121 "to BIOS defaults\n");
2122 }
2123}
2124
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03002125static int __init hotkey_init(struct ibm_init_struct *iibm)
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03002126{
Henrique de Moraes Holschuh0f089142008-01-08 13:02:38 -02002127 /* Requirements for changing the default keymaps:
2128 *
2129 * 1. Many of the keys are mapped to KEY_RESERVED for very
2130 * good reasons. Do not change them unless you have deep
2131 * knowledge on the IBM and Lenovo ThinkPad firmware for
2132 * the various ThinkPad models. The driver behaves
2133 * differently for KEY_RESERVED: such keys have their
2134 * hot key mask *unset* in mask_recommended, and also
2135 * in the initial hot key mask programmed into the
2136 * firmware at driver load time, which means the firm-
2137 * ware may react very differently if you change them to
2138 * something else;
2139 *
2140 * 2. You must be subscribed to the linux-thinkpad and
2141 * ibm-acpi-devel mailing lists, and you should read the
2142 * list archives since 2007 if you want to change the
2143 * keymaps. This requirement exists so that you will
2144 * know the past history of problems with the thinkpad-
2145 * acpi driver keymaps, and also that you will be
2146 * listening to any bug reports;
2147 *
2148 * 3. Do not send thinkpad-acpi specific patches directly to
2149 * for merging, *ever*. Send them to the linux-acpi
2150 * mailinglist for comments. Merging is to be done only
2151 * through acpi-test and the ACPI maintainer.
2152 *
2153 * If the above is too much to ask, don't change the keymap.
2154 * Ask the thinkpad-acpi maintainer to do it, instead.
2155 */
Henrique de Moraes Holschuhedf0e0e2007-07-18 23:45:44 -03002156 static u16 ibm_keycode_map[] __initdata = {
2157 /* Scan Codes 0x00 to 0x0B: ACPI HKEY FN+F1..F12 */
2158 KEY_FN_F1, KEY_FN_F2, KEY_COFFEE, KEY_SLEEP,
2159 KEY_WLAN, KEY_FN_F6, KEY_SWITCHVIDEOMODE, KEY_FN_F8,
2160 KEY_FN_F9, KEY_FN_F10, KEY_FN_F11, KEY_SUSPEND,
Henrique de Moraes Holschuh0f089142008-01-08 13:02:38 -02002161
2162 /* Scan codes 0x0C to 0x1F: Other ACPI HKEY hot keys */
Henrique de Moraes Holschuhedf0e0e2007-07-18 23:45:44 -03002163 KEY_UNKNOWN, /* 0x0C: FN+BACKSPACE */
2164 KEY_UNKNOWN, /* 0x0D: FN+INSERT */
2165 KEY_UNKNOWN, /* 0x0E: FN+DELETE */
Henrique de Moraes Holschuh0f089142008-01-08 13:02:38 -02002166
2167 /* brightness: firmware always reacts to them, unless
2168 * X.org did some tricks in the radeon BIOS scratch
2169 * registers of *some* models */
Henrique de Moraes Holschuhe927c082007-10-30 17:46:19 -02002170 KEY_RESERVED, /* 0x0F: FN+HOME (brightness up) */
Henrique de Moraes Holschuhe927c082007-10-30 17:46:19 -02002171 KEY_RESERVED, /* 0x10: FN+END (brightness down) */
Henrique de Moraes Holschuh0f089142008-01-08 13:02:38 -02002172
2173 /* Thinklight: firmware always react to it */
Henrique de Moraes Holschuhedf0e0e2007-07-18 23:45:44 -03002174 KEY_RESERVED, /* 0x11: FN+PGUP (thinklight toggle) */
Henrique de Moraes Holschuh0f089142008-01-08 13:02:38 -02002175
Henrique de Moraes Holschuhedf0e0e2007-07-18 23:45:44 -03002176 KEY_UNKNOWN, /* 0x12: FN+PGDOWN */
2177 KEY_ZOOM, /* 0x13: FN+SPACE (zoom) */
Henrique de Moraes Holschuh0f089142008-01-08 13:02:38 -02002178
2179 /* Volume: firmware always react to it and reprograms
2180 * the built-in *extra* mixer. Never map it to control
2181 * another mixer by default. */
Henrique de Moraes Holschuhe927c082007-10-30 17:46:19 -02002182 KEY_RESERVED, /* 0x14: VOLUME UP */
2183 KEY_RESERVED, /* 0x15: VOLUME DOWN */
2184 KEY_RESERVED, /* 0x16: MUTE */
Henrique de Moraes Holschuh0f089142008-01-08 13:02:38 -02002185
Henrique de Moraes Holschuhedf0e0e2007-07-18 23:45:44 -03002186 KEY_VENDOR, /* 0x17: Thinkpad/AccessIBM/Lenovo */
Henrique de Moraes Holschuh0f089142008-01-08 13:02:38 -02002187
Henrique de Moraes Holschuhedf0e0e2007-07-18 23:45:44 -03002188 /* (assignments unknown, please report if found) */
2189 KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
2190 KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
2191 };
2192 static u16 lenovo_keycode_map[] __initdata = {
2193 /* Scan Codes 0x00 to 0x0B: ACPI HKEY FN+F1..F12 */
2194 KEY_FN_F1, KEY_COFFEE, KEY_BATTERY, KEY_SLEEP,
2195 KEY_WLAN, KEY_FN_F6, KEY_SWITCHVIDEOMODE, KEY_FN_F8,
2196 KEY_FN_F9, KEY_FN_F10, KEY_FN_F11, KEY_SUSPEND,
Henrique de Moraes Holschuh0f089142008-01-08 13:02:38 -02002197
2198 /* Scan codes 0x0C to 0x1F: Other ACPI HKEY hot keys */
Henrique de Moraes Holschuhedf0e0e2007-07-18 23:45:44 -03002199 KEY_UNKNOWN, /* 0x0C: FN+BACKSPACE */
2200 KEY_UNKNOWN, /* 0x0D: FN+INSERT */
2201 KEY_UNKNOWN, /* 0x0E: FN+DELETE */
Henrique de Moraes Holschuh0f089142008-01-08 13:02:38 -02002202
Henrique de Moraes Holschuhb5972792008-04-26 01:02:17 -03002203 /* These either have to go through ACPI video, or
2204 * act like in the IBM ThinkPads, so don't ever
2205 * enable them by default */
Henrique de Moraes Holschuh56a185b2007-12-13 12:14:09 -02002206 KEY_RESERVED, /* 0x0F: FN+HOME (brightness up) */
Henrique de Moraes Holschuh56a185b2007-12-13 12:14:09 -02002207 KEY_RESERVED, /* 0x10: FN+END (brightness down) */
Henrique de Moraes Holschuh0f089142008-01-08 13:02:38 -02002208
Henrique de Moraes Holschuhedf0e0e2007-07-18 23:45:44 -03002209 KEY_RESERVED, /* 0x11: FN+PGUP (thinklight toggle) */
Henrique de Moraes Holschuh0f089142008-01-08 13:02:38 -02002210
Henrique de Moraes Holschuhedf0e0e2007-07-18 23:45:44 -03002211 KEY_UNKNOWN, /* 0x12: FN+PGDOWN */
2212 KEY_ZOOM, /* 0x13: FN+SPACE (zoom) */
Henrique de Moraes Holschuh0f089142008-01-08 13:02:38 -02002213
2214 /* Volume: z60/z61, T60 (BIOS version?): firmware always
2215 * react to it and reprograms the built-in *extra* mixer.
2216 * Never map it to control another mixer by default.
2217 *
2218 * T60?, T61, R60?, R61: firmware and EC tries to send
2219 * these over the regular keyboard, so these are no-ops,
2220 * but there are still weird bugs re. MUTE, so do not
2221 * change unless you get test reports from all Lenovo
2222 * models. May cause the BIOS to interfere with the
2223 * HDA mixer.
2224 */
Henrique de Moraes Holschuhe927c082007-10-30 17:46:19 -02002225 KEY_RESERVED, /* 0x14: VOLUME UP */
2226 KEY_RESERVED, /* 0x15: VOLUME DOWN */
2227 KEY_RESERVED, /* 0x16: MUTE */
Henrique de Moraes Holschuh0f089142008-01-08 13:02:38 -02002228
Henrique de Moraes Holschuhedf0e0e2007-07-18 23:45:44 -03002229 KEY_VENDOR, /* 0x17: Thinkpad/AccessIBM/Lenovo */
Henrique de Moraes Holschuh0f089142008-01-08 13:02:38 -02002230
Henrique de Moraes Holschuhedf0e0e2007-07-18 23:45:44 -03002231 /* (assignments unknown, please report if found) */
2232 KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
2233 KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
2234 };
2235
2236#define TPACPI_HOTKEY_MAP_LEN ARRAY_SIZE(ibm_keycode_map)
2237#define TPACPI_HOTKEY_MAP_SIZE sizeof(ibm_keycode_map)
2238#define TPACPI_HOTKEY_MAP_TYPESIZE sizeof(ibm_keycode_map[0])
2239
Henrique de Moraes Holschuh6a38abb2007-07-18 23:45:35 -03002240 int res, i;
Henrique de Moraes Holschuh74941a62007-07-18 23:45:31 -03002241 int status;
Henrique de Moraes Holschuh1b6521d2007-09-23 11:39:03 -03002242 int hkeyv;
Henrique de Moraes Holschuhb86c4722007-04-21 11:08:39 -03002243
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03002244 vdbg_printk(TPACPI_DBG_INIT, "initializing hotkey subdriver\n");
2245
Henrique de Moraes Holschuh6a38abb2007-07-18 23:45:35 -03002246 BUG_ON(!tpacpi_inputdev);
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02002247 BUG_ON(tpacpi_inputdev->open != NULL ||
2248 tpacpi_inputdev->close != NULL);
Henrique de Moraes Holschuh6a38abb2007-07-18 23:45:35 -03002249
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02002250 TPACPI_ACPIHANDLE_INIT(hkey);
Henrique de Moraes Holschuh40ca9fd2007-04-24 11:48:15 -03002251 mutex_init(&hotkey_mutex);
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03002252
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02002253#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
2254 mutex_init(&hotkey_thread_mutex);
2255 mutex_init(&hotkey_thread_data_mutex);
2256#endif
2257
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03002258 /* hotkey not supported on 570 */
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03002259 tp_features.hotkey = hkey_handle != NULL;
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03002260
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03002261 vdbg_printk(TPACPI_DBG_INIT, "hotkeys are %s\n",
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03002262 str_supported(tp_features.hotkey));
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03002263
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03002264 if (!tp_features.hotkey)
2265 return 1;
2266
Henrique de Moraes Holschuhd64c81c2008-10-18 14:23:55 -03002267 tpacpi_disable_brightness_delay();
2268
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03002269 hotkey_dev_attributes = create_attr_set(13, NULL);
2270 if (!hotkey_dev_attributes)
2271 return -ENOMEM;
2272 res = add_many_to_attr_set(hotkey_dev_attributes,
2273 hotkey_attributes,
2274 ARRAY_SIZE(hotkey_attributes));
2275 if (res)
2276 goto err_exit;
2277
2278 /* mask not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
2279 A30, R30, R31, T20-22, X20-21, X22-24. Detected by checking
2280 for HKEY interface version 0x100 */
2281 if (acpi_evalf(hkey_handle, &hkeyv, "MHKV", "qd")) {
2282 if ((hkeyv >> 8) != 1) {
2283 printk(TPACPI_ERR "unknown version of the "
2284 "HKEY interface: 0x%x\n", hkeyv);
2285 printk(TPACPI_ERR "please report this to %s\n",
2286 TPACPI_MAIL);
2287 } else {
2288 /*
2289 * MHKV 0x100 in A31, R40, R40e,
2290 * T4x, X31, and later
2291 */
2292 tp_features.hotkey_mask = 1;
2293 }
2294 }
2295
2296 vdbg_printk(TPACPI_DBG_INIT, "hotkey masks are %s\n",
2297 str_supported(tp_features.hotkey_mask));
2298
2299 if (tp_features.hotkey_mask) {
2300 if (!acpi_evalf(hkey_handle, &hotkey_all_mask,
2301 "MHKA", "qd")) {
2302 printk(TPACPI_ERR
2303 "missing MHKA handler, "
2304 "please report this to %s\n",
2305 TPACPI_MAIL);
2306 /* FN+F12, FN+F4, FN+F3 */
2307 hotkey_all_mask = 0x080cU;
2308 }
2309 }
2310
2311 /* hotkey_source_mask *must* be zero for
2312 * the first hotkey_mask_get */
2313 res = hotkey_status_get(&hotkey_orig_status);
2314 if (res)
2315 goto err_exit;
2316
2317 if (tp_features.hotkey_mask) {
2318 res = hotkey_mask_get();
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03002319 if (res)
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03002320 goto err_exit;
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03002321
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03002322 hotkey_orig_mask = hotkey_mask;
2323 res = add_many_to_attr_set(
2324 hotkey_dev_attributes,
2325 hotkey_mask_attributes,
2326 ARRAY_SIZE(hotkey_mask_attributes));
2327 if (res)
2328 goto err_exit;
2329 }
Henrique de Moraes Holschuh74941a62007-07-18 23:45:31 -03002330
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02002331#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03002332 if (tp_features.hotkey_mask) {
2333 hotkey_source_mask = TPACPI_HKEY_NVRAM_GOOD_MASK
2334 & ~hotkey_all_mask;
2335 } else {
2336 hotkey_source_mask = TPACPI_HKEY_NVRAM_GOOD_MASK;
2337 }
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02002338
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03002339 vdbg_printk(TPACPI_DBG_INIT,
2340 "hotkey source mask 0x%08x, polling freq %d\n",
2341 hotkey_source_mask, hotkey_poll_freq);
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02002342#endif
2343
Henrique de Moraes Holschuha73f3092009-01-11 03:01:00 -02002344#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
2345 if (dbg_wlswemul) {
2346 tp_features.hotkey_wlsw = 1;
2347 printk(TPACPI_INFO
2348 "radio switch emulation enabled\n");
2349 } else
2350#endif
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03002351 /* Not all thinkpads have a hardware radio switch */
2352 if (acpi_evalf(hkey_handle, &status, "WLSW", "qd")) {
2353 tp_features.hotkey_wlsw = 1;
2354 printk(TPACPI_INFO
2355 "radio switch found; radios are %s\n",
2356 enabled(status, 0));
Henrique de Moraes Holschuh3a872082008-07-21 09:15:49 -03002357 }
2358 if (tp_features.hotkey_wlsw)
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03002359 res = add_to_attr_set(hotkey_dev_attributes,
2360 &dev_attr_hotkey_radio_sw.attr);
Henrique de Moraes Holschuh74941a62007-07-18 23:45:31 -03002361
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03002362 /* For X41t, X60t, X61t Tablets... */
2363 if (!res && acpi_evalf(hkey_handle, &status, "MHKG", "qd")) {
2364 tp_features.hotkey_tablet = 1;
2365 printk(TPACPI_INFO
2366 "possible tablet mode switch found; "
2367 "ThinkPad in %s mode\n",
2368 (status & TP_HOTKEY_TABLET_MASK)?
2369 "tablet" : "laptop");
2370 res = add_to_attr_set(hotkey_dev_attributes,
2371 &dev_attr_hotkey_tablet_mode.attr);
2372 }
Henrique de Moraes Holschuh6c231bd2008-02-16 02:17:58 -02002373
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03002374 if (!res)
2375 res = register_attr_set_with_sysfs(
2376 hotkey_dev_attributes,
2377 &tpacpi_pdev->dev.kobj);
2378 if (res)
2379 goto err_exit;
Henrique de Moraes Holschuh6a38abb2007-07-18 23:45:35 -03002380
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03002381 /* Set up key map */
Henrique de Moraes Holschuhedf0e0e2007-07-18 23:45:44 -03002382
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03002383 hotkey_keycode_map = kmalloc(TPACPI_HOTKEY_MAP_SIZE,
2384 GFP_KERNEL);
2385 if (!hotkey_keycode_map) {
2386 printk(TPACPI_ERR
2387 "failed to allocate memory for key map\n");
2388 res = -ENOMEM;
2389 goto err_exit;
2390 }
Henrique de Moraes Holschuhedf0e0e2007-07-18 23:45:44 -03002391
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03002392 if (thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO) {
2393 dbg_printk(TPACPI_DBG_INIT,
2394 "using Lenovo default hot key map\n");
2395 memcpy(hotkey_keycode_map, &lenovo_keycode_map,
2396 TPACPI_HOTKEY_MAP_SIZE);
2397 } else {
2398 dbg_printk(TPACPI_DBG_INIT,
2399 "using IBM default hot key map\n");
2400 memcpy(hotkey_keycode_map, &ibm_keycode_map,
2401 TPACPI_HOTKEY_MAP_SIZE);
2402 }
2403
2404 set_bit(EV_KEY, tpacpi_inputdev->evbit);
2405 set_bit(EV_MSC, tpacpi_inputdev->evbit);
2406 set_bit(MSC_SCAN, tpacpi_inputdev->mscbit);
2407 tpacpi_inputdev->keycodesize = TPACPI_HOTKEY_MAP_TYPESIZE;
2408 tpacpi_inputdev->keycodemax = TPACPI_HOTKEY_MAP_LEN;
2409 tpacpi_inputdev->keycode = hotkey_keycode_map;
2410 for (i = 0; i < TPACPI_HOTKEY_MAP_LEN; i++) {
2411 if (hotkey_keycode_map[i] != KEY_RESERVED) {
2412 set_bit(hotkey_keycode_map[i],
2413 tpacpi_inputdev->keybit);
Henrique de Moraes Holschuhedf0e0e2007-07-18 23:45:44 -03002414 } else {
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03002415 if (i < sizeof(hotkey_reserved_mask)*8)
2416 hotkey_reserved_mask |= 1 << i;
Henrique de Moraes Holschuhedf0e0e2007-07-18 23:45:44 -03002417 }
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03002418 }
2419
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03002420 if (tp_features.hotkey_wlsw) {
2421 set_bit(EV_SW, tpacpi_inputdev->evbit);
2422 set_bit(SW_RFKILL_ALL, tpacpi_inputdev->swbit);
2423 }
2424 if (tp_features.hotkey_tablet) {
2425 set_bit(EV_SW, tpacpi_inputdev->evbit);
2426 set_bit(SW_TABLET_MODE, tpacpi_inputdev->swbit);
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03002427 }
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03002428
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03002429 /* Do not issue duplicate brightness change events to
2430 * userspace */
2431 if (!tp_features.bright_acpimode)
2432 /* update bright_acpimode... */
2433 tpacpi_check_std_acpi_brightness_support();
2434
2435 if (tp_features.bright_acpimode) {
2436 printk(TPACPI_INFO
2437 "This ThinkPad has standard ACPI backlight "
2438 "brightness control, supported by the ACPI "
2439 "video driver\n");
2440 printk(TPACPI_NOTICE
2441 "Disabling thinkpad-acpi brightness events "
2442 "by default...\n");
2443
2444 /* The hotkey_reserved_mask change below is not
2445 * necessary while the keys are at KEY_RESERVED in the
2446 * default map, but better safe than sorry, leave it
2447 * here as a marker of what we have to do, especially
2448 * when we finally become able to set this at runtime
2449 * on response to X.org requests */
2450 hotkey_reserved_mask |=
2451 (1 << TP_ACPI_HOTKEYSCAN_FNHOME)
2452 | (1 << TP_ACPI_HOTKEYSCAN_FNEND);
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03002453 }
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03002454
2455 dbg_printk(TPACPI_DBG_INIT, "enabling hot key handling\n");
2456 res = hotkey_status_set(1);
2457 if (res) {
2458 hotkey_exit();
2459 return res;
2460 }
2461 res = hotkey_mask_set(((hotkey_all_mask | hotkey_source_mask)
2462 & ~hotkey_reserved_mask)
2463 | hotkey_orig_mask);
2464 if (res < 0 && res != -ENXIO) {
2465 hotkey_exit();
2466 return res;
2467 }
2468
2469 dbg_printk(TPACPI_DBG_INIT,
2470 "legacy hot key reporting over procfs %s\n",
2471 (hotkey_report_mode < 2) ?
2472 "enabled" : "disabled");
2473
2474 tpacpi_inputdev->open = &hotkey_inputdev_open;
2475 tpacpi_inputdev->close = &hotkey_inputdev_close;
2476
2477 hotkey_poll_setup_safe(1);
Henrique de Moraes Holschuh733e27c2008-07-21 09:15:49 -03002478 tpacpi_send_radiosw_update();
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03002479 tpacpi_input_send_tabletsw();
2480
2481 return 0;
2482
2483err_exit:
2484 delete_attr_set(hotkey_dev_attributes, &tpacpi_pdev->dev.kobj);
2485 hotkey_dev_attributes = NULL;
2486
2487 return (res < 0)? res : 1;
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03002488}
2489
2490static void hotkey_notify(struct ibm_struct *ibm, u32 event)
2491{
Henrique de Moraes Holschuh6a38abb2007-07-18 23:45:35 -03002492 u32 hkey;
Henrique de Moraes Holschuhb7c8c202008-01-08 13:02:40 -02002493 unsigned int scancode;
Henrique de Moraes Holschuh3eea1232007-09-23 11:39:04 -03002494 int send_acpi_ev;
Henrique de Moraes Holschuh3e5ce912007-09-23 11:39:05 -03002495 int ignore_acpi_ev;
Henrique de Moraes Holschuh3b64b512008-01-08 13:02:51 -02002496 int unk_ev;
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03002497
Henrique de Moraes Holschuh3eea1232007-09-23 11:39:04 -03002498 if (event != 0x80) {
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02002499 printk(TPACPI_ERR
2500 "unknown HKEY notification event %d\n", event);
Henrique de Moraes Holschuh3eea1232007-09-23 11:39:04 -03002501 /* forward it to userspace, maybe it knows how to handle it */
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02002502 acpi_bus_generate_netlink_event(
2503 ibm->acpi->device->pnp.device_class,
Kay Sieverse0b36fc2009-01-11 03:00:59 -02002504 dev_name(&ibm->acpi->device->dev),
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02002505 event, 0);
Henrique de Moraes Holschuh3eea1232007-09-23 11:39:04 -03002506 return;
2507 }
2508
2509 while (1) {
2510 if (!acpi_evalf(hkey_handle, &hkey, "MHKP", "d")) {
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02002511 printk(TPACPI_ERR "failed to retrieve HKEY event\n");
Henrique de Moraes Holschuh3eea1232007-09-23 11:39:04 -03002512 return;
2513 }
2514
2515 if (hkey == 0) {
2516 /* queue empty */
2517 return;
2518 }
2519
Henrique de Moraes Holschuh3b64b512008-01-08 13:02:51 -02002520 send_acpi_ev = 1;
Henrique de Moraes Holschuh3e5ce912007-09-23 11:39:05 -03002521 ignore_acpi_ev = 0;
Henrique de Moraes Holschuh3b64b512008-01-08 13:02:51 -02002522 unk_ev = 0;
Henrique de Moraes Holschuh3eea1232007-09-23 11:39:04 -03002523
Henrique de Moraes Holschuhff80f132007-09-04 11:13:15 -03002524 switch (hkey >> 12) {
2525 case 1:
2526 /* 0x1000-0x1FFF: key presses */
2527 scancode = hkey & 0xfff;
2528 if (scancode > 0 && scancode < 0x21) {
2529 scancode--;
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02002530 if (!(hotkey_source_mask & (1 << scancode))) {
2531 tpacpi_input_send_key(scancode);
Henrique de Moraes Holschuh3b64b512008-01-08 13:02:51 -02002532 send_acpi_ev = 0;
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02002533 } else {
2534 ignore_acpi_ev = 1;
2535 }
Henrique de Moraes Holschuhff80f132007-09-04 11:13:15 -03002536 } else {
Henrique de Moraes Holschuh3b64b512008-01-08 13:02:51 -02002537 unk_ev = 1;
Henrique de Moraes Holschuh6a38abb2007-07-18 23:45:35 -03002538 }
Henrique de Moraes Holschuhff80f132007-09-04 11:13:15 -03002539 break;
Henrique de Moraes Holschuha713b4d2008-01-08 13:02:52 -02002540 case 2:
2541 /* Wakeup reason */
2542 switch (hkey) {
2543 case 0x2304: /* suspend, undock */
2544 case 0x2404: /* hibernation, undock */
2545 hotkey_wakeup_reason = TP_ACPI_WAKEUP_UNDOCK;
2546 ignore_acpi_ev = 1;
2547 break;
2548 case 0x2305: /* suspend, bay eject */
2549 case 0x2405: /* hibernation, bay eject */
2550 hotkey_wakeup_reason = TP_ACPI_WAKEUP_BAYEJ;
2551 ignore_acpi_ev = 1;
2552 break;
2553 default:
2554 unk_ev = 1;
2555 }
2556 if (hotkey_wakeup_reason != TP_ACPI_WAKEUP_NONE) {
2557 printk(TPACPI_INFO
2558 "woke up due to a hot-unplug "
2559 "request...\n");
Henrique de Moraes Holschuh50ebec02008-01-08 13:02:55 -02002560 hotkey_wakeup_reason_notify_change();
Henrique de Moraes Holschuha713b4d2008-01-08 13:02:52 -02002561 }
2562 break;
2563 case 3:
2564 /* bay-related wakeups */
2565 if (hkey == 0x3003) {
2566 hotkey_autosleep_ack = 1;
2567 printk(TPACPI_INFO
2568 "bay ejected\n");
Henrique de Moraes Holschuh50ebec02008-01-08 13:02:55 -02002569 hotkey_wakeup_hotunplug_complete_notify_change();
Henrique de Moraes Holschuha713b4d2008-01-08 13:02:52 -02002570 } else {
2571 unk_ev = 1;
2572 }
2573 break;
2574 case 4:
2575 /* dock-related wakeups */
2576 if (hkey == 0x4003) {
2577 hotkey_autosleep_ack = 1;
2578 printk(TPACPI_INFO
2579 "undocked\n");
Henrique de Moraes Holschuh50ebec02008-01-08 13:02:55 -02002580 hotkey_wakeup_hotunplug_complete_notify_change();
Henrique de Moraes Holschuha713b4d2008-01-08 13:02:52 -02002581 } else {
2582 unk_ev = 1;
2583 }
2584 break;
Henrique de Moraes Holschuhff80f132007-09-04 11:13:15 -03002585 case 5:
Henrique de Moraes Holschuhd1edb2b2008-01-08 13:02:53 -02002586 /* 0x5000-0x5FFF: human interface helpers */
Henrique de Moraes Holschuh3b64b512008-01-08 13:02:51 -02002587 switch (hkey) {
Henrique de Moraes Holschuhd1edb2b2008-01-08 13:02:53 -02002588 case 0x5010: /* Lenovo new BIOS: brightness changed */
Henrique de Moraes Holschuhd1edb2b2008-01-08 13:02:53 -02002589 case 0x500b: /* X61t: tablet pen inserted into bay */
2590 case 0x500c: /* X61t: tablet pen removed from bay */
Henrique de Moraes Holschuh3b64b512008-01-08 13:02:51 -02002591 break;
Henrique de Moraes Holschuh6c231bd2008-02-16 02:17:58 -02002592 case 0x5009: /* X41t-X61t: swivel up (tablet mode) */
2593 case 0x500a: /* X41t-X61t: swivel down (normal mode) */
2594 tpacpi_input_send_tabletsw();
2595 hotkey_tablet_mode_notify_change();
Henrique de Moraes Holschuhb3ec6f92008-02-16 02:17:55 -02002596 send_acpi_ev = 0;
2597 break;
Henrique de Moraes Holschuh3b64b512008-01-08 13:02:51 -02002598 case 0x5001:
2599 case 0x5002:
2600 /* LID switch events. Do not propagate */
Henrique de Moraes Holschuh3e5ce912007-09-23 11:39:05 -03002601 ignore_acpi_ev = 1;
Henrique de Moraes Holschuh3b64b512008-01-08 13:02:51 -02002602 break;
2603 default:
2604 unk_ev = 1;
Henrique de Moraes Holschuhff80f132007-09-04 11:13:15 -03002605 }
2606 break;
2607 case 7:
2608 /* 0x7000-0x7FFF: misc */
2609 if (tp_features.hotkey_wlsw && hkey == 0x7000) {
Henrique de Moraes Holschuh733e27c2008-07-21 09:15:49 -03002610 tpacpi_send_radiosw_update();
Henrique de Moraes Holschuh3b64b512008-01-08 13:02:51 -02002611 send_acpi_ev = 0;
Henrique de Moraes Holschuhff80f132007-09-04 11:13:15 -03002612 break;
2613 }
2614 /* fallthrough to default */
2615 default:
Henrique de Moraes Holschuh3b64b512008-01-08 13:02:51 -02002616 unk_ev = 1;
2617 }
2618 if (unk_ev) {
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02002619 printk(TPACPI_NOTICE
2620 "unhandled HKEY event 0x%04x\n", hkey);
Henrique de Moraes Holschuh6a38abb2007-07-18 23:45:35 -03002621 }
Henrique de Moraes Holschuhff80f132007-09-04 11:13:15 -03002622
Henrique de Moraes Holschuh3eea1232007-09-23 11:39:04 -03002623 /* Legacy events */
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02002624 if (!ignore_acpi_ev &&
2625 (send_acpi_ev || hotkey_report_mode < 2)) {
2626 acpi_bus_generate_proc_event(ibm->acpi->device,
2627 event, hkey);
Henrique de Moraes Holschuh3e5ce912007-09-23 11:39:05 -03002628 }
Henrique de Moraes Holschuhff80f132007-09-04 11:13:15 -03002629
Henrique de Moraes Holschuh3eea1232007-09-23 11:39:04 -03002630 /* netlink events */
Henrique de Moraes Holschuh3e5ce912007-09-23 11:39:05 -03002631 if (!ignore_acpi_ev && send_acpi_ev) {
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02002632 acpi_bus_generate_netlink_event(
2633 ibm->acpi->device->pnp.device_class,
Kay Sieverse0b36fc2009-01-11 03:00:59 -02002634 dev_name(&ibm->acpi->device->dev),
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02002635 event, hkey);
Henrique de Moraes Holschuh3eea1232007-09-23 11:39:04 -03002636 }
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03002637 }
2638}
2639
Henrique de Moraes Holschuha713b4d2008-01-08 13:02:52 -02002640static void hotkey_suspend(pm_message_t state)
2641{
2642 /* Do these on suspend, we get the events on early resume! */
2643 hotkey_wakeup_reason = TP_ACPI_WAKEUP_NONE;
2644 hotkey_autosleep_ack = 0;
2645}
2646
Henrique de Moraes Holschuh5c29d582007-07-18 23:45:38 -03002647static void hotkey_resume(void)
2648{
Henrique de Moraes Holschuhd64c81c2008-10-18 14:23:55 -03002649 tpacpi_disable_brightness_delay();
2650
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02002651 if (hotkey_mask_get())
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02002652 printk(TPACPI_ERR
2653 "error while trying to read hot key mask "
2654 "from firmware\n");
Henrique de Moraes Holschuh733e27c2008-07-21 09:15:49 -03002655 tpacpi_send_radiosw_update();
Henrique de Moraes Holschuh6c231bd2008-02-16 02:17:58 -02002656 hotkey_tablet_mode_notify_change();
Henrique de Moraes Holschuh50ebec02008-01-08 13:02:55 -02002657 hotkey_wakeup_reason_notify_change();
2658 hotkey_wakeup_hotunplug_complete_notify_change();
Henrique de Moraes Holschuh01e88f22008-01-08 13:02:41 -02002659 hotkey_poll_setup_safe(0);
Henrique de Moraes Holschuh5c29d582007-07-18 23:45:38 -03002660}
2661
Henrique de Moraes Holschuha0416422007-04-27 22:00:16 -03002662/* procfs -------------------------------------------------------------- */
Borislav Deianov78f81cc2005-08-17 00:00:00 -04002663static int hotkey_read(char *p)
Linus Torvalds1da177e2005-04-16 15:20:36 -07002664{
Henrique de Moraes Holschuhae92bd12007-07-18 23:45:29 -03002665 int res, status;
Linus Torvalds1da177e2005-04-16 15:20:36 -07002666 int len = 0;
2667
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03002668 if (!tp_features.hotkey) {
Borislav Deianov78f81cc2005-08-17 00:00:00 -04002669 len += sprintf(p + len, "status:\t\tnot supported\n");
2670 return len;
2671 }
2672
Henrique de Moraes Holschuhfc589a32007-10-30 17:46:24 -02002673 if (mutex_lock_interruptible(&hotkey_mutex))
2674 return -ERESTARTSYS;
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02002675 res = hotkey_status_get(&status);
2676 if (!res)
2677 res = hotkey_mask_get();
Henrique de Moraes Holschuh40ca9fd2007-04-24 11:48:15 -03002678 mutex_unlock(&hotkey_mutex);
Henrique de Moraes Holschuhb86c4722007-04-21 11:08:39 -03002679 if (res)
2680 return res;
Linus Torvalds1da177e2005-04-16 15:20:36 -07002681
2682 len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 0));
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03002683 if (tp_features.hotkey_mask) {
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02002684 len += sprintf(p + len, "mask:\t\t0x%08x\n", hotkey_mask);
Linus Torvalds1da177e2005-04-16 15:20:36 -07002685 len += sprintf(p + len,
2686 "commands:\tenable, disable, reset, <mask>\n");
2687 } else {
2688 len += sprintf(p + len, "mask:\t\tnot supported\n");
2689 len += sprintf(p + len, "commands:\tenable, disable, reset\n");
2690 }
2691
2692 return len;
2693}
2694
Borislav Deianov78f81cc2005-08-17 00:00:00 -04002695static int hotkey_write(char *buf)
Linus Torvalds1da177e2005-04-16 15:20:36 -07002696{
Henrique de Moraes Holschuhae92bd12007-07-18 23:45:29 -03002697 int res, status;
2698 u32 mask;
Linus Torvalds1da177e2005-04-16 15:20:36 -07002699 char *cmd;
Linus Torvalds1da177e2005-04-16 15:20:36 -07002700
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03002701 if (!tp_features.hotkey)
Linus Torvalds1da177e2005-04-16 15:20:36 -07002702 return -ENODEV;
2703
Henrique de Moraes Holschuhfc589a32007-10-30 17:46:24 -02002704 if (mutex_lock_interruptible(&hotkey_mutex))
2705 return -ERESTARTSYS;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04002706
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02002707 status = -1;
2708 mask = hotkey_mask;
Henrique de Moraes Holschuh40ca9fd2007-04-24 11:48:15 -03002709
2710 res = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -07002711 while ((cmd = next_cmd(&buf))) {
2712 if (strlencmp(cmd, "enable") == 0) {
2713 status = 1;
2714 } else if (strlencmp(cmd, "disable") == 0) {
2715 status = 0;
2716 } else if (strlencmp(cmd, "reset") == 0) {
Borislav Deianov78f81cc2005-08-17 00:00:00 -04002717 status = hotkey_orig_status;
2718 mask = hotkey_orig_mask;
Linus Torvalds1da177e2005-04-16 15:20:36 -07002719 } else if (sscanf(cmd, "0x%x", &mask) == 1) {
2720 /* mask set */
2721 } else if (sscanf(cmd, "%x", &mask) == 1) {
2722 /* mask set */
Henrique de Moraes Holschuh40ca9fd2007-04-24 11:48:15 -03002723 } else {
2724 res = -EINVAL;
2725 goto errexit;
2726 }
Linus Torvalds1da177e2005-04-16 15:20:36 -07002727 }
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02002728 if (status != -1)
2729 res = hotkey_status_set(status);
Linus Torvalds1da177e2005-04-16 15:20:36 -07002730
Henrique de Moraes Holschuhb2c985e2008-01-08 13:02:39 -02002731 if (!res && mask != hotkey_mask)
2732 res = hotkey_mask_set(mask);
Linus Torvalds1da177e2005-04-16 15:20:36 -07002733
Henrique de Moraes Holschuh40ca9fd2007-04-24 11:48:15 -03002734errexit:
2735 mutex_unlock(&hotkey_mutex);
2736 return res;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04002737}
Linus Torvalds1da177e2005-04-16 15:20:36 -07002738
Thomas Renninger1ba90e32007-07-23 14:44:41 +02002739static const struct acpi_device_id ibm_htk_device_ids[] = {
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02002740 {TPACPI_ACPI_HKEY_HID, 0},
Thomas Renninger1ba90e32007-07-23 14:44:41 +02002741 {"", 0},
2742};
2743
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -03002744static struct tp_acpi_drv_struct ibm_hotkey_acpidriver = {
Thomas Renninger1ba90e32007-07-23 14:44:41 +02002745 .hid = ibm_htk_device_ids,
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03002746 .notify = hotkey_notify,
2747 .handle = &hkey_handle,
2748 .type = ACPI_DEVICE_NOTIFY,
2749};
2750
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -03002751static struct ibm_struct hotkey_driver_data = {
2752 .name = "hotkey",
2753 .read = hotkey_read,
2754 .write = hotkey_write,
2755 .exit = hotkey_exit,
Henrique de Moraes Holschuh5c29d582007-07-18 23:45:38 -03002756 .resume = hotkey_resume,
Henrique de Moraes Holschuha713b4d2008-01-08 13:02:52 -02002757 .suspend = hotkey_suspend,
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -03002758 .acpi = &ibm_hotkey_acpidriver,
2759};
2760
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03002761/*************************************************************************
2762 * Bluetooth subdriver
2763 */
Linus Torvalds1da177e2005-04-16 15:20:36 -07002764
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -02002765enum {
2766 /* ACPI GBDC/SBDC bits */
2767 TP_ACPI_BLUETOOTH_HWPRESENT = 0x01, /* Bluetooth hw available */
2768 TP_ACPI_BLUETOOTH_RADIOSSW = 0x02, /* Bluetooth radio enabled */
2769 TP_ACPI_BLUETOOTH_UNK = 0x04, /* unknown function */
2770};
2771
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03002772static struct rfkill *tpacpi_bluetooth_rfkill;
2773
Henrique de Moraes Holschuh07431ec2008-07-21 09:15:50 -03002774static int bluetooth_get_radiosw(void)
2775{
2776 int status;
2777
2778 if (!tp_features.bluetooth)
2779 return -ENODEV;
2780
Henrique de Moraes Holschuh133ec3b2008-07-21 09:15:50 -03002781 /* WLSW overrides bluetooth in firmware/hardware, reflect that */
2782 if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status)
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03002783 return RFKILL_STATE_HARD_BLOCKED;
Henrique de Moraes Holschuh133ec3b2008-07-21 09:15:50 -03002784
Henrique de Moraes Holschuha73f3092009-01-11 03:01:00 -02002785#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
2786 if (dbg_bluetoothemul)
2787 return (tpacpi_bluetooth_emulstate) ?
2788 RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED;
2789#endif
2790
Henrique de Moraes Holschuh07431ec2008-07-21 09:15:50 -03002791 if (!acpi_evalf(hkey_handle, &status, "GBDC", "d"))
2792 return -EIO;
2793
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03002794 return ((status & TP_ACPI_BLUETOOTH_RADIOSSW) != 0) ?
2795 RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED;
Henrique de Moraes Holschuh07431ec2008-07-21 09:15:50 -03002796}
2797
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03002798static void bluetooth_update_rfk(void)
2799{
2800 int status;
2801
2802 if (!tpacpi_bluetooth_rfkill)
2803 return;
2804
2805 status = bluetooth_get_radiosw();
2806 if (status < 0)
2807 return;
2808 rfkill_force_state(tpacpi_bluetooth_rfkill, status);
2809}
2810
2811static int bluetooth_set_radiosw(int radio_on, int update_rfk)
Henrique de Moraes Holschuh07431ec2008-07-21 09:15:50 -03002812{
2813 int status;
2814
2815 if (!tp_features.bluetooth)
2816 return -ENODEV;
2817
Henrique de Moraes Holschuh133ec3b2008-07-21 09:15:50 -03002818 /* WLSW overrides bluetooth in firmware/hardware, but there is no
2819 * reason to risk weird behaviour. */
2820 if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status
2821 && radio_on)
2822 return -EPERM;
2823
Henrique de Moraes Holschuha73f3092009-01-11 03:01:00 -02002824#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
2825 if (dbg_bluetoothemul) {
2826 tpacpi_bluetooth_emulstate = !!radio_on;
2827 if (update_rfk)
2828 bluetooth_update_rfk();
2829 return 0;
2830 }
2831#endif
2832
Henrique de Moraes Holschuh07431ec2008-07-21 09:15:50 -03002833 if (!acpi_evalf(hkey_handle, &status, "GBDC", "d"))
2834 return -EIO;
2835 if (radio_on)
2836 status |= TP_ACPI_BLUETOOTH_RADIOSSW;
2837 else
2838 status &= ~TP_ACPI_BLUETOOTH_RADIOSSW;
2839 if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status))
2840 return -EIO;
2841
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03002842 if (update_rfk)
2843 bluetooth_update_rfk();
2844
Henrique de Moraes Holschuh07431ec2008-07-21 09:15:50 -03002845 return 0;
2846}
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -02002847
Henrique de Moraes Holschuhd3a6ade2007-04-27 22:00:17 -03002848/* sysfs bluetooth enable ---------------------------------------------- */
2849static ssize_t bluetooth_enable_show(struct device *dev,
2850 struct device_attribute *attr,
2851 char *buf)
2852{
2853 int status;
2854
2855 status = bluetooth_get_radiosw();
2856 if (status < 0)
2857 return status;
2858
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03002859 return snprintf(buf, PAGE_SIZE, "%d\n",
2860 (status == RFKILL_STATE_UNBLOCKED) ? 1 : 0);
Henrique de Moraes Holschuhd3a6ade2007-04-27 22:00:17 -03002861}
2862
2863static ssize_t bluetooth_enable_store(struct device *dev,
2864 struct device_attribute *attr,
2865 const char *buf, size_t count)
2866{
2867 unsigned long t;
2868 int res;
2869
2870 if (parse_strtoul(buf, 1, &t))
2871 return -EINVAL;
2872
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03002873 res = bluetooth_set_radiosw(t, 1);
Henrique de Moraes Holschuhd3a6ade2007-04-27 22:00:17 -03002874
2875 return (res) ? res : count;
2876}
2877
2878static struct device_attribute dev_attr_bluetooth_enable =
Henrique de Moraes Holschuhcc4c24e2007-05-30 20:50:14 -03002879 __ATTR(bluetooth_enable, S_IWUSR | S_IRUGO,
Henrique de Moraes Holschuhd3a6ade2007-04-27 22:00:17 -03002880 bluetooth_enable_show, bluetooth_enable_store);
2881
2882/* --------------------------------------------------------------------- */
2883
2884static struct attribute *bluetooth_attributes[] = {
2885 &dev_attr_bluetooth_enable.attr,
2886 NULL
2887};
2888
2889static const struct attribute_group bluetooth_attr_group = {
Henrique de Moraes Holschuhd3a6ade2007-04-27 22:00:17 -03002890 .attrs = bluetooth_attributes,
2891};
2892
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03002893static int tpacpi_bluetooth_rfk_get(void *data, enum rfkill_state *state)
2894{
2895 int bts = bluetooth_get_radiosw();
2896
2897 if (bts < 0)
2898 return bts;
2899
2900 *state = bts;
2901 return 0;
2902}
2903
2904static int tpacpi_bluetooth_rfk_set(void *data, enum rfkill_state state)
2905{
2906 return bluetooth_set_radiosw((state == RFKILL_STATE_UNBLOCKED), 0);
2907}
2908
Henrique de Moraes Holschuh07431ec2008-07-21 09:15:50 -03002909static void bluetooth_exit(void)
2910{
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03002911 if (tpacpi_bluetooth_rfkill)
2912 rfkill_unregister(tpacpi_bluetooth_rfkill);
2913
Henrique de Moraes Holschuh07431ec2008-07-21 09:15:50 -03002914 sysfs_remove_group(&tpacpi_pdev->dev.kobj,
2915 &bluetooth_attr_group);
2916}
2917
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03002918static int __init bluetooth_init(struct ibm_init_struct *iibm)
Linus Torvalds1da177e2005-04-16 15:20:36 -07002919{
Henrique de Moraes Holschuhd3a6ade2007-04-27 22:00:17 -03002920 int res;
Henrique de Moraes Holschuhd6fdd1e2007-04-21 11:08:40 -03002921 int status = 0;
2922
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03002923 vdbg_printk(TPACPI_DBG_INIT, "initializing bluetooth subdriver\n");
2924
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02002925 TPACPI_ACPIHANDLE_INIT(hkey);
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03002926
Borislav Deianov78f81cc2005-08-17 00:00:00 -04002927 /* bluetooth not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
2928 G4x, R30, R31, R40e, R50e, T20-22, X20-21 */
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03002929 tp_features.bluetooth = hkey_handle &&
Henrique de Moraes Holschuhd6fdd1e2007-04-21 11:08:40 -03002930 acpi_evalf(hkey_handle, &status, "GBDC", "qd");
Linus Torvalds1da177e2005-04-16 15:20:36 -07002931
Henrique de Moraes Holschuhd6fdd1e2007-04-21 11:08:40 -03002932 vdbg_printk(TPACPI_DBG_INIT, "bluetooth is %s, status 0x%02x\n",
2933 str_supported(tp_features.bluetooth),
2934 status);
2935
Henrique de Moraes Holschuha73f3092009-01-11 03:01:00 -02002936#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
2937 if (dbg_bluetoothemul) {
2938 tp_features.bluetooth = 1;
2939 printk(TPACPI_INFO
2940 "bluetooth switch emulation enabled\n");
2941 } else
2942#endif
Henrique de Moraes Holschuh3a872082008-07-21 09:15:49 -03002943 if (tp_features.bluetooth &&
2944 !(status & TP_ACPI_BLUETOOTH_HWPRESENT)) {
2945 /* no bluetooth hardware present in system */
2946 tp_features.bluetooth = 0;
2947 dbg_printk(TPACPI_DBG_INIT,
2948 "bluetooth hardware not installed\n");
2949 }
2950
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03002951 if (!tp_features.bluetooth)
2952 return 1;
2953
2954 res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
Henrique de Moraes Holschuh3a872082008-07-21 09:15:49 -03002955 &bluetooth_attr_group);
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03002956 if (res)
2957 return res;
2958
2959 res = tpacpi_new_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID,
2960 &tpacpi_bluetooth_rfkill,
2961 RFKILL_TYPE_BLUETOOTH,
2962 "tpacpi_bluetooth_sw",
2963 tpacpi_bluetooth_rfk_set,
2964 tpacpi_bluetooth_rfk_get);
2965 if (res) {
2966 bluetooth_exit();
2967 return res;
Henrique de Moraes Holschuhd6fdd1e2007-04-21 11:08:40 -03002968 }
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03002969
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03002970 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -07002971}
2972
Henrique de Moraes Holschuhd3a6ade2007-04-27 22:00:17 -03002973/* procfs -------------------------------------------------------------- */
Borislav Deianov78f81cc2005-08-17 00:00:00 -04002974static int bluetooth_read(char *p)
Linus Torvalds1da177e2005-04-16 15:20:36 -07002975{
2976 int len = 0;
Henrique de Moraes Holschuhd6fdd1e2007-04-21 11:08:40 -03002977 int status = bluetooth_get_radiosw();
Linus Torvalds1da177e2005-04-16 15:20:36 -07002978
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03002979 if (!tp_features.bluetooth)
Linus Torvalds1da177e2005-04-16 15:20:36 -07002980 len += sprintf(p + len, "status:\t\tnot supported\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -07002981 else {
Henrique de Moraes Holschuhd6fdd1e2007-04-21 11:08:40 -03002982 len += sprintf(p + len, "status:\t\t%s\n",
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03002983 (status == RFKILL_STATE_UNBLOCKED) ?
2984 "enabled" : "disabled");
Linus Torvalds1da177e2005-04-16 15:20:36 -07002985 len += sprintf(p + len, "commands:\tenable, disable\n");
2986 }
2987
2988 return len;
2989}
2990
Borislav Deianov78f81cc2005-08-17 00:00:00 -04002991static int bluetooth_write(char *buf)
Linus Torvalds1da177e2005-04-16 15:20:36 -07002992{
Linus Torvalds1da177e2005-04-16 15:20:36 -07002993 char *cmd;
Linus Torvalds1da177e2005-04-16 15:20:36 -07002994
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03002995 if (!tp_features.bluetooth)
Borislav Deianov78f81cc2005-08-17 00:00:00 -04002996 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -07002997
2998 while ((cmd = next_cmd(&buf))) {
2999 if (strlencmp(cmd, "enable") == 0) {
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03003000 bluetooth_set_radiosw(1, 1);
Linus Torvalds1da177e2005-04-16 15:20:36 -07003001 } else if (strlencmp(cmd, "disable") == 0) {
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03003002 bluetooth_set_radiosw(0, 1);
Linus Torvalds1da177e2005-04-16 15:20:36 -07003003 } else
3004 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -07003005 }
3006
Linus Torvalds1da177e2005-04-16 15:20:36 -07003007 return 0;
3008}
3009
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03003010static struct ibm_struct bluetooth_driver_data = {
3011 .name = "bluetooth",
3012 .read = bluetooth_read,
3013 .write = bluetooth_write,
Henrique de Moraes Holschuhd3a6ade2007-04-27 22:00:17 -03003014 .exit = bluetooth_exit,
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03003015};
3016
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003017/*************************************************************************
3018 * Wan subdriver
3019 */
3020
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -02003021enum {
3022 /* ACPI GWAN/SWAN bits */
3023 TP_ACPI_WANCARD_HWPRESENT = 0x01, /* Wan hw available */
3024 TP_ACPI_WANCARD_RADIOSSW = 0x02, /* Wan radio enabled */
3025 TP_ACPI_WANCARD_UNK = 0x04, /* unknown function */
3026};
3027
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03003028static struct rfkill *tpacpi_wan_rfkill;
3029
Henrique de Moraes Holschuh07431ec2008-07-21 09:15:50 -03003030static int wan_get_radiosw(void)
3031{
3032 int status;
3033
3034 if (!tp_features.wan)
3035 return -ENODEV;
3036
Henrique de Moraes Holschuh133ec3b2008-07-21 09:15:50 -03003037 /* WLSW overrides WWAN in firmware/hardware, reflect that */
3038 if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status)
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03003039 return RFKILL_STATE_HARD_BLOCKED;
Henrique de Moraes Holschuh133ec3b2008-07-21 09:15:50 -03003040
Henrique de Moraes Holschuha73f3092009-01-11 03:01:00 -02003041#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
3042 if (dbg_wwanemul)
3043 return (tpacpi_wwan_emulstate) ?
3044 RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED;
3045#endif
3046
Henrique de Moraes Holschuh07431ec2008-07-21 09:15:50 -03003047 if (!acpi_evalf(hkey_handle, &status, "GWAN", "d"))
3048 return -EIO;
3049
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03003050 return ((status & TP_ACPI_WANCARD_RADIOSSW) != 0) ?
3051 RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED;
Henrique de Moraes Holschuh07431ec2008-07-21 09:15:50 -03003052}
3053
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03003054static void wan_update_rfk(void)
3055{
3056 int status;
3057
3058 if (!tpacpi_wan_rfkill)
3059 return;
3060
3061 status = wan_get_radiosw();
3062 if (status < 0)
3063 return;
3064 rfkill_force_state(tpacpi_wan_rfkill, status);
3065}
3066
3067static int wan_set_radiosw(int radio_on, int update_rfk)
Henrique de Moraes Holschuh07431ec2008-07-21 09:15:50 -03003068{
3069 int status;
3070
3071 if (!tp_features.wan)
3072 return -ENODEV;
3073
Henrique de Moraes Holschuh133ec3b2008-07-21 09:15:50 -03003074 /* WLSW overrides bluetooth in firmware/hardware, but there is no
3075 * reason to risk weird behaviour. */
3076 if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status
3077 && radio_on)
3078 return -EPERM;
3079
Henrique de Moraes Holschuha73f3092009-01-11 03:01:00 -02003080#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
3081 if (dbg_wwanemul) {
3082 tpacpi_wwan_emulstate = !!radio_on;
3083 if (update_rfk)
3084 wan_update_rfk();
3085 return 0;
3086 }
3087#endif
3088
Henrique de Moraes Holschuh07431ec2008-07-21 09:15:50 -03003089 if (!acpi_evalf(hkey_handle, &status, "GWAN", "d"))
3090 return -EIO;
3091 if (radio_on)
3092 status |= TP_ACPI_WANCARD_RADIOSSW;
3093 else
3094 status &= ~TP_ACPI_WANCARD_RADIOSSW;
3095 if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status))
3096 return -EIO;
3097
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03003098 if (update_rfk)
3099 wan_update_rfk();
3100
Henrique de Moraes Holschuh07431ec2008-07-21 09:15:50 -03003101 return 0;
3102}
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -02003103
Henrique de Moraes Holschuhd3a6ade2007-04-27 22:00:17 -03003104/* sysfs wan enable ---------------------------------------------------- */
3105static ssize_t wan_enable_show(struct device *dev,
3106 struct device_attribute *attr,
3107 char *buf)
3108{
3109 int status;
3110
3111 status = wan_get_radiosw();
3112 if (status < 0)
3113 return status;
3114
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03003115 return snprintf(buf, PAGE_SIZE, "%d\n",
3116 (status == RFKILL_STATE_UNBLOCKED) ? 1 : 0);
Henrique de Moraes Holschuhd3a6ade2007-04-27 22:00:17 -03003117}
3118
3119static ssize_t wan_enable_store(struct device *dev,
3120 struct device_attribute *attr,
3121 const char *buf, size_t count)
3122{
3123 unsigned long t;
3124 int res;
3125
3126 if (parse_strtoul(buf, 1, &t))
3127 return -EINVAL;
3128
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03003129 res = wan_set_radiosw(t, 1);
Henrique de Moraes Holschuhd3a6ade2007-04-27 22:00:17 -03003130
3131 return (res) ? res : count;
3132}
3133
3134static struct device_attribute dev_attr_wan_enable =
Henrique de Moraes Holschuhcc4c24e2007-05-30 20:50:14 -03003135 __ATTR(wwan_enable, S_IWUSR | S_IRUGO,
Henrique de Moraes Holschuhd3a6ade2007-04-27 22:00:17 -03003136 wan_enable_show, wan_enable_store);
3137
3138/* --------------------------------------------------------------------- */
3139
3140static struct attribute *wan_attributes[] = {
3141 &dev_attr_wan_enable.attr,
3142 NULL
3143};
3144
3145static const struct attribute_group wan_attr_group = {
Henrique de Moraes Holschuhd3a6ade2007-04-27 22:00:17 -03003146 .attrs = wan_attributes,
3147};
3148
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03003149static int tpacpi_wan_rfk_get(void *data, enum rfkill_state *state)
3150{
3151 int wans = wan_get_radiosw();
3152
3153 if (wans < 0)
3154 return wans;
3155
3156 *state = wans;
3157 return 0;
3158}
3159
3160static int tpacpi_wan_rfk_set(void *data, enum rfkill_state state)
3161{
3162 return wan_set_radiosw((state == RFKILL_STATE_UNBLOCKED), 0);
3163}
3164
Henrique de Moraes Holschuh07431ec2008-07-21 09:15:50 -03003165static void wan_exit(void)
3166{
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03003167 if (tpacpi_wan_rfkill)
3168 rfkill_unregister(tpacpi_wan_rfkill);
3169
Henrique de Moraes Holschuh07431ec2008-07-21 09:15:50 -03003170 sysfs_remove_group(&tpacpi_pdev->dev.kobj,
3171 &wan_attr_group);
3172}
3173
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03003174static int __init wan_init(struct ibm_init_struct *iibm)
Jeremy Fitzhardinge42adb532006-06-01 17:41:00 -04003175{
Henrique de Moraes Holschuhd3a6ade2007-04-27 22:00:17 -03003176 int res;
Henrique de Moraes Holschuhd6fdd1e2007-04-21 11:08:40 -03003177 int status = 0;
3178
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03003179 vdbg_printk(TPACPI_DBG_INIT, "initializing wan subdriver\n");
3180
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02003181 TPACPI_ACPIHANDLE_INIT(hkey);
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03003182
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03003183 tp_features.wan = hkey_handle &&
Henrique de Moraes Holschuhd6fdd1e2007-04-21 11:08:40 -03003184 acpi_evalf(hkey_handle, &status, "GWAN", "qd");
Jeremy Fitzhardinge42adb532006-06-01 17:41:00 -04003185
Henrique de Moraes Holschuhd6fdd1e2007-04-21 11:08:40 -03003186 vdbg_printk(TPACPI_DBG_INIT, "wan is %s, status 0x%02x\n",
3187 str_supported(tp_features.wan),
3188 status);
3189
Henrique de Moraes Holschuha73f3092009-01-11 03:01:00 -02003190#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
3191 if (dbg_wwanemul) {
3192 tp_features.wan = 1;
3193 printk(TPACPI_INFO
3194 "wwan switch emulation enabled\n");
3195 } else
3196#endif
Henrique de Moraes Holschuh3a872082008-07-21 09:15:49 -03003197 if (tp_features.wan &&
3198 !(status & TP_ACPI_WANCARD_HWPRESENT)) {
3199 /* no wan hardware present in system */
3200 tp_features.wan = 0;
3201 dbg_printk(TPACPI_DBG_INIT,
3202 "wan hardware not installed\n");
3203 }
3204
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03003205 if (!tp_features.wan)
3206 return 1;
3207
3208 res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
Henrique de Moraes Holschuh3a872082008-07-21 09:15:49 -03003209 &wan_attr_group);
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03003210 if (res)
3211 return res;
3212
3213 res = tpacpi_new_rfkill(TPACPI_RFK_WWAN_SW_ID,
3214 &tpacpi_wan_rfkill,
3215 RFKILL_TYPE_WWAN,
3216 "tpacpi_wwan_sw",
3217 tpacpi_wan_rfk_set,
3218 tpacpi_wan_rfk_get);
3219 if (res) {
3220 wan_exit();
3221 return res;
Henrique de Moraes Holschuhd6fdd1e2007-04-21 11:08:40 -03003222 }
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03003223
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03003224 return 0;
Jeremy Fitzhardinge42adb532006-06-01 17:41:00 -04003225}
3226
Henrique de Moraes Holschuhd3a6ade2007-04-27 22:00:17 -03003227/* procfs -------------------------------------------------------------- */
Jeremy Fitzhardinge42adb532006-06-01 17:41:00 -04003228static int wan_read(char *p)
3229{
3230 int len = 0;
Henrique de Moraes Holschuhd6fdd1e2007-04-21 11:08:40 -03003231 int status = wan_get_radiosw();
Jeremy Fitzhardinge42adb532006-06-01 17:41:00 -04003232
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03003233 if (!tp_features.wan)
Jeremy Fitzhardinge42adb532006-06-01 17:41:00 -04003234 len += sprintf(p + len, "status:\t\tnot supported\n");
Jeremy Fitzhardinge42adb532006-06-01 17:41:00 -04003235 else {
Henrique de Moraes Holschuhd6fdd1e2007-04-21 11:08:40 -03003236 len += sprintf(p + len, "status:\t\t%s\n",
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03003237 (status == RFKILL_STATE_UNBLOCKED) ?
3238 "enabled" : "disabled");
Jeremy Fitzhardinge42adb532006-06-01 17:41:00 -04003239 len += sprintf(p + len, "commands:\tenable, disable\n");
3240 }
3241
3242 return len;
3243}
3244
3245static int wan_write(char *buf)
3246{
Jeremy Fitzhardinge42adb532006-06-01 17:41:00 -04003247 char *cmd;
Jeremy Fitzhardinge42adb532006-06-01 17:41:00 -04003248
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03003249 if (!tp_features.wan)
Jeremy Fitzhardinge42adb532006-06-01 17:41:00 -04003250 return -ENODEV;
3251
3252 while ((cmd = next_cmd(&buf))) {
3253 if (strlencmp(cmd, "enable") == 0) {
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03003254 wan_set_radiosw(1, 1);
Jeremy Fitzhardinge42adb532006-06-01 17:41:00 -04003255 } else if (strlencmp(cmd, "disable") == 0) {
Henrique de Moraes Holschuh0e74dc22008-07-21 09:15:51 -03003256 wan_set_radiosw(0, 1);
Jeremy Fitzhardinge42adb532006-06-01 17:41:00 -04003257 } else
3258 return -EINVAL;
Jeremy Fitzhardinge42adb532006-06-01 17:41:00 -04003259 }
3260
Jeremy Fitzhardinge42adb532006-06-01 17:41:00 -04003261 return 0;
3262}
3263
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03003264static struct ibm_struct wan_driver_data = {
3265 .name = "wan",
3266 .read = wan_read,
3267 .write = wan_write,
Henrique de Moraes Holschuhd3a6ade2007-04-27 22:00:17 -03003268 .exit = wan_exit,
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03003269};
3270
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003271/*************************************************************************
3272 * Video subdriver
3273 */
3274
Henrique de Moraes Holschuhd7c1d172008-02-16 02:17:54 -02003275#ifdef CONFIG_THINKPAD_ACPI_VIDEO
3276
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -02003277enum video_access_mode {
3278 TPACPI_VIDEO_NONE = 0,
3279 TPACPI_VIDEO_570, /* 570 */
3280 TPACPI_VIDEO_770, /* 600e/x, 770e, 770x */
3281 TPACPI_VIDEO_NEW, /* all others */
3282};
3283
3284enum { /* video status flags, based on VIDEO_570 */
3285 TP_ACPI_VIDEO_S_LCD = 0x01, /* LCD output enabled */
3286 TP_ACPI_VIDEO_S_CRT = 0x02, /* CRT output enabled */
3287 TP_ACPI_VIDEO_S_DVI = 0x08, /* DVI output enabled */
3288};
3289
3290enum { /* TPACPI_VIDEO_570 constants */
3291 TP_ACPI_VIDEO_570_PHSCMD = 0x87, /* unknown magic constant :( */
3292 TP_ACPI_VIDEO_570_PHSMASK = 0x03, /* PHS bits that map to
3293 * video_status_flags */
3294 TP_ACPI_VIDEO_570_PHS2CMD = 0x8b, /* unknown magic constant :( */
3295 TP_ACPI_VIDEO_570_PHS2SET = 0x80, /* unknown magic constant :( */
3296};
3297
Henrique de Moraes Holschuh9a8e1732006-11-25 16:36:00 -02003298static enum video_access_mode video_supported;
3299static int video_orig_autosw;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003300
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -02003301static int video_autosw_get(void);
3302static int video_autosw_set(int enable);
3303
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02003304TPACPI_HANDLE(vid2, root, "\\_SB.PCI0.AGPB.VID"); /* G41 */
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003305
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03003306static int __init video_init(struct ibm_init_struct *iibm)
Linus Torvalds1da177e2005-04-16 15:20:36 -07003307{
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003308 int ivga;
3309
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03003310 vdbg_printk(TPACPI_DBG_INIT, "initializing video subdriver\n");
3311
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02003312 TPACPI_ACPIHANDLE_INIT(vid);
3313 TPACPI_ACPIHANDLE_INIT(vid2);
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03003314
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003315 if (vid2_handle && acpi_evalf(NULL, &ivga, "\\IVGA", "d") && ivga)
3316 /* G41, assume IVGA doesn't change */
3317 vid_handle = vid2_handle;
3318
3319 if (!vid_handle)
3320 /* video switching not supported on R30, R31 */
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03003321 video_supported = TPACPI_VIDEO_NONE;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003322 else if (acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd"))
3323 /* 570 */
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03003324 video_supported = TPACPI_VIDEO_570;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003325 else if (acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd"))
3326 /* 600e/x, 770e, 770x */
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03003327 video_supported = TPACPI_VIDEO_770;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003328 else
3329 /* all others */
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03003330 video_supported = TPACPI_VIDEO_NEW;
Linus Torvalds1da177e2005-04-16 15:20:36 -07003331
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03003332 vdbg_printk(TPACPI_DBG_INIT, "video is %s, mode %d\n",
3333 str_supported(video_supported != TPACPI_VIDEO_NONE),
3334 video_supported);
3335
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03003336 return (video_supported != TPACPI_VIDEO_NONE)? 0 : 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -07003337}
3338
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003339static void video_exit(void)
3340{
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003341 dbg_printk(TPACPI_DBG_EXIT,
3342 "restoring original video autoswitch mode\n");
3343 if (video_autosw_set(video_orig_autosw))
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02003344 printk(TPACPI_ERR "error while trying to restore original "
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003345 "video autoswitch mode\n");
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003346}
3347
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003348static int video_outputsw_get(void)
Linus Torvalds1da177e2005-04-16 15:20:36 -07003349{
3350 int status = 0;
3351 int i;
3352
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003353 switch (video_supported) {
3354 case TPACPI_VIDEO_570:
3355 if (!acpi_evalf(NULL, &i, "\\_SB.PHS", "dd",
3356 TP_ACPI_VIDEO_570_PHSCMD))
3357 return -EIO;
3358 status = i & TP_ACPI_VIDEO_570_PHSMASK;
3359 break;
3360 case TPACPI_VIDEO_770:
3361 if (!acpi_evalf(NULL, &i, "\\VCDL", "d"))
3362 return -EIO;
3363 if (i)
3364 status |= TP_ACPI_VIDEO_S_LCD;
3365 if (!acpi_evalf(NULL, &i, "\\VCDC", "d"))
3366 return -EIO;
3367 if (i)
3368 status |= TP_ACPI_VIDEO_S_CRT;
3369 break;
3370 case TPACPI_VIDEO_NEW:
3371 if (!acpi_evalf(NULL, NULL, "\\VUPS", "vd", 1) ||
3372 !acpi_evalf(NULL, &i, "\\VCDC", "d"))
3373 return -EIO;
3374 if (i)
3375 status |= TP_ACPI_VIDEO_S_CRT;
Linus Torvalds1da177e2005-04-16 15:20:36 -07003376
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003377 if (!acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0) ||
3378 !acpi_evalf(NULL, &i, "\\VCDL", "d"))
3379 return -EIO;
3380 if (i)
3381 status |= TP_ACPI_VIDEO_S_LCD;
3382 if (!acpi_evalf(NULL, &i, "\\VCDD", "d"))
3383 return -EIO;
3384 if (i)
3385 status |= TP_ACPI_VIDEO_S_DVI;
3386 break;
3387 default:
3388 return -ENOSYS;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003389 }
Linus Torvalds1da177e2005-04-16 15:20:36 -07003390
3391 return status;
3392}
3393
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003394static int video_outputsw_set(int status)
3395{
3396 int autosw;
3397 int res = 0;
3398
3399 switch (video_supported) {
3400 case TPACPI_VIDEO_570:
3401 res = acpi_evalf(NULL, NULL,
3402 "\\_SB.PHS2", "vdd",
3403 TP_ACPI_VIDEO_570_PHS2CMD,
3404 status | TP_ACPI_VIDEO_570_PHS2SET);
3405 break;
3406 case TPACPI_VIDEO_770:
3407 autosw = video_autosw_get();
3408 if (autosw < 0)
3409 return autosw;
3410
3411 res = video_autosw_set(1);
3412 if (res)
3413 return res;
3414 res = acpi_evalf(vid_handle, NULL,
3415 "ASWT", "vdd", status * 0x100, 0);
3416 if (!autosw && video_autosw_set(autosw)) {
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02003417 printk(TPACPI_ERR
3418 "video auto-switch left enabled due to error\n");
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003419 return -EIO;
3420 }
3421 break;
3422 case TPACPI_VIDEO_NEW:
3423 res = acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0x80) &&
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02003424 acpi_evalf(NULL, NULL, "\\VSDS", "vdd", status, 1);
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003425 break;
3426 default:
3427 return -ENOSYS;
3428 }
3429
3430 return (res)? 0 : -EIO;
3431}
3432
3433static int video_autosw_get(void)
Linus Torvalds1da177e2005-04-16 15:20:36 -07003434{
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003435 int autosw = 0;
3436
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003437 switch (video_supported) {
3438 case TPACPI_VIDEO_570:
3439 if (!acpi_evalf(vid_handle, &autosw, "SWIT", "d"))
3440 return -EIO;
3441 break;
3442 case TPACPI_VIDEO_770:
3443 case TPACPI_VIDEO_NEW:
3444 if (!acpi_evalf(vid_handle, &autosw, "^VDEE", "d"))
3445 return -EIO;
3446 break;
3447 default:
3448 return -ENOSYS;
3449 }
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003450
3451 return autosw & 1;
3452}
3453
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003454static int video_autosw_set(int enable)
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003455{
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003456 if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", (enable)? 1 : 0))
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003457 return -EIO;
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003458 return 0;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003459}
3460
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003461static int video_outputsw_cycle(void)
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003462{
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003463 int autosw = video_autosw_get();
3464 int res;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003465
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003466 if (autosw < 0)
3467 return autosw;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003468
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003469 switch (video_supported) {
3470 case TPACPI_VIDEO_570:
3471 res = video_autosw_set(1);
3472 if (res)
3473 return res;
3474 res = acpi_evalf(ec_handle, NULL, "_Q16", "v");
3475 break;
3476 case TPACPI_VIDEO_770:
3477 case TPACPI_VIDEO_NEW:
3478 res = video_autosw_set(1);
3479 if (res)
3480 return res;
3481 res = acpi_evalf(vid_handle, NULL, "VSWT", "v");
3482 break;
3483 default:
3484 return -ENOSYS;
3485 }
3486 if (!autosw && video_autosw_set(autosw)) {
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02003487 printk(TPACPI_ERR
3488 "video auto-switch left enabled due to error\n");
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003489 return -EIO;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003490 }
3491
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003492 return (res)? 0 : -EIO;
3493}
3494
3495static int video_expand_toggle(void)
3496{
3497 switch (video_supported) {
3498 case TPACPI_VIDEO_570:
3499 return acpi_evalf(ec_handle, NULL, "_Q17", "v")?
3500 0 : -EIO;
3501 case TPACPI_VIDEO_770:
3502 return acpi_evalf(vid_handle, NULL, "VEXP", "v")?
3503 0 : -EIO;
3504 case TPACPI_VIDEO_NEW:
3505 return acpi_evalf(NULL, NULL, "\\VEXP", "v")?
3506 0 : -EIO;
3507 default:
3508 return -ENOSYS;
3509 }
3510 /* not reached */
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003511}
3512
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003513static int video_read(char *p)
3514{
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003515 int status, autosw;
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003516 int len = 0;
3517
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003518 if (video_supported == TPACPI_VIDEO_NONE) {
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003519 len += sprintf(p + len, "status:\t\tnot supported\n");
3520 return len;
3521 }
3522
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003523 status = video_outputsw_get();
3524 if (status < 0)
3525 return status;
3526
3527 autosw = video_autosw_get();
3528 if (autosw < 0)
3529 return autosw;
3530
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003531 len += sprintf(p + len, "status:\t\tsupported\n");
3532 len += sprintf(p + len, "lcd:\t\t%s\n", enabled(status, 0));
3533 len += sprintf(p + len, "crt:\t\t%s\n", enabled(status, 1));
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03003534 if (video_supported == TPACPI_VIDEO_NEW)
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003535 len += sprintf(p + len, "dvi:\t\t%s\n", enabled(status, 3));
3536 len += sprintf(p + len, "auto:\t\t%s\n", enabled(autosw, 0));
3537 len += sprintf(p + len, "commands:\tlcd_enable, lcd_disable\n");
3538 len += sprintf(p + len, "commands:\tcrt_enable, crt_disable\n");
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03003539 if (video_supported == TPACPI_VIDEO_NEW)
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003540 len += sprintf(p + len, "commands:\tdvi_enable, dvi_disable\n");
3541 len += sprintf(p + len, "commands:\tauto_enable, auto_disable\n");
3542 len += sprintf(p + len, "commands:\tvideo_switch, expand_toggle\n");
3543
3544 return len;
3545}
3546
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003547static int video_write(char *buf)
Linus Torvalds1da177e2005-04-16 15:20:36 -07003548{
3549 char *cmd;
3550 int enable, disable, status;
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003551 int res;
Linus Torvalds1da177e2005-04-16 15:20:36 -07003552
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003553 if (video_supported == TPACPI_VIDEO_NONE)
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003554 return -ENODEV;
3555
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003556 enable = 0;
3557 disable = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -07003558
3559 while ((cmd = next_cmd(&buf))) {
3560 if (strlencmp(cmd, "lcd_enable") == 0) {
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003561 enable |= TP_ACPI_VIDEO_S_LCD;
Linus Torvalds1da177e2005-04-16 15:20:36 -07003562 } else if (strlencmp(cmd, "lcd_disable") == 0) {
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003563 disable |= TP_ACPI_VIDEO_S_LCD;
Linus Torvalds1da177e2005-04-16 15:20:36 -07003564 } else if (strlencmp(cmd, "crt_enable") == 0) {
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003565 enable |= TP_ACPI_VIDEO_S_CRT;
Linus Torvalds1da177e2005-04-16 15:20:36 -07003566 } else if (strlencmp(cmd, "crt_disable") == 0) {
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003567 disable |= TP_ACPI_VIDEO_S_CRT;
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03003568 } else if (video_supported == TPACPI_VIDEO_NEW &&
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003569 strlencmp(cmd, "dvi_enable") == 0) {
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003570 enable |= TP_ACPI_VIDEO_S_DVI;
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03003571 } else if (video_supported == TPACPI_VIDEO_NEW &&
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003572 strlencmp(cmd, "dvi_disable") == 0) {
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003573 disable |= TP_ACPI_VIDEO_S_DVI;
Linus Torvalds1da177e2005-04-16 15:20:36 -07003574 } else if (strlencmp(cmd, "auto_enable") == 0) {
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003575 res = video_autosw_set(1);
3576 if (res)
3577 return res;
Linus Torvalds1da177e2005-04-16 15:20:36 -07003578 } else if (strlencmp(cmd, "auto_disable") == 0) {
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003579 res = video_autosw_set(0);
3580 if (res)
3581 return res;
Linus Torvalds1da177e2005-04-16 15:20:36 -07003582 } else if (strlencmp(cmd, "video_switch") == 0) {
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003583 res = video_outputsw_cycle();
3584 if (res)
3585 return res;
Linus Torvalds1da177e2005-04-16 15:20:36 -07003586 } else if (strlencmp(cmd, "expand_toggle") == 0) {
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003587 res = video_expand_toggle();
3588 if (res)
3589 return res;
Linus Torvalds1da177e2005-04-16 15:20:36 -07003590 } else
3591 return -EINVAL;
3592 }
3593
3594 if (enable || disable) {
Henrique de Moraes Holschuh83f34722007-04-21 11:08:41 -03003595 status = video_outputsw_get();
3596 if (status < 0)
3597 return status;
3598 res = video_outputsw_set((status & ~disable) | enable);
3599 if (res)
3600 return res;
Linus Torvalds1da177e2005-04-16 15:20:36 -07003601 }
3602
3603 return 0;
3604}
3605
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03003606static struct ibm_struct video_driver_data = {
3607 .name = "video",
3608 .read = video_read,
3609 .write = video_write,
3610 .exit = video_exit,
3611};
3612
Henrique de Moraes Holschuhd7c1d172008-02-16 02:17:54 -02003613#endif /* CONFIG_THINKPAD_ACPI_VIDEO */
3614
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003615/*************************************************************************
3616 * Light (thinklight) subdriver
3617 */
Linus Torvalds1da177e2005-04-16 15:20:36 -07003618
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02003619TPACPI_HANDLE(lght, root, "\\LGHT"); /* A21e, A2xm/p, T20-22, X20-21 */
3620TPACPI_HANDLE(ledb, ec, "LEDB"); /* G4x */
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003621
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03003622static int light_get_status(void)
3623{
3624 int status = 0;
3625
3626 if (tp_features.light_status) {
3627 if (!acpi_evalf(ec_handle, &status, "KBLT", "d"))
3628 return -EIO;
3629 return (!!status);
3630 }
3631
3632 return -ENXIO;
3633}
3634
3635static int light_set_status(int status)
3636{
3637 int rc;
3638
3639 if (tp_features.light) {
3640 if (cmos_handle) {
3641 rc = acpi_evalf(cmos_handle, NULL, NULL, "vd",
3642 (status)?
3643 TP_CMOS_THINKLIGHT_ON :
3644 TP_CMOS_THINKLIGHT_OFF);
3645 } else {
3646 rc = acpi_evalf(lght_handle, NULL, NULL, "vd",
3647 (status)? 1 : 0);
3648 }
3649 return (rc)? 0 : -EIO;
3650 }
3651
3652 return -ENXIO;
3653}
3654
Henrique de Moraes Holschuhe3065012008-04-26 01:02:24 -03003655static void light_set_status_worker(struct work_struct *work)
3656{
3657 struct tpacpi_led_classdev *data =
3658 container_of(work, struct tpacpi_led_classdev, work);
3659
3660 if (likely(tpacpi_lifecycle == TPACPI_LIFE_RUNNING))
3661 light_set_status((data->new_brightness != LED_OFF));
3662}
3663
3664static void light_sysfs_set(struct led_classdev *led_cdev,
3665 enum led_brightness brightness)
3666{
3667 struct tpacpi_led_classdev *data =
3668 container_of(led_cdev,
3669 struct tpacpi_led_classdev,
3670 led_classdev);
3671 data->new_brightness = brightness;
Henrique de Moraes Holschuhe0e3c062008-04-26 01:02:28 -03003672 queue_work(tpacpi_wq, &data->work);
Henrique de Moraes Holschuhe3065012008-04-26 01:02:24 -03003673}
3674
3675static enum led_brightness light_sysfs_get(struct led_classdev *led_cdev)
3676{
3677 return (light_get_status() == 1)? LED_FULL : LED_OFF;
3678}
3679
3680static struct tpacpi_led_classdev tpacpi_led_thinklight = {
3681 .led_classdev = {
3682 .name = "tpacpi::thinklight",
3683 .brightness_set = &light_sysfs_set,
3684 .brightness_get = &light_sysfs_get,
3685 }
3686};
3687
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03003688static int __init light_init(struct ibm_init_struct *iibm)
Linus Torvalds1da177e2005-04-16 15:20:36 -07003689{
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03003690 int rc;
Henrique de Moraes Holschuhe3065012008-04-26 01:02:24 -03003691
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03003692 vdbg_printk(TPACPI_DBG_INIT, "initializing light subdriver\n");
3693
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02003694 TPACPI_ACPIHANDLE_INIT(ledb);
3695 TPACPI_ACPIHANDLE_INIT(lght);
3696 TPACPI_ACPIHANDLE_INIT(cmos);
Henrique de Moraes Holschuhe3065012008-04-26 01:02:24 -03003697 INIT_WORK(&tpacpi_led_thinklight.work, light_set_status_worker);
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03003698
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003699 /* light not supported on 570, 600e/x, 770e, 770x, G4x, R30, R31 */
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03003700 tp_features.light = (cmos_handle || lght_handle) && !ledb_handle;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003701
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03003702 if (tp_features.light)
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003703 /* light status not supported on
3704 570, 600e/x, 770e, 770x, G4x, R30, R31, R32, X20 */
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03003705 tp_features.light_status =
3706 acpi_evalf(ec_handle, NULL, "KBLT", "qv");
Linus Torvalds1da177e2005-04-16 15:20:36 -07003707
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03003708 vdbg_printk(TPACPI_DBG_INIT, "light is %s, light status is %s\n",
3709 str_supported(tp_features.light),
3710 str_supported(tp_features.light_status));
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03003711
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03003712 if (!tp_features.light)
3713 return 1;
3714
3715 rc = led_classdev_register(&tpacpi_pdev->dev,
3716 &tpacpi_led_thinklight.led_classdev);
Henrique de Moraes Holschuhe3065012008-04-26 01:02:24 -03003717
3718 if (rc < 0) {
3719 tp_features.light = 0;
3720 tp_features.light_status = 0;
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03003721 } else {
3722 rc = 0;
Henrique de Moraes Holschuhe3065012008-04-26 01:02:24 -03003723 }
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03003724
Henrique de Moraes Holschuhe3065012008-04-26 01:02:24 -03003725 return rc;
3726}
3727
3728static void light_exit(void)
3729{
3730 led_classdev_unregister(&tpacpi_led_thinklight.led_classdev);
3731 if (work_pending(&tpacpi_led_thinklight.work))
Henrique de Moraes Holschuhe0e3c062008-04-26 01:02:28 -03003732 flush_workqueue(tpacpi_wq);
Linus Torvalds1da177e2005-04-16 15:20:36 -07003733}
3734
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003735static int light_read(char *p)
Linus Torvalds1da177e2005-04-16 15:20:36 -07003736{
3737 int len = 0;
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03003738 int status;
Linus Torvalds1da177e2005-04-16 15:20:36 -07003739
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03003740 if (!tp_features.light) {
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003741 len += sprintf(p + len, "status:\t\tnot supported\n");
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03003742 } else if (!tp_features.light_status) {
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003743 len += sprintf(p + len, "status:\t\tunknown\n");
3744 len += sprintf(p + len, "commands:\ton, off\n");
3745 } else {
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03003746 status = light_get_status();
3747 if (status < 0)
3748 return status;
Linus Torvalds1da177e2005-04-16 15:20:36 -07003749 len += sprintf(p + len, "status:\t\t%s\n", onoff(status, 0));
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003750 len += sprintf(p + len, "commands:\ton, off\n");
3751 }
Linus Torvalds1da177e2005-04-16 15:20:36 -07003752
3753 return len;
3754}
3755
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003756static int light_write(char *buf)
Linus Torvalds1da177e2005-04-16 15:20:36 -07003757{
Linus Torvalds1da177e2005-04-16 15:20:36 -07003758 char *cmd;
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03003759 int newstatus = 0;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003760
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03003761 if (!tp_features.light)
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003762 return -ENODEV;
3763
Linus Torvalds1da177e2005-04-16 15:20:36 -07003764 while ((cmd = next_cmd(&buf))) {
3765 if (strlencmp(cmd, "on") == 0) {
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03003766 newstatus = 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -07003767 } else if (strlencmp(cmd, "off") == 0) {
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03003768 newstatus = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -07003769 } else
3770 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -07003771 }
3772
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03003773 return light_set_status(newstatus);
Linus Torvalds1da177e2005-04-16 15:20:36 -07003774}
3775
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03003776static struct ibm_struct light_driver_data = {
3777 .name = "light",
3778 .read = light_read,
3779 .write = light_write,
Henrique de Moraes Holschuhe3065012008-04-26 01:02:24 -03003780 .exit = light_exit,
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03003781};
3782
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003783/*************************************************************************
3784 * Dock subdriver
3785 */
Linus Torvalds1da177e2005-04-16 15:20:36 -07003786
Henrique de Moraes Holschuh85998242007-03-29 01:58:41 -03003787#ifdef CONFIG_THINKPAD_ACPI_DOCK
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003788
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -02003789static void dock_notify(struct ibm_struct *ibm, u32 event);
3790static int dock_read(char *p);
3791static int dock_write(char *buf);
3792
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02003793TPACPI_HANDLE(dock, root, "\\_SB.GDCK", /* X30, X31, X40 */
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003794 "\\_SB.PCI0.DOCK", /* 600e/x,770e,770x,A2xm/p,T20-22,X20-21 */
3795 "\\_SB.PCI0.PCI1.DOCK", /* all others */
3796 "\\_SB.PCI.ISA.SLCE", /* 570 */
3797 ); /* A21e,G4x,R30,R31,R32,R40,R40e,R50e */
3798
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03003799/* don't list other alternatives as we install a notify handler on the 570 */
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02003800TPACPI_HANDLE(pci, root, "\\_SB.PCI"); /* 570 */
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03003801
Thomas Renninger1ba90e32007-07-23 14:44:41 +02003802static const struct acpi_device_id ibm_pci_device_ids[] = {
3803 {PCI_ROOT_HID_STRING, 0},
3804 {"", 0},
3805};
3806
Henrique de Moraes Holschuhd94a7f12007-04-27 22:00:15 -03003807static struct tp_acpi_drv_struct ibm_dock_acpidriver[2] = {
3808 {
3809 .notify = dock_notify,
3810 .handle = &dock_handle,
3811 .type = ACPI_SYSTEM_NOTIFY,
3812 },
3813 {
Henrique de Moraes Holschuh996fba02007-07-18 23:45:40 -03003814 /* THIS ONE MUST NEVER BE USED FOR DRIVER AUTOLOADING.
3815 * We just use it to get notifications of dock hotplug
3816 * in very old thinkpads */
Thomas Renninger1ba90e32007-07-23 14:44:41 +02003817 .hid = ibm_pci_device_ids,
Henrique de Moraes Holschuhd94a7f12007-04-27 22:00:15 -03003818 .notify = dock_notify,
3819 .handle = &pci_handle,
3820 .type = ACPI_SYSTEM_NOTIFY,
3821 },
3822};
3823
3824static struct ibm_struct dock_driver_data[2] = {
3825 {
3826 .name = "dock",
3827 .read = dock_read,
3828 .write = dock_write,
3829 .acpi = &ibm_dock_acpidriver[0],
3830 },
3831 {
3832 .name = "dock",
3833 .acpi = &ibm_dock_acpidriver[1],
3834 },
3835};
3836
Linus Torvalds1da177e2005-04-16 15:20:36 -07003837#define dock_docked() (_sta(dock_handle) & 1)
3838
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03003839static int __init dock_init(struct ibm_init_struct *iibm)
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03003840{
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03003841 vdbg_printk(TPACPI_DBG_INIT, "initializing dock subdriver\n");
3842
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02003843 TPACPI_ACPIHANDLE_INIT(dock);
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03003844
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03003845 vdbg_printk(TPACPI_DBG_INIT, "dock is %s\n",
3846 str_supported(dock_handle != NULL));
3847
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03003848 return (dock_handle)? 0 : 1;
3849}
3850
Henrique de Moraes Holschuhd94a7f12007-04-27 22:00:15 -03003851static int __init dock_init2(struct ibm_init_struct *iibm)
3852{
3853 int dock2_needed;
3854
3855 vdbg_printk(TPACPI_DBG_INIT, "initializing dock subdriver part 2\n");
3856
3857 if (dock_driver_data[0].flags.acpi_driver_registered &&
3858 dock_driver_data[0].flags.acpi_notify_installed) {
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02003859 TPACPI_ACPIHANDLE_INIT(pci);
Henrique de Moraes Holschuhd94a7f12007-04-27 22:00:15 -03003860 dock2_needed = (pci_handle != NULL);
3861 vdbg_printk(TPACPI_DBG_INIT,
3862 "dock PCI handler for the TP 570 is %s\n",
3863 str_supported(dock2_needed));
3864 } else {
3865 vdbg_printk(TPACPI_DBG_INIT,
3866 "dock subdriver part 2 not required\n");
3867 dock2_needed = 0;
3868 }
3869
3870 return (dock2_needed)? 0 : 1;
3871}
3872
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003873static void dock_notify(struct ibm_struct *ibm, u32 event)
3874{
3875 int docked = dock_docked();
Thomas Renninger1ba90e32007-07-23 14:44:41 +02003876 int pci = ibm->acpi->hid && ibm->acpi->device &&
3877 acpi_match_device_ids(ibm->acpi->device, ibm_pci_device_ids);
Zhang Rui962ce8c2007-08-23 01:24:31 +08003878 int data;
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003879
3880 if (event == 1 && !pci) /* 570 */
Zhang Rui962ce8c2007-08-23 01:24:31 +08003881 data = 1; /* button */
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003882 else if (event == 1 && pci) /* 570 */
Zhang Rui962ce8c2007-08-23 01:24:31 +08003883 data = 3; /* dock */
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003884 else if (event == 3 && docked)
Zhang Rui962ce8c2007-08-23 01:24:31 +08003885 data = 1; /* button */
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003886 else if (event == 3 && !docked)
Zhang Rui962ce8c2007-08-23 01:24:31 +08003887 data = 2; /* undock */
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003888 else if (event == 0 && docked)
Zhang Rui962ce8c2007-08-23 01:24:31 +08003889 data = 3; /* dock */
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003890 else {
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02003891 printk(TPACPI_ERR "unknown dock event %d, status %d\n",
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003892 event, _sta(dock_handle));
Zhang Rui962ce8c2007-08-23 01:24:31 +08003893 data = 0; /* unknown */
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003894 }
Len Brown14e04fb2007-08-23 15:20:26 -04003895 acpi_bus_generate_proc_event(ibm->acpi->device, event, data);
Zhang Rui962ce8c2007-08-23 01:24:31 +08003896 acpi_bus_generate_netlink_event(ibm->acpi->device->pnp.device_class,
Kay Sieverse0b36fc2009-01-11 03:00:59 -02003897 dev_name(&ibm->acpi->device->dev),
Zhang Rui962ce8c2007-08-23 01:24:31 +08003898 event, data);
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003899}
3900
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003901static int dock_read(char *p)
Linus Torvalds1da177e2005-04-16 15:20:36 -07003902{
3903 int len = 0;
3904 int docked = dock_docked();
3905
3906 if (!dock_handle)
3907 len += sprintf(p + len, "status:\t\tnot supported\n");
3908 else if (!docked)
3909 len += sprintf(p + len, "status:\t\tundocked\n");
3910 else {
3911 len += sprintf(p + len, "status:\t\tdocked\n");
3912 len += sprintf(p + len, "commands:\tdock, undock\n");
3913 }
3914
3915 return len;
3916}
3917
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003918static int dock_write(char *buf)
Linus Torvalds1da177e2005-04-16 15:20:36 -07003919{
3920 char *cmd;
3921
3922 if (!dock_docked())
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003923 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -07003924
3925 while ((cmd = next_cmd(&buf))) {
3926 if (strlencmp(cmd, "undock") == 0) {
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003927 if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 0) ||
3928 !acpi_evalf(dock_handle, NULL, "_EJ0", "vd", 1))
Linus Torvalds1da177e2005-04-16 15:20:36 -07003929 return -EIO;
3930 } else if (strlencmp(cmd, "dock") == 0) {
3931 if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 1))
3932 return -EIO;
3933 } else
3934 return -EINVAL;
3935 }
3936
3937 return 0;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003938}
Linus Torvalds1da177e2005-04-16 15:20:36 -07003939
Henrique de Moraes Holschuh85998242007-03-29 01:58:41 -03003940#endif /* CONFIG_THINKPAD_ACPI_DOCK */
Linus Torvalds1da177e2005-04-16 15:20:36 -07003941
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003942/*************************************************************************
3943 * Bay subdriver
3944 */
Linus Torvalds1da177e2005-04-16 15:20:36 -07003945
Henrique de Moraes Holschuh85998242007-03-29 01:58:41 -03003946#ifdef CONFIG_THINKPAD_ACPI_BAY
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -02003947
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02003948TPACPI_HANDLE(bay, root, "\\_SB.PCI.IDE.SECN.MAST", /* 570 */
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003949 "\\_SB.PCI0.IDE0.IDES.IDSM", /* 600e/x, 770e, 770x */
3950 "\\_SB.PCI0.SATA.SCND.MSTR", /* T60, X60, Z60 */
3951 "\\_SB.PCI0.IDE0.SCND.MSTR", /* all others */
3952 ); /* A21e, R30, R31 */
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02003953TPACPI_HANDLE(bay_ej, bay, "_EJ3", /* 600e/x, A2xm/p, A3x */
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003954 "_EJ0", /* all others */
3955 ); /* 570,A21e,G4x,R30,R31,R32,R40e,R50e */
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02003956TPACPI_HANDLE(bay2, root, "\\_SB.PCI0.IDE0.PRIM.SLAV", /* A3x, R32 */
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003957 "\\_SB.PCI0.IDE0.IDEP.IDPS", /* 600e/x, 770e, 770x */
3958 ); /* all others */
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02003959TPACPI_HANDLE(bay2_ej, bay2, "_EJ3", /* 600e/x, 770e, A3x */
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003960 "_EJ0", /* 770x */
3961 ); /* all others */
3962
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03003963static int __init bay_init(struct ibm_init_struct *iibm)
Linus Torvalds1da177e2005-04-16 15:20:36 -07003964{
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03003965 vdbg_printk(TPACPI_DBG_INIT, "initializing bay subdriver\n");
3966
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02003967 TPACPI_ACPIHANDLE_INIT(bay);
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03003968 if (bay_handle)
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02003969 TPACPI_ACPIHANDLE_INIT(bay_ej);
3970 TPACPI_ACPIHANDLE_INIT(bay2);
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03003971 if (bay2_handle)
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02003972 TPACPI_ACPIHANDLE_INIT(bay2_ej);
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03003973
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03003974 tp_features.bay_status = bay_handle &&
3975 acpi_evalf(bay_handle, NULL, "_STA", "qv");
3976 tp_features.bay_status2 = bay2_handle &&
3977 acpi_evalf(bay2_handle, NULL, "_STA", "qv");
Borislav Deianov78f81cc2005-08-17 00:00:00 -04003978
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03003979 tp_features.bay_eject = bay_handle && bay_ej_handle &&
3980 (strlencmp(bay_ej_path, "_EJ0") == 0 || experimental);
3981 tp_features.bay_eject2 = bay2_handle && bay2_ej_handle &&
3982 (strlencmp(bay2_ej_path, "_EJ0") == 0 || experimental);
Linus Torvalds1da177e2005-04-16 15:20:36 -07003983
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03003984 vdbg_printk(TPACPI_DBG_INIT,
3985 "bay 1: status %s, eject %s; bay 2: status %s, eject %s\n",
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03003986 str_supported(tp_features.bay_status),
3987 str_supported(tp_features.bay_eject),
3988 str_supported(tp_features.bay_status2),
3989 str_supported(tp_features.bay_eject2));
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03003990
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03003991 return (tp_features.bay_status || tp_features.bay_eject ||
3992 tp_features.bay_status2 || tp_features.bay_eject2)? 0 : 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -07003993}
3994
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03003995static void bay_notify(struct ibm_struct *ibm, u32 event)
3996{
Len Brown14e04fb2007-08-23 15:20:26 -04003997 acpi_bus_generate_proc_event(ibm->acpi->device, event, 0);
Zhang Rui962ce8c2007-08-23 01:24:31 +08003998 acpi_bus_generate_netlink_event(ibm->acpi->device->pnp.device_class,
Kay Sieverse0b36fc2009-01-11 03:00:59 -02003999 dev_name(&ibm->acpi->device->dev),
Zhang Rui962ce8c2007-08-23 01:24:31 +08004000 event, 0);
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03004001}
4002
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004003#define bay_occupied(b) (_sta(b##_handle) & 1)
4004
4005static int bay_read(char *p)
Linus Torvalds1da177e2005-04-16 15:20:36 -07004006{
4007 int len = 0;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004008 int occupied = bay_occupied(bay);
4009 int occupied2 = bay_occupied(bay2);
4010 int eject, eject2;
4011
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03004012 len += sprintf(p + len, "status:\t\t%s\n",
4013 tp_features.bay_status ?
4014 (occupied ? "occupied" : "unoccupied") :
4015 "not supported");
4016 if (tp_features.bay_status2)
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004017 len += sprintf(p + len, "status2:\t%s\n", occupied2 ?
4018 "occupied" : "unoccupied");
4019
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03004020 eject = tp_features.bay_eject && occupied;
4021 eject2 = tp_features.bay_eject2 && occupied2;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004022
4023 if (eject && eject2)
4024 len += sprintf(p + len, "commands:\teject, eject2\n");
4025 else if (eject)
Linus Torvalds1da177e2005-04-16 15:20:36 -07004026 len += sprintf(p + len, "commands:\teject\n");
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004027 else if (eject2)
4028 len += sprintf(p + len, "commands:\teject2\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -07004029
4030 return len;
4031}
4032
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004033static int bay_write(char *buf)
Linus Torvalds1da177e2005-04-16 15:20:36 -07004034{
4035 char *cmd;
4036
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03004037 if (!tp_features.bay_eject && !tp_features.bay_eject2)
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004038 return -ENODEV;
4039
Linus Torvalds1da177e2005-04-16 15:20:36 -07004040 while ((cmd = next_cmd(&buf))) {
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03004041 if (tp_features.bay_eject && strlencmp(cmd, "eject") == 0) {
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004042 if (!acpi_evalf(bay_ej_handle, NULL, NULL, "vd", 1))
4043 return -EIO;
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03004044 } else if (tp_features.bay_eject2 &&
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004045 strlencmp(cmd, "eject2") == 0) {
4046 if (!acpi_evalf(bay2_ej_handle, NULL, NULL, "vd", 1))
Linus Torvalds1da177e2005-04-16 15:20:36 -07004047 return -EIO;
4048 } else
4049 return -EINVAL;
4050 }
4051
4052 return 0;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004053}
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03004054
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -03004055static struct tp_acpi_drv_struct ibm_bay_acpidriver = {
4056 .notify = bay_notify,
4057 .handle = &bay_handle,
4058 .type = ACPI_SYSTEM_NOTIFY,
4059};
4060
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03004061static struct ibm_struct bay_driver_data = {
4062 .name = "bay",
4063 .read = bay_read,
4064 .write = bay_write,
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -03004065 .acpi = &ibm_bay_acpidriver,
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03004066};
4067
Henrique de Moraes Holschuh85998242007-03-29 01:58:41 -03004068#endif /* CONFIG_THINKPAD_ACPI_BAY */
Linus Torvalds1da177e2005-04-16 15:20:36 -07004069
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03004070/*************************************************************************
4071 * CMOS subdriver
4072 */
4073
Henrique de Moraes Holschuhb6160042007-04-24 11:48:19 -03004074/* sysfs cmos_command -------------------------------------------------- */
4075static ssize_t cmos_command_store(struct device *dev,
4076 struct device_attribute *attr,
4077 const char *buf, size_t count)
4078{
4079 unsigned long cmos_cmd;
4080 int res;
4081
4082 if (parse_strtoul(buf, 21, &cmos_cmd))
4083 return -EINVAL;
4084
4085 res = issue_thinkpad_cmos_command(cmos_cmd);
4086 return (res)? res : count;
4087}
4088
4089static struct device_attribute dev_attr_cmos_command =
4090 __ATTR(cmos_command, S_IWUSR, NULL, cmos_command_store);
4091
4092/* --------------------------------------------------------------------- */
4093
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03004094static int __init cmos_init(struct ibm_init_struct *iibm)
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03004095{
Henrique de Moraes Holschuhb6160042007-04-24 11:48:19 -03004096 int res;
4097
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03004098 vdbg_printk(TPACPI_DBG_INIT,
4099 "initializing cmos commands subdriver\n");
4100
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02004101 TPACPI_ACPIHANDLE_INIT(cmos);
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03004102
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03004103 vdbg_printk(TPACPI_DBG_INIT, "cmos commands are %s\n",
4104 str_supported(cmos_handle != NULL));
Henrique de Moraes Holschuhb6160042007-04-24 11:48:19 -03004105
4106 res = device_create_file(&tpacpi_pdev->dev, &dev_attr_cmos_command);
4107 if (res)
4108 return res;
4109
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03004110 return (cmos_handle)? 0 : 1;
4111}
4112
Henrique de Moraes Holschuhb6160042007-04-24 11:48:19 -03004113static void cmos_exit(void)
4114{
4115 device_remove_file(&tpacpi_pdev->dev, &dev_attr_cmos_command);
4116}
4117
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004118static int cmos_read(char *p)
Linus Torvalds1da177e2005-04-16 15:20:36 -07004119{
4120 int len = 0;
4121
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004122 /* cmos not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
4123 R30, R31, T20-22, X20-21 */
Linus Torvalds1da177e2005-04-16 15:20:36 -07004124 if (!cmos_handle)
4125 len += sprintf(p + len, "status:\t\tnot supported\n");
4126 else {
4127 len += sprintf(p + len, "status:\t\tsupported\n");
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004128 len += sprintf(p + len, "commands:\t<cmd> (<cmd> is 0-21)\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -07004129 }
4130
4131 return len;
4132}
4133
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004134static int cmos_write(char *buf)
Linus Torvalds1da177e2005-04-16 15:20:36 -07004135{
4136 char *cmd;
Henrique de Moraes Holschuhc9bea992007-04-21 11:08:42 -03004137 int cmos_cmd, res;
Linus Torvalds1da177e2005-04-16 15:20:36 -07004138
4139 while ((cmd = next_cmd(&buf))) {
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004140 if (sscanf(cmd, "%u", &cmos_cmd) == 1 &&
4141 cmos_cmd >= 0 && cmos_cmd <= 21) {
Linus Torvalds1da177e2005-04-16 15:20:36 -07004142 /* cmos_cmd set */
4143 } else
4144 return -EINVAL;
4145
Henrique de Moraes Holschuhc9bea992007-04-21 11:08:42 -03004146 res = issue_thinkpad_cmos_command(cmos_cmd);
4147 if (res)
4148 return res;
Linus Torvalds1da177e2005-04-16 15:20:36 -07004149 }
4150
4151 return 0;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004152}
4153
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03004154static struct ibm_struct cmos_driver_data = {
4155 .name = "cmos",
4156 .read = cmos_read,
4157 .write = cmos_write,
Henrique de Moraes Holschuhb6160042007-04-24 11:48:19 -03004158 .exit = cmos_exit,
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03004159};
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03004160
4161/*************************************************************************
4162 * LED subdriver
4163 */
4164
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -02004165enum led_access_mode {
4166 TPACPI_LED_NONE = 0,
4167 TPACPI_LED_570, /* 570 */
4168 TPACPI_LED_OLD, /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */
4169 TPACPI_LED_NEW, /* all others */
4170};
4171
4172enum { /* For TPACPI_LED_OLD */
4173 TPACPI_LED_EC_HLCL = 0x0c, /* EC reg to get led to power on */
4174 TPACPI_LED_EC_HLBL = 0x0d, /* EC reg to blink a lit led */
4175 TPACPI_LED_EC_HLMS = 0x0e, /* EC reg to select led to command */
4176};
4177
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03004178enum led_status_t {
4179 TPACPI_LED_OFF = 0,
4180 TPACPI_LED_ON,
4181 TPACPI_LED_BLINK,
4182};
4183
Henrique de Moraes Holschuh9a8e1732006-11-25 16:36:00 -02004184static enum led_access_mode led_supported;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004185
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02004186TPACPI_HANDLE(led, ec, "SLED", /* 570 */
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02004187 "SYSL", /* 600e/x, 770e, 770x, A21e, A2xm/p, */
4188 /* T20-22, X20-21 */
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03004189 "LED", /* all others */
4190 ); /* R30, R31 */
4191
Henrique de Moraes Holschuhaf116102008-04-26 01:02:25 -03004192#define TPACPI_LED_NUMLEDS 8
4193static struct tpacpi_led_classdev *tpacpi_leds;
4194static enum led_status_t tpacpi_led_state_cache[TPACPI_LED_NUMLEDS];
Harvey Harrisone3aa51f2008-05-29 17:51:57 -07004195static const char * const tpacpi_led_names[TPACPI_LED_NUMLEDS] = {
Henrique de Moraes Holschuhaf116102008-04-26 01:02:25 -03004196 /* there's a limit of 19 chars + NULL before 2.6.26 */
4197 "tpacpi::power",
4198 "tpacpi:orange:batt",
4199 "tpacpi:green:batt",
4200 "tpacpi::dock_active",
4201 "tpacpi::bay_active",
4202 "tpacpi::dock_batt",
4203 "tpacpi::unknown_led",
4204 "tpacpi::standby",
4205};
4206
Henrique de Moraes Holschuh24e45bb2008-06-03 23:36:11 -03004207static int led_get_status(const unsigned int led)
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03004208{
4209 int status;
Henrique de Moraes Holschuhaf116102008-04-26 01:02:25 -03004210 enum led_status_t led_s;
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03004211
4212 switch (led_supported) {
4213 case TPACPI_LED_570:
4214 if (!acpi_evalf(ec_handle,
4215 &status, "GLED", "dd", 1 << led))
4216 return -EIO;
Henrique de Moraes Holschuhaf116102008-04-26 01:02:25 -03004217 led_s = (status == 0)?
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03004218 TPACPI_LED_OFF :
4219 ((status == 1)?
4220 TPACPI_LED_ON :
4221 TPACPI_LED_BLINK);
Henrique de Moraes Holschuhaf116102008-04-26 01:02:25 -03004222 tpacpi_led_state_cache[led] = led_s;
4223 return led_s;
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03004224 default:
4225 return -ENXIO;
4226 }
4227
4228 /* not reached */
4229}
4230
Henrique de Moraes Holschuh24e45bb2008-06-03 23:36:11 -03004231static int led_set_status(const unsigned int led,
4232 const enum led_status_t ledstatus)
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03004233{
4234 /* off, on, blink. Index is led_status_t */
Henrique de Moraes Holschuh24e45bb2008-06-03 23:36:11 -03004235 static const unsigned int led_sled_arg1[] = { 0, 1, 3 };
4236 static const unsigned int led_led_arg1[] = { 0, 0x80, 0xc0 };
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03004237
4238 int rc = 0;
4239
4240 switch (led_supported) {
4241 case TPACPI_LED_570:
Henrique de Moraes Holschuh24e45bb2008-06-03 23:36:11 -03004242 /* 570 */
4243 if (led > 7)
4244 return -EINVAL;
4245 if (!acpi_evalf(led_handle, NULL, NULL, "vdd",
4246 (1 << led), led_sled_arg1[ledstatus]))
4247 rc = -EIO;
4248 break;
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03004249 case TPACPI_LED_OLD:
Henrique de Moraes Holschuh24e45bb2008-06-03 23:36:11 -03004250 /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */
4251 if (led > 7)
4252 return -EINVAL;
4253 rc = ec_write(TPACPI_LED_EC_HLMS, (1 << led));
4254 if (rc >= 0)
4255 rc = ec_write(TPACPI_LED_EC_HLBL,
4256 (ledstatus == TPACPI_LED_BLINK) << led);
4257 if (rc >= 0)
4258 rc = ec_write(TPACPI_LED_EC_HLCL,
4259 (ledstatus != TPACPI_LED_OFF) << led);
4260 break;
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03004261 case TPACPI_LED_NEW:
Henrique de Moraes Holschuh24e45bb2008-06-03 23:36:11 -03004262 /* all others */
4263 if (!acpi_evalf(led_handle, NULL, NULL, "vdd",
4264 led, led_led_arg1[ledstatus]))
4265 rc = -EIO;
4266 break;
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03004267 default:
4268 rc = -ENXIO;
4269 }
4270
Henrique de Moraes Holschuhaf116102008-04-26 01:02:25 -03004271 if (!rc)
4272 tpacpi_led_state_cache[led] = ledstatus;
4273
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03004274 return rc;
4275}
4276
Henrique de Moraes Holschuhaf116102008-04-26 01:02:25 -03004277static void led_sysfs_set_status(unsigned int led,
4278 enum led_brightness brightness)
4279{
4280 led_set_status(led,
4281 (brightness == LED_OFF) ?
4282 TPACPI_LED_OFF :
4283 (tpacpi_led_state_cache[led] == TPACPI_LED_BLINK) ?
4284 TPACPI_LED_BLINK : TPACPI_LED_ON);
4285}
4286
4287static void led_set_status_worker(struct work_struct *work)
4288{
4289 struct tpacpi_led_classdev *data =
4290 container_of(work, struct tpacpi_led_classdev, work);
4291
4292 if (likely(tpacpi_lifecycle == TPACPI_LIFE_RUNNING))
4293 led_sysfs_set_status(data->led, data->new_brightness);
4294}
4295
4296static void led_sysfs_set(struct led_classdev *led_cdev,
4297 enum led_brightness brightness)
4298{
4299 struct tpacpi_led_classdev *data = container_of(led_cdev,
4300 struct tpacpi_led_classdev, led_classdev);
4301
4302 data->new_brightness = brightness;
Henrique de Moraes Holschuhe0e3c062008-04-26 01:02:28 -03004303 queue_work(tpacpi_wq, &data->work);
Henrique de Moraes Holschuhaf116102008-04-26 01:02:25 -03004304}
4305
4306static int led_sysfs_blink_set(struct led_classdev *led_cdev,
4307 unsigned long *delay_on, unsigned long *delay_off)
4308{
4309 struct tpacpi_led_classdev *data = container_of(led_cdev,
4310 struct tpacpi_led_classdev, led_classdev);
4311
4312 /* Can we choose the flash rate? */
4313 if (*delay_on == 0 && *delay_off == 0) {
4314 /* yes. set them to the hardware blink rate (1 Hz) */
4315 *delay_on = 500; /* ms */
4316 *delay_off = 500; /* ms */
4317 } else if ((*delay_on != 500) || (*delay_off != 500))
4318 return -EINVAL;
4319
4320 data->new_brightness = TPACPI_LED_BLINK;
Henrique de Moraes Holschuhe0e3c062008-04-26 01:02:28 -03004321 queue_work(tpacpi_wq, &data->work);
Henrique de Moraes Holschuhaf116102008-04-26 01:02:25 -03004322
4323 return 0;
4324}
4325
4326static enum led_brightness led_sysfs_get(struct led_classdev *led_cdev)
4327{
4328 int rc;
4329
4330 struct tpacpi_led_classdev *data = container_of(led_cdev,
4331 struct tpacpi_led_classdev, led_classdev);
4332
4333 rc = led_get_status(data->led);
4334
4335 if (rc == TPACPI_LED_OFF || rc < 0)
4336 rc = LED_OFF; /* no error handling in led class :( */
4337 else
4338 rc = LED_FULL;
4339
4340 return rc;
4341}
4342
4343static void led_exit(void)
4344{
4345 unsigned int i;
4346
4347 for (i = 0; i < TPACPI_LED_NUMLEDS; i++) {
4348 if (tpacpi_leds[i].led_classdev.name)
4349 led_classdev_unregister(&tpacpi_leds[i].led_classdev);
4350 }
4351
4352 kfree(tpacpi_leds);
Henrique de Moraes Holschuhaf116102008-04-26 01:02:25 -03004353}
4354
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03004355static int __init led_init(struct ibm_init_struct *iibm)
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004356{
Henrique de Moraes Holschuhaf116102008-04-26 01:02:25 -03004357 unsigned int i;
4358 int rc;
4359
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03004360 vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n");
4361
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02004362 TPACPI_ACPIHANDLE_INIT(led);
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03004363
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004364 if (!led_handle)
4365 /* led not supported on R30, R31 */
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03004366 led_supported = TPACPI_LED_NONE;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004367 else if (strlencmp(led_path, "SLED") == 0)
4368 /* 570 */
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03004369 led_supported = TPACPI_LED_570;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004370 else if (strlencmp(led_path, "SYSL") == 0)
4371 /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03004372 led_supported = TPACPI_LED_OLD;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004373 else
4374 /* all others */
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03004375 led_supported = TPACPI_LED_NEW;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004376
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03004377 vdbg_printk(TPACPI_DBG_INIT, "LED commands are %s, mode %d\n",
4378 str_supported(led_supported), led_supported);
4379
Henrique de Moraes Holschuhaf116102008-04-26 01:02:25 -03004380 tpacpi_leds = kzalloc(sizeof(*tpacpi_leds) * TPACPI_LED_NUMLEDS,
4381 GFP_KERNEL);
4382 if (!tpacpi_leds) {
4383 printk(TPACPI_ERR "Out of memory for LED data\n");
4384 return -ENOMEM;
4385 }
4386
4387 for (i = 0; i < TPACPI_LED_NUMLEDS; i++) {
4388 tpacpi_leds[i].led = i;
4389
4390 tpacpi_leds[i].led_classdev.brightness_set = &led_sysfs_set;
4391 tpacpi_leds[i].led_classdev.blink_set = &led_sysfs_blink_set;
4392 if (led_supported == TPACPI_LED_570)
4393 tpacpi_leds[i].led_classdev.brightness_get =
4394 &led_sysfs_get;
4395
4396 tpacpi_leds[i].led_classdev.name = tpacpi_led_names[i];
4397
4398 INIT_WORK(&tpacpi_leds[i].work, led_set_status_worker);
4399
4400 rc = led_classdev_register(&tpacpi_pdev->dev,
4401 &tpacpi_leds[i].led_classdev);
4402 if (rc < 0) {
4403 tpacpi_leds[i].led_classdev.name = NULL;
4404 led_exit();
4405 return rc;
4406 }
4407 }
4408
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03004409 return (led_supported != TPACPI_LED_NONE)? 0 : 1;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004410}
4411
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03004412#define str_led_status(s) \
4413 ((s) == TPACPI_LED_OFF ? "off" : \
4414 ((s) == TPACPI_LED_ON ? "on" : "blinking"))
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004415
4416static int led_read(char *p)
Linus Torvalds1da177e2005-04-16 15:20:36 -07004417{
4418 int len = 0;
4419
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004420 if (!led_supported) {
4421 len += sprintf(p + len, "status:\t\tnot supported\n");
4422 return len;
4423 }
4424 len += sprintf(p + len, "status:\t\tsupported\n");
4425
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03004426 if (led_supported == TPACPI_LED_570) {
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004427 /* 570 */
4428 int i, status;
4429 for (i = 0; i < 8; i++) {
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03004430 status = led_get_status(i);
4431 if (status < 0)
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004432 return -EIO;
4433 len += sprintf(p + len, "%d:\t\t%s\n",
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03004434 i, str_led_status(status));
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004435 }
4436 }
4437
Linus Torvalds1da177e2005-04-16 15:20:36 -07004438 len += sprintf(p + len, "commands:\t"
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004439 "<led> on, <led> off, <led> blink (<led> is 0-7)\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -07004440
4441 return len;
4442}
4443
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004444static int led_write(char *buf)
Linus Torvalds1da177e2005-04-16 15:20:36 -07004445{
4446 char *cmd;
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03004447 int led, rc;
4448 enum led_status_t s;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004449
4450 if (!led_supported)
4451 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -07004452
4453 while ((cmd = next_cmd(&buf))) {
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004454 if (sscanf(cmd, "%d", &led) != 1 || led < 0 || led > 7)
Linus Torvalds1da177e2005-04-16 15:20:36 -07004455 return -EINVAL;
4456
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004457 if (strstr(cmd, "off")) {
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03004458 s = TPACPI_LED_OFF;
Linus Torvalds1da177e2005-04-16 15:20:36 -07004459 } else if (strstr(cmd, "on")) {
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03004460 s = TPACPI_LED_ON;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004461 } else if (strstr(cmd, "blink")) {
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03004462 s = TPACPI_LED_BLINK;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004463 } else {
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03004464 return -EINVAL;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004465 }
Henrique de Moraes Holschuh4fa68112008-04-26 01:02:23 -03004466
4467 rc = led_set_status(led, s);
4468 if (rc < 0)
4469 return rc;
Linus Torvalds1da177e2005-04-16 15:20:36 -07004470 }
4471
4472 return 0;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004473}
4474
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03004475static struct ibm_struct led_driver_data = {
4476 .name = "led",
4477 .read = led_read,
4478 .write = led_write,
Henrique de Moraes Holschuhaf116102008-04-26 01:02:25 -03004479 .exit = led_exit,
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03004480};
4481
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03004482/*************************************************************************
4483 * Beep subdriver
4484 */
4485
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02004486TPACPI_HANDLE(beep, ec, "BEEP"); /* all except R30, R31 */
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03004487
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03004488static int __init beep_init(struct ibm_init_struct *iibm)
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03004489{
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03004490 vdbg_printk(TPACPI_DBG_INIT, "initializing beep subdriver\n");
4491
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02004492 TPACPI_ACPIHANDLE_INIT(beep);
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03004493
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03004494 vdbg_printk(TPACPI_DBG_INIT, "beep is %s\n",
4495 str_supported(beep_handle != NULL));
4496
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03004497 return (beep_handle)? 0 : 1;
4498}
4499
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004500static int beep_read(char *p)
Linus Torvalds1da177e2005-04-16 15:20:36 -07004501{
4502 int len = 0;
4503
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004504 if (!beep_handle)
4505 len += sprintf(p + len, "status:\t\tnot supported\n");
4506 else {
4507 len += sprintf(p + len, "status:\t\tsupported\n");
4508 len += sprintf(p + len, "commands:\t<cmd> (<cmd> is 0-17)\n");
4509 }
Linus Torvalds1da177e2005-04-16 15:20:36 -07004510
4511 return len;
4512}
4513
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004514static int beep_write(char *buf)
Linus Torvalds1da177e2005-04-16 15:20:36 -07004515{
4516 char *cmd;
4517 int beep_cmd;
4518
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004519 if (!beep_handle)
4520 return -ENODEV;
4521
Linus Torvalds1da177e2005-04-16 15:20:36 -07004522 while ((cmd = next_cmd(&buf))) {
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004523 if (sscanf(cmd, "%u", &beep_cmd) == 1 &&
4524 beep_cmd >= 0 && beep_cmd <= 17) {
Linus Torvalds1da177e2005-04-16 15:20:36 -07004525 /* beep_cmd set */
4526 } else
4527 return -EINVAL;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004528 if (!acpi_evalf(beep_handle, NULL, NULL, "vdd", beep_cmd, 0))
Linus Torvalds1da177e2005-04-16 15:20:36 -07004529 return -EIO;
4530 }
4531
4532 return 0;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004533}
4534
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03004535static struct ibm_struct beep_driver_data = {
4536 .name = "beep",
4537 .read = beep_read,
4538 .write = beep_write,
4539};
4540
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03004541/*************************************************************************
4542 * Thermal subdriver
4543 */
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004544
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -02004545enum thermal_access_mode {
4546 TPACPI_THERMAL_NONE = 0, /* No thermal support */
4547 TPACPI_THERMAL_ACPI_TMP07, /* Use ACPI TMP0-7 */
4548 TPACPI_THERMAL_ACPI_UPDT, /* Use ACPI TMP0-7 with UPDT */
4549 TPACPI_THERMAL_TPEC_8, /* Use ACPI EC regs, 8 sensors */
4550 TPACPI_THERMAL_TPEC_16, /* Use ACPI EC regs, 16 sensors */
4551};
4552
4553enum { /* TPACPI_THERMAL_TPEC_* */
4554 TP_EC_THERMAL_TMP0 = 0x78, /* ACPI EC regs TMP 0..7 */
4555 TP_EC_THERMAL_TMP8 = 0xC0, /* ACPI EC regs TMP 8..15 */
4556 TP_EC_THERMAL_TMP_NA = -128, /* ACPI EC sensor not available */
4557};
4558
4559#define TPACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */
4560struct ibm_thermal_sensors_struct {
4561 s32 temp[TPACPI_MAX_THERMAL_SENSORS];
4562};
4563
Henrique de Moraes Holschuha26f8782006-11-24 11:47:08 -02004564static enum thermal_access_mode thermal_read_mode;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004565
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02004566/* idx is zero-based */
4567static int thermal_get_sensor(int idx, s32 *value)
4568{
4569 int t;
4570 s8 tmp;
4571 char tmpi[5];
4572
4573 t = TP_EC_THERMAL_TMP0;
4574
4575 switch (thermal_read_mode) {
4576#if TPACPI_MAX_THERMAL_SENSORS >= 16
4577 case TPACPI_THERMAL_TPEC_16:
4578 if (idx >= 8 && idx <= 15) {
4579 t = TP_EC_THERMAL_TMP8;
4580 idx -= 8;
4581 }
4582 /* fallthrough */
4583#endif
4584 case TPACPI_THERMAL_TPEC_8:
4585 if (idx <= 7) {
4586 if (!acpi_ec_read(t + idx, &tmp))
4587 return -EIO;
4588 *value = tmp * 1000;
4589 return 0;
4590 }
4591 break;
4592
4593 case TPACPI_THERMAL_ACPI_UPDT:
4594 if (idx <= 7) {
4595 snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx);
4596 if (!acpi_evalf(ec_handle, NULL, "UPDT", "v"))
4597 return -EIO;
4598 if (!acpi_evalf(ec_handle, &t, tmpi, "d"))
4599 return -EIO;
4600 *value = (t - 2732) * 100;
4601 return 0;
4602 }
4603 break;
4604
4605 case TPACPI_THERMAL_ACPI_TMP07:
4606 if (idx <= 7) {
4607 snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx);
4608 if (!acpi_evalf(ec_handle, &t, tmpi, "d"))
4609 return -EIO;
4610 if (t > 127 || t < -127)
4611 t = TP_EC_THERMAL_TMP_NA;
4612 *value = t * 1000;
4613 return 0;
4614 }
4615 break;
4616
4617 case TPACPI_THERMAL_NONE:
4618 default:
4619 return -ENOSYS;
4620 }
4621
4622 return -EINVAL;
4623}
4624
4625static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s)
4626{
4627 int res, i;
4628 int n;
4629
4630 n = 8;
4631 i = 0;
4632
4633 if (!s)
4634 return -EINVAL;
4635
4636 if (thermal_read_mode == TPACPI_THERMAL_TPEC_16)
4637 n = 16;
4638
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02004639 for (i = 0 ; i < n; i++) {
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02004640 res = thermal_get_sensor(i, &s->temp[i]);
4641 if (res)
4642 return res;
4643 }
4644
4645 return n;
4646}
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -02004647
Henrique de Moraes Holschuh2c37aa42007-04-24 11:48:16 -03004648/* sysfs temp##_input -------------------------------------------------- */
4649
4650static ssize_t thermal_temp_input_show(struct device *dev,
4651 struct device_attribute *attr,
4652 char *buf)
4653{
4654 struct sensor_device_attribute *sensor_attr =
4655 to_sensor_dev_attr(attr);
4656 int idx = sensor_attr->index;
4657 s32 value;
4658 int res;
4659
4660 res = thermal_get_sensor(idx, &value);
4661 if (res)
4662 return res;
4663 if (value == TP_EC_THERMAL_TMP_NA * 1000)
4664 return -ENXIO;
4665
4666 return snprintf(buf, PAGE_SIZE, "%d\n", value);
4667}
4668
4669#define THERMAL_SENSOR_ATTR_TEMP(_idxA, _idxB) \
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02004670 SENSOR_ATTR(temp##_idxA##_input, S_IRUGO, \
4671 thermal_temp_input_show, NULL, _idxB)
Henrique de Moraes Holschuh2c37aa42007-04-24 11:48:16 -03004672
4673static struct sensor_device_attribute sensor_dev_attr_thermal_temp_input[] = {
4674 THERMAL_SENSOR_ATTR_TEMP(1, 0),
4675 THERMAL_SENSOR_ATTR_TEMP(2, 1),
4676 THERMAL_SENSOR_ATTR_TEMP(3, 2),
4677 THERMAL_SENSOR_ATTR_TEMP(4, 3),
4678 THERMAL_SENSOR_ATTR_TEMP(5, 4),
4679 THERMAL_SENSOR_ATTR_TEMP(6, 5),
4680 THERMAL_SENSOR_ATTR_TEMP(7, 6),
4681 THERMAL_SENSOR_ATTR_TEMP(8, 7),
4682 THERMAL_SENSOR_ATTR_TEMP(9, 8),
4683 THERMAL_SENSOR_ATTR_TEMP(10, 9),
4684 THERMAL_SENSOR_ATTR_TEMP(11, 10),
4685 THERMAL_SENSOR_ATTR_TEMP(12, 11),
4686 THERMAL_SENSOR_ATTR_TEMP(13, 12),
4687 THERMAL_SENSOR_ATTR_TEMP(14, 13),
4688 THERMAL_SENSOR_ATTR_TEMP(15, 14),
4689 THERMAL_SENSOR_ATTR_TEMP(16, 15),
4690};
4691
4692#define THERMAL_ATTRS(X) \
4693 &sensor_dev_attr_thermal_temp_input[X].dev_attr.attr
4694
4695static struct attribute *thermal_temp_input_attr[] = {
4696 THERMAL_ATTRS(8),
4697 THERMAL_ATTRS(9),
4698 THERMAL_ATTRS(10),
4699 THERMAL_ATTRS(11),
4700 THERMAL_ATTRS(12),
4701 THERMAL_ATTRS(13),
4702 THERMAL_ATTRS(14),
4703 THERMAL_ATTRS(15),
4704 THERMAL_ATTRS(0),
4705 THERMAL_ATTRS(1),
4706 THERMAL_ATTRS(2),
4707 THERMAL_ATTRS(3),
4708 THERMAL_ATTRS(4),
4709 THERMAL_ATTRS(5),
4710 THERMAL_ATTRS(6),
4711 THERMAL_ATTRS(7),
4712 NULL
4713};
4714
4715static const struct attribute_group thermal_temp_input16_group = {
4716 .attrs = thermal_temp_input_attr
4717};
4718
4719static const struct attribute_group thermal_temp_input8_group = {
4720 .attrs = &thermal_temp_input_attr[8]
4721};
4722
4723#undef THERMAL_SENSOR_ATTR_TEMP
4724#undef THERMAL_ATTRS
4725
4726/* --------------------------------------------------------------------- */
4727
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03004728static int __init thermal_init(struct ibm_init_struct *iibm)
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004729{
Henrique de Moraes Holschuh60eb0b32006-11-24 11:47:08 -02004730 u8 t, ta1, ta2;
4731 int i;
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03004732 int acpi_tmp7;
Henrique de Moraes Holschuh2c37aa42007-04-24 11:48:16 -03004733 int res;
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03004734
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03004735 vdbg_printk(TPACPI_DBG_INIT, "initializing thermal subdriver\n");
4736
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03004737 acpi_tmp7 = acpi_evalf(ec_handle, NULL, "TMP7", "qv");
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004738
Henrique de Moraes Holschuh3d6f99c2007-07-18 23:45:46 -03004739 if (thinkpad_id.ec_model) {
Henrique de Moraes Holschuh60eb0b32006-11-24 11:47:08 -02004740 /*
4741 * Direct EC access mode: sensors at registers
4742 * 0x78-0x7F, 0xC0-0xC7. Registers return 0x00 for
4743 * non-implemented, thermal sensors return 0x80 when
4744 * not available
4745 */
4746
4747 ta1 = ta2 = 0;
4748 for (i = 0; i < 8; i++) {
Henrique de Moraes Holschuh04cc8622007-04-21 11:08:43 -03004749 if (acpi_ec_read(TP_EC_THERMAL_TMP0 + i, &t)) {
Henrique de Moraes Holschuh60eb0b32006-11-24 11:47:08 -02004750 ta1 |= t;
4751 } else {
4752 ta1 = 0;
4753 break;
4754 }
Henrique de Moraes Holschuh04cc8622007-04-21 11:08:43 -03004755 if (acpi_ec_read(TP_EC_THERMAL_TMP8 + i, &t)) {
Henrique de Moraes Holschuh60eb0b32006-11-24 11:47:08 -02004756 ta2 |= t;
4757 } else {
4758 ta1 = 0;
4759 break;
4760 }
4761 }
4762 if (ta1 == 0) {
4763 /* This is sheer paranoia, but we handle it anyway */
4764 if (acpi_tmp7) {
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02004765 printk(TPACPI_ERR
Henrique de Moraes Holschuh60eb0b32006-11-24 11:47:08 -02004766 "ThinkPad ACPI EC access misbehaving, "
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02004767 "falling back to ACPI TMPx access "
4768 "mode\n");
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03004769 thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07;
Henrique de Moraes Holschuh60eb0b32006-11-24 11:47:08 -02004770 } else {
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02004771 printk(TPACPI_ERR
Henrique de Moraes Holschuh60eb0b32006-11-24 11:47:08 -02004772 "ThinkPad ACPI EC access misbehaving, "
4773 "disabling thermal sensors access\n");
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03004774 thermal_read_mode = TPACPI_THERMAL_NONE;
Henrique de Moraes Holschuh60eb0b32006-11-24 11:47:08 -02004775 }
4776 } else {
4777 thermal_read_mode =
4778 (ta2 != 0) ?
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03004779 TPACPI_THERMAL_TPEC_16 : TPACPI_THERMAL_TPEC_8;
Henrique de Moraes Holschuh60eb0b32006-11-24 11:47:08 -02004780 }
4781 } else if (acpi_tmp7) {
Henrique de Moraes Holschuha26f8782006-11-24 11:47:08 -02004782 if (acpi_evalf(ec_handle, NULL, "UPDT", "qv")) {
4783 /* 600e/x, 770e, 770x */
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03004784 thermal_read_mode = TPACPI_THERMAL_ACPI_UPDT;
Henrique de Moraes Holschuha26f8782006-11-24 11:47:08 -02004785 } else {
4786 /* Standard ACPI TMPx access, max 8 sensors */
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03004787 thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07;
Henrique de Moraes Holschuha26f8782006-11-24 11:47:08 -02004788 }
4789 } else {
4790 /* temperatures not supported on 570, G4x, R30, R31, R32 */
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03004791 thermal_read_mode = TPACPI_THERMAL_NONE;
Henrique de Moraes Holschuha26f8782006-11-24 11:47:08 -02004792 }
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004793
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03004794 vdbg_printk(TPACPI_DBG_INIT, "thermal is %s, mode %d\n",
4795 str_supported(thermal_read_mode != TPACPI_THERMAL_NONE),
4796 thermal_read_mode);
4797
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02004798 switch (thermal_read_mode) {
Henrique de Moraes Holschuh2c37aa42007-04-24 11:48:16 -03004799 case TPACPI_THERMAL_TPEC_16:
Henrique de Moraes Holschuh7fd40022007-09-25 06:38:03 -03004800 res = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj,
Henrique de Moraes Holschuh2c37aa42007-04-24 11:48:16 -03004801 &thermal_temp_input16_group);
4802 if (res)
4803 return res;
4804 break;
4805 case TPACPI_THERMAL_TPEC_8:
4806 case TPACPI_THERMAL_ACPI_TMP07:
4807 case TPACPI_THERMAL_ACPI_UPDT:
Henrique de Moraes Holschuh7fd40022007-09-25 06:38:03 -03004808 res = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj,
Henrique de Moraes Holschuh2c37aa42007-04-24 11:48:16 -03004809 &thermal_temp_input8_group);
4810 if (res)
4811 return res;
4812 break;
4813 case TPACPI_THERMAL_NONE:
4814 default:
4815 return 1;
4816 }
4817
4818 return 0;
4819}
4820
4821static void thermal_exit(void)
4822{
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02004823 switch (thermal_read_mode) {
Henrique de Moraes Holschuh2c37aa42007-04-24 11:48:16 -03004824 case TPACPI_THERMAL_TPEC_16:
Henrique de Moraes Holschuh7fd40022007-09-25 06:38:03 -03004825 sysfs_remove_group(&tpacpi_sensors_pdev->dev.kobj,
Henrique de Moraes Holschuh2c37aa42007-04-24 11:48:16 -03004826 &thermal_temp_input16_group);
4827 break;
4828 case TPACPI_THERMAL_TPEC_8:
4829 case TPACPI_THERMAL_ACPI_TMP07:
4830 case TPACPI_THERMAL_ACPI_UPDT:
Henrique de Moraes Holschuh7fd40022007-09-25 06:38:03 -03004831 sysfs_remove_group(&tpacpi_sensors_pdev->dev.kobj,
Henrique de Moraes Holschuh2c37aa42007-04-24 11:48:16 -03004832 &thermal_temp_input16_group);
4833 break;
4834 case TPACPI_THERMAL_NONE:
4835 default:
4836 break;
4837 }
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004838}
4839
4840static int thermal_read(char *p)
4841{
4842 int len = 0;
Henrique de Moraes Holschuha26f8782006-11-24 11:47:08 -02004843 int n, i;
4844 struct ibm_thermal_sensors_struct t;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004845
Henrique de Moraes Holschuha26f8782006-11-24 11:47:08 -02004846 n = thermal_get_sensors(&t);
4847 if (unlikely(n < 0))
4848 return n;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004849
Henrique de Moraes Holschuha26f8782006-11-24 11:47:08 -02004850 len += sprintf(p + len, "temperatures:\t");
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004851
Henrique de Moraes Holschuha26f8782006-11-24 11:47:08 -02004852 if (n > 0) {
4853 for (i = 0; i < (n - 1); i++)
4854 len += sprintf(p + len, "%d ", t.temp[i] / 1000);
4855 len += sprintf(p + len, "%d\n", t.temp[i] / 1000);
4856 } else
4857 len += sprintf(p + len, "not supported\n");
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004858
4859 return len;
4860}
4861
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03004862static struct ibm_struct thermal_driver_data = {
4863 .name = "thermal",
4864 .read = thermal_read,
Henrique de Moraes Holschuh2c37aa42007-04-24 11:48:16 -03004865 .exit = thermal_exit,
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03004866};
4867
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03004868/*************************************************************************
4869 * EC Dump subdriver
4870 */
4871
Borislav Deianov78f81cc2005-08-17 00:00:00 -04004872static u8 ecdump_regs[256];
4873
4874static int ecdump_read(char *p)
4875{
4876 int len = 0;
4877 int i, j;
4878 u8 v;
4879
4880 len += sprintf(p + len, "EC "
4881 " +00 +01 +02 +03 +04 +05 +06 +07"
4882 " +08 +09 +0a +0b +0c +0d +0e +0f\n");
4883 for (i = 0; i < 256; i += 16) {
4884 len += sprintf(p + len, "EC 0x%02x:", i);
4885 for (j = 0; j < 16; j++) {
4886 if (!acpi_ec_read(i + j, &v))
4887 break;
4888 if (v != ecdump_regs[i + j])
4889 len += sprintf(p + len, " *%02x", v);
4890 else
4891 len += sprintf(p + len, " %02x", v);
4892 ecdump_regs[i + j] = v;
4893 }
4894 len += sprintf(p + len, "\n");
4895 if (j != 16)
4896 break;
4897 }
4898
4899 /* These are way too dangerous to advertise openly... */
4900#if 0
4901 len += sprintf(p + len, "commands:\t0x<offset> 0x<value>"
4902 " (<offset> is 00-ff, <value> is 00-ff)\n");
4903 len += sprintf(p + len, "commands:\t0x<offset> <value> "
4904 " (<offset> is 00-ff, <value> is 0-255)\n");
4905#endif
4906 return len;
4907}
4908
4909static int ecdump_write(char *buf)
4910{
4911 char *cmd;
4912 int i, v;
4913
4914 while ((cmd = next_cmd(&buf))) {
4915 if (sscanf(cmd, "0x%x 0x%x", &i, &v) == 2) {
4916 /* i and v set */
4917 } else if (sscanf(cmd, "0x%x %u", &i, &v) == 2) {
4918 /* i and v set */
4919 } else
4920 return -EINVAL;
4921 if (i >= 0 && i < 256 && v >= 0 && v < 256) {
4922 if (!acpi_ec_write(i, v))
4923 return -EIO;
4924 } else
4925 return -EINVAL;
4926 }
4927
4928 return 0;
4929}
4930
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03004931static struct ibm_struct ecdump_driver_data = {
4932 .name = "ecdump",
4933 .read = ecdump_read,
4934 .write = ecdump_write,
Henrique de Moraes Holschuh92641172007-04-21 11:08:35 -03004935 .flags.experimental = 1,
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03004936};
4937
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03004938/*************************************************************************
4939 * Backlight/brightness subdriver
4940 */
Holger Macht8acb0252006-10-20 14:30:28 -07004941
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -02004942#define TPACPI_BACKLIGHT_DEV_NAME "thinkpad_screen"
4943
Henrique de Moraes Holschuhe11aecf2008-04-26 01:02:21 -03004944enum {
4945 TP_EC_BACKLIGHT = 0x31,
4946
4947 /* TP_EC_BACKLIGHT bitmasks */
4948 TP_EC_BACKLIGHT_LVLMSK = 0x1F,
4949 TP_EC_BACKLIGHT_CMDMSK = 0xE0,
4950 TP_EC_BACKLIGHT_MAPSW = 0x20,
4951};
4952
Henrique de Moraes Holschuh94954cc2007-07-18 23:45:27 -03004953static struct backlight_device *ibm_backlight_device;
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -02004954static int brightness_mode;
4955static unsigned int brightness_enable = 2; /* 2 = auto, 0 = no, 1 = yes */
4956
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02004957static struct mutex brightness_mutex;
4958
4959/*
4960 * ThinkPads can read brightness from two places: EC 0x31, or
4961 * CMOS NVRAM byte 0x5E, bits 0-3.
Henrique de Moraes Holschuhe11aecf2008-04-26 01:02:21 -03004962 *
4963 * EC 0x31 has the following layout
4964 * Bit 7: unknown function
4965 * Bit 6: unknown function
4966 * Bit 5: Z: honour scale changes, NZ: ignore scale changes
4967 * Bit 4: must be set to zero to avoid problems
4968 * Bit 3-0: backlight brightness level
4969 *
4970 * brightness_get_raw returns status data in the EC 0x31 layout
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02004971 */
Henrique de Moraes Holschuhe11aecf2008-04-26 01:02:21 -03004972static int brightness_get_raw(int *status)
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02004973{
4974 u8 lec = 0, lcmos = 0, level = 0;
4975
4976 if (brightness_mode & 1) {
Henrique de Moraes Holschuhe11aecf2008-04-26 01:02:21 -03004977 if (!acpi_ec_read(TP_EC_BACKLIGHT, &lec))
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02004978 return -EIO;
Henrique de Moraes Holschuhe11aecf2008-04-26 01:02:21 -03004979 level = lec & TP_EC_BACKLIGHT_LVLMSK;
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02004980 };
4981 if (brightness_mode & 2) {
4982 lcmos = (nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS)
4983 & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
4984 >> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
4985 lcmos &= (tp_features.bright_16levels)? 0x0f : 0x07;
4986 level = lcmos;
4987 }
4988
Henrique de Moraes Holschuh2d5e94d2008-04-26 01:02:20 -03004989 if (brightness_mode == 3) {
Henrique de Moraes Holschuhe11aecf2008-04-26 01:02:21 -03004990 *status = lec; /* Prefer EC, CMOS is just a backing store */
4991 lec &= TP_EC_BACKLIGHT_LVLMSK;
Henrique de Moraes Holschuh2d5e94d2008-04-26 01:02:20 -03004992 if (lec == lcmos)
4993 tp_warned.bright_cmos_ec_unsync = 0;
4994 else {
4995 if (!tp_warned.bright_cmos_ec_unsync) {
4996 printk(TPACPI_ERR
4997 "CMOS NVRAM (%u) and EC (%u) do not "
4998 "agree on display brightness level\n",
4999 (unsigned int) lcmos,
5000 (unsigned int) lec);
5001 tp_warned.bright_cmos_ec_unsync = 1;
5002 }
5003 return -EIO;
5004 }
Henrique de Moraes Holschuhe11aecf2008-04-26 01:02:21 -03005005 } else {
5006 *status = level;
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02005007 }
5008
Henrique de Moraes Holschuhe11aecf2008-04-26 01:02:21 -03005009 return 0;
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02005010}
5011
5012/* May return EINTR which can always be mapped to ERESTARTSYS */
5013static int brightness_set(int value)
5014{
5015 int cmos_cmd, inc, i, res;
5016 int current_value;
Henrique de Moraes Holschuhe11aecf2008-04-26 01:02:21 -03005017 int command_bits;
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02005018
Henrique de Moraes Holschuhe11aecf2008-04-26 01:02:21 -03005019 if (value > ((tp_features.bright_16levels)? 15 : 7) ||
5020 value < 0)
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02005021 return -EINVAL;
5022
5023 res = mutex_lock_interruptible(&brightness_mutex);
5024 if (res < 0)
5025 return res;
5026
Henrique de Moraes Holschuhe11aecf2008-04-26 01:02:21 -03005027 res = brightness_get_raw(&current_value);
5028 if (res < 0)
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02005029 goto errout;
Henrique de Moraes Holschuhe11aecf2008-04-26 01:02:21 -03005030
5031 command_bits = current_value & TP_EC_BACKLIGHT_CMDMSK;
5032 current_value &= TP_EC_BACKLIGHT_LVLMSK;
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02005033
5034 cmos_cmd = value > current_value ?
5035 TP_CMOS_BRIGHTNESS_UP :
5036 TP_CMOS_BRIGHTNESS_DOWN;
5037 inc = (value > current_value)? 1 : -1;
5038
5039 res = 0;
5040 for (i = current_value; i != value; i += inc) {
5041 if ((brightness_mode & 2) &&
5042 issue_thinkpad_cmos_command(cmos_cmd)) {
5043 res = -EIO;
5044 goto errout;
5045 }
5046 if ((brightness_mode & 1) &&
Henrique de Moraes Holschuhe11aecf2008-04-26 01:02:21 -03005047 !acpi_ec_write(TP_EC_BACKLIGHT,
5048 (i + inc) | command_bits)) {
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02005049 res = -EIO;
5050 goto errout;;
5051 }
5052 }
5053
5054errout:
5055 mutex_unlock(&brightness_mutex);
5056 return res;
5057}
5058
5059/* sysfs backlight class ----------------------------------------------- */
5060
5061static int brightness_update_status(struct backlight_device *bd)
5062{
5063 /* it is the backlight class's job (caller) to handle
5064 * EINTR and other errors properly */
5065 return brightness_set(
5066 (bd->props.fb_blank == FB_BLANK_UNBLANK &&
5067 bd->props.power == FB_BLANK_UNBLANK) ?
5068 bd->props.brightness : 0);
5069}
Holger Macht8acb0252006-10-20 14:30:28 -07005070
Henrique de Moraes Holschuhe11aecf2008-04-26 01:02:21 -03005071static int brightness_get(struct backlight_device *bd)
5072{
5073 int status, res;
5074
5075 res = brightness_get_raw(&status);
5076 if (res < 0)
5077 return 0; /* FIXME: teach backlight about error handling */
5078
5079 return status & TP_EC_BACKLIGHT_LVLMSK;
5080}
5081
Richard Purdie599a52d2007-02-10 23:07:48 +00005082static struct backlight_ops ibm_backlight_data = {
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02005083 .get_brightness = brightness_get,
5084 .update_status = brightness_update_status,
Henrique de Moraes Holschuhfb87a812006-11-25 16:35:09 -02005085};
5086
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02005087/* --------------------------------------------------------------------- */
Henrique de Moraes Holschuhf4322552007-07-18 23:45:48 -03005088
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03005089static int __init brightness_init(struct ibm_init_struct *iibm)
Henrique de Moraes Holschuhfb87a812006-11-25 16:35:09 -02005090{
Henrique de Moraes Holschuhadb00582007-02-22 16:04:55 -02005091 int b;
5092
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03005093 vdbg_printk(TPACPI_DBG_INIT, "initializing brightness subdriver\n");
5094
Henrique de Moraes Holschuhf4322552007-07-18 23:45:48 -03005095 mutex_init(&brightness_mutex);
5096
Henrique de Moraes Holschuhb5972792008-04-26 01:02:17 -03005097 /*
5098 * We always attempt to detect acpi support, so as to switch
5099 * Lenovo Vista BIOS to ACPI brightness mode even if we are not
5100 * going to publish a backlight interface
5101 */
5102 b = tpacpi_check_std_acpi_brightness_support();
5103 if (b > 0) {
Thomas Renninger2dba1b52008-08-01 17:38:03 +02005104
5105 if (acpi_video_backlight_support()) {
5106 if (brightness_enable > 1) {
5107 printk(TPACPI_NOTICE
5108 "Standard ACPI backlight interface "
5109 "available, not loading native one.\n");
5110 return 1;
5111 } else if (brightness_enable == 1) {
5112 printk(TPACPI_NOTICE
5113 "Backlight control force enabled, even if standard "
5114 "ACPI backlight interface is available\n");
5115 }
5116 } else {
5117 if (brightness_enable > 1) {
5118 printk(TPACPI_NOTICE
5119 "Standard ACPI backlight interface not "
5120 "available, thinkpad_acpi native "
5121 "brightness control enabled\n");
5122 }
Henrique de Moraes Holschuhe11e2112007-10-30 17:46:22 -02005123 }
Henrique de Moraes Holschuh87cc5372007-10-30 18:02:07 -02005124 }
5125
Henrique de Moraes Holschuhb5972792008-04-26 01:02:17 -03005126 if (!brightness_enable) {
5127 dbg_printk(TPACPI_DBG_INIT,
5128 "brightness support disabled by "
5129 "module parameter\n");
5130 return 1;
5131 }
5132
5133 if (b > 16) {
5134 printk(TPACPI_ERR
5135 "Unsupported brightness interface, "
5136 "please contact %s\n", TPACPI_MAIL);
5137 return 1;
5138 }
5139 if (b == 16)
5140 tp_features.bright_16levels = 1;
5141
Henrique de Moraes Holschuh24d3b772007-07-18 23:45:43 -03005142 if (!brightness_mode) {
5143 if (thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO)
5144 brightness_mode = 2;
5145 else
5146 brightness_mode = 3;
5147
5148 dbg_printk(TPACPI_DBG_INIT, "selected brightness_mode=%d\n",
5149 brightness_mode);
5150 }
5151
5152 if (brightness_mode > 3)
5153 return -EINVAL;
5154
Henrique de Moraes Holschuhe11aecf2008-04-26 01:02:21 -03005155 if (brightness_get_raw(&b) < 0)
Henrique de Moraes Holschuh24d3b772007-07-18 23:45:43 -03005156 return 1;
Henrique de Moraes Holschuhadb00582007-02-22 16:04:55 -02005157
Henrique de Moraes Holschuha3f104c2007-10-30 17:46:20 -02005158 if (tp_features.bright_16levels)
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02005159 printk(TPACPI_INFO
5160 "detected a 16-level brightness capable ThinkPad\n");
Henrique de Moraes Holschuha3f104c2007-10-30 17:46:20 -02005161
Henrique de Moraes Holschuh7d5a0152007-04-24 11:48:20 -03005162 ibm_backlight_device = backlight_device_register(
5163 TPACPI_BACKLIGHT_DEV_NAME, NULL, NULL,
5164 &ibm_backlight_data);
Henrique de Moraes Holschuhfb87a812006-11-25 16:35:09 -02005165 if (IS_ERR(ibm_backlight_device)) {
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02005166 printk(TPACPI_ERR "Could not register backlight device\n");
Henrique de Moraes Holschuhfb87a812006-11-25 16:35:09 -02005167 return PTR_ERR(ibm_backlight_device);
5168 }
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03005169 vdbg_printk(TPACPI_DBG_INIT, "brightness is supported\n");
Henrique de Moraes Holschuhfb87a812006-11-25 16:35:09 -02005170
Henrique de Moraes Holschuha3f104c2007-10-30 17:46:20 -02005171 ibm_backlight_device->props.max_brightness =
5172 (tp_features.bright_16levels)? 15 : 7;
Henrique de Moraes Holschuhe11aecf2008-04-26 01:02:21 -03005173 ibm_backlight_device->props.brightness = b & TP_EC_BACKLIGHT_LVLMSK;
Henrique de Moraes Holschuhadb00582007-02-22 16:04:55 -02005174 backlight_update_status(ibm_backlight_device);
Richard Purdie599a52d2007-02-10 23:07:48 +00005175
Henrique de Moraes Holschuhfb87a812006-11-25 16:35:09 -02005176 return 0;
5177}
5178
5179static void brightness_exit(void)
5180{
5181 if (ibm_backlight_device) {
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03005182 vdbg_printk(TPACPI_DBG_EXIT,
5183 "calling backlight_device_unregister()\n");
Henrique de Moraes Holschuhfb87a812006-11-25 16:35:09 -02005184 backlight_device_unregister(ibm_backlight_device);
Henrique de Moraes Holschuhfb87a812006-11-25 16:35:09 -02005185 }
5186}
5187
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005188static int brightness_read(char *p)
5189{
5190 int len = 0;
5191 int level;
5192
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02005193 level = brightness_get(NULL);
5194 if (level < 0) {
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005195 len += sprintf(p + len, "level:\t\tunreadable\n");
5196 } else {
Henrique de Moraes Holschuha3f104c2007-10-30 17:46:20 -02005197 len += sprintf(p + len, "level:\t\t%d\n", level);
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005198 len += sprintf(p + len, "commands:\tup, down\n");
5199 len += sprintf(p + len, "commands:\tlevel <level>"
Henrique de Moraes Holschuha3f104c2007-10-30 17:46:20 -02005200 " (<level> is 0-%d)\n",
5201 (tp_features.bright_16levels) ? 15 : 7);
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005202 }
5203
5204 return len;
5205}
5206
5207static int brightness_write(char *buf)
5208{
5209 int level;
Henrique de Moraes Holschuh4273af82007-10-30 17:46:25 -02005210 int rc;
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005211 char *cmd;
Henrique de Moraes Holschuha3f104c2007-10-30 17:46:20 -02005212 int max_level = (tp_features.bright_16levels) ? 15 : 7;
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005213
Henrique de Moraes Holschuh4273af82007-10-30 17:46:25 -02005214 level = brightness_get(NULL);
5215 if (level < 0)
5216 return level;
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005217
Henrique de Moraes Holschuh4273af82007-10-30 17:46:25 -02005218 while ((cmd = next_cmd(&buf))) {
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005219 if (strlencmp(cmd, "up") == 0) {
Henrique de Moraes Holschuh4273af82007-10-30 17:46:25 -02005220 if (level < max_level)
5221 level++;
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005222 } else if (strlencmp(cmd, "down") == 0) {
Henrique de Moraes Holschuh4273af82007-10-30 17:46:25 -02005223 if (level > 0)
5224 level--;
5225 } else if (sscanf(cmd, "level %d", &level) == 1 &&
5226 level >= 0 && level <= max_level) {
5227 /* new level set */
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005228 } else
5229 return -EINVAL;
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005230 }
5231
Henrique de Moraes Holschuh4273af82007-10-30 17:46:25 -02005232 /*
5233 * Now we know what the final level should be, so we try to set it.
5234 * Doing it this way makes the syscall restartable in case of EINTR
5235 */
5236 rc = brightness_set(level);
5237 return (rc == -EINTR)? ERESTARTSYS : rc;
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005238}
5239
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03005240static struct ibm_struct brightness_driver_data = {
5241 .name = "brightness",
5242 .read = brightness_read,
5243 .write = brightness_write,
5244 .exit = brightness_exit,
5245};
5246
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005247/*************************************************************************
5248 * Volume subdriver
5249 */
5250
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -02005251static int volume_offset = 0x30;
5252
Borislav Deianov78f81cc2005-08-17 00:00:00 -04005253static int volume_read(char *p)
5254{
5255 int len = 0;
5256 u8 level;
5257
5258 if (!acpi_ec_read(volume_offset, &level)) {
5259 len += sprintf(p + len, "level:\t\tunreadable\n");
5260 } else {
5261 len += sprintf(p + len, "level:\t\t%d\n", level & 0xf);
5262 len += sprintf(p + len, "mute:\t\t%s\n", onoff(level, 6));
5263 len += sprintf(p + len, "commands:\tup, down, mute\n");
5264 len += sprintf(p + len, "commands:\tlevel <level>"
5265 " (<level> is 0-15)\n");
5266 }
5267
5268 return len;
5269}
5270
Borislav Deianov78f81cc2005-08-17 00:00:00 -04005271static int volume_write(char *buf)
5272{
5273 int cmos_cmd, inc, i;
5274 u8 level, mute;
5275 int new_level, new_mute;
5276 char *cmd;
5277
5278 while ((cmd = next_cmd(&buf))) {
5279 if (!acpi_ec_read(volume_offset, &level))
5280 return -EIO;
5281 new_mute = mute = level & 0x40;
5282 new_level = level = level & 0xf;
5283
5284 if (strlencmp(cmd, "up") == 0) {
5285 if (mute)
5286 new_mute = 0;
5287 else
5288 new_level = level == 15 ? 15 : level + 1;
5289 } else if (strlencmp(cmd, "down") == 0) {
5290 if (mute)
5291 new_mute = 0;
5292 else
5293 new_level = level == 0 ? 0 : level - 1;
5294 } else if (sscanf(cmd, "level %d", &new_level) == 1 &&
5295 new_level >= 0 && new_level <= 15) {
5296 /* new_level set */
5297 } else if (strlencmp(cmd, "mute") == 0) {
5298 new_mute = 0x40;
5299 } else
5300 return -EINVAL;
5301
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02005302 if (new_level != level) {
5303 /* mute doesn't change */
5304
5305 cmos_cmd = (new_level > level) ?
5306 TP_CMOS_VOLUME_UP : TP_CMOS_VOLUME_DOWN;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04005307 inc = new_level > level ? 1 : -1;
5308
Henrique de Moraes Holschuhc9bea992007-04-21 11:08:42 -03005309 if (mute && (issue_thinkpad_cmos_command(cmos_cmd) ||
Borislav Deianov78f81cc2005-08-17 00:00:00 -04005310 !acpi_ec_write(volume_offset, level)))
5311 return -EIO;
5312
5313 for (i = level; i != new_level; i += inc)
Henrique de Moraes Holschuhc9bea992007-04-21 11:08:42 -03005314 if (issue_thinkpad_cmos_command(cmos_cmd) ||
Borislav Deianov78f81cc2005-08-17 00:00:00 -04005315 !acpi_ec_write(volume_offset, i + inc))
5316 return -EIO;
5317
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02005318 if (mute &&
5319 (issue_thinkpad_cmos_command(TP_CMOS_VOLUME_MUTE) ||
5320 !acpi_ec_write(volume_offset, new_level + mute))) {
Borislav Deianov78f81cc2005-08-17 00:00:00 -04005321 return -EIO;
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02005322 }
Borislav Deianov78f81cc2005-08-17 00:00:00 -04005323 }
5324
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02005325 if (new_mute != mute) {
5326 /* level doesn't change */
5327
5328 cmos_cmd = (new_mute) ?
5329 TP_CMOS_VOLUME_MUTE : TP_CMOS_VOLUME_UP;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04005330
Henrique de Moraes Holschuhc9bea992007-04-21 11:08:42 -03005331 if (issue_thinkpad_cmos_command(cmos_cmd) ||
Borislav Deianov78f81cc2005-08-17 00:00:00 -04005332 !acpi_ec_write(volume_offset, level + new_mute))
5333 return -EIO;
5334 }
5335 }
5336
5337 return 0;
5338}
5339
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03005340static struct ibm_struct volume_driver_data = {
5341 .name = "volume",
5342 .read = volume_read,
5343 .write = volume_write,
5344};
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005345
5346/*************************************************************************
5347 * Fan subdriver
5348 */
5349
5350/*
5351 * FAN ACCESS MODES
5352 *
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03005353 * TPACPI_FAN_RD_ACPI_GFAN:
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005354 * ACPI GFAN method: returns fan level
5355 *
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03005356 * see TPACPI_FAN_WR_ACPI_SFAN
Henrique de Moraes Holschuhf51d1a32007-04-21 11:08:29 -03005357 * EC 0x2f (HFSP) not available if GFAN exists
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005358 *
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03005359 * TPACPI_FAN_WR_ACPI_SFAN:
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005360 * ACPI SFAN method: sets fan level, 0 (stop) to 7 (max)
5361 *
Henrique de Moraes Holschuhf51d1a32007-04-21 11:08:29 -03005362 * EC 0x2f (HFSP) might be available *for reading*, but do not use
5363 * it for writing.
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005364 *
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03005365 * TPACPI_FAN_WR_TPEC:
Henrique de Moraes Holschuhf51d1a32007-04-21 11:08:29 -03005366 * ThinkPad EC register 0x2f (HFSP): fan control loop mode
5367 * Supported on almost all ThinkPads
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005368 *
5369 * Fan speed changes of any sort (including those caused by the
5370 * disengaged mode) are usually done slowly by the firmware as the
5371 * maximum ammount of fan duty cycle change per second seems to be
5372 * limited.
5373 *
5374 * Reading is not available if GFAN exists.
5375 * Writing is not available if SFAN exists.
5376 *
5377 * Bits
5378 * 7 automatic mode engaged;
5379 * (default operation mode of the ThinkPad)
5380 * fan level is ignored in this mode.
Henrique de Moraes Holschuhf51d1a32007-04-21 11:08:29 -03005381 * 6 full speed mode (takes precedence over bit 7);
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005382 * not available on all thinkpads. May disable
Henrique de Moraes Holschuhf51d1a32007-04-21 11:08:29 -03005383 * the tachometer while the fan controller ramps up
5384 * the speed (which can take up to a few *minutes*).
5385 * Speeds up fan to 100% duty-cycle, which is far above
5386 * the standard RPM levels. It is not impossible that
5387 * it could cause hardware damage.
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005388 * 5-3 unused in some models. Extra bits for fan level
5389 * in others, but still useless as all values above
5390 * 7 map to the same speed as level 7 in these models.
5391 * 2-0 fan level (0..7 usually)
5392 * 0x00 = stop
5393 * 0x07 = max (set when temperatures critical)
5394 * Some ThinkPads may have other levels, see
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03005395 * TPACPI_FAN_WR_ACPI_FANS (X31/X40/X41)
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005396 *
5397 * FIRMWARE BUG: on some models, EC 0x2f might not be initialized at
5398 * boot. Apparently the EC does not intialize it, so unless ACPI DSDT
5399 * does so, its initial value is meaningless (0x07).
5400 *
5401 * For firmware bugs, refer to:
5402 * http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues
5403 *
5404 * ----
5405 *
5406 * ThinkPad EC register 0x84 (LSB), 0x85 (MSB):
5407 * Main fan tachometer reading (in RPM)
5408 *
5409 * This register is present on all ThinkPads with a new-style EC, and
5410 * it is known not to be present on the A21m/e, and T22, as there is
5411 * something else in offset 0x84 according to the ACPI DSDT. Other
5412 * ThinkPads from this same time period (and earlier) probably lack the
5413 * tachometer as well.
5414 *
5415 * Unfortunately a lot of ThinkPads with new-style ECs but whose firwmare
5416 * was never fixed by IBM to report the EC firmware version string
5417 * probably support the tachometer (like the early X models), so
5418 * detecting it is quite hard. We need more data to know for sure.
5419 *
5420 * FIRMWARE BUG: always read 0x84 first, otherwise incorrect readings
5421 * might result.
5422 *
Henrique de Moraes Holschuhf51d1a32007-04-21 11:08:29 -03005423 * FIRMWARE BUG: may go stale while the EC is switching to full speed
5424 * mode.
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005425 *
5426 * For firmware bugs, refer to:
5427 * http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues
5428 *
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03005429 * TPACPI_FAN_WR_ACPI_FANS:
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005430 * ThinkPad X31, X40, X41. Not available in the X60.
5431 *
5432 * FANS ACPI handle: takes three arguments: low speed, medium speed,
5433 * high speed. ACPI DSDT seems to map these three speeds to levels
5434 * as follows: STOP LOW LOW MED MED HIGH HIGH HIGH HIGH
5435 * (this map is stored on FAN0..FAN8 as "0,1,1,2,2,3,3,3,3")
5436 *
5437 * The speeds are stored on handles
5438 * (FANA:FAN9), (FANC:FANB), (FANE:FAND).
5439 *
5440 * There are three default speed sets, acessible as handles:
5441 * FS1L,FS1M,FS1H; FS2L,FS2M,FS2H; FS3L,FS3M,FS3H
5442 *
5443 * ACPI DSDT switches which set is in use depending on various
5444 * factors.
5445 *
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03005446 * TPACPI_FAN_WR_TPEC is also available and should be used to
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005447 * command the fan. The X31/X40/X41 seems to have 8 fan levels,
5448 * but the ACPI tables just mention level 7.
5449 */
5450
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -02005451enum { /* Fan control constants */
5452 fan_status_offset = 0x2f, /* EC register 0x2f */
5453 fan_rpm_offset = 0x84, /* EC register 0x84: LSB, 0x85 MSB (RPM)
5454 * 0x84 must be read before 0x85 */
5455
5456 TP_EC_FAN_FULLSPEED = 0x40, /* EC fan mode: full speed */
5457 TP_EC_FAN_AUTO = 0x80, /* EC fan mode: auto fan control */
5458
5459 TPACPI_FAN_LAST_LEVEL = 0x100, /* Use cached last-seen fan level */
5460};
5461
5462enum fan_status_access_mode {
5463 TPACPI_FAN_NONE = 0, /* No fan status or control */
5464 TPACPI_FAN_RD_ACPI_GFAN, /* Use ACPI GFAN */
5465 TPACPI_FAN_RD_TPEC, /* Use ACPI EC regs 0x2f, 0x84-0x85 */
5466};
5467
5468enum fan_control_access_mode {
5469 TPACPI_FAN_WR_NONE = 0, /* No fan control */
5470 TPACPI_FAN_WR_ACPI_SFAN, /* Use ACPI SFAN */
5471 TPACPI_FAN_WR_TPEC, /* Use ACPI EC reg 0x2f */
5472 TPACPI_FAN_WR_ACPI_FANS, /* Use ACPI FANS and EC reg 0x2f */
5473};
5474
5475enum fan_control_commands {
5476 TPACPI_FAN_CMD_SPEED = 0x0001, /* speed command */
5477 TPACPI_FAN_CMD_LEVEL = 0x0002, /* level command */
5478 TPACPI_FAN_CMD_ENABLE = 0x0004, /* enable/disable cmd,
5479 * and also watchdog cmd */
5480};
5481
5482static int fan_control_allowed;
5483
Henrique de Moraes Holschuh69ba91c2006-11-24 11:47:09 -02005484static enum fan_status_access_mode fan_status_access_mode;
5485static enum fan_control_access_mode fan_control_access_mode;
5486static enum fan_control_commands fan_control_commands;
5487
Henrique de Moraes Holschuh778b4d72006-11-24 11:47:14 -02005488static u8 fan_control_initial_status;
Henrique de Moraes Holschuhfe98a522007-04-24 11:48:17 -03005489static u8 fan_control_desired_level;
Henrique de Moraes Holschuh0081b162008-11-09 10:54:02 -02005490static u8 fan_control_resume_level;
Henrique de Moraes Holschuh16663a82006-11-24 11:47:14 -02005491static int fan_watchdog_maxinterval;
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -02005492
5493static struct mutex fan_mutex;
5494
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -02005495static void fan_watchdog_fire(struct work_struct *ignored);
Len Brown25c68a32006-12-08 04:43:41 -05005496static DECLARE_DELAYED_WORK(fan_watchdog_task, fan_watchdog_fire);
Henrique de Moraes Holschuh16663a82006-11-24 11:47:14 -02005497
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02005498TPACPI_HANDLE(fans, ec, "FANS"); /* X31, X40, X41 */
5499TPACPI_HANDLE(gfan, ec, "GFAN", /* 570 */
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005500 "\\FSPD", /* 600e/x, 770e, 770x */
5501 ); /* all others */
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02005502TPACPI_HANDLE(sfan, ec, "SFAN", /* 570 */
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03005503 "JFNS", /* 770x-JL */
5504 ); /* all others */
5505
Henrique de Moraes Holschuhfe98a522007-04-24 11:48:17 -03005506/*
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02005507 * Call with fan_mutex held
5508 */
5509static void fan_update_desired_level(u8 status)
5510{
5511 if ((status &
5512 (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) {
5513 if (status > 7)
5514 fan_control_desired_level = 7;
5515 else
5516 fan_control_desired_level = status;
5517 }
5518}
5519
5520static int fan_get_status(u8 *status)
5521{
5522 u8 s;
5523
5524 /* TODO:
5525 * Add TPACPI_FAN_RD_ACPI_FANS ? */
5526
5527 switch (fan_status_access_mode) {
5528 case TPACPI_FAN_RD_ACPI_GFAN:
5529 /* 570, 600e/x, 770e, 770x */
5530
5531 if (unlikely(!acpi_evalf(gfan_handle, &s, NULL, "d")))
5532 return -EIO;
5533
5534 if (likely(status))
5535 *status = s & 0x07;
5536
5537 break;
5538
5539 case TPACPI_FAN_RD_TPEC:
5540 /* all except 570, 600e/x, 770e, 770x */
5541 if (unlikely(!acpi_ec_read(fan_status_offset, &s)))
5542 return -EIO;
5543
5544 if (likely(status))
5545 *status = s;
5546
5547 break;
5548
5549 default:
5550 return -ENXIO;
5551 }
5552
5553 return 0;
5554}
5555
5556static int fan_get_status_safe(u8 *status)
5557{
5558 int rc;
5559 u8 s;
5560
5561 if (mutex_lock_interruptible(&fan_mutex))
5562 return -ERESTARTSYS;
5563 rc = fan_get_status(&s);
5564 if (!rc)
5565 fan_update_desired_level(s);
5566 mutex_unlock(&fan_mutex);
5567
5568 if (status)
5569 *status = s;
5570
5571 return rc;
5572}
5573
5574static int fan_get_speed(unsigned int *speed)
5575{
5576 u8 hi, lo;
5577
5578 switch (fan_status_access_mode) {
5579 case TPACPI_FAN_RD_TPEC:
5580 /* all except 570, 600e/x, 770e, 770x */
5581 if (unlikely(!acpi_ec_read(fan_rpm_offset, &lo) ||
5582 !acpi_ec_read(fan_rpm_offset + 1, &hi)))
5583 return -EIO;
5584
5585 if (likely(speed))
5586 *speed = (hi << 8) | lo;
5587
5588 break;
5589
5590 default:
5591 return -ENXIO;
5592 }
5593
5594 return 0;
5595}
5596
5597static int fan_set_level(int level)
5598{
5599 if (!fan_control_allowed)
5600 return -EPERM;
5601
5602 switch (fan_control_access_mode) {
5603 case TPACPI_FAN_WR_ACPI_SFAN:
5604 if (level >= 0 && level <= 7) {
5605 if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", level))
5606 return -EIO;
5607 } else
5608 return -EINVAL;
5609 break;
5610
5611 case TPACPI_FAN_WR_ACPI_FANS:
5612 case TPACPI_FAN_WR_TPEC:
Henrique de Moraes Holschuh0081b162008-11-09 10:54:02 -02005613 if (!(level & TP_EC_FAN_AUTO) &&
5614 !(level & TP_EC_FAN_FULLSPEED) &&
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02005615 ((level < 0) || (level > 7)))
5616 return -EINVAL;
5617
5618 /* safety net should the EC not support AUTO
5619 * or FULLSPEED mode bits and just ignore them */
5620 if (level & TP_EC_FAN_FULLSPEED)
5621 level |= 7; /* safety min speed 7 */
Roel Kluin547266e2008-02-05 00:24:56 +01005622 else if (level & TP_EC_FAN_AUTO)
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02005623 level |= 4; /* safety min speed 4 */
5624
5625 if (!acpi_ec_write(fan_status_offset, level))
5626 return -EIO;
5627 else
5628 tp_features.fan_ctrl_status_undef = 0;
5629 break;
5630
5631 default:
5632 return -ENXIO;
5633 }
5634 return 0;
5635}
5636
5637static int fan_set_level_safe(int level)
5638{
5639 int rc;
5640
5641 if (!fan_control_allowed)
5642 return -EPERM;
5643
5644 if (mutex_lock_interruptible(&fan_mutex))
5645 return -ERESTARTSYS;
5646
5647 if (level == TPACPI_FAN_LAST_LEVEL)
5648 level = fan_control_desired_level;
5649
5650 rc = fan_set_level(level);
5651 if (!rc)
5652 fan_update_desired_level(level);
5653
5654 mutex_unlock(&fan_mutex);
5655 return rc;
5656}
5657
5658static int fan_set_enable(void)
5659{
5660 u8 s;
5661 int rc;
5662
5663 if (!fan_control_allowed)
5664 return -EPERM;
5665
5666 if (mutex_lock_interruptible(&fan_mutex))
5667 return -ERESTARTSYS;
5668
5669 switch (fan_control_access_mode) {
5670 case TPACPI_FAN_WR_ACPI_FANS:
5671 case TPACPI_FAN_WR_TPEC:
5672 rc = fan_get_status(&s);
5673 if (rc < 0)
5674 break;
5675
5676 /* Don't go out of emergency fan mode */
5677 if (s != 7) {
5678 s &= 0x07;
5679 s |= TP_EC_FAN_AUTO | 4; /* min fan speed 4 */
5680 }
5681
5682 if (!acpi_ec_write(fan_status_offset, s))
5683 rc = -EIO;
5684 else {
5685 tp_features.fan_ctrl_status_undef = 0;
5686 rc = 0;
5687 }
5688 break;
5689
5690 case TPACPI_FAN_WR_ACPI_SFAN:
5691 rc = fan_get_status(&s);
5692 if (rc < 0)
5693 break;
5694
5695 s &= 0x07;
5696
5697 /* Set fan to at least level 4 */
5698 s |= 4;
5699
5700 if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", s))
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02005701 rc = -EIO;
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02005702 else
5703 rc = 0;
5704 break;
5705
5706 default:
5707 rc = -ENXIO;
5708 }
5709
5710 mutex_unlock(&fan_mutex);
5711 return rc;
5712}
5713
5714static int fan_set_disable(void)
5715{
5716 int rc;
5717
5718 if (!fan_control_allowed)
5719 return -EPERM;
5720
5721 if (mutex_lock_interruptible(&fan_mutex))
5722 return -ERESTARTSYS;
5723
5724 rc = 0;
5725 switch (fan_control_access_mode) {
5726 case TPACPI_FAN_WR_ACPI_FANS:
5727 case TPACPI_FAN_WR_TPEC:
5728 if (!acpi_ec_write(fan_status_offset, 0x00))
5729 rc = -EIO;
5730 else {
5731 fan_control_desired_level = 0;
5732 tp_features.fan_ctrl_status_undef = 0;
5733 }
5734 break;
5735
5736 case TPACPI_FAN_WR_ACPI_SFAN:
5737 if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", 0x00))
5738 rc = -EIO;
5739 else
5740 fan_control_desired_level = 0;
5741 break;
5742
5743 default:
5744 rc = -ENXIO;
5745 }
5746
5747
5748 mutex_unlock(&fan_mutex);
5749 return rc;
5750}
5751
5752static int fan_set_speed(int speed)
5753{
5754 int rc;
5755
5756 if (!fan_control_allowed)
5757 return -EPERM;
5758
5759 if (mutex_lock_interruptible(&fan_mutex))
5760 return -ERESTARTSYS;
5761
5762 rc = 0;
5763 switch (fan_control_access_mode) {
5764 case TPACPI_FAN_WR_ACPI_FANS:
5765 if (speed >= 0 && speed <= 65535) {
5766 if (!acpi_evalf(fans_handle, NULL, NULL, "vddd",
5767 speed, speed, speed))
5768 rc = -EIO;
5769 } else
5770 rc = -EINVAL;
5771 break;
5772
5773 default:
5774 rc = -ENXIO;
5775 }
5776
5777 mutex_unlock(&fan_mutex);
5778 return rc;
5779}
5780
5781static void fan_watchdog_reset(void)
5782{
5783 static int fan_watchdog_active;
5784
5785 if (fan_control_access_mode == TPACPI_FAN_WR_NONE)
5786 return;
5787
5788 if (fan_watchdog_active)
5789 cancel_delayed_work(&fan_watchdog_task);
5790
5791 if (fan_watchdog_maxinterval > 0 &&
5792 tpacpi_lifecycle != TPACPI_LIFE_EXITING) {
5793 fan_watchdog_active = 1;
Henrique de Moraes Holschuhe0e3c062008-04-26 01:02:28 -03005794 if (!queue_delayed_work(tpacpi_wq, &fan_watchdog_task,
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02005795 msecs_to_jiffies(fan_watchdog_maxinterval
5796 * 1000))) {
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02005797 printk(TPACPI_ERR
Henrique de Moraes Holschuhe0e3c062008-04-26 01:02:28 -03005798 "failed to queue the fan watchdog, "
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02005799 "watchdog will not trigger\n");
5800 }
5801 } else
5802 fan_watchdog_active = 0;
5803}
5804
5805static void fan_watchdog_fire(struct work_struct *ignored)
5806{
5807 int rc;
5808
5809 if (tpacpi_lifecycle != TPACPI_LIFE_RUNNING)
5810 return;
5811
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02005812 printk(TPACPI_NOTICE "fan watchdog: enabling fan\n");
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02005813 rc = fan_set_enable();
5814 if (rc < 0) {
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02005815 printk(TPACPI_ERR "fan watchdog: error %d while enabling fan, "
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02005816 "will try again later...\n", -rc);
5817 /* reschedule for later */
5818 fan_watchdog_reset();
5819 }
5820}
5821
5822/*
Henrique de Moraes Holschuhfe98a522007-04-24 11:48:17 -03005823 * SYSFS fan layout: hwmon compatible (device)
5824 *
5825 * pwm*_enable:
5826 * 0: "disengaged" mode
5827 * 1: manual mode
5828 * 2: native EC "auto" mode (recommended, hardware default)
5829 *
5830 * pwm*: set speed in manual mode, ignored otherwise.
5831 * 0 is level 0; 255 is level 7. Intermediate points done with linear
5832 * interpolation.
5833 *
5834 * fan*_input: tachometer reading, RPM
5835 *
5836 *
5837 * SYSFS fan layout: extensions
5838 *
5839 * fan_watchdog (driver):
5840 * fan watchdog interval in seconds, 0 disables (default), max 120
5841 */
5842
5843/* sysfs fan pwm1_enable ----------------------------------------------- */
5844static ssize_t fan_pwm1_enable_show(struct device *dev,
5845 struct device_attribute *attr,
5846 char *buf)
5847{
5848 int res, mode;
5849 u8 status;
5850
5851 res = fan_get_status_safe(&status);
5852 if (res)
5853 return res;
5854
5855 if (unlikely(tp_features.fan_ctrl_status_undef)) {
5856 if (status != fan_control_initial_status) {
5857 tp_features.fan_ctrl_status_undef = 0;
5858 } else {
5859 /* Return most likely status. In fact, it
5860 * might be the only possible status */
5861 status = TP_EC_FAN_AUTO;
5862 }
5863 }
5864
5865 if (status & TP_EC_FAN_FULLSPEED) {
5866 mode = 0;
5867 } else if (status & TP_EC_FAN_AUTO) {
5868 mode = 2;
5869 } else
5870 mode = 1;
5871
5872 return snprintf(buf, PAGE_SIZE, "%d\n", mode);
5873}
5874
5875static ssize_t fan_pwm1_enable_store(struct device *dev,
5876 struct device_attribute *attr,
5877 const char *buf, size_t count)
5878{
5879 unsigned long t;
5880 int res, level;
5881
5882 if (parse_strtoul(buf, 2, &t))
5883 return -EINVAL;
5884
5885 switch (t) {
5886 case 0:
5887 level = TP_EC_FAN_FULLSPEED;
5888 break;
5889 case 1:
5890 level = TPACPI_FAN_LAST_LEVEL;
5891 break;
5892 case 2:
5893 level = TP_EC_FAN_AUTO;
5894 break;
5895 case 3:
5896 /* reserved for software-controlled auto mode */
5897 return -ENOSYS;
5898 default:
5899 return -EINVAL;
5900 }
5901
5902 res = fan_set_level_safe(level);
Henrique de Moraes Holschuhc573ddb2007-04-27 22:00:12 -03005903 if (res == -ENXIO)
5904 return -EINVAL;
5905 else if (res < 0)
Henrique de Moraes Holschuhfe98a522007-04-24 11:48:17 -03005906 return res;
5907
5908 fan_watchdog_reset();
5909
5910 return count;
5911}
5912
5913static struct device_attribute dev_attr_fan_pwm1_enable =
5914 __ATTR(pwm1_enable, S_IWUSR | S_IRUGO,
5915 fan_pwm1_enable_show, fan_pwm1_enable_store);
5916
5917/* sysfs fan pwm1 ------------------------------------------------------ */
5918static ssize_t fan_pwm1_show(struct device *dev,
5919 struct device_attribute *attr,
5920 char *buf)
5921{
5922 int res;
5923 u8 status;
5924
5925 res = fan_get_status_safe(&status);
5926 if (res)
5927 return res;
5928
5929 if (unlikely(tp_features.fan_ctrl_status_undef)) {
5930 if (status != fan_control_initial_status) {
5931 tp_features.fan_ctrl_status_undef = 0;
5932 } else {
5933 status = TP_EC_FAN_AUTO;
5934 }
5935 }
5936
5937 if ((status &
5938 (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) != 0)
5939 status = fan_control_desired_level;
5940
5941 if (status > 7)
5942 status = 7;
5943
5944 return snprintf(buf, PAGE_SIZE, "%u\n", (status * 255) / 7);
5945}
5946
5947static ssize_t fan_pwm1_store(struct device *dev,
5948 struct device_attribute *attr,
5949 const char *buf, size_t count)
5950{
5951 unsigned long s;
5952 int rc;
5953 u8 status, newlevel;
5954
5955 if (parse_strtoul(buf, 255, &s))
5956 return -EINVAL;
5957
5958 /* scale down from 0-255 to 0-7 */
5959 newlevel = (s >> 5) & 0x07;
5960
Henrique de Moraes Holschuhfc589a32007-10-30 17:46:24 -02005961 if (mutex_lock_interruptible(&fan_mutex))
5962 return -ERESTARTSYS;
Henrique de Moraes Holschuhfe98a522007-04-24 11:48:17 -03005963
5964 rc = fan_get_status(&status);
5965 if (!rc && (status &
5966 (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) {
5967 rc = fan_set_level(newlevel);
Henrique de Moraes Holschuhc573ddb2007-04-27 22:00:12 -03005968 if (rc == -ENXIO)
5969 rc = -EINVAL;
5970 else if (!rc) {
Henrique de Moraes Holschuhfe98a522007-04-24 11:48:17 -03005971 fan_update_desired_level(newlevel);
Henrique de Moraes Holschuhca4ac2f2007-04-27 22:00:11 -03005972 fan_watchdog_reset();
5973 }
Henrique de Moraes Holschuhfe98a522007-04-24 11:48:17 -03005974 }
5975
5976 mutex_unlock(&fan_mutex);
5977 return (rc)? rc : count;
5978}
5979
5980static struct device_attribute dev_attr_fan_pwm1 =
5981 __ATTR(pwm1, S_IWUSR | S_IRUGO,
5982 fan_pwm1_show, fan_pwm1_store);
5983
5984/* sysfs fan fan1_input ------------------------------------------------ */
5985static ssize_t fan_fan1_input_show(struct device *dev,
5986 struct device_attribute *attr,
5987 char *buf)
5988{
5989 int res;
5990 unsigned int speed;
5991
5992 res = fan_get_speed(&speed);
5993 if (res < 0)
5994 return res;
5995
5996 return snprintf(buf, PAGE_SIZE, "%u\n", speed);
5997}
5998
5999static struct device_attribute dev_attr_fan_fan1_input =
6000 __ATTR(fan1_input, S_IRUGO,
6001 fan_fan1_input_show, NULL);
6002
Henrique de Moraes Holschuh7fd40022007-09-25 06:38:03 -03006003/* sysfs fan fan_watchdog (hwmon driver) ------------------------------- */
Henrique de Moraes Holschuhfe98a522007-04-24 11:48:17 -03006004static ssize_t fan_fan_watchdog_show(struct device_driver *drv,
6005 char *buf)
6006{
6007 return snprintf(buf, PAGE_SIZE, "%u\n", fan_watchdog_maxinterval);
6008}
6009
6010static ssize_t fan_fan_watchdog_store(struct device_driver *drv,
6011 const char *buf, size_t count)
6012{
6013 unsigned long t;
6014
6015 if (parse_strtoul(buf, 120, &t))
6016 return -EINVAL;
6017
Henrique de Moraes Holschuhecf2a802007-04-27 22:00:09 -03006018 if (!fan_control_allowed)
6019 return -EPERM;
6020
Henrique de Moraes Holschuhfe98a522007-04-24 11:48:17 -03006021 fan_watchdog_maxinterval = t;
6022 fan_watchdog_reset();
6023
6024 return count;
6025}
6026
6027static DRIVER_ATTR(fan_watchdog, S_IWUSR | S_IRUGO,
6028 fan_fan_watchdog_show, fan_fan_watchdog_store);
6029
6030/* --------------------------------------------------------------------- */
6031static struct attribute *fan_attributes[] = {
6032 &dev_attr_fan_pwm1_enable.attr, &dev_attr_fan_pwm1.attr,
6033 &dev_attr_fan_fan1_input.attr,
6034 NULL
6035};
6036
6037static const struct attribute_group fan_attr_group = {
6038 .attrs = fan_attributes,
6039};
6040
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03006041static int __init fan_init(struct ibm_init_struct *iibm)
Henrique de Moraes Holschuh69ba91c2006-11-24 11:47:09 -02006042{
Henrique de Moraes Holschuhfe98a522007-04-24 11:48:17 -03006043 int rc;
6044
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03006045 vdbg_printk(TPACPI_DBG_INIT, "initializing fan subdriver\n");
6046
Henrique de Moraes Holschuh40ca9fd2007-04-24 11:48:15 -03006047 mutex_init(&fan_mutex);
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006048 fan_status_access_mode = TPACPI_FAN_NONE;
6049 fan_control_access_mode = TPACPI_FAN_WR_NONE;
Henrique de Moraes Holschuh69ba91c2006-11-24 11:47:09 -02006050 fan_control_commands = 0;
Henrique de Moraes Holschuh16663a82006-11-24 11:47:14 -02006051 fan_watchdog_maxinterval = 0;
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03006052 tp_features.fan_ctrl_status_undef = 0;
Henrique de Moraes Holschuhfe98a522007-04-24 11:48:17 -03006053 fan_control_desired_level = 7;
Henrique de Moraes Holschuh69ba91c2006-11-24 11:47:09 -02006054
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02006055 TPACPI_ACPIHANDLE_INIT(fans);
6056 TPACPI_ACPIHANDLE_INIT(gfan);
6057 TPACPI_ACPIHANDLE_INIT(sfan);
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03006058
Henrique de Moraes Holschuh69ba91c2006-11-24 11:47:09 -02006059 if (gfan_handle) {
6060 /* 570, 600e/x, 770e, 770x */
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006061 fan_status_access_mode = TPACPI_FAN_RD_ACPI_GFAN;
Henrique de Moraes Holschuh69ba91c2006-11-24 11:47:09 -02006062 } else {
6063 /* all other ThinkPads: note that even old-style
6064 * ThinkPad ECs supports the fan control register */
Henrique de Moraes Holschuh778b4d72006-11-24 11:47:14 -02006065 if (likely(acpi_ec_read(fan_status_offset,
6066 &fan_control_initial_status))) {
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006067 fan_status_access_mode = TPACPI_FAN_RD_TPEC;
Henrique de Moraes Holschuh778b4d72006-11-24 11:47:14 -02006068
6069 /* In some ThinkPads, neither the EC nor the ACPI
6070 * DSDT initialize the fan status, and it ends up
6071 * being set to 0x07 when it *could* be either
6072 * 0x07 or 0x80.
6073 *
6074 * Enable for TP-1Y (T43), TP-78 (R51e),
6075 * TP-76 (R52), TP-70 (T43, R52), which are known
6076 * to be buggy. */
Henrique de Moraes Holschuhd5a2f2f2007-07-18 23:45:42 -03006077 if (fan_control_initial_status == 0x07) {
6078 switch (thinkpad_id.ec_model) {
6079 case 0x5931: /* TP-1Y */
6080 case 0x3837: /* TP-78 */
6081 case 0x3637: /* TP-76 */
6082 case 0x3037: /* TP-70 */
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02006083 printk(TPACPI_NOTICE
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02006084 "fan_init: initial fan status "
6085 "is unknown, assuming it is "
6086 "in auto mode\n");
Henrique de Moraes Holschuhd5a2f2f2007-07-18 23:45:42 -03006087 tp_features.fan_ctrl_status_undef = 1;
6088 ;;
6089 }
Henrique de Moraes Holschuh778b4d72006-11-24 11:47:14 -02006090 }
Henrique de Moraes Holschuh69ba91c2006-11-24 11:47:09 -02006091 } else {
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02006092 printk(TPACPI_ERR
Henrique de Moraes Holschuh69ba91c2006-11-24 11:47:09 -02006093 "ThinkPad ACPI EC access misbehaving, "
6094 "fan status and control unavailable\n");
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03006095 return 1;
Henrique de Moraes Holschuh69ba91c2006-11-24 11:47:09 -02006096 }
6097 }
6098
6099 if (sfan_handle) {
6100 /* 570, 770x-JL */
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006101 fan_control_access_mode = TPACPI_FAN_WR_ACPI_SFAN;
Henrique de Moraes Holschuh1c6a3342006-11-24 11:47:12 -02006102 fan_control_commands |=
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006103 TPACPI_FAN_CMD_LEVEL | TPACPI_FAN_CMD_ENABLE;
Henrique de Moraes Holschuh69ba91c2006-11-24 11:47:09 -02006104 } else {
6105 if (!gfan_handle) {
6106 /* gfan without sfan means no fan control */
6107 /* all other models implement TP EC 0x2f control */
6108
6109 if (fans_handle) {
Henrique de Moraes Holschuha8b7a662006-11-24 11:47:11 -02006110 /* X31, X40, X41 */
Henrique de Moraes Holschuh69ba91c2006-11-24 11:47:09 -02006111 fan_control_access_mode =
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006112 TPACPI_FAN_WR_ACPI_FANS;
Henrique de Moraes Holschuh69ba91c2006-11-24 11:47:09 -02006113 fan_control_commands |=
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006114 TPACPI_FAN_CMD_SPEED |
6115 TPACPI_FAN_CMD_LEVEL |
6116 TPACPI_FAN_CMD_ENABLE;
Henrique de Moraes Holschuh69ba91c2006-11-24 11:47:09 -02006117 } else {
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006118 fan_control_access_mode = TPACPI_FAN_WR_TPEC;
Henrique de Moraes Holschuha12095c2006-11-24 11:47:13 -02006119 fan_control_commands |=
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006120 TPACPI_FAN_CMD_LEVEL |
6121 TPACPI_FAN_CMD_ENABLE;
Henrique de Moraes Holschuh69ba91c2006-11-24 11:47:09 -02006122 }
6123 }
6124 }
6125
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03006126 vdbg_printk(TPACPI_DBG_INIT, "fan is %s, modes %d, %d\n",
6127 str_supported(fan_status_access_mode != TPACPI_FAN_NONE ||
6128 fan_control_access_mode != TPACPI_FAN_WR_NONE),
6129 fan_status_access_mode, fan_control_access_mode);
6130
Henrique de Moraes Holschuhecf2a802007-04-27 22:00:09 -03006131 /* fan control master switch */
6132 if (!fan_control_allowed) {
6133 fan_control_access_mode = TPACPI_FAN_WR_NONE;
6134 fan_control_commands = 0;
6135 dbg_printk(TPACPI_DBG_INIT,
6136 "fan control features disabled by parameter\n");
6137 }
6138
Henrique de Moraes Holschuhfe98a522007-04-24 11:48:17 -03006139 /* update fan_control_desired_level */
6140 if (fan_status_access_mode != TPACPI_FAN_NONE)
6141 fan_get_status_safe(NULL);
6142
6143 if (fan_status_access_mode != TPACPI_FAN_NONE ||
6144 fan_control_access_mode != TPACPI_FAN_WR_NONE) {
Henrique de Moraes Holschuh7fd40022007-09-25 06:38:03 -03006145 rc = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj,
Henrique de Moraes Holschuhfe98a522007-04-24 11:48:17 -03006146 &fan_attr_group);
Henrique de Moraes Holschuhfe98a522007-04-24 11:48:17 -03006147 if (rc < 0)
6148 return rc;
Henrique de Moraes Holschuh9c0a76e2008-06-03 23:36:10 -03006149
6150 rc = driver_create_file(&tpacpi_hwmon_pdriver.driver,
6151 &driver_attr_fan_watchdog);
6152 if (rc < 0) {
6153 sysfs_remove_group(&tpacpi_sensors_pdev->dev.kobj,
6154 &fan_attr_group);
6155 return rc;
6156 }
Henrique de Moraes Holschuhfe98a522007-04-24 11:48:17 -03006157 return 0;
6158 } else
6159 return 1;
6160}
6161
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006162static void fan_exit(void)
6163{
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02006164 vdbg_printk(TPACPI_DBG_EXIT,
6165 "cancelling any pending fan watchdog tasks\n");
Henrique de Moraes Holschuhfe98a522007-04-24 11:48:17 -03006166
6167 /* FIXME: can we really do this unconditionally? */
Henrique de Moraes Holschuh7fd40022007-09-25 06:38:03 -03006168 sysfs_remove_group(&tpacpi_sensors_pdev->dev.kobj, &fan_attr_group);
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02006169 driver_remove_file(&tpacpi_hwmon_pdriver.driver,
6170 &driver_attr_fan_watchdog);
Henrique de Moraes Holschuhfe98a522007-04-24 11:48:17 -03006171
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006172 cancel_delayed_work(&fan_watchdog_task);
Henrique de Moraes Holschuhe0e3c062008-04-26 01:02:28 -03006173 flush_workqueue(tpacpi_wq);
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006174}
6175
Henrique de Moraes Holschuh75700e52008-10-18 14:23:52 -03006176static void fan_suspend(pm_message_t state)
6177{
Henrique de Moraes Holschuh0081b162008-11-09 10:54:02 -02006178 int rc;
6179
Henrique de Moraes Holschuh75700e52008-10-18 14:23:52 -03006180 if (!fan_control_allowed)
6181 return;
6182
6183 /* Store fan status in cache */
Henrique de Moraes Holschuh0081b162008-11-09 10:54:02 -02006184 fan_control_resume_level = 0;
6185 rc = fan_get_status_safe(&fan_control_resume_level);
6186 if (rc < 0)
6187 printk(TPACPI_NOTICE
6188 "failed to read fan level for later "
6189 "restore during resume: %d\n", rc);
6190
6191 /* if it is undefined, don't attempt to restore it.
6192 * KEEP THIS LAST */
Henrique de Moraes Holschuh75700e52008-10-18 14:23:52 -03006193 if (tp_features.fan_ctrl_status_undef)
Henrique de Moraes Holschuh0081b162008-11-09 10:54:02 -02006194 fan_control_resume_level = 0;
Henrique de Moraes Holschuh75700e52008-10-18 14:23:52 -03006195}
6196
6197static void fan_resume(void)
6198{
Henrique de Moraes Holschuh75700e52008-10-18 14:23:52 -03006199 u8 current_level = 7;
6200 bool do_set = false;
Henrique de Moraes Holschuh0081b162008-11-09 10:54:02 -02006201 int rc;
Henrique de Moraes Holschuh75700e52008-10-18 14:23:52 -03006202
6203 /* DSDT *always* updates status on resume */
6204 tp_features.fan_ctrl_status_undef = 0;
6205
Henrique de Moraes Holschuh75700e52008-10-18 14:23:52 -03006206 if (!fan_control_allowed ||
Henrique de Moraes Holschuh0081b162008-11-09 10:54:02 -02006207 !fan_control_resume_level ||
Henrique de Moraes Holschuh75700e52008-10-18 14:23:52 -03006208 (fan_get_status_safe(&current_level) < 0))
6209 return;
6210
6211 switch (fan_control_access_mode) {
6212 case TPACPI_FAN_WR_ACPI_SFAN:
Henrique de Moraes Holschuh0081b162008-11-09 10:54:02 -02006213 /* never decrease fan level */
6214 do_set = (fan_control_resume_level > current_level);
Henrique de Moraes Holschuh75700e52008-10-18 14:23:52 -03006215 break;
6216 case TPACPI_FAN_WR_ACPI_FANS:
6217 case TPACPI_FAN_WR_TPEC:
Henrique de Moraes Holschuh0081b162008-11-09 10:54:02 -02006218 /* never decrease fan level, scale is:
6219 * TP_EC_FAN_FULLSPEED > 7 >= TP_EC_FAN_AUTO
6220 *
6221 * We expect the firmware to set either 7 or AUTO, but we
6222 * handle FULLSPEED out of paranoia.
6223 *
6224 * So, we can safely only restore FULLSPEED or 7, anything
6225 * else could slow the fan. Restoring AUTO is useless, at
6226 * best that's exactly what the DSDT already set (it is the
6227 * slower it uses).
6228 *
6229 * Always keep in mind that the DSDT *will* have set the
6230 * fans to what the vendor supposes is the best level. We
6231 * muck with it only to speed the fan up.
6232 */
6233 if (fan_control_resume_level != 7 &&
6234 !(fan_control_resume_level & TP_EC_FAN_FULLSPEED))
6235 return;
6236 else
6237 do_set = !(current_level & TP_EC_FAN_FULLSPEED) &&
6238 (current_level != fan_control_resume_level);
Henrique de Moraes Holschuh75700e52008-10-18 14:23:52 -03006239 break;
6240 default:
6241 return;
6242 }
6243 if (do_set) {
6244 printk(TPACPI_NOTICE
6245 "restoring fan level to 0x%02x\n",
Henrique de Moraes Holschuh0081b162008-11-09 10:54:02 -02006246 fan_control_resume_level);
6247 rc = fan_set_level_safe(fan_control_resume_level);
6248 if (rc < 0)
6249 printk(TPACPI_NOTICE
6250 "failed to restore fan level: %d\n", rc);
Henrique de Moraes Holschuh75700e52008-10-18 14:23:52 -03006251 }
6252}
6253
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006254static int fan_read(char *p)
6255{
6256 int len = 0;
6257 int rc;
6258 u8 status;
6259 unsigned int speed = 0;
6260
6261 switch (fan_status_access_mode) {
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006262 case TPACPI_FAN_RD_ACPI_GFAN:
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006263 /* 570, 600e/x, 770e, 770x */
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02006264 rc = fan_get_status_safe(&status);
6265 if (rc < 0)
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006266 return rc;
6267
6268 len += sprintf(p + len, "status:\t\t%s\n"
6269 "level:\t\t%d\n",
6270 (status != 0) ? "enabled" : "disabled", status);
6271 break;
6272
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006273 case TPACPI_FAN_RD_TPEC:
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006274 /* all except 570, 600e/x, 770e, 770x */
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02006275 rc = fan_get_status_safe(&status);
6276 if (rc < 0)
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006277 return rc;
6278
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03006279 if (unlikely(tp_features.fan_ctrl_status_undef)) {
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006280 if (status != fan_control_initial_status)
Henrique de Moraes Holschuhd8fd94d2007-04-21 11:08:36 -03006281 tp_features.fan_ctrl_status_undef = 0;
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006282 else
6283 /* Return most likely status. In fact, it
6284 * might be the only possible status */
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006285 status = TP_EC_FAN_AUTO;
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006286 }
6287
6288 len += sprintf(p + len, "status:\t\t%s\n",
6289 (status != 0) ? "enabled" : "disabled");
6290
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02006291 rc = fan_get_speed(&speed);
6292 if (rc < 0)
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006293 return rc;
6294
6295 len += sprintf(p + len, "speed:\t\t%d\n", speed);
6296
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006297 if (status & TP_EC_FAN_FULLSPEED)
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006298 /* Disengaged mode takes precedence */
6299 len += sprintf(p + len, "level:\t\tdisengaged\n");
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006300 else if (status & TP_EC_FAN_AUTO)
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006301 len += sprintf(p + len, "level:\t\tauto\n");
6302 else
6303 len += sprintf(p + len, "level:\t\t%d\n", status);
6304 break;
6305
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006306 case TPACPI_FAN_NONE:
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006307 default:
6308 len += sprintf(p + len, "status:\t\tnot supported\n");
6309 }
6310
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006311 if (fan_control_commands & TPACPI_FAN_CMD_LEVEL) {
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006312 len += sprintf(p + len, "commands:\tlevel <level>");
6313
6314 switch (fan_control_access_mode) {
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006315 case TPACPI_FAN_WR_ACPI_SFAN:
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006316 len += sprintf(p + len, " (<level> is 0-7)\n");
6317 break;
6318
6319 default:
6320 len += sprintf(p + len, " (<level> is 0-7, "
Henrique de Moraes Holschuhfe98a522007-04-24 11:48:17 -03006321 "auto, disengaged, full-speed)\n");
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006322 break;
6323 }
6324 }
6325
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006326 if (fan_control_commands & TPACPI_FAN_CMD_ENABLE)
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006327 len += sprintf(p + len, "commands:\tenable, disable\n"
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02006328 "commands:\twatchdog <timeout> (<timeout> "
6329 "is 0 (off), 1-120 (seconds))\n");
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006330
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006331 if (fan_control_commands & TPACPI_FAN_CMD_SPEED)
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006332 len += sprintf(p + len, "commands:\tspeed <speed>"
6333 " (<speed> is 0-65535)\n");
6334
6335 return len;
6336}
6337
Henrique de Moraes Holschuh18ad7992006-11-24 11:47:11 -02006338static int fan_write_cmd_level(const char *cmd, int *rc)
6339{
6340 int level;
6341
Henrique de Moraes Holschuha12095c2006-11-24 11:47:13 -02006342 if (strlencmp(cmd, "level auto") == 0)
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006343 level = TP_EC_FAN_AUTO;
Henrique de Moraes Holschuhfe98a522007-04-24 11:48:17 -03006344 else if ((strlencmp(cmd, "level disengaged") == 0) |
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02006345 (strlencmp(cmd, "level full-speed") == 0))
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006346 level = TP_EC_FAN_FULLSPEED;
Henrique de Moraes Holschuha12095c2006-11-24 11:47:13 -02006347 else if (sscanf(cmd, "level %d", &level) != 1)
Henrique de Moraes Holschuh18ad7992006-11-24 11:47:11 -02006348 return 0;
6349
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02006350 *rc = fan_set_level_safe(level);
6351 if (*rc == -ENXIO)
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02006352 printk(TPACPI_ERR "level command accepted for unsupported "
Henrique de Moraes Holschuh18ad7992006-11-24 11:47:11 -02006353 "access mode %d", fan_control_access_mode);
6354
6355 return 1;
6356}
6357
6358static int fan_write_cmd_enable(const char *cmd, int *rc)
6359{
6360 if (strlencmp(cmd, "enable") != 0)
6361 return 0;
6362
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02006363 *rc = fan_set_enable();
6364 if (*rc == -ENXIO)
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02006365 printk(TPACPI_ERR "enable command accepted for unsupported "
Henrique de Moraes Holschuh18ad7992006-11-24 11:47:11 -02006366 "access mode %d", fan_control_access_mode);
6367
6368 return 1;
6369}
6370
6371static int fan_write_cmd_disable(const char *cmd, int *rc)
6372{
6373 if (strlencmp(cmd, "disable") != 0)
6374 return 0;
6375
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02006376 *rc = fan_set_disable();
6377 if (*rc == -ENXIO)
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02006378 printk(TPACPI_ERR "disable command accepted for unsupported "
Henrique de Moraes Holschuh18ad7992006-11-24 11:47:11 -02006379 "access mode %d", fan_control_access_mode);
6380
6381 return 1;
6382}
6383
6384static int fan_write_cmd_speed(const char *cmd, int *rc)
6385{
6386 int speed;
6387
Henrique de Moraes Holschuha8b7a662006-11-24 11:47:11 -02006388 /* TODO:
6389 * Support speed <low> <medium> <high> ? */
6390
Henrique de Moraes Holschuh18ad7992006-11-24 11:47:11 -02006391 if (sscanf(cmd, "speed %d", &speed) != 1)
6392 return 0;
6393
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02006394 *rc = fan_set_speed(speed);
6395 if (*rc == -ENXIO)
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02006396 printk(TPACPI_ERR "speed command accepted for unsupported "
Henrique de Moraes Holschuh18ad7992006-11-24 11:47:11 -02006397 "access mode %d", fan_control_access_mode);
6398
6399 return 1;
6400}
6401
Henrique de Moraes Holschuh16663a82006-11-24 11:47:14 -02006402static int fan_write_cmd_watchdog(const char *cmd, int *rc)
6403{
6404 int interval;
6405
6406 if (sscanf(cmd, "watchdog %d", &interval) != 1)
6407 return 0;
6408
6409 if (interval < 0 || interval > 120)
6410 *rc = -EINVAL;
6411 else
6412 fan_watchdog_maxinterval = interval;
6413
6414 return 1;
6415}
6416
Henrique de Moraes Holschuh18ad7992006-11-24 11:47:11 -02006417static int fan_write(char *buf)
6418{
6419 char *cmd;
6420 int rc = 0;
6421
6422 while (!rc && (cmd = next_cmd(&buf))) {
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006423 if (!((fan_control_commands & TPACPI_FAN_CMD_LEVEL) &&
Henrique de Moraes Holschuh18ad7992006-11-24 11:47:11 -02006424 fan_write_cmd_level(cmd, &rc)) &&
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006425 !((fan_control_commands & TPACPI_FAN_CMD_ENABLE) &&
Henrique de Moraes Holschuh18ad7992006-11-24 11:47:11 -02006426 (fan_write_cmd_enable(cmd, &rc) ||
Henrique de Moraes Holschuh16663a82006-11-24 11:47:14 -02006427 fan_write_cmd_disable(cmd, &rc) ||
6428 fan_write_cmd_watchdog(cmd, &rc))) &&
Henrique de Moraes Holschuhefa27142007-04-21 11:08:28 -03006429 !((fan_control_commands & TPACPI_FAN_CMD_SPEED) &&
Henrique de Moraes Holschuh18ad7992006-11-24 11:47:11 -02006430 fan_write_cmd_speed(cmd, &rc))
6431 )
6432 rc = -EINVAL;
Henrique de Moraes Holschuh16663a82006-11-24 11:47:14 -02006433 else if (!rc)
6434 fan_watchdog_reset();
Borislav Deianov78f81cc2005-08-17 00:00:00 -04006435 }
6436
Henrique de Moraes Holschuh18ad7992006-11-24 11:47:11 -02006437 return rc;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04006438}
6439
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03006440static struct ibm_struct fan_driver_data = {
6441 .name = "fan",
6442 .read = fan_read,
6443 .write = fan_write,
6444 .exit = fan_exit,
Henrique de Moraes Holschuh75700e52008-10-18 14:23:52 -03006445 .suspend = fan_suspend,
6446 .resume = fan_resume,
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03006447};
6448
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006449/****************************************************************************
6450 ****************************************************************************
6451 *
6452 * Infrastructure
6453 *
6454 ****************************************************************************
6455 ****************************************************************************/
Linus Torvalds1da177e2005-04-16 15:20:36 -07006456
Henrique de Moraes Holschuh7fd40022007-09-25 06:38:03 -03006457/* sysfs name ---------------------------------------------------------- */
6458static ssize_t thinkpad_acpi_pdev_name_show(struct device *dev,
6459 struct device_attribute *attr,
6460 char *buf)
6461{
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02006462 return snprintf(buf, PAGE_SIZE, "%s\n", TPACPI_NAME);
Henrique de Moraes Holschuh7fd40022007-09-25 06:38:03 -03006463}
6464
6465static struct device_attribute dev_attr_thinkpad_acpi_pdev_name =
6466 __ATTR(name, S_IRUGO, thinkpad_acpi_pdev_name_show, NULL);
6467
6468/* --------------------------------------------------------------------- */
6469
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006470/* /proc support */
Henrique de Moraes Holschuh94954cc2007-07-18 23:45:27 -03006471static struct proc_dir_entry *proc_dir;
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006472
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006473/*
6474 * Module and infrastructure proble, init and exit handling
6475 */
Linus Torvalds1da177e2005-04-16 15:20:36 -07006476
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02006477static int force_load;
6478
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03006479#ifdef CONFIG_THINKPAD_ACPI_DEBUG
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03006480static const char * __init str_supported(int is_supported)
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03006481{
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03006482 static char text_unsupported[] __initdata = "not supported";
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03006483
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03006484 return (is_supported)? &text_unsupported[4] : &text_unsupported[0];
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03006485}
6486#endif /* CONFIG_THINKPAD_ACPI_DEBUG */
6487
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02006488static void ibm_exit(struct ibm_struct *ibm)
6489{
6490 dbg_printk(TPACPI_DBG_EXIT, "removing %s\n", ibm->name);
6491
6492 list_del_init(&ibm->all_drivers);
6493
6494 if (ibm->flags.acpi_notify_installed) {
6495 dbg_printk(TPACPI_DBG_EXIT,
6496 "%s: acpi_remove_notify_handler\n", ibm->name);
6497 BUG_ON(!ibm->acpi);
6498 acpi_remove_notify_handler(*ibm->acpi->handle,
6499 ibm->acpi->type,
6500 dispatch_acpi_notify);
6501 ibm->flags.acpi_notify_installed = 0;
6502 ibm->flags.acpi_notify_installed = 0;
6503 }
6504
6505 if (ibm->flags.proc_created) {
6506 dbg_printk(TPACPI_DBG_EXIT,
6507 "%s: remove_proc_entry\n", ibm->name);
6508 remove_proc_entry(ibm->name, proc_dir);
6509 ibm->flags.proc_created = 0;
6510 }
6511
6512 if (ibm->flags.acpi_driver_registered) {
6513 dbg_printk(TPACPI_DBG_EXIT,
6514 "%s: acpi_bus_unregister_driver\n", ibm->name);
6515 BUG_ON(!ibm->acpi);
6516 acpi_bus_unregister_driver(ibm->acpi->driver);
6517 kfree(ibm->acpi->driver);
6518 ibm->acpi->driver = NULL;
6519 ibm->flags.acpi_driver_registered = 0;
6520 }
6521
6522 if (ibm->flags.init_called && ibm->exit) {
6523 ibm->exit();
6524 ibm->flags.init_called = 0;
6525 }
6526
6527 dbg_printk(TPACPI_DBG_INIT, "finished removing %s\n", ibm->name);
6528}
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -02006529
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03006530static int __init ibm_init(struct ibm_init_struct *iibm)
Linus Torvalds1da177e2005-04-16 15:20:36 -07006531{
6532 int ret;
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03006533 struct ibm_struct *ibm = iibm->data;
Linus Torvalds1da177e2005-04-16 15:20:36 -07006534 struct proc_dir_entry *entry;
6535
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03006536 BUG_ON(ibm == NULL);
6537
6538 INIT_LIST_HEAD(&ibm->all_drivers);
6539
Henrique de Moraes Holschuh92641172007-04-21 11:08:35 -03006540 if (ibm->flags.experimental && !experimental)
Linus Torvalds1da177e2005-04-16 15:20:36 -07006541 return 0;
6542
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03006543 dbg_printk(TPACPI_DBG_INIT,
6544 "probing for %s\n", ibm->name);
6545
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03006546 if (iibm->init) {
6547 ret = iibm->init(iibm);
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03006548 if (ret > 0)
6549 return 0; /* probe failed */
6550 if (ret)
Linus Torvalds1da177e2005-04-16 15:20:36 -07006551 return ret;
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03006552
Henrique de Moraes Holschuh92641172007-04-21 11:08:35 -03006553 ibm->flags.init_called = 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -07006554 }
6555
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -03006556 if (ibm->acpi) {
6557 if (ibm->acpi->hid) {
6558 ret = register_tpacpi_subdriver(ibm);
6559 if (ret)
6560 goto err_out;
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03006561 }
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -03006562
6563 if (ibm->acpi->notify) {
6564 ret = setup_acpi_notify(ibm);
6565 if (ret == -ENODEV) {
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02006566 printk(TPACPI_NOTICE "disabling subdriver %s\n",
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -03006567 ibm->name);
6568 ret = 0;
6569 goto err_out;
6570 }
6571 if (ret < 0)
6572 goto err_out;
6573 }
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03006574 }
6575
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03006576 dbg_printk(TPACPI_DBG_INIT,
6577 "%s installed\n", ibm->name);
6578
Borislav Deianov78f81cc2005-08-17 00:00:00 -04006579 if (ibm->read) {
6580 entry = create_proc_entry(ibm->name,
6581 S_IFREG | S_IRUGO | S_IWUSR,
6582 proc_dir);
6583 if (!entry) {
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02006584 printk(TPACPI_ERR "unable to create proc entry %s\n",
Borislav Deianov78f81cc2005-08-17 00:00:00 -04006585 ibm->name);
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03006586 ret = -ENODEV;
6587 goto err_out;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04006588 }
6589 entry->owner = THIS_MODULE;
6590 entry->data = ibm;
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -03006591 entry->read_proc = &dispatch_procfs_read;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04006592 if (ibm->write)
Henrique de Moraes Holschuh8d376cd2007-04-21 11:08:37 -03006593 entry->write_proc = &dispatch_procfs_write;
Henrique de Moraes Holschuh92641172007-04-21 11:08:35 -03006594 ibm->flags.proc_created = 1;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04006595 }
Linus Torvalds1da177e2005-04-16 15:20:36 -07006596
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03006597 list_add_tail(&ibm->all_drivers, &tpacpi_all_drivers);
6598
Linus Torvalds1da177e2005-04-16 15:20:36 -07006599 return 0;
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03006600
6601err_out:
Henrique de Moraes Holschuhfe08bc42007-04-21 11:08:32 -03006602 dbg_printk(TPACPI_DBG_INIT,
6603 "%s: at error exit path with result %d\n",
6604 ibm->name, ret);
6605
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03006606 ibm_exit(ibm);
6607 return (ret < 0)? ret : 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -07006608}
6609
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006610/* Probing */
Linus Torvalds1da177e2005-04-16 15:20:36 -07006611
Henrique de Moraes Holschuhbf20e742008-07-21 09:15:51 -03006612/* returns 0 - probe ok, or < 0 - probe error.
6613 * Probe ok doesn't mean thinkpad found.
6614 * On error, kfree() cleanup on tp->* is not performed, caller must do it */
6615static int __must_check __init get_thinkpad_model_data(
6616 struct thinkpad_id_data *tp)
Henrique de Moraes Holschuh60eb0b32006-11-24 11:47:08 -02006617{
Jeff Garzik18552562007-10-03 15:15:40 -04006618 const struct dmi_device *dev = NULL;
Henrique de Moraes Holschuh49a13cd2006-11-24 11:47:13 -02006619 char ec_fw_string[18];
Henrique de Moraes Holschuhbf20e742008-07-21 09:15:51 -03006620 char const *s;
Henrique de Moraes Holschuh60eb0b32006-11-24 11:47:08 -02006621
Henrique de Moraes Holschuhd5a2f2f2007-07-18 23:45:42 -03006622 if (!tp)
Henrique de Moraes Holschuhbf20e742008-07-21 09:15:51 -03006623 return -EINVAL;
Henrique de Moraes Holschuhd5a2f2f2007-07-18 23:45:42 -03006624
6625 memset(tp, 0, sizeof(*tp));
6626
6627 if (dmi_name_in_vendors("IBM"))
6628 tp->vendor = PCI_VENDOR_ID_IBM;
6629 else if (dmi_name_in_vendors("LENOVO"))
6630 tp->vendor = PCI_VENDOR_ID_LENOVO;
6631 else
Henrique de Moraes Holschuhbf20e742008-07-21 09:15:51 -03006632 return 0;
Henrique de Moraes Holschuhd5a2f2f2007-07-18 23:45:42 -03006633
Henrique de Moraes Holschuhbf20e742008-07-21 09:15:51 -03006634 s = dmi_get_system_info(DMI_BIOS_VERSION);
6635 tp->bios_version_str = kstrdup(s, GFP_KERNEL);
6636 if (s && !tp->bios_version_str)
6637 return -ENOMEM;
Henrique de Moraes Holschuhd5a2f2f2007-07-18 23:45:42 -03006638 if (!tp->bios_version_str)
Henrique de Moraes Holschuhbf20e742008-07-21 09:15:51 -03006639 return 0;
Henrique de Moraes Holschuhd5a2f2f2007-07-18 23:45:42 -03006640 tp->bios_model = tp->bios_version_str[0]
6641 | (tp->bios_version_str[1] << 8);
6642
Henrique de Moraes Holschuh60eb0b32006-11-24 11:47:08 -02006643 /*
6644 * ThinkPad T23 or newer, A31 or newer, R50e or newer,
6645 * X32 or newer, all Z series; Some models must have an
6646 * up-to-date BIOS or they will not be detected.
6647 *
6648 * See http://thinkwiki.org/wiki/List_of_DMI_IDs
6649 */
6650 while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) {
Henrique de Moraes Holschuh49a13cd2006-11-24 11:47:13 -02006651 if (sscanf(dev->name,
6652 "IBM ThinkPad Embedded Controller -[%17c",
6653 ec_fw_string) == 1) {
6654 ec_fw_string[sizeof(ec_fw_string) - 1] = 0;
6655 ec_fw_string[strcspn(ec_fw_string, " ]")] = 0;
Henrique de Moraes Holschuhd5a2f2f2007-07-18 23:45:42 -03006656
6657 tp->ec_version_str = kstrdup(ec_fw_string, GFP_KERNEL);
Henrique de Moraes Holschuhbf20e742008-07-21 09:15:51 -03006658 if (!tp->ec_version_str)
6659 return -ENOMEM;
Henrique de Moraes Holschuhd5a2f2f2007-07-18 23:45:42 -03006660 tp->ec_model = ec_fw_string[0]
6661 | (ec_fw_string[1] << 8);
6662 break;
Henrique de Moraes Holschuh49a13cd2006-11-24 11:47:13 -02006663 }
Henrique de Moraes Holschuh60eb0b32006-11-24 11:47:08 -02006664 }
Henrique de Moraes Holschuhd5a2f2f2007-07-18 23:45:42 -03006665
Henrique de Moraes Holschuhbf20e742008-07-21 09:15:51 -03006666 s = dmi_get_system_info(DMI_PRODUCT_VERSION);
6667 if (s && !strnicmp(s, "ThinkPad", 8)) {
6668 tp->model_str = kstrdup(s, GFP_KERNEL);
6669 if (!tp->model_str)
6670 return -ENOMEM;
Henrique de Moraes Holschuhd5a2f2f2007-07-18 23:45:42 -03006671 }
Henrique de Moraes Holschuh8c74adb2008-04-26 01:02:19 -03006672
Henrique de Moraes Holschuhbf20e742008-07-21 09:15:51 -03006673 s = dmi_get_system_info(DMI_PRODUCT_NAME);
6674 tp->nummodel_str = kstrdup(s, GFP_KERNEL);
6675 if (s && !tp->nummodel_str)
6676 return -ENOMEM;
6677
6678 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -07006679}
6680
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03006681static int __init probe_for_thinkpad(void)
6682{
6683 int is_thinkpad;
6684
6685 if (acpi_disabled)
6686 return -ENODEV;
6687
6688 /*
6689 * Non-ancient models have better DMI tagging, but very old models
6690 * don't.
6691 */
Henrique de Moraes Holschuhd5a2f2f2007-07-18 23:45:42 -03006692 is_thinkpad = (thinkpad_id.model_str != NULL);
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03006693
6694 /* ec is required because many other handles are relative to it */
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02006695 TPACPI_ACPIHANDLE_INIT(ec);
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03006696 if (!ec_handle) {
6697 if (is_thinkpad)
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02006698 printk(TPACPI_ERR
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03006699 "Not yet supported ThinkPad detected!\n");
6700 return -ENODEV;
6701 }
6702
Henrique de Moraes Holschuh0dcef772007-04-21 11:08:34 -03006703 /*
6704 * Risks a regression on very old machines, but reduces potential
6705 * false positives a damn great deal
6706 */
6707 if (!is_thinkpad)
Henrique de Moraes Holschuhd5a2f2f2007-07-18 23:45:42 -03006708 is_thinkpad = (thinkpad_id.vendor == PCI_VENDOR_ID_IBM);
Henrique de Moraes Holschuh0dcef772007-04-21 11:08:34 -03006709
6710 if (!is_thinkpad && !force_load)
6711 return -ENODEV;
6712
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03006713 return 0;
6714}
6715
6716
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006717/* Module init, exit, parameters */
6718
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03006719static struct ibm_init_struct ibms_init[] __initdata = {
6720 {
6721 .init = thinkpad_acpi_driver_init,
6722 .data = &thinkpad_acpi_driver_data,
6723 },
6724 {
6725 .init = hotkey_init,
6726 .data = &hotkey_driver_data,
6727 },
6728 {
6729 .init = bluetooth_init,
6730 .data = &bluetooth_driver_data,
6731 },
6732 {
6733 .init = wan_init,
6734 .data = &wan_driver_data,
6735 },
Henrique de Moraes Holschuhd7c1d172008-02-16 02:17:54 -02006736#ifdef CONFIG_THINKPAD_ACPI_VIDEO
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03006737 {
6738 .init = video_init,
6739 .data = &video_driver_data,
6740 },
Henrique de Moraes Holschuhd7c1d172008-02-16 02:17:54 -02006741#endif
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03006742 {
6743 .init = light_init,
6744 .data = &light_driver_data,
6745 },
6746#ifdef CONFIG_THINKPAD_ACPI_DOCK
6747 {
6748 .init = dock_init,
6749 .data = &dock_driver_data[0],
6750 },
6751 {
Henrique de Moraes Holschuhd94a7f12007-04-27 22:00:15 -03006752 .init = dock_init2,
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03006753 .data = &dock_driver_data[1],
6754 },
6755#endif
6756#ifdef CONFIG_THINKPAD_ACPI_BAY
6757 {
6758 .init = bay_init,
6759 .data = &bay_driver_data,
6760 },
6761#endif
6762 {
6763 .init = cmos_init,
6764 .data = &cmos_driver_data,
6765 },
6766 {
6767 .init = led_init,
6768 .data = &led_driver_data,
6769 },
6770 {
6771 .init = beep_init,
6772 .data = &beep_driver_data,
6773 },
6774 {
6775 .init = thermal_init,
6776 .data = &thermal_driver_data,
6777 },
6778 {
6779 .data = &ecdump_driver_data,
6780 },
6781 {
6782 .init = brightness_init,
6783 .data = &brightness_driver_data,
6784 },
6785 {
6786 .data = &volume_driver_data,
6787 },
6788 {
6789 .init = fan_init,
6790 .data = &fan_driver_data,
6791 },
6792};
6793
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006794static int __init set_ibm_param(const char *val, struct kernel_param *kp)
6795{
6796 unsigned int i;
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03006797 struct ibm_struct *ibm;
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006798
Henrique de Moraes Holschuh59f91ff2007-11-18 09:18:29 -02006799 if (!kp || !kp->name || !val)
6800 return -EINVAL;
6801
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03006802 for (i = 0; i < ARRAY_SIZE(ibms_init); i++) {
6803 ibm = ibms_init[i].data;
Henrique de Moraes Holschuh59f91ff2007-11-18 09:18:29 -02006804 WARN_ON(ibm == NULL);
6805
6806 if (!ibm || !ibm->name)
6807 continue;
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03006808
6809 if (strcmp(ibm->name, kp->name) == 0 && ibm->write) {
6810 if (strlen(val) > sizeof(ibms_init[i].param) - 2)
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006811 return -ENOSPC;
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03006812 strcpy(ibms_init[i].param, val);
6813 strcat(ibms_init[i].param, ",");
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006814 return 0;
6815 }
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03006816 }
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006817
6818 return -EINVAL;
6819}
6820
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006821module_param(experimental, int, 0);
Henrique de Moraes Holschuhf68080f2008-01-08 13:02:47 -02006822MODULE_PARM_DESC(experimental,
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02006823 "Enables experimental features when non-zero");
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006824
Henrique de Moraes Holschuh132ce092007-04-21 11:08:30 -03006825module_param_named(debug, dbg_level, uint, 0);
Henrique de Moraes Holschuhf68080f2008-01-08 13:02:47 -02006826MODULE_PARM_DESC(debug, "Sets debug level bit-mask");
Henrique de Moraes Holschuh132ce092007-04-21 11:08:30 -03006827
Henrique de Moraes Holschuh86cc9442007-07-18 23:45:41 -03006828module_param(force_load, bool, 0);
Henrique de Moraes Holschuhf68080f2008-01-08 13:02:47 -02006829MODULE_PARM_DESC(force_load,
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02006830 "Attempts to load the driver even on a "
6831 "mis-identified ThinkPad when true");
Henrique de Moraes Holschuh0dcef772007-04-21 11:08:34 -03006832
Henrique de Moraes Holschuh86cc9442007-07-18 23:45:41 -03006833module_param_named(fan_control, fan_control_allowed, bool, 0);
Henrique de Moraes Holschuhf68080f2008-01-08 13:02:47 -02006834MODULE_PARM_DESC(fan_control,
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02006835 "Enables setting fan parameters features when true");
Henrique de Moraes Holschuhecf2a802007-04-27 22:00:09 -03006836
Henrique de Moraes Holschuh24d3b772007-07-18 23:45:43 -03006837module_param_named(brightness_mode, brightness_mode, int, 0);
Henrique de Moraes Holschuhf68080f2008-01-08 13:02:47 -02006838MODULE_PARM_DESC(brightness_mode,
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02006839 "Selects brightness control strategy: "
6840 "0=auto, 1=EC, 2=CMOS, 3=both");
Henrique de Moraes Holschuh24d3b772007-07-18 23:45:43 -03006841
Henrique de Moraes Holschuh87cc5372007-10-30 18:02:07 -02006842module_param(brightness_enable, uint, 0);
Henrique de Moraes Holschuhf68080f2008-01-08 13:02:47 -02006843MODULE_PARM_DESC(brightness_enable,
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02006844 "Enables backlight control when 1, disables when 0");
Henrique de Moraes Holschuh87cc5372007-10-30 18:02:07 -02006845
Henrique de Moraes Holschuhff80f132007-09-04 11:13:15 -03006846module_param(hotkey_report_mode, uint, 0);
Henrique de Moraes Holschuhf68080f2008-01-08 13:02:47 -02006847MODULE_PARM_DESC(hotkey_report_mode,
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02006848 "used for backwards compatibility with userspace, "
6849 "see documentation");
Henrique de Moraes Holschuhff80f132007-09-04 11:13:15 -03006850
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02006851#define TPACPI_PARAM(feature) \
Henrique de Moraes Holschuhf68080f2008-01-08 13:02:47 -02006852 module_param_call(feature, set_ibm_param, NULL, NULL, 0); \
Henrique de Moraes Holschuhcbb14842008-02-16 02:17:50 -02006853 MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command " \
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02006854 "at module load, see documentation")
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006855
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02006856TPACPI_PARAM(hotkey);
6857TPACPI_PARAM(bluetooth);
6858TPACPI_PARAM(video);
6859TPACPI_PARAM(light);
Henrique de Moraes Holschuh85998242007-03-29 01:58:41 -03006860#ifdef CONFIG_THINKPAD_ACPI_DOCK
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02006861TPACPI_PARAM(dock);
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006862#endif
Henrique de Moraes Holschuh85998242007-03-29 01:58:41 -03006863#ifdef CONFIG_THINKPAD_ACPI_BAY
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02006864TPACPI_PARAM(bay);
Henrique de Moraes Holschuh85998242007-03-29 01:58:41 -03006865#endif /* CONFIG_THINKPAD_ACPI_BAY */
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02006866TPACPI_PARAM(cmos);
6867TPACPI_PARAM(led);
6868TPACPI_PARAM(beep);
6869TPACPI_PARAM(ecdump);
6870TPACPI_PARAM(brightness);
6871TPACPI_PARAM(volume);
6872TPACPI_PARAM(fan);
Henrique de Moraes Holschuh56b6aeb2007-03-23 17:33:57 -03006873
Henrique de Moraes Holschuha73f3092009-01-11 03:01:00 -02006874#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
6875module_param(dbg_wlswemul, uint, 0);
6876MODULE_PARM_DESC(dbg_wlswemul, "Enables WLSW emulation");
6877module_param_named(wlsw_state, tpacpi_wlsw_emulstate, bool, 0);
6878MODULE_PARM_DESC(wlsw_state,
6879 "Initial state of the emulated WLSW switch");
6880
6881module_param(dbg_bluetoothemul, uint, 0);
6882MODULE_PARM_DESC(dbg_bluetoothemul, "Enables bluetooth switch emulation");
6883module_param_named(bluetooth_state, tpacpi_bluetooth_emulstate, bool, 0);
6884MODULE_PARM_DESC(bluetooth_state,
6885 "Initial state of the emulated bluetooth switch");
6886
6887module_param(dbg_wwanemul, uint, 0);
6888MODULE_PARM_DESC(dbg_wwanemul, "Enables WWAN switch emulation");
6889module_param_named(wwan_state, tpacpi_wwan_emulstate, bool, 0);
6890MODULE_PARM_DESC(wwan_state,
6891 "Initial state of the emulated WWAN switch");
6892#endif
6893
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02006894static void thinkpad_acpi_module_exit(void)
6895{
6896 struct ibm_struct *ibm, *itmp;
6897
6898 tpacpi_lifecycle = TPACPI_LIFE_EXITING;
6899
6900 list_for_each_entry_safe_reverse(ibm, itmp,
6901 &tpacpi_all_drivers,
6902 all_drivers) {
6903 ibm_exit(ibm);
6904 }
6905
6906 dbg_printk(TPACPI_DBG_INIT, "finished subdriver exit path...\n");
6907
6908 if (tpacpi_inputdev) {
6909 if (tp_features.input_device_registered)
6910 input_unregister_device(tpacpi_inputdev);
6911 else
6912 input_free_device(tpacpi_inputdev);
6913 }
6914
6915 if (tpacpi_hwmon)
6916 hwmon_device_unregister(tpacpi_hwmon);
6917
6918 if (tp_features.sensors_pdev_attrs_registered)
6919 device_remove_file(&tpacpi_sensors_pdev->dev,
6920 &dev_attr_thinkpad_acpi_pdev_name);
6921 if (tpacpi_sensors_pdev)
6922 platform_device_unregister(tpacpi_sensors_pdev);
6923 if (tpacpi_pdev)
6924 platform_device_unregister(tpacpi_pdev);
6925
6926 if (tp_features.sensors_pdrv_attrs_registered)
6927 tpacpi_remove_driver_attributes(&tpacpi_hwmon_pdriver.driver);
6928 if (tp_features.platform_drv_attrs_registered)
6929 tpacpi_remove_driver_attributes(&tpacpi_pdriver.driver);
6930
6931 if (tp_features.sensors_pdrv_registered)
6932 platform_driver_unregister(&tpacpi_hwmon_pdriver);
6933
6934 if (tp_features.platform_drv_registered)
6935 platform_driver_unregister(&tpacpi_pdriver);
6936
6937 if (proc_dir)
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02006938 remove_proc_entry(TPACPI_PROC_DIR, acpi_root_dir);
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02006939
Henrique de Moraes Holschuhe0e3c062008-04-26 01:02:28 -03006940 if (tpacpi_wq)
6941 destroy_workqueue(tpacpi_wq);
6942
Henrique de Moraes Holschuhb21a15f2008-01-08 13:02:45 -02006943 kfree(thinkpad_id.bios_version_str);
6944 kfree(thinkpad_id.ec_version_str);
6945 kfree(thinkpad_id.model_str);
6946}
6947
Henrique de Moraes Holschuhf74a27d2008-01-08 13:02:44 -02006948
Henrique de Moraes Holschuh1def7112007-04-21 11:08:27 -03006949static int __init thinkpad_acpi_module_init(void)
Linus Torvalds1da177e2005-04-16 15:20:36 -07006950{
6951 int ret, i;
6952
Henrique de Moraes Holschuh8fef5022007-09-23 11:39:02 -03006953 tpacpi_lifecycle = TPACPI_LIFE_INIT;
6954
Henrique de Moraes Holschuhff80f132007-09-04 11:13:15 -03006955 /* Parameter checking */
6956 if (hotkey_report_mode > 2)
6957 return -EINVAL;
6958
Henrique de Moraes Holschuh54ae1502007-04-24 11:48:12 -03006959 /* Driver-level probe */
Henrique de Moraes Holschuhd5a2f2f2007-07-18 23:45:42 -03006960
Henrique de Moraes Holschuhbf20e742008-07-21 09:15:51 -03006961 ret = get_thinkpad_model_data(&thinkpad_id);
6962 if (ret) {
6963 printk(TPACPI_ERR
6964 "unable to get DMI data: %d\n", ret);
6965 thinkpad_acpi_module_exit();
6966 return ret;
6967 }
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03006968 ret = probe_for_thinkpad();
Henrique de Moraes Holschuhd5a2f2f2007-07-18 23:45:42 -03006969 if (ret) {
6970 thinkpad_acpi_module_exit();
Henrique de Moraes Holschuh5fba3442007-04-21 11:08:31 -03006971 return ret;
Henrique de Moraes Holschuhd5a2f2f2007-07-18 23:45:42 -03006972 }
Linus Torvalds1da177e2005-04-16 15:20:36 -07006973
Henrique de Moraes Holschuh54ae1502007-04-24 11:48:12 -03006974 /* Driver initialization */
Henrique de Moraes Holschuhd5a2f2f2007-07-18 23:45:42 -03006975
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02006976 TPACPI_ACPIHANDLE_INIT(ecrd);
6977 TPACPI_ACPIHANDLE_INIT(ecwr);
Linus Torvalds1da177e2005-04-16 15:20:36 -07006978
Henrique de Moraes Holschuhe0e3c062008-04-26 01:02:28 -03006979 tpacpi_wq = create_singlethread_workqueue(TPACPI_WORKQUEUE_NAME);
6980 if (!tpacpi_wq) {
6981 thinkpad_acpi_module_exit();
6982 return -ENOMEM;
6983 }
6984
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02006985 proc_dir = proc_mkdir(TPACPI_PROC_DIR, acpi_root_dir);
Linus Torvalds1da177e2005-04-16 15:20:36 -07006986 if (!proc_dir) {
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02006987 printk(TPACPI_ERR
6988 "unable to create proc dir " TPACPI_PROC_DIR);
Henrique de Moraes Holschuh1def7112007-04-21 11:08:27 -03006989 thinkpad_acpi_module_exit();
Linus Torvalds1da177e2005-04-16 15:20:36 -07006990 return -ENODEV;
6991 }
6992 proc_dir->owner = THIS_MODULE;
Borislav Deianov78f81cc2005-08-17 00:00:00 -04006993
Henrique de Moraes Holschuh54ae1502007-04-24 11:48:12 -03006994 ret = platform_driver_register(&tpacpi_pdriver);
6995 if (ret) {
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02006996 printk(TPACPI_ERR
6997 "unable to register main platform driver\n");
Henrique de Moraes Holschuh54ae1502007-04-24 11:48:12 -03006998 thinkpad_acpi_module_exit();
6999 return ret;
7000 }
Henrique de Moraes Holschuhac363932007-07-27 17:04:40 -03007001 tp_features.platform_drv_registered = 1;
7002
Henrique de Moraes Holschuh7fd40022007-09-25 06:38:03 -03007003 ret = platform_driver_register(&tpacpi_hwmon_pdriver);
7004 if (ret) {
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02007005 printk(TPACPI_ERR
7006 "unable to register hwmon platform driver\n");
Henrique de Moraes Holschuh7fd40022007-09-25 06:38:03 -03007007 thinkpad_acpi_module_exit();
7008 return ret;
7009 }
7010 tp_features.sensors_pdrv_registered = 1;
7011
Henrique de Moraes Holschuh176750d2007-04-24 11:48:13 -03007012 ret = tpacpi_create_driver_attributes(&tpacpi_pdriver.driver);
Henrique de Moraes Holschuh2369cc92007-09-23 11:39:07 -03007013 if (!ret) {
7014 tp_features.platform_drv_attrs_registered = 1;
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02007015 ret = tpacpi_create_driver_attributes(
7016 &tpacpi_hwmon_pdriver.driver);
Henrique de Moraes Holschuh2369cc92007-09-23 11:39:07 -03007017 }
Henrique de Moraes Holschuh176750d2007-04-24 11:48:13 -03007018 if (ret) {
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02007019 printk(TPACPI_ERR
7020 "unable to create sysfs driver attributes\n");
Henrique de Moraes Holschuh176750d2007-04-24 11:48:13 -03007021 thinkpad_acpi_module_exit();
7022 return ret;
7023 }
Henrique de Moraes Holschuh2369cc92007-09-23 11:39:07 -03007024 tp_features.sensors_pdrv_attrs_registered = 1;
Henrique de Moraes Holschuh176750d2007-04-24 11:48:13 -03007025
Henrique de Moraes Holschuh54ae1502007-04-24 11:48:12 -03007026
7027 /* Device initialization */
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02007028 tpacpi_pdev = platform_device_register_simple(TPACPI_DRVR_NAME, -1,
Henrique de Moraes Holschuh54ae1502007-04-24 11:48:12 -03007029 NULL, 0);
7030 if (IS_ERR(tpacpi_pdev)) {
7031 ret = PTR_ERR(tpacpi_pdev);
7032 tpacpi_pdev = NULL;
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02007033 printk(TPACPI_ERR "unable to register platform device\n");
Henrique de Moraes Holschuh54ae1502007-04-24 11:48:12 -03007034 thinkpad_acpi_module_exit();
7035 return ret;
7036 }
Henrique de Moraes Holschuh7fd40022007-09-25 06:38:03 -03007037 tpacpi_sensors_pdev = platform_device_register_simple(
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02007038 TPACPI_HWMON_DRVR_NAME,
7039 -1, NULL, 0);
Henrique de Moraes Holschuh7fd40022007-09-25 06:38:03 -03007040 if (IS_ERR(tpacpi_sensors_pdev)) {
7041 ret = PTR_ERR(tpacpi_sensors_pdev);
7042 tpacpi_sensors_pdev = NULL;
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02007043 printk(TPACPI_ERR
7044 "unable to register hwmon platform device\n");
Henrique de Moraes Holschuh7fd40022007-09-25 06:38:03 -03007045 thinkpad_acpi_module_exit();
7046 return ret;
7047 }
7048 ret = device_create_file(&tpacpi_sensors_pdev->dev,
7049 &dev_attr_thinkpad_acpi_pdev_name);
7050 if (ret) {
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02007051 printk(TPACPI_ERR
Henrique de Moraes Holschuh35ff8b92008-01-08 13:02:49 -02007052 "unable to create sysfs hwmon device attributes\n");
Henrique de Moraes Holschuh7fd40022007-09-25 06:38:03 -03007053 thinkpad_acpi_module_exit();
7054 return ret;
7055 }
7056 tp_features.sensors_pdev_attrs_registered = 1;
7057 tpacpi_hwmon = hwmon_device_register(&tpacpi_sensors_pdev->dev);
Henrique de Moraes Holschuh54ae1502007-04-24 11:48:12 -03007058 if (IS_ERR(tpacpi_hwmon)) {
7059 ret = PTR_ERR(tpacpi_hwmon);
7060 tpacpi_hwmon = NULL;
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02007061 printk(TPACPI_ERR "unable to register hwmon device\n");
Henrique de Moraes Holschuh54ae1502007-04-24 11:48:12 -03007062 thinkpad_acpi_module_exit();
7063 return ret;
7064 }
Henrique de Moraes Holschuh8523ed62007-09-23 11:39:01 -03007065 mutex_init(&tpacpi_inputdev_send_mutex);
Henrique de Moraes Holschuh7f5d1cd2007-07-18 23:45:34 -03007066 tpacpi_inputdev = input_allocate_device();
7067 if (!tpacpi_inputdev) {
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02007068 printk(TPACPI_ERR "unable to allocate input device\n");
Henrique de Moraes Holschuh7f5d1cd2007-07-18 23:45:34 -03007069 thinkpad_acpi_module_exit();
7070 return -ENOMEM;
7071 } else {
7072 /* Prepare input device, but don't register */
7073 tpacpi_inputdev->name = "ThinkPad Extra Buttons";
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02007074 tpacpi_inputdev->phys = TPACPI_DRVR_NAME "/input0";
Henrique de Moraes Holschuh7f5d1cd2007-07-18 23:45:34 -03007075 tpacpi_inputdev->id.bustype = BUS_HOST;
Henrique de Moraes Holschuhedf0e0e2007-07-18 23:45:44 -03007076 tpacpi_inputdev->id.vendor = (thinkpad_id.vendor) ?
7077 thinkpad_id.vendor :
7078 PCI_VENDOR_ID_IBM;
Henrique de Moraes Holschuh7f5d1cd2007-07-18 23:45:34 -03007079 tpacpi_inputdev->id.product = TPACPI_HKEY_INPUT_PRODUCT;
7080 tpacpi_inputdev->id.version = TPACPI_HKEY_INPUT_VERSION;
7081 }
Henrique de Moraes Holschuha5763f22007-04-21 11:08:33 -03007082 for (i = 0; i < ARRAY_SIZE(ibms_init); i++) {
7083 ret = ibm_init(&ibms_init[i]);
7084 if (ret >= 0 && *ibms_init[i].param)
7085 ret = ibms_init[i].data->write(ibms_init[i].param);
Linus Torvalds1da177e2005-04-16 15:20:36 -07007086 if (ret < 0) {
Henrique de Moraes Holschuh1def7112007-04-21 11:08:27 -03007087 thinkpad_acpi_module_exit();
Linus Torvalds1da177e2005-04-16 15:20:36 -07007088 return ret;
7089 }
7090 }
Henrique de Moraes Holschuh7f5d1cd2007-07-18 23:45:34 -03007091 ret = input_register_device(tpacpi_inputdev);
7092 if (ret < 0) {
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02007093 printk(TPACPI_ERR "unable to register input device\n");
Henrique de Moraes Holschuh7f5d1cd2007-07-18 23:45:34 -03007094 thinkpad_acpi_module_exit();
7095 return ret;
7096 } else {
7097 tp_features.input_device_registered = 1;
7098 }
Linus Torvalds1da177e2005-04-16 15:20:36 -07007099
Henrique de Moraes Holschuh8fef5022007-09-23 11:39:02 -03007100 tpacpi_lifecycle = TPACPI_LIFE_RUNNING;
Linus Torvalds1da177e2005-04-16 15:20:36 -07007101 return 0;
7102}
7103
Henrique de Moraes Holschuhf68080f2008-01-08 13:02:47 -02007104/* Please remove this in year 2009 */
7105MODULE_ALIAS("ibm_acpi");
7106
Henrique de Moraes Holschuh95e57ab2008-04-26 01:02:22 -03007107MODULE_ALIAS(TPACPI_DRVR_SHORTNAME);
7108
Henrique de Moraes Holschuhf68080f2008-01-08 13:02:47 -02007109/*
7110 * DMI matching for module autoloading
7111 *
7112 * See http://thinkwiki.org/wiki/List_of_DMI_IDs
7113 * See http://thinkwiki.org/wiki/BIOS_Upgrade_Downloads
7114 *
7115 * Only models listed in thinkwiki will be supported, so add yours
7116 * if it is not there yet.
7117 */
7118#define IBM_BIOS_MODULE_ALIAS(__type) \
7119 MODULE_ALIAS("dmi:bvnIBM:bvr" __type "ET??WW")
7120
7121/* Non-ancient thinkpads */
7122MODULE_ALIAS("dmi:bvnIBM:*:svnIBM:*:pvrThinkPad*:rvnIBM:*");
7123MODULE_ALIAS("dmi:bvnLENOVO:*:svnLENOVO:*:pvrThinkPad*:rvnLENOVO:*");
7124
7125/* Ancient thinkpad BIOSes have to be identified by
7126 * BIOS type or model number, and there are far less
7127 * BIOS types than model numbers... */
7128IBM_BIOS_MODULE_ALIAS("I[B,D,H,I,M,N,O,T,W,V,Y,Z]");
7129IBM_BIOS_MODULE_ALIAS("1[0,3,6,8,A-G,I,K,M-P,S,T]");
7130IBM_BIOS_MODULE_ALIAS("K[U,X-Z]");
7131
7132MODULE_AUTHOR("Borislav Deianov, Henrique de Moraes Holschuh");
Henrique de Moraes Holschuhe0c7dfe2008-01-08 13:02:48 -02007133MODULE_DESCRIPTION(TPACPI_DESC);
7134MODULE_VERSION(TPACPI_VERSION);
Henrique de Moraes Holschuhf68080f2008-01-08 13:02:47 -02007135MODULE_LICENSE("GPL");
7136
Henrique de Moraes Holschuh1def7112007-04-21 11:08:27 -03007137module_init(thinkpad_acpi_module_init);
7138module_exit(thinkpad_acpi_module_exit);