blob: a85f795ab4ec96459bae9a17a2735e27ce6a8fcf [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
2 * acpi_ec.c - ACPI Embedded Controller Driver ($Revision: 38 $)
3 *
4 * Copyright (C) 2004 Luming Yu <luming.yu@intel.com>
5 * Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
6 * Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
7 *
8 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or (at
13 * your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful, but
16 * WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License along
21 * with this program; if not, write to the Free Software Foundation, Inc.,
22 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
23 *
24 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
25 */
26
27#include <linux/kernel.h>
28#include <linux/module.h>
29#include <linux/init.h>
30#include <linux/types.h>
31#include <linux/delay.h>
32#include <linux/proc_fs.h>
33#include <linux/seq_file.h>
Dmitry Torokhov451566f2005-03-19 01:10:05 -050034#include <linux/interrupt.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070035#include <asm/io.h>
36#include <acpi/acpi_bus.h>
37#include <acpi/acpi_drivers.h>
38#include <acpi/actypes.h>
39
40#define _COMPONENT ACPI_EC_COMPONENT
Len Brown50526df2005-08-11 17:32:05 -040041ACPI_MODULE_NAME("acpi_ec")
Linus Torvalds1da177e2005-04-16 15:20:36 -070042#define ACPI_EC_COMPONENT 0x00100000
43#define ACPI_EC_CLASS "embedded_controller"
44#define ACPI_EC_HID "PNP0C09"
45#define ACPI_EC_DRIVER_NAME "ACPI Embedded Controller Driver"
46#define ACPI_EC_DEVICE_NAME "Embedded Controller"
47#define ACPI_EC_FILE_INFO "info"
Denis M. Sadykov703959d2006-09-26 19:50:33 +040048
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +030049#undef PREFIX
50#define PREFIX "ACPI: EC: "
51
Denis M. Sadykov703959d2006-09-26 19:50:33 +040052/* EC status register */
Linus Torvalds1da177e2005-04-16 15:20:36 -070053#define ACPI_EC_FLAG_OBF 0x01 /* Output buffer full */
54#define ACPI_EC_FLAG_IBF 0x02 /* Input buffer full */
Dmitry Torokhov451566f2005-03-19 01:10:05 -050055#define ACPI_EC_FLAG_BURST 0x10 /* burst mode */
Linus Torvalds1da177e2005-04-16 15:20:36 -070056#define ACPI_EC_FLAG_SCI 0x20 /* EC-SCI occurred */
Denis M. Sadykov703959d2006-09-26 19:50:33 +040057
58/* EC commands */
Linus Torvalds1da177e2005-04-16 15:20:36 -070059#define ACPI_EC_COMMAND_READ 0x80
60#define ACPI_EC_COMMAND_WRITE 0x81
Dmitry Torokhov451566f2005-03-19 01:10:05 -050061#define ACPI_EC_BURST_ENABLE 0x82
62#define ACPI_EC_BURST_DISABLE 0x83
Linus Torvalds1da177e2005-04-16 15:20:36 -070063#define ACPI_EC_COMMAND_QUERY 0x84
Denis M. Sadykov703959d2006-09-26 19:50:33 +040064
65/* EC events */
66enum {
67 ACPI_EC_EVENT_OBF_1 = 1, /* Output buffer full */
68 ACPI_EC_EVENT_IBF_0, /* Input buffer empty */
69};
70
Alexey Starikovskiy5c406412006-12-07 18:42:16 +030071#define ACPI_EC_DELAY 500 /* Wait 500ms max. during EC ops */
Denis M. Sadykov703959d2006-09-26 19:50:33 +040072#define ACPI_EC_UDELAY_GLK 1000 /* Wait 1ms max. to get global lock */
73#define ACPI_EC_UDELAY 100 /* Poll @ 100us increments */
Alexey Starikovskiy5c406412006-12-07 18:42:16 +030074#define ACPI_EC_UDELAY_COUNT 1000 /* Wait 100ms max. during EC ops */
Denis M. Sadykov703959d2006-09-26 19:50:33 +040075
76enum {
77 EC_INTR = 1, /* Output buffer full */
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +040078 EC_POLL, /* Input buffer empty */
Denis M. Sadykov703959d2006-09-26 19:50:33 +040079};
80
Len Brown50526df2005-08-11 17:32:05 -040081static int acpi_ec_remove(struct acpi_device *device, int type);
82static int acpi_ec_start(struct acpi_device *device);
83static int acpi_ec_stop(struct acpi_device *device, int type);
Denis M. Sadykov703959d2006-09-26 19:50:33 +040084static int acpi_ec_add(struct acpi_device *device);
Linus Torvalds1da177e2005-04-16 15:20:36 -070085
86static struct acpi_driver acpi_ec_driver = {
Len Brown50526df2005-08-11 17:32:05 -040087 .name = ACPI_EC_DRIVER_NAME,
88 .class = ACPI_EC_CLASS,
89 .ids = ACPI_EC_HID,
90 .ops = {
Denis M. Sadykov703959d2006-09-26 19:50:33 +040091 .add = acpi_ec_add,
Len Brown50526df2005-08-11 17:32:05 -040092 .remove = acpi_ec_remove,
93 .start = acpi_ec_start,
94 .stop = acpi_ec_stop,
95 },
Linus Torvalds1da177e2005-04-16 15:20:36 -070096};
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +040097
98/* If we find an EC via the ECDT, we need to keep a ptr to its context */
Denis M. Sadykov703959d2006-09-26 19:50:33 +040099struct acpi_ec {
100 acpi_handle handle;
101 unsigned long uid;
102 unsigned long gpe_bit;
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400103 unsigned long command_addr;
104 unsigned long data_addr;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400105 unsigned long global_lock;
106 struct semaphore sem;
Alexey Starikovskiy5d0c2882006-12-07 18:42:16 +0300107 atomic_t query_pending;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400108 atomic_t leaving_burst; /* 0 : No, 1 : Yes, 2: abort */
109 wait_queue_head_t wait;
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400110} *ec_ecdt;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400111
112/* External interfaces use first EC only, so remember */
113static struct acpi_device *first_ec;
114static int acpi_ec_mode = EC_INTR;
115
Linus Torvalds1da177e2005-04-16 15:20:36 -0700116/* --------------------------------------------------------------------------
117 Transaction Management
118 -------------------------------------------------------------------------- */
119
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400120static inline u8 acpi_ec_read_status(struct acpi_ec *ec)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700121{
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400122 return inb(ec->command_addr);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700123}
124
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400125static inline u8 acpi_ec_read_data(struct acpi_ec *ec)
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400126{
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400127 return inb(ec->data_addr);
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400128}
129
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400130static inline void acpi_ec_write_cmd(struct acpi_ec *ec, u8 command)
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400131{
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400132 outb(command, ec->command_addr);
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400133}
134
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400135static inline void acpi_ec_write_data(struct acpi_ec *ec, u8 data)
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400136{
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400137 outb(data, ec->data_addr);
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400138}
139
Alexey Starikovskiybec5a1e2006-12-07 18:42:16 +0300140static int acpi_ec_check_status(struct acpi_ec *ec, u8 event)
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400141{
Alexey Starikovskiybec5a1e2006-12-07 18:42:16 +0300142 u8 status = acpi_ec_read_status(ec);
Denis M. Sadykov7c6db5e2006-09-26 19:50:33 +0400143 switch (event) {
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400144 case ACPI_EC_EVENT_OBF_1:
Denis M. Sadykov7c6db5e2006-09-26 19:50:33 +0400145 if (status & ACPI_EC_FLAG_OBF)
146 return 1;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400147 break;
148 case ACPI_EC_EVENT_IBF_0:
Denis M. Sadykov7c6db5e2006-09-26 19:50:33 +0400149 if (!(status & ACPI_EC_FLAG_IBF))
150 return 1;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400151 break;
Denis M. Sadykov7c6db5e2006-09-26 19:50:33 +0400152 default:
153 break;
154 }
155
156 return 0;
157}
158
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400159static int acpi_ec_wait(struct acpi_ec *ec, u8 event)
Luming Yu45bea152005-07-23 04:08:00 -0400160{
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300161 if (acpi_ec_mode == EC_POLL) {
162 int i;
163 for (i = 0; i < ACPI_EC_UDELAY_COUNT; ++i) {
164 if (acpi_ec_check_status(ec, event))
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400165 return 0;
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300166 udelay(ACPI_EC_UDELAY);
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400167 }
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300168 } else {
169 if (wait_event_timeout(ec->wait,
170 acpi_ec_check_status(ec, event),
171 msecs_to_jiffies(ACPI_EC_DELAY)) ||
172 acpi_ec_check_status(ec, event)) {
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400173 return 0;
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300174 } else {
175 printk(KERN_ERR PREFIX "acpi_ec_wait timeout,"
176 " status = %d, expect_event = %d\n",
177 acpi_ec_read_status(ec), event);
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400178 }
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300179 }
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400180
Patrick Mocheld550d982006-06-27 00:41:40 -0400181 return -ETIME;
Dmitry Torokhov451566f2005-03-19 01:10:05 -0500182}
183
Len Brown02b28a32005-12-05 16:33:04 -0500184#ifdef ACPI_FUTURE_USAGE
Luming Yu06a2a382005-09-27 00:43:00 -0400185/*
186 * Note: samsung nv5000 doesn't work with ec burst mode.
187 * http://bugzilla.kernel.org/show_bug.cgi?id=4980
188 */
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400189int acpi_ec_enter_burst_mode(struct acpi_ec *ec)
Dmitry Torokhov451566f2005-03-19 01:10:05 -0500190{
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400191 u8 tmp = 0;
192 u8 status = 0;
Dmitry Torokhov451566f2005-03-19 01:10:05 -0500193
Dmitry Torokhov451566f2005-03-19 01:10:05 -0500194
195 status = acpi_ec_read_status(ec);
Len Brown50526df2005-08-11 17:32:05 -0400196 if (status != -EINVAL && !(status & ACPI_EC_FLAG_BURST)) {
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400197 status = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0);
Len Brown50526df2005-08-11 17:32:05 -0400198 if (status)
Luming Yu716e0842005-08-10 01:40:00 -0400199 goto end;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400200 acpi_ec_write_cmd(ec, ACPI_EC_BURST_ENABLE);
201 status = acpi_ec_wait(ec, ACPI_EC_EVENT_OBF_1);
202 tmp = acpi_ec_read_data(ec);
Len Brown50526df2005-08-11 17:32:05 -0400203 if (tmp != 0x90) { /* Burst ACK byte */
Patrick Mocheld550d982006-06-27 00:41:40 -0400204 return -EINVAL;
Dmitry Torokhov451566f2005-03-19 01:10:05 -0500205 }
Luming Yu668d74c2005-07-23 00:26:33 -0400206 }
207
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400208 atomic_set(&ec->leaving_burst, 0);
Patrick Mocheld550d982006-06-27 00:41:40 -0400209 return 0;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400210 end:
211 ACPI_EXCEPTION((AE_INFO, status, "EC wait, burst mode"));
Patrick Mocheld550d982006-06-27 00:41:40 -0400212 return -1;
Dmitry Torokhov451566f2005-03-19 01:10:05 -0500213}
214
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400215int acpi_ec_leave_burst_mode(struct acpi_ec *ec)
Dmitry Torokhov451566f2005-03-19 01:10:05 -0500216{
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400217 u8 status = 0;
Dmitry Torokhov451566f2005-03-19 01:10:05 -0500218
Dmitry Torokhov451566f2005-03-19 01:10:05 -0500219
Luming Yu06a2a382005-09-27 00:43:00 -0400220 status = acpi_ec_read_status(ec);
221 if (status != -EINVAL && (status & ACPI_EC_FLAG_BURST)){
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400222 status = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0);
Luming Yu06a2a382005-09-27 00:43:00 -0400223 if(status)
224 goto end;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400225 acpi_ec_write_cmd(ec, ACPI_EC_BURST_DISABLE);
226 acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0);
Denis M. Sadykov7c6db5e2006-09-26 19:50:33 +0400227 }
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400228 atomic_set(&ec->leaving_burst, 1);
Patrick Mocheld550d982006-06-27 00:41:40 -0400229 return 0;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400230 end:
231 ACPI_EXCEPTION((AE_INFO, status, "EC leave burst mode"));
Patrick Mocheld550d982006-06-27 00:41:40 -0400232 return -1;
Dmitry Torokhov451566f2005-03-19 01:10:05 -0500233}
Len Brown02b28a32005-12-05 16:33:04 -0500234#endif /* ACPI_FUTURE_USAGE */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700235
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400236static int acpi_ec_transaction_unlocked(struct acpi_ec *ec, u8 command,
Denis M. Sadykov3576cf62006-09-26 19:50:33 +0400237 const u8 *wdata, unsigned wdata_len,
238 u8 *rdata, unsigned rdata_len)
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400239{
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300240 int result = 0;
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400241
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400242 acpi_ec_write_cmd(ec, command);
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400243
Denis M. Sadykov7c6db5e2006-09-26 19:50:33 +0400244 for (; wdata_len > 0; wdata_len --) {
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400245 result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0);
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300246 if (result) {
247 printk(KERN_ERR PREFIX "write_cmd timeout, command = %d\n",
248 command);
249 goto end;
250 }
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400251 acpi_ec_write_data(ec, *(wdata++));
Denis M. Sadykov3576cf62006-09-26 19:50:33 +0400252 }
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400253
Alexey Starikovskiyd91df1a2006-12-07 18:42:16 +0300254 if (!rdata_len) {
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400255 result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0);
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300256 if (result) {
257 printk(KERN_ERR PREFIX "finish-write timeout, command = %d\n",
258 command);
259 goto end;
260 }
Alexey Starikovskiy5d0c2882006-12-07 18:42:16 +0300261 } else if (command == ACPI_EC_COMMAND_QUERY) {
262 atomic_set(&ec->query_pending, 0);
Denis M. Sadykov7c6db5e2006-09-26 19:50:33 +0400263 }
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400264
Denis M. Sadykov7c6db5e2006-09-26 19:50:33 +0400265 for (; rdata_len > 0; rdata_len --) {
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400266 result = acpi_ec_wait(ec, ACPI_EC_EVENT_OBF_1);
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300267 if (result) {
268 printk(KERN_ERR PREFIX "read timeout, command = %d\n",
269 command);
270 goto end;
271 }
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400272
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400273 *(rdata++) = acpi_ec_read_data(ec);
Denis M. Sadykov7c6db5e2006-09-26 19:50:33 +0400274 }
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300275 end:
276 return result;
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400277}
278
Denis M. Sadykov3576cf62006-09-26 19:50:33 +0400279static int acpi_ec_transaction(struct acpi_ec *ec, u8 command,
280 const u8 *wdata, unsigned wdata_len,
281 u8 *rdata, unsigned rdata_len)
Luming Yu45bea152005-07-23 04:08:00 -0400282{
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400283 int status;
Len Brown50526df2005-08-11 17:32:05 -0400284 u32 glk;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700285
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400286 if (!ec || (wdata_len && !wdata) || (rdata_len && !rdata))
Patrick Mocheld550d982006-06-27 00:41:40 -0400287 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700288
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400289 if (rdata)
290 memset(rdata, 0, rdata_len);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700291
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400292 if (ec->global_lock) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700293 status = acpi_acquire_global_lock(ACPI_EC_UDELAY_GLK, &glk);
294 if (ACPI_FAILURE(status))
Patrick Mocheld550d982006-06-27 00:41:40 -0400295 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700296 }
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400297 down(&ec->sem);
Dmitry Torokhov451566f2005-03-19 01:10:05 -0500298
Alexey Starikovskiy5d57a6a2006-12-07 18:42:16 +0300299 /* Make sure GPE is enabled before doing transaction */
300 acpi_enable_gpe(NULL, ec->gpe_bit, ACPI_NOT_ISR);
301
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400302 status = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0);
Luming Yu716e0842005-08-10 01:40:00 -0400303 if (status) {
Denis M. Sadykov3576cf62006-09-26 19:50:33 +0400304 printk(KERN_DEBUG PREFIX "read EC, IB not empty\n");
Dmitry Torokhov451566f2005-03-19 01:10:05 -0500305 goto end;
Luming Yu716e0842005-08-10 01:40:00 -0400306 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700307
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400308 status = acpi_ec_transaction_unlocked(ec, command,
309 wdata, wdata_len,
310 rdata, rdata_len);
Len Brown50526df2005-08-11 17:32:05 -0400311
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400312end:
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400313 up(&ec->sem);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700314
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400315 if (ec->global_lock)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700316 acpi_release_global_lock(glk);
317
Patrick Mocheld550d982006-06-27 00:41:40 -0400318 return status;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700319}
320
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400321static int acpi_ec_read(struct acpi_ec *ec, u8 address, u8 *data)
Denis M. Sadykov3576cf62006-09-26 19:50:33 +0400322{
323 int result;
324 u8 d;
325
326 result = acpi_ec_transaction(ec, ACPI_EC_COMMAND_READ,
327 &address, 1, &d, 1);
328 *data = d;
329 return result;
330}
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400331
Denis M. Sadykov3576cf62006-09-26 19:50:33 +0400332static int acpi_ec_write(struct acpi_ec *ec, u8 address, u8 data)
333{
334 u8 wdata[2] = { address, data };
335 return acpi_ec_transaction(ec, ACPI_EC_COMMAND_WRITE,
336 wdata, 2, NULL, 0);
337}
338
Linus Torvalds1da177e2005-04-16 15:20:36 -0700339/*
340 * Externally callable EC access functions. For now, assume 1 EC only
341 */
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400342int ec_read(u8 addr, u8 *val)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700343{
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400344 struct acpi_ec *ec;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700345 int err;
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400346 u8 temp_data;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700347
348 if (!first_ec)
349 return -ENODEV;
350
351 ec = acpi_driver_data(first_ec);
352
353 err = acpi_ec_read(ec, addr, &temp_data);
354
355 if (!err) {
356 *val = temp_data;
357 return 0;
Len Brown50526df2005-08-11 17:32:05 -0400358 } else
Linus Torvalds1da177e2005-04-16 15:20:36 -0700359 return err;
360}
Len Brown50526df2005-08-11 17:32:05 -0400361
Linus Torvalds1da177e2005-04-16 15:20:36 -0700362EXPORT_SYMBOL(ec_read);
363
Len Brown50526df2005-08-11 17:32:05 -0400364int ec_write(u8 addr, u8 val)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700365{
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400366 struct acpi_ec *ec;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700367 int err;
368
369 if (!first_ec)
370 return -ENODEV;
371
372 ec = acpi_driver_data(first_ec);
373
374 err = acpi_ec_write(ec, addr, val);
375
376 return err;
377}
Len Brown50526df2005-08-11 17:32:05 -0400378
Linus Torvalds1da177e2005-04-16 15:20:36 -0700379EXPORT_SYMBOL(ec_write);
380
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400381extern int ec_transaction(u8 command,
382 const u8 *wdata, unsigned wdata_len,
383 u8 *rdata, unsigned rdata_len)
Luming Yu45bea152005-07-23 04:08:00 -0400384{
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400385 struct acpi_ec *ec;
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400386
387 if (!first_ec)
388 return -ENODEV;
389
390 ec = acpi_driver_data(first_ec);
391
Denis M. Sadykov3576cf62006-09-26 19:50:33 +0400392 return acpi_ec_transaction(ec, command, wdata,
393 wdata_len, rdata, rdata_len);
Luming Yu45bea152005-07-23 04:08:00 -0400394}
Luming Yu45bea152005-07-23 04:08:00 -0400395
Lennart Poetteringab9e43c2006-10-03 22:49:00 -0400396EXPORT_SYMBOL(ec_transaction);
397
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400398static int acpi_ec_query(struct acpi_ec *ec, u8 *data)
Denis M. Sadykov3576cf62006-09-26 19:50:33 +0400399{
400 int result;
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400401 u8 d;
Luming Yu45bea152005-07-23 04:08:00 -0400402
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400403 if (!ec || !data)
404 return -EINVAL;
Luming Yu45bea152005-07-23 04:08:00 -0400405
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400406 /*
407 * Query the EC to find out which _Qxx method we need to evaluate.
408 * Note that successful completion of the query causes the ACPI_EC_SCI
409 * bit to be cleared (and thus clearing the interrupt source).
410 */
Luming Yu45bea152005-07-23 04:08:00 -0400411
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400412 result = acpi_ec_transaction(ec, ACPI_EC_COMMAND_QUERY, NULL, 0, &d, 1);
413 if (result)
414 return result;
Luming Yu45bea152005-07-23 04:08:00 -0400415
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400416 if (!d)
417 return -ENODATA;
Luming Yu45bea152005-07-23 04:08:00 -0400418
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400419 *data = d;
420 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700421}
422
Linus Torvalds1da177e2005-04-16 15:20:36 -0700423/* --------------------------------------------------------------------------
424 Event Management
425 -------------------------------------------------------------------------- */
426
Len Brown50526df2005-08-11 17:32:05 -0400427static void acpi_ec_gpe_query(void *ec_cxt)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700428{
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400429 struct acpi_ec *ec = (struct acpi_ec *)ec_cxt;
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400430 u8 value = 0;
Alexey Starikovskiy5d0c2882006-12-07 18:42:16 +0300431 char object_name[8];
Luming Yu45bea152005-07-23 04:08:00 -0400432
Alexey Starikovskiy5d0c2882006-12-07 18:42:16 +0300433 if (!ec || acpi_ec_query(ec, &value))
Alexey Starikovskiye41334c2006-12-07 18:42:16 +0300434 return;
Luming Yu45bea152005-07-23 04:08:00 -0400435
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400436 snprintf(object_name, 8, "_Q%2.2X", value);
Luming Yu45bea152005-07-23 04:08:00 -0400437
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300438 printk(KERN_INFO PREFIX "evaluating %s\n", object_name);
Luming Yu45bea152005-07-23 04:08:00 -0400439
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400440 acpi_evaluate_object(ec->handle, object_name, NULL, NULL);
Luming Yu45bea152005-07-23 04:08:00 -0400441}
Linus Torvalds1da177e2005-04-16 15:20:36 -0700442
Len Brown50526df2005-08-11 17:32:05 -0400443static u32 acpi_ec_gpe_handler(void *data)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700444{
Len Brown50526df2005-08-11 17:32:05 -0400445 acpi_status status = AE_OK;
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400446 u8 value;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400447 struct acpi_ec *ec = (struct acpi_ec *)data;
Luming Yu45bea152005-07-23 04:08:00 -0400448
Linus Torvalds1da177e2005-04-16 15:20:36 -0700449
Denis M. Sadykov8e0341b2006-09-26 19:50:33 +0400450 if (acpi_ec_mode == EC_INTR) {
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300451 wake_up(&ec->wait);
Dmitry Torokhov451566f2005-03-19 01:10:05 -0500452 }
453
Alexey Starikovskiybec5a1e2006-12-07 18:42:16 +0300454 value = acpi_ec_read_status(ec);
Alexey Starikovskiy5d0c2882006-12-07 18:42:16 +0300455 if ((value & ACPI_EC_FLAG_SCI) && !atomic_read(&ec->query_pending)) {
456 atomic_set(&ec->query_pending, 1);
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400457 status = acpi_os_execute(OSL_EC_BURST_HANDLER, acpi_ec_gpe_query, ec);
Len Brown50526df2005-08-11 17:32:05 -0400458 }
Alexey Starikovskiye41334c2006-12-07 18:42:16 +0300459
Dmitry Torokhov451566f2005-03-19 01:10:05 -0500460 return status == AE_OK ?
Len Brown50526df2005-08-11 17:32:05 -0400461 ACPI_INTERRUPT_HANDLED : ACPI_INTERRUPT_NOT_HANDLED;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700462}
463
464/* --------------------------------------------------------------------------
465 Address Space Management
466 -------------------------------------------------------------------------- */
467
468static acpi_status
Len Brown50526df2005-08-11 17:32:05 -0400469acpi_ec_space_setup(acpi_handle region_handle,
470 u32 function, void *handler_context, void **return_context)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700471{
472 /*
473 * The EC object is in the handler context and is needed
474 * when calling the acpi_ec_space_handler.
475 */
Len Brown50526df2005-08-11 17:32:05 -0400476 *return_context = (function != ACPI_REGION_DEACTIVATE) ?
477 handler_context : NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700478
479 return AE_OK;
480}
481
Linus Torvalds1da177e2005-04-16 15:20:36 -0700482static acpi_status
Len Brown50526df2005-08-11 17:32:05 -0400483acpi_ec_space_handler(u32 function,
484 acpi_physical_address address,
485 u32 bit_width,
486 acpi_integer * value,
487 void *handler_context, void *region_context)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700488{
Len Brown50526df2005-08-11 17:32:05 -0400489 int result = 0;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400490 struct acpi_ec *ec = NULL;
Len Brown50526df2005-08-11 17:32:05 -0400491 u64 temp = *value;
492 acpi_integer f_v = 0;
493 int i = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700494
Linus Torvalds1da177e2005-04-16 15:20:36 -0700495
496 if ((address > 0xFF) || !value || !handler_context)
Patrick Mocheld550d982006-06-27 00:41:40 -0400497 return AE_BAD_PARAMETER;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700498
Luming Yufa9cd542005-03-19 01:54:47 -0500499 if (bit_width != 8 && acpi_strict) {
Patrick Mocheld550d982006-06-27 00:41:40 -0400500 return AE_BAD_PARAMETER;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700501 }
502
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400503 ec = (struct acpi_ec *)handler_context;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700504
Len Brown50526df2005-08-11 17:32:05 -0400505 next_byte:
Linus Torvalds1da177e2005-04-16 15:20:36 -0700506 switch (function) {
507 case ACPI_READ:
Luming Yufa9cd542005-03-19 01:54:47 -0500508 temp = 0;
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400509 result = acpi_ec_read(ec, (u8) address, (u8 *) &temp);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700510 break;
511 case ACPI_WRITE:
Luming Yufa9cd542005-03-19 01:54:47 -0500512 result = acpi_ec_write(ec, (u8) address, (u8) temp);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700513 break;
514 default:
515 result = -EINVAL;
516 goto out;
517 break;
518 }
519
520 bit_width -= 8;
Luming Yufa9cd542005-03-19 01:54:47 -0500521 if (bit_width) {
522 if (function == ACPI_READ)
523 f_v |= temp << 8 * i;
524 if (function == ACPI_WRITE)
525 temp >>= 8;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700526 i++;
Andrew Morton83ea7442005-03-30 22:12:13 -0500527 address++;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700528 goto next_byte;
529 }
530
Luming Yufa9cd542005-03-19 01:54:47 -0500531 if (function == ACPI_READ) {
532 f_v |= temp << 8 * i;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700533 *value = f_v;
534 }
535
Len Brown50526df2005-08-11 17:32:05 -0400536 out:
Linus Torvalds1da177e2005-04-16 15:20:36 -0700537 switch (result) {
538 case -EINVAL:
Patrick Mocheld550d982006-06-27 00:41:40 -0400539 return AE_BAD_PARAMETER;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700540 break;
541 case -ENODEV:
Patrick Mocheld550d982006-06-27 00:41:40 -0400542 return AE_NOT_FOUND;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700543 break;
544 case -ETIME:
Patrick Mocheld550d982006-06-27 00:41:40 -0400545 return AE_TIME;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700546 break;
547 default:
Patrick Mocheld550d982006-06-27 00:41:40 -0400548 return AE_OK;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700549 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700550}
551
Linus Torvalds1da177e2005-04-16 15:20:36 -0700552/* --------------------------------------------------------------------------
553 FS Interface (/proc)
554 -------------------------------------------------------------------------- */
555
Len Brown50526df2005-08-11 17:32:05 -0400556static struct proc_dir_entry *acpi_ec_dir;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700557
Len Brown50526df2005-08-11 17:32:05 -0400558static int acpi_ec_read_info(struct seq_file *seq, void *offset)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700559{
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400560 struct acpi_ec *ec = (struct acpi_ec *)seq->private;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700561
Linus Torvalds1da177e2005-04-16 15:20:36 -0700562
563 if (!ec)
564 goto end;
565
566 seq_printf(seq, "gpe bit: 0x%02x\n",
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400567 (u32) ec->gpe_bit);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700568 seq_printf(seq, "ports: 0x%02x, 0x%02x\n",
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400569 (u32) ec->command_addr,
570 (u32) ec->data_addr);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700571 seq_printf(seq, "use global lock: %s\n",
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400572 ec->global_lock ? "yes" : "no");
573 acpi_enable_gpe(NULL, ec->gpe_bit, ACPI_NOT_ISR);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700574
Len Brown50526df2005-08-11 17:32:05 -0400575 end:
Patrick Mocheld550d982006-06-27 00:41:40 -0400576 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700577}
578
579static int acpi_ec_info_open_fs(struct inode *inode, struct file *file)
580{
581 return single_open(file, acpi_ec_read_info, PDE(inode)->data);
582}
583
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400584static struct file_operations acpi_ec_info_ops = {
Len Brown50526df2005-08-11 17:32:05 -0400585 .open = acpi_ec_info_open_fs,
586 .read = seq_read,
587 .llseek = seq_lseek,
588 .release = single_release,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700589 .owner = THIS_MODULE,
590};
591
Len Brown50526df2005-08-11 17:32:05 -0400592static int acpi_ec_add_fs(struct acpi_device *device)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700593{
Len Brown50526df2005-08-11 17:32:05 -0400594 struct proc_dir_entry *entry = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700595
Linus Torvalds1da177e2005-04-16 15:20:36 -0700596
597 if (!acpi_device_dir(device)) {
598 acpi_device_dir(device) = proc_mkdir(acpi_device_bid(device),
Len Brown50526df2005-08-11 17:32:05 -0400599 acpi_ec_dir);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700600 if (!acpi_device_dir(device))
Patrick Mocheld550d982006-06-27 00:41:40 -0400601 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700602 }
603
604 entry = create_proc_entry(ACPI_EC_FILE_INFO, S_IRUGO,
Len Brown50526df2005-08-11 17:32:05 -0400605 acpi_device_dir(device));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700606 if (!entry)
Patrick Mocheld550d982006-06-27 00:41:40 -0400607 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700608 else {
609 entry->proc_fops = &acpi_ec_info_ops;
610 entry->data = acpi_driver_data(device);
611 entry->owner = THIS_MODULE;
612 }
613
Patrick Mocheld550d982006-06-27 00:41:40 -0400614 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700615}
616
Len Brown50526df2005-08-11 17:32:05 -0400617static int acpi_ec_remove_fs(struct acpi_device *device)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700618{
Linus Torvalds1da177e2005-04-16 15:20:36 -0700619
620 if (acpi_device_dir(device)) {
621 remove_proc_entry(ACPI_EC_FILE_INFO, acpi_device_dir(device));
622 remove_proc_entry(acpi_device_bid(device), acpi_ec_dir);
623 acpi_device_dir(device) = NULL;
624 }
625
Patrick Mocheld550d982006-06-27 00:41:40 -0400626 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700627}
628
Linus Torvalds1da177e2005-04-16 15:20:36 -0700629/* --------------------------------------------------------------------------
630 Driver Interface
631 -------------------------------------------------------------------------- */
632
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400633static int acpi_ec_add(struct acpi_device *device)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700634{
Len Brown50526df2005-08-11 17:32:05 -0400635 int result = 0;
636 acpi_status status = AE_OK;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400637 struct acpi_ec *ec = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700638
Linus Torvalds1da177e2005-04-16 15:20:36 -0700639
640 if (!device)
Patrick Mocheld550d982006-06-27 00:41:40 -0400641 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700642
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400643 ec = kmalloc(sizeof(struct acpi_ec), GFP_KERNEL);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700644 if (!ec)
Patrick Mocheld550d982006-06-27 00:41:40 -0400645 return -ENOMEM;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400646 memset(ec, 0, sizeof(struct acpi_ec));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700647
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400648 ec->handle = device->handle;
649 ec->uid = -1;
650 init_MUTEX(&ec->sem);
Alexey Starikovskiy5d0c2882006-12-07 18:42:16 +0300651 atomic_set(&ec->query_pending, 0);
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400652 if (acpi_ec_mode == EC_INTR) {
653 atomic_set(&ec->leaving_burst, 1);
654 init_waitqueue_head(&ec->wait);
655 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700656 strcpy(acpi_device_name(device), ACPI_EC_DEVICE_NAME);
657 strcpy(acpi_device_class(device), ACPI_EC_CLASS);
658 acpi_driver_data(device) = ec;
659
660 /* Use the global lock for all EC transactions? */
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400661 acpi_evaluate_integer(ec->handle, "_GLK", NULL,
662 &ec->global_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700663
Jiri Slabyff2fc3e2006-03-28 17:04:00 -0500664 /* XXX we don't test uids, because on some boxes ecdt uid = 0, see:
665 http://bugzilla.kernel.org/show_bug.cgi?id=6111 */
666 if (ec_ecdt) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700667 acpi_remove_address_space_handler(ACPI_ROOT_OBJECT,
Len Brown50526df2005-08-11 17:32:05 -0400668 ACPI_ADR_SPACE_EC,
669 &acpi_ec_space_handler);
670
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400671 acpi_remove_gpe_handler(NULL, ec_ecdt->gpe_bit,
Len Brown50526df2005-08-11 17:32:05 -0400672 &acpi_ec_gpe_handler);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700673
674 kfree(ec_ecdt);
675 }
676
677 /* Get GPE bit assignment (EC events). */
678 /* TODO: Add support for _GPE returning a package */
Len Brown50526df2005-08-11 17:32:05 -0400679 status =
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400680 acpi_evaluate_integer(ec->handle, "_GPE", NULL,
681 &ec->gpe_bit);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700682 if (ACPI_FAILURE(status)) {
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400683 ACPI_EXCEPTION((AE_INFO, status, "Obtaining GPE bit assignment"));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700684 result = -ENODEV;
685 goto end;
686 }
687
688 result = acpi_ec_add_fs(device);
689 if (result)
690 goto end;
691
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400692 ACPI_DEBUG_PRINT((ACPI_DB_INFO, "%s [%s] (gpe %d) interrupt mode.",
Len Brown50526df2005-08-11 17:32:05 -0400693 acpi_device_name(device), acpi_device_bid(device),
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400694 (u32) ec->gpe_bit));
Luming Yu45bea152005-07-23 04:08:00 -0400695
696 if (!first_ec)
697 first_ec = device;
698
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400699 end:
Linus Torvalds1da177e2005-04-16 15:20:36 -0700700 if (result)
701 kfree(ec);
702
Patrick Mocheld550d982006-06-27 00:41:40 -0400703 return result;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700704}
705
Len Brown50526df2005-08-11 17:32:05 -0400706static int acpi_ec_remove(struct acpi_device *device, int type)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700707{
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400708 struct acpi_ec *ec = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700709
Linus Torvalds1da177e2005-04-16 15:20:36 -0700710
711 if (!device)
Patrick Mocheld550d982006-06-27 00:41:40 -0400712 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700713
714 ec = acpi_driver_data(device);
715
716 acpi_ec_remove_fs(device);
717
718 kfree(ec);
719
Patrick Mocheld550d982006-06-27 00:41:40 -0400720 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700721}
722
Linus Torvalds1da177e2005-04-16 15:20:36 -0700723static acpi_status
Len Brown50526df2005-08-11 17:32:05 -0400724acpi_ec_io_ports(struct acpi_resource *resource, void *context)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700725{
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400726 struct acpi_ec *ec = (struct acpi_ec *)context;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700727
Bob Moore50eca3e2005-09-30 19:03:00 -0400728 if (resource->type != ACPI_RESOURCE_TYPE_IO) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700729 return AE_OK;
730 }
731
732 /*
733 * The first address region returned is the data port, and
734 * the second address region returned is the status/command
735 * port.
736 */
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400737 if (ec->data_addr == 0) {
738 ec->data_addr = resource->data.io.minimum;
739 } else if (ec->command_addr == 0) {
740 ec->command_addr = resource->data.io.minimum;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700741 } else {
742 return AE_CTRL_TERMINATE;
743 }
744
Linus Torvalds1da177e2005-04-16 15:20:36 -0700745 return AE_OK;
746}
747
Len Brown50526df2005-08-11 17:32:05 -0400748static int acpi_ec_start(struct acpi_device *device)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700749{
Len Brown50526df2005-08-11 17:32:05 -0400750 acpi_status status = AE_OK;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400751 struct acpi_ec *ec = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700752
Linus Torvalds1da177e2005-04-16 15:20:36 -0700753
754 if (!device)
Patrick Mocheld550d982006-06-27 00:41:40 -0400755 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700756
757 ec = acpi_driver_data(device);
758
759 if (!ec)
Patrick Mocheld550d982006-06-27 00:41:40 -0400760 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700761
762 /*
763 * Get I/O port addresses. Convert to GAS format.
764 */
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400765 status = acpi_walk_resources(ec->handle, METHOD_NAME__CRS,
Len Brown50526df2005-08-11 17:32:05 -0400766 acpi_ec_io_ports, ec);
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400767 if (ACPI_FAILURE(status) || ec->command_addr == 0) {
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400768 ACPI_EXCEPTION((AE_INFO, status,
769 "Error getting I/O port addresses"));
Patrick Mocheld550d982006-06-27 00:41:40 -0400770 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700771 }
772
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400773 ACPI_DEBUG_PRINT((ACPI_DB_INFO, "gpe=0x%02lx, ports=0x%2lx,0x%2lx",
774 ec->gpe_bit, ec->command_addr, ec->data_addr));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700775
776 /*
777 * Install GPE handler
778 */
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400779 status = acpi_install_gpe_handler(NULL, ec->gpe_bit,
Len Brown50526df2005-08-11 17:32:05 -0400780 ACPI_GPE_EDGE_TRIGGERED,
781 &acpi_ec_gpe_handler, ec);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700782 if (ACPI_FAILURE(status)) {
Patrick Mocheld550d982006-06-27 00:41:40 -0400783 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700784 }
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400785 acpi_set_gpe_type(NULL, ec->gpe_bit, ACPI_GPE_TYPE_RUNTIME);
786 acpi_enable_gpe(NULL, ec->gpe_bit, ACPI_NOT_ISR);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700787
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400788 status = acpi_install_address_space_handler(ec->handle,
Len Brown50526df2005-08-11 17:32:05 -0400789 ACPI_ADR_SPACE_EC,
790 &acpi_ec_space_handler,
791 &acpi_ec_space_setup, ec);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700792 if (ACPI_FAILURE(status)) {
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400793 acpi_remove_gpe_handler(NULL, ec->gpe_bit,
Len Brown50526df2005-08-11 17:32:05 -0400794 &acpi_ec_gpe_handler);
Patrick Mocheld550d982006-06-27 00:41:40 -0400795 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700796 }
797
Patrick Mocheld550d982006-06-27 00:41:40 -0400798 return AE_OK;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700799}
800
Len Brown50526df2005-08-11 17:32:05 -0400801static int acpi_ec_stop(struct acpi_device *device, int type)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700802{
Len Brown50526df2005-08-11 17:32:05 -0400803 acpi_status status = AE_OK;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400804 struct acpi_ec *ec = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700805
Linus Torvalds1da177e2005-04-16 15:20:36 -0700806
807 if (!device)
Patrick Mocheld550d982006-06-27 00:41:40 -0400808 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700809
810 ec = acpi_driver_data(device);
811
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400812 status = acpi_remove_address_space_handler(ec->handle,
Len Brown50526df2005-08-11 17:32:05 -0400813 ACPI_ADR_SPACE_EC,
814 &acpi_ec_space_handler);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700815 if (ACPI_FAILURE(status))
Patrick Mocheld550d982006-06-27 00:41:40 -0400816 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700817
Len Brown50526df2005-08-11 17:32:05 -0400818 status =
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400819 acpi_remove_gpe_handler(NULL, ec->gpe_bit,
Len Brown50526df2005-08-11 17:32:05 -0400820 &acpi_ec_gpe_handler);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700821 if (ACPI_FAILURE(status))
Patrick Mocheld550d982006-06-27 00:41:40 -0400822 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700823
Patrick Mocheld550d982006-06-27 00:41:40 -0400824 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700825}
826
827static acpi_status __init
Len Brown50526df2005-08-11 17:32:05 -0400828acpi_fake_ecdt_callback(acpi_handle handle,
829 u32 Level, void *context, void **retval)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700830{
Len Brown50526df2005-08-11 17:32:05 -0400831 acpi_status status;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700832
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400833 init_MUTEX(&ec_ecdt->sem);
834 if (acpi_ec_mode == EC_INTR) {
835 init_waitqueue_head(&ec_ecdt->wait);
836 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700837 status = acpi_walk_resources(handle, METHOD_NAME__CRS,
Len Brown50526df2005-08-11 17:32:05 -0400838 acpi_ec_io_ports, ec_ecdt);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700839 if (ACPI_FAILURE(status))
840 return status;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700841
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400842 ec_ecdt->uid = -1;
843 acpi_evaluate_integer(handle, "_UID", NULL, &ec_ecdt->uid);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700844
Len Brown50526df2005-08-11 17:32:05 -0400845 status =
846 acpi_evaluate_integer(handle, "_GPE", NULL,
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400847 &ec_ecdt->gpe_bit);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700848 if (ACPI_FAILURE(status))
849 return status;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400850 ec_ecdt->global_lock = TRUE;
851 ec_ecdt->handle = handle;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700852
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400853 ACPI_DEBUG_PRINT((ACPI_DB_INFO, "GPE=0x%02lx, ports=0x%2lx, 0x%2lx",
854 ec_ecdt->gpe_bit, ec_ecdt->command_addr, ec_ecdt->data_addr));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700855
856 return AE_CTRL_TERMINATE;
857}
858
859/*
860 * Some BIOS (such as some from Gateway laptops) access EC region very early
861 * such as in BAT0._INI or EC._INI before an EC device is found and
862 * do not provide an ECDT. According to ACPI spec, ECDT isn't mandatorily
863 * required, but if EC regison is accessed early, it is required.
864 * The routine tries to workaround the BIOS bug by pre-scan EC device
865 * It assumes that _CRS, _HID, _GPE, _UID methods of EC don't touch any
866 * op region (since _REG isn't invoked yet). The assumption is true for
867 * all systems found.
868 */
Len Brown50526df2005-08-11 17:32:05 -0400869static int __init acpi_ec_fake_ecdt(void)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700870{
Len Brown50526df2005-08-11 17:32:05 -0400871 acpi_status status;
872 int ret = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700873
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400874 ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Try to make an fake ECDT"));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700875
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400876 ec_ecdt = kmalloc(sizeof(struct acpi_ec), GFP_KERNEL);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700877 if (!ec_ecdt) {
878 ret = -ENOMEM;
879 goto error;
880 }
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400881 memset(ec_ecdt, 0, sizeof(struct acpi_ec));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700882
Len Brown50526df2005-08-11 17:32:05 -0400883 status = acpi_get_devices(ACPI_EC_HID,
884 acpi_fake_ecdt_callback, NULL, NULL);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700885 if (ACPI_FAILURE(status)) {
886 kfree(ec_ecdt);
887 ec_ecdt = NULL;
888 ret = -ENODEV;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400889 ACPI_EXCEPTION((AE_INFO, status, "Can't make an fake ECDT"));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700890 goto error;
891 }
892 return 0;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400893 error:
Linus Torvalds1da177e2005-04-16 15:20:36 -0700894 return ret;
895}
896
Len Brown50526df2005-08-11 17:32:05 -0400897static int __init acpi_ec_get_real_ecdt(void)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700898{
Len Brown50526df2005-08-11 17:32:05 -0400899 acpi_status status;
900 struct acpi_table_ecdt *ecdt_ptr;
Luming Yu45bea152005-07-23 04:08:00 -0400901
Len Brown50526df2005-08-11 17:32:05 -0400902 status = acpi_get_firmware_table("ECDT", 1, ACPI_LOGICAL_ADDRESSING,
903 (struct acpi_table_header **)
904 &ecdt_ptr);
Luming Yu45bea152005-07-23 04:08:00 -0400905 if (ACPI_FAILURE(status))
906 return -ENODEV;
907
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400908 ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found ECDT"));
Luming Yu45bea152005-07-23 04:08:00 -0400909
910 /*
911 * Generate a temporary ec context to use until the namespace is scanned
912 */
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400913 ec_ecdt = kmalloc(sizeof(struct acpi_ec), GFP_KERNEL);
Luming Yu45bea152005-07-23 04:08:00 -0400914 if (!ec_ecdt)
915 return -ENOMEM;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400916 memset(ec_ecdt, 0, sizeof(struct acpi_ec));
Luming Yu45bea152005-07-23 04:08:00 -0400917
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400918 init_MUTEX(&ec_ecdt->sem);
919 if (acpi_ec_mode == EC_INTR) {
920 init_waitqueue_head(&ec_ecdt->wait);
921 }
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400922 ec_ecdt->command_addr = ecdt_ptr->ec_control.address;
923 ec_ecdt->data_addr = ecdt_ptr->ec_data.address;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400924 ec_ecdt->gpe_bit = ecdt_ptr->gpe_bit;
Luming Yu45bea152005-07-23 04:08:00 -0400925 /* use the GL just to be safe */
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400926 ec_ecdt->global_lock = TRUE;
927 ec_ecdt->uid = ecdt_ptr->uid;
Luming Yu45bea152005-07-23 04:08:00 -0400928
Len Brown50526df2005-08-11 17:32:05 -0400929 status =
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400930 acpi_get_handle(NULL, ecdt_ptr->ec_id, &ec_ecdt->handle);
Luming Yu45bea152005-07-23 04:08:00 -0400931 if (ACPI_FAILURE(status)) {
932 goto error;
933 }
934
935 return 0;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400936 error:
937 ACPI_EXCEPTION((AE_INFO, status, "Could not use ECDT"));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700938 kfree(ec_ecdt);
939 ec_ecdt = NULL;
940
941 return -ENODEV;
942}
943
944static int __initdata acpi_fake_ecdt_enabled;
Len Brown50526df2005-08-11 17:32:05 -0400945int __init acpi_ec_ecdt_probe(void)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700946{
Len Brown50526df2005-08-11 17:32:05 -0400947 acpi_status status;
948 int ret;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700949
950 ret = acpi_ec_get_real_ecdt();
951 /* Try to make a fake ECDT */
952 if (ret && acpi_fake_ecdt_enabled) {
953 ret = acpi_ec_fake_ecdt();
954 }
955
956 if (ret)
957 return 0;
958
959 /*
960 * Install GPE handler
961 */
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400962 status = acpi_install_gpe_handler(NULL, ec_ecdt->gpe_bit,
Len Brown50526df2005-08-11 17:32:05 -0400963 ACPI_GPE_EDGE_TRIGGERED,
964 &acpi_ec_gpe_handler, ec_ecdt);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700965 if (ACPI_FAILURE(status)) {
966 goto error;
967 }
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400968 acpi_set_gpe_type(NULL, ec_ecdt->gpe_bit, ACPI_GPE_TYPE_RUNTIME);
969 acpi_enable_gpe(NULL, ec_ecdt->gpe_bit, ACPI_NOT_ISR);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700970
Len Brown50526df2005-08-11 17:32:05 -0400971 status = acpi_install_address_space_handler(ACPI_ROOT_OBJECT,
972 ACPI_ADR_SPACE_EC,
973 &acpi_ec_space_handler,
974 &acpi_ec_space_setup,
975 ec_ecdt);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700976 if (ACPI_FAILURE(status)) {
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400977 acpi_remove_gpe_handler(NULL, ec_ecdt->gpe_bit,
Len Brown50526df2005-08-11 17:32:05 -0400978 &acpi_ec_gpe_handler);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700979 goto error;
980 }
981
982 return 0;
983
Len Brown50526df2005-08-11 17:32:05 -0400984 error:
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400985 ACPI_EXCEPTION((AE_INFO, status, "Could not use ECDT"));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700986 kfree(ec_ecdt);
987 ec_ecdt = NULL;
988
989 return -ENODEV;
990}
991
Len Brown50526df2005-08-11 17:32:05 -0400992static int __init acpi_ec_init(void)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700993{
Len Brown50526df2005-08-11 17:32:05 -0400994 int result = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700995
Linus Torvalds1da177e2005-04-16 15:20:36 -0700996
997 if (acpi_disabled)
Patrick Mocheld550d982006-06-27 00:41:40 -0400998 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700999
1000 acpi_ec_dir = proc_mkdir(ACPI_EC_CLASS, acpi_root_dir);
1001 if (!acpi_ec_dir)
Patrick Mocheld550d982006-06-27 00:41:40 -04001002 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001003
1004 /* Now register the driver for the EC */
1005 result = acpi_bus_register_driver(&acpi_ec_driver);
1006 if (result < 0) {
1007 remove_proc_entry(ACPI_EC_CLASS, acpi_root_dir);
Patrick Mocheld550d982006-06-27 00:41:40 -04001008 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001009 }
1010
Patrick Mocheld550d982006-06-27 00:41:40 -04001011 return result;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001012}
1013
1014subsys_initcall(acpi_ec_init);
1015
1016/* EC driver currently not unloadable */
1017#if 0
Len Brown50526df2005-08-11 17:32:05 -04001018static void __exit acpi_ec_exit(void)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001019{
Linus Torvalds1da177e2005-04-16 15:20:36 -07001020
1021 acpi_bus_unregister_driver(&acpi_ec_driver);
1022
1023 remove_proc_entry(ACPI_EC_CLASS, acpi_root_dir);
1024
Patrick Mocheld550d982006-06-27 00:41:40 -04001025 return;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001026}
Len Brown50526df2005-08-11 17:32:05 -04001027#endif /* 0 */
Linus Torvalds1da177e2005-04-16 15:20:36 -07001028
1029static int __init acpi_fake_ecdt_setup(char *str)
1030{
1031 acpi_fake_ecdt_enabled = 1;
OGAWA Hirofumi9b410462006-03-31 02:30:33 -08001032 return 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001033}
Luming Yu7b15f5e2005-08-03 17:38:04 -04001034
Linus Torvalds1da177e2005-04-16 15:20:36 -07001035__setup("acpi_fake_ecdt", acpi_fake_ecdt_setup);
Len Brown02b28a32005-12-05 16:33:04 -05001036static int __init acpi_ec_set_intr_mode(char *str)
Luming Yu45bea152005-07-23 04:08:00 -04001037{
Len Brown02b28a32005-12-05 16:33:04 -05001038 int intr;
Luming Yu7b15f5e2005-08-03 17:38:04 -04001039
Len Brown02b28a32005-12-05 16:33:04 -05001040 if (!get_option(&str, &intr))
Luming Yu7b15f5e2005-08-03 17:38:04 -04001041 return 0;
1042
Len Brown02b28a32005-12-05 16:33:04 -05001043 if (intr) {
Denis M. Sadykov703959d2006-09-26 19:50:33 +04001044 acpi_ec_mode = EC_INTR;
Luming Yu7b15f5e2005-08-03 17:38:04 -04001045 } else {
Denis M. Sadykov703959d2006-09-26 19:50:33 +04001046 acpi_ec_mode = EC_POLL;
Luming Yu7b15f5e2005-08-03 17:38:04 -04001047 }
Denis M. Sadykov703959d2006-09-26 19:50:33 +04001048 acpi_ec_driver.ops.add = acpi_ec_add;
1049 ACPI_DEBUG_PRINT((ACPI_DB_INFO, "EC %s mode.\n", intr ? "interrupt" : "polling"));
1050
OGAWA Hirofumi9b410462006-03-31 02:30:33 -08001051 return 1;
Luming Yu45bea152005-07-23 04:08:00 -04001052}
Len Brown50526df2005-08-11 17:32:05 -04001053
Len Brown53f11d42005-12-05 16:46:36 -05001054__setup("ec_intr=", acpi_ec_set_intr_mode);