blob: ffb8361bc1bf97d902bcb1c6e38dc0061889cda2 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
Alexey Starikovskiy01f22462007-03-07 22:28:00 +03002 * ec.c - ACPI Embedded Controller Driver (v2.0)
Linus Torvalds1da177e2005-04-16 15:20:36 -07003 *
Alexey Starikovskiy01f22462007-03-07 22:28:00 +03004 * Copyright (C) 2006, 2007 Alexey Starikovskiy <alexey.y.starikovskiy@intel.com>
5 * Copyright (C) 2006 Denis Sadykov <denis.m.sadykov@intel.com>
Linus Torvalds1da177e2005-04-16 15:20:36 -07006 * Copyright (C) 2004 Luming Yu <luming.yu@intel.com>
7 * Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
8 * Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
9 *
10 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or (at
15 * your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License along
23 * with this program; if not, write to the Free Software Foundation, Inc.,
24 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
25 *
26 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
27 */
28
29#include <linux/kernel.h>
30#include <linux/module.h>
31#include <linux/init.h>
32#include <linux/types.h>
33#include <linux/delay.h>
34#include <linux/proc_fs.h>
35#include <linux/seq_file.h>
Dmitry Torokhov451566f2005-03-19 01:10:05 -050036#include <linux/interrupt.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070037#include <asm/io.h>
38#include <acpi/acpi_bus.h>
39#include <acpi/acpi_drivers.h>
40#include <acpi/actypes.h>
41
Linus Torvalds1da177e2005-04-16 15:20:36 -070042#define ACPI_EC_CLASS "embedded_controller"
43#define ACPI_EC_HID "PNP0C09"
Linus Torvalds1da177e2005-04-16 15:20:36 -070044#define ACPI_EC_DEVICE_NAME "Embedded Controller"
45#define ACPI_EC_FILE_INFO "info"
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +030046#undef PREFIX
47#define PREFIX "ACPI: EC: "
Alexey Starikovskiy43509332007-05-29 16:42:57 +040048
Denis M. Sadykov703959d2006-09-26 19:50:33 +040049/* EC status register */
Linus Torvalds1da177e2005-04-16 15:20:36 -070050#define ACPI_EC_FLAG_OBF 0x01 /* Output buffer full */
51#define ACPI_EC_FLAG_IBF 0x02 /* Input buffer full */
Dmitry Torokhov451566f2005-03-19 01:10:05 -050052#define ACPI_EC_FLAG_BURST 0x10 /* burst mode */
Linus Torvalds1da177e2005-04-16 15:20:36 -070053#define ACPI_EC_FLAG_SCI 0x20 /* EC-SCI occurred */
Alexey Starikovskiy43509332007-05-29 16:42:57 +040054
Denis M. Sadykov703959d2006-09-26 19:50:33 +040055/* EC commands */
Alexey Starikovskiy3261ff42006-12-07 18:42:17 +030056enum ec_command {
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +030057 ACPI_EC_COMMAND_READ = 0x80,
58 ACPI_EC_COMMAND_WRITE = 0x81,
59 ACPI_EC_BURST_ENABLE = 0x82,
60 ACPI_EC_BURST_DISABLE = 0x83,
61 ACPI_EC_COMMAND_QUERY = 0x84,
Alexey Starikovskiy3261ff42006-12-07 18:42:17 +030062};
Denis M. Sadykov703959d2006-09-26 19:50:33 +040063/* EC events */
Alexey Starikovskiy3261ff42006-12-07 18:42:17 +030064enum ec_event {
Denis M. Sadykov703959d2006-09-26 19:50:33 +040065 ACPI_EC_EVENT_OBF_1 = 1, /* Output buffer full */
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +030066 ACPI_EC_EVENT_IBF_0, /* Input buffer empty */
Denis M. Sadykov703959d2006-09-26 19:50:33 +040067};
68
Alexey Starikovskiy5c406412006-12-07 18:42:16 +030069#define ACPI_EC_DELAY 500 /* Wait 500ms max. during EC ops */
Denis M. Sadykov703959d2006-09-26 19:50:33 +040070#define ACPI_EC_UDELAY_GLK 1000 /* Wait 1ms max. to get global lock */
Denis M. Sadykov703959d2006-09-26 19:50:33 +040071
Alexey Starikovskiy3261ff42006-12-07 18:42:17 +030072static enum ec_mode {
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +030073 EC_INTR = 1, /* Output buffer full */
74 EC_POLL, /* Input buffer empty */
Alexey Starikovskiy3261ff42006-12-07 18:42:17 +030075} acpi_ec_mode = EC_INTR;
Denis M. Sadykov703959d2006-09-26 19:50:33 +040076
Len Brown50526df2005-08-11 17:32:05 -040077static int acpi_ec_remove(struct acpi_device *device, int type);
78static int acpi_ec_start(struct acpi_device *device);
79static int acpi_ec_stop(struct acpi_device *device, int type);
Denis M. Sadykov703959d2006-09-26 19:50:33 +040080static int acpi_ec_add(struct acpi_device *device);
Linus Torvalds1da177e2005-04-16 15:20:36 -070081
82static struct acpi_driver acpi_ec_driver = {
Len Brownc2b6705b2007-02-12 23:33:40 -050083 .name = "ec",
Len Brown50526df2005-08-11 17:32:05 -040084 .class = ACPI_EC_CLASS,
85 .ids = ACPI_EC_HID,
86 .ops = {
Denis M. Sadykov703959d2006-09-26 19:50:33 +040087 .add = acpi_ec_add,
Len Brown50526df2005-08-11 17:32:05 -040088 .remove = acpi_ec_remove,
89 .start = acpi_ec_start,
90 .stop = acpi_ec_stop,
91 },
Linus Torvalds1da177e2005-04-16 15:20:36 -070092};
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +040093
94/* If we find an EC via the ECDT, we need to keep a ptr to its context */
Alexey Starikovskiyd0338792007-03-07 22:28:00 +030095/* External interfaces use first EC only, so remember */
Adrian Bunka854e082006-12-19 12:56:12 -080096static struct acpi_ec {
Denis M. Sadykov703959d2006-09-26 19:50:33 +040097 acpi_handle handle;
Alexey Starikovskiya86e2772006-12-07 18:42:16 +030098 unsigned long gpe;
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +040099 unsigned long command_addr;
100 unsigned long data_addr;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400101 unsigned long global_lock;
Alexey Starikovskiyc787a852006-12-07 18:42:16 +0300102 struct mutex lock;
Alexey Starikovskiy5d0c2882006-12-07 18:42:16 +0300103 atomic_t query_pending;
Alexey Starikovskiy9e197212007-03-07 18:29:35 -0500104 atomic_t event_count;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400105 wait_queue_head_t wait;
Alexey Starikovskiyd0338792007-03-07 22:28:00 +0300106} *boot_ec, *first_ec;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400107
Linus Torvalds1da177e2005-04-16 15:20:36 -0700108/* --------------------------------------------------------------------------
109 Transaction Management
110 -------------------------------------------------------------------------- */
111
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400112static inline u8 acpi_ec_read_status(struct acpi_ec *ec)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700113{
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400114 return inb(ec->command_addr);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700115}
116
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400117static inline u8 acpi_ec_read_data(struct acpi_ec *ec)
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400118{
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400119 return inb(ec->data_addr);
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400120}
121
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400122static inline void acpi_ec_write_cmd(struct acpi_ec *ec, u8 command)
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400123{
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400124 outb(command, ec->command_addr);
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400125}
126
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400127static inline void acpi_ec_write_data(struct acpi_ec *ec, u8 data)
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400128{
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400129 outb(data, ec->data_addr);
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400130}
131
Alexey Starikovskiy9e197212007-03-07 18:29:35 -0500132static inline int acpi_ec_check_status(struct acpi_ec *ec, enum ec_event event,
133 unsigned old_count)
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400134{
Alexey Starikovskiybec5a1e2006-12-07 18:42:16 +0300135 u8 status = acpi_ec_read_status(ec);
Alexey Starikovskiy9e197212007-03-07 18:29:35 -0500136 if (old_count == atomic_read(&ec->event_count))
137 return 0;
Alexey Starikovskiy78d0af32006-12-07 18:42:17 +0300138 if (event == ACPI_EC_EVENT_OBF_1) {
Denis M. Sadykov7c6db5e2006-09-26 19:50:33 +0400139 if (status & ACPI_EC_FLAG_OBF)
140 return 1;
Alexey Starikovskiy78d0af32006-12-07 18:42:17 +0300141 } else if (event == ACPI_EC_EVENT_IBF_0) {
Denis M. Sadykov7c6db5e2006-09-26 19:50:33 +0400142 if (!(status & ACPI_EC_FLAG_IBF))
143 return 1;
Denis M. Sadykov7c6db5e2006-09-26 19:50:33 +0400144 }
145
146 return 0;
147}
148
Lennart Poettering00eb43a2007-05-04 14:16:19 +0200149static int acpi_ec_wait(struct acpi_ec *ec, enum ec_event event,
150 unsigned count, int force_poll)
Luming Yu45bea152005-07-23 04:08:00 -0400151{
Lennart Poettering00eb43a2007-05-04 14:16:19 +0200152 if (unlikely(force_poll) || acpi_ec_mode == EC_POLL) {
Alexey Starikovskiy50c1e112006-12-07 18:42:17 +0300153 unsigned long delay = jiffies + msecs_to_jiffies(ACPI_EC_DELAY);
154 while (time_before(jiffies, delay)) {
Alexey Starikovskiy9e197212007-03-07 18:29:35 -0500155 if (acpi_ec_check_status(ec, event, 0))
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400156 return 0;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400157 }
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300158 } else {
159 if (wait_event_timeout(ec->wait,
Alexey Starikovskiy9e197212007-03-07 18:29:35 -0500160 acpi_ec_check_status(ec, event, count),
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300161 msecs_to_jiffies(ACPI_EC_DELAY)) ||
Alexey Starikovskiy9e197212007-03-07 18:29:35 -0500162 acpi_ec_check_status(ec, event, 0)) {
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400163 return 0;
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300164 } else {
165 printk(KERN_ERR PREFIX "acpi_ec_wait timeout,"
166 " status = %d, expect_event = %d\n",
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +0300167 acpi_ec_read_status(ec), event);
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400168 }
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300169 }
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400170
Patrick Mocheld550d982006-06-27 00:41:40 -0400171 return -ETIME;
Dmitry Torokhov451566f2005-03-19 01:10:05 -0500172}
173
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400174static int acpi_ec_transaction_unlocked(struct acpi_ec *ec, u8 command,
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +0300175 const u8 * wdata, unsigned wdata_len,
Lennart Poettering00eb43a2007-05-04 14:16:19 +0200176 u8 * rdata, unsigned rdata_len,
177 int force_poll)
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400178{
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300179 int result = 0;
Alexey Starikovskiy9e197212007-03-07 18:29:35 -0500180 unsigned count = atomic_read(&ec->event_count);
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400181 acpi_ec_write_cmd(ec, command);
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400182
Alexey Starikovskiy78d0af32006-12-07 18:42:17 +0300183 for (; wdata_len > 0; --wdata_len) {
Lennart Poettering00eb43a2007-05-04 14:16:19 +0200184 result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0, count, force_poll);
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300185 if (result) {
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +0300186 printk(KERN_ERR PREFIX
187 "write_cmd timeout, command = %d\n", command);
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300188 goto end;
189 }
Alexey Starikovskiy9e197212007-03-07 18:29:35 -0500190 count = atomic_read(&ec->event_count);
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400191 acpi_ec_write_data(ec, *(wdata++));
Denis M. Sadykov3576cf62006-09-26 19:50:33 +0400192 }
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400193
Alexey Starikovskiyd91df1a2006-12-07 18:42:16 +0300194 if (!rdata_len) {
Lennart Poettering00eb43a2007-05-04 14:16:19 +0200195 result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0, count, force_poll);
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300196 if (result) {
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +0300197 printk(KERN_ERR PREFIX
198 "finish-write timeout, command = %d\n", command);
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300199 goto end;
200 }
Alexey Starikovskiy5d0c2882006-12-07 18:42:16 +0300201 } else if (command == ACPI_EC_COMMAND_QUERY) {
202 atomic_set(&ec->query_pending, 0);
Denis M. Sadykov7c6db5e2006-09-26 19:50:33 +0400203 }
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400204
Alexey Starikovskiy78d0af32006-12-07 18:42:17 +0300205 for (; rdata_len > 0; --rdata_len) {
Lennart Poettering00eb43a2007-05-04 14:16:19 +0200206 result = acpi_ec_wait(ec, ACPI_EC_EVENT_OBF_1, count, force_poll);
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300207 if (result) {
208 printk(KERN_ERR PREFIX "read timeout, command = %d\n",
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +0300209 command);
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300210 goto end;
211 }
Alexey Starikovskiy9e197212007-03-07 18:29:35 -0500212 count = atomic_read(&ec->event_count);
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400213 *(rdata++) = acpi_ec_read_data(ec);
Denis M. Sadykov7c6db5e2006-09-26 19:50:33 +0400214 }
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300215 end:
216 return result;
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400217}
218
Denis M. Sadykov3576cf62006-09-26 19:50:33 +0400219static int acpi_ec_transaction(struct acpi_ec *ec, u8 command,
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +0300220 const u8 * wdata, unsigned wdata_len,
Lennart Poettering00eb43a2007-05-04 14:16:19 +0200221 u8 * rdata, unsigned rdata_len,
222 int force_poll)
Luming Yu45bea152005-07-23 04:08:00 -0400223{
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400224 int status;
Len Brown50526df2005-08-11 17:32:05 -0400225 u32 glk;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700226
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400227 if (!ec || (wdata_len && !wdata) || (rdata_len && !rdata))
Patrick Mocheld550d982006-06-27 00:41:40 -0400228 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700229
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +0300230 if (rdata)
231 memset(rdata, 0, rdata_len);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700232
Alexey Starikovskiy523953b2006-12-07 18:42:17 +0300233 mutex_lock(&ec->lock);
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400234 if (ec->global_lock) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700235 status = acpi_acquire_global_lock(ACPI_EC_UDELAY_GLK, &glk);
Alexey Starikovskiyc24e9122007-02-15 23:16:18 +0300236 if (ACPI_FAILURE(status)) {
237 mutex_unlock(&ec->lock);
Patrick Mocheld550d982006-06-27 00:41:40 -0400238 return -ENODEV;
Alexey Starikovskiyc24e9122007-02-15 23:16:18 +0300239 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700240 }
Dmitry Torokhov451566f2005-03-19 01:10:05 -0500241
Alexey Starikovskiy5d57a6a2006-12-07 18:42:16 +0300242 /* Make sure GPE is enabled before doing transaction */
Alexey Starikovskiya86e2772006-12-07 18:42:16 +0300243 acpi_enable_gpe(NULL, ec->gpe, ACPI_NOT_ISR);
Alexey Starikovskiy5d57a6a2006-12-07 18:42:16 +0300244
Lennart Poettering00eb43a2007-05-04 14:16:19 +0200245 status = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0, 0, 0);
Luming Yu716e0842005-08-10 01:40:00 -0400246 if (status) {
Alexey Starikovskiy43509332007-05-29 16:42:57 +0400247 printk(KERN_ERR PREFIX
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +0300248 "input buffer is not empty, aborting transaction\n");
Dmitry Torokhov451566f2005-03-19 01:10:05 -0500249 goto end;
Luming Yu716e0842005-08-10 01:40:00 -0400250 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700251
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +0300252 status = acpi_ec_transaction_unlocked(ec, command,
253 wdata, wdata_len,
Lennart Poettering00eb43a2007-05-04 14:16:19 +0200254 rdata, rdata_len,
255 force_poll);
Len Brown50526df2005-08-11 17:32:05 -0400256
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +0300257 end:
Linus Torvalds1da177e2005-04-16 15:20:36 -0700258
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400259 if (ec->global_lock)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700260 acpi_release_global_lock(glk);
Alexey Starikovskiy523953b2006-12-07 18:42:17 +0300261 mutex_unlock(&ec->lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700262
Patrick Mocheld550d982006-06-27 00:41:40 -0400263 return status;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700264}
265
Alexey Starikovskiyc45aac42007-03-07 22:28:00 +0300266/*
267 * Note: samsung nv5000 doesn't work with ec burst mode.
268 * http://bugzilla.kernel.org/show_bug.cgi?id=4980
269 */
270int acpi_ec_burst_enable(struct acpi_ec *ec)
271{
272 u8 d;
Lennart Poettering00eb43a2007-05-04 14:16:19 +0200273 return acpi_ec_transaction(ec, ACPI_EC_BURST_ENABLE, NULL, 0, &d, 1, 0);
Alexey Starikovskiyc45aac42007-03-07 22:28:00 +0300274}
275
276int acpi_ec_burst_disable(struct acpi_ec *ec)
277{
Lennart Poettering00eb43a2007-05-04 14:16:19 +0200278 return acpi_ec_transaction(ec, ACPI_EC_BURST_DISABLE, NULL, 0, NULL, 0, 0);
Alexey Starikovskiyc45aac42007-03-07 22:28:00 +0300279}
280
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +0300281static int acpi_ec_read(struct acpi_ec *ec, u8 address, u8 * data)
Denis M. Sadykov3576cf62006-09-26 19:50:33 +0400282{
283 int result;
284 u8 d;
285
286 result = acpi_ec_transaction(ec, ACPI_EC_COMMAND_READ,
Lennart Poettering00eb43a2007-05-04 14:16:19 +0200287 &address, 1, &d, 1, 0);
Denis M. Sadykov3576cf62006-09-26 19:50:33 +0400288 *data = d;
289 return result;
290}
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400291
Denis M. Sadykov3576cf62006-09-26 19:50:33 +0400292static int acpi_ec_write(struct acpi_ec *ec, u8 address, u8 data)
293{
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +0300294 u8 wdata[2] = { address, data };
295 return acpi_ec_transaction(ec, ACPI_EC_COMMAND_WRITE,
Lennart Poettering00eb43a2007-05-04 14:16:19 +0200296 wdata, 2, NULL, 0, 0);
Denis M. Sadykov3576cf62006-09-26 19:50:33 +0400297}
298
Linus Torvalds1da177e2005-04-16 15:20:36 -0700299/*
300 * Externally callable EC access functions. For now, assume 1 EC only
301 */
Alexey Starikovskiyc45aac42007-03-07 22:28:00 +0300302int ec_burst_enable(void)
303{
Alexey Starikovskiyc45aac42007-03-07 22:28:00 +0300304 if (!first_ec)
305 return -ENODEV;
Alexey Starikovskiyd0338792007-03-07 22:28:00 +0300306 return acpi_ec_burst_enable(first_ec);
Alexey Starikovskiyc45aac42007-03-07 22:28:00 +0300307}
308
309EXPORT_SYMBOL(ec_burst_enable);
310
311int ec_burst_disable(void)
312{
Alexey Starikovskiyc45aac42007-03-07 22:28:00 +0300313 if (!first_ec)
314 return -ENODEV;
Alexey Starikovskiyd0338792007-03-07 22:28:00 +0300315 return acpi_ec_burst_disable(first_ec);
Alexey Starikovskiyc45aac42007-03-07 22:28:00 +0300316}
317
318EXPORT_SYMBOL(ec_burst_disable);
319
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +0300320int ec_read(u8 addr, u8 * val)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700321{
Linus Torvalds1da177e2005-04-16 15:20:36 -0700322 int err;
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400323 u8 temp_data;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700324
325 if (!first_ec)
326 return -ENODEV;
327
Alexey Starikovskiyd0338792007-03-07 22:28:00 +0300328 err = acpi_ec_read(first_ec, addr, &temp_data);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700329
330 if (!err) {
331 *val = temp_data;
332 return 0;
Len Brown50526df2005-08-11 17:32:05 -0400333 } else
Linus Torvalds1da177e2005-04-16 15:20:36 -0700334 return err;
335}
Len Brown50526df2005-08-11 17:32:05 -0400336
Linus Torvalds1da177e2005-04-16 15:20:36 -0700337EXPORT_SYMBOL(ec_read);
338
Len Brown50526df2005-08-11 17:32:05 -0400339int ec_write(u8 addr, u8 val)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700340{
Linus Torvalds1da177e2005-04-16 15:20:36 -0700341 int err;
342
343 if (!first_ec)
344 return -ENODEV;
345
Alexey Starikovskiyd0338792007-03-07 22:28:00 +0300346 err = acpi_ec_write(first_ec, addr, val);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700347
348 return err;
349}
Len Brown50526df2005-08-11 17:32:05 -0400350
Linus Torvalds1da177e2005-04-16 15:20:36 -0700351EXPORT_SYMBOL(ec_write);
352
Randy Dunlap616362d2006-10-27 01:47:34 -0400353int ec_transaction(u8 command,
Alexey Starikovskiy9e197212007-03-07 18:29:35 -0500354 const u8 * wdata, unsigned wdata_len,
Lennart Poettering00eb43a2007-05-04 14:16:19 +0200355 u8 * rdata, unsigned rdata_len,
356 int force_poll)
Luming Yu45bea152005-07-23 04:08:00 -0400357{
Lennart Poetteringd7a76e42006-09-05 12:12:24 -0400358 if (!first_ec)
359 return -ENODEV;
360
Alexey Starikovskiyd0338792007-03-07 22:28:00 +0300361 return acpi_ec_transaction(first_ec, command, wdata,
Lennart Poettering00eb43a2007-05-04 14:16:19 +0200362 wdata_len, rdata, rdata_len,
363 force_poll);
Luming Yu45bea152005-07-23 04:08:00 -0400364}
Luming Yu45bea152005-07-23 04:08:00 -0400365
Lennart Poetteringab9e43c2006-10-03 22:49:00 -0400366EXPORT_SYMBOL(ec_transaction);
367
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +0300368static int acpi_ec_query(struct acpi_ec *ec, u8 * data)
Denis M. Sadykov3576cf62006-09-26 19:50:33 +0400369{
370 int result;
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +0300371 u8 d;
Luming Yu45bea152005-07-23 04:08:00 -0400372
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +0300373 if (!ec || !data)
374 return -EINVAL;
Luming Yu45bea152005-07-23 04:08:00 -0400375
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +0300376 /*
377 * Query the EC to find out which _Qxx method we need to evaluate.
378 * Note that successful completion of the query causes the ACPI_EC_SCI
379 * bit to be cleared (and thus clearing the interrupt source).
380 */
Luming Yu45bea152005-07-23 04:08:00 -0400381
Lennart Poettering00eb43a2007-05-04 14:16:19 +0200382 result = acpi_ec_transaction(ec, ACPI_EC_COMMAND_QUERY, NULL, 0, &d, 1, 0);
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +0300383 if (result)
384 return result;
Luming Yu45bea152005-07-23 04:08:00 -0400385
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +0300386 if (!d)
387 return -ENODATA;
Luming Yu45bea152005-07-23 04:08:00 -0400388
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +0300389 *data = d;
390 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700391}
392
Linus Torvalds1da177e2005-04-16 15:20:36 -0700393/* --------------------------------------------------------------------------
394 Event Management
395 -------------------------------------------------------------------------- */
396
Len Brown50526df2005-08-11 17:32:05 -0400397static void acpi_ec_gpe_query(void *ec_cxt)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700398{
Alexey Starikovskiy3d02b902007-03-07 22:28:00 +0300399 struct acpi_ec *ec = ec_cxt;
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400400 u8 value = 0;
Alexey Starikovskiy5d0c2882006-12-07 18:42:16 +0300401 char object_name[8];
Luming Yu45bea152005-07-23 04:08:00 -0400402
Alexey Starikovskiy5d0c2882006-12-07 18:42:16 +0300403 if (!ec || acpi_ec_query(ec, &value))
Alexey Starikovskiye41334c2006-12-07 18:42:16 +0300404 return;
Luming Yu45bea152005-07-23 04:08:00 -0400405
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400406 snprintf(object_name, 8, "_Q%2.2X", value);
Luming Yu45bea152005-07-23 04:08:00 -0400407
Guillaume Chazarainc6e19192006-12-24 22:19:02 +0100408 ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Evaluating %s", object_name));
Luming Yu45bea152005-07-23 04:08:00 -0400409
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400410 acpi_evaluate_object(ec->handle, object_name, NULL, NULL);
Luming Yu45bea152005-07-23 04:08:00 -0400411}
Linus Torvalds1da177e2005-04-16 15:20:36 -0700412
Len Brown50526df2005-08-11 17:32:05 -0400413static u32 acpi_ec_gpe_handler(void *data)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700414{
Len Brown50526df2005-08-11 17:32:05 -0400415 acpi_status status = AE_OK;
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400416 u8 value;
Alexey Starikovskiy3d02b902007-03-07 22:28:00 +0300417 struct acpi_ec *ec = data;
Lennart Poettering00eb43a2007-05-04 14:16:19 +0200418
Alexey Starikovskiy9e197212007-03-07 18:29:35 -0500419 atomic_inc(&ec->event_count);
Alexey Starikovskiy3d02b902007-03-07 22:28:00 +0300420
Denis M. Sadykov8e0341b2006-09-26 19:50:33 +0400421 if (acpi_ec_mode == EC_INTR) {
Alexey Starikovskiyaf3fd142006-12-07 18:42:16 +0300422 wake_up(&ec->wait);
Dmitry Torokhov451566f2005-03-19 01:10:05 -0500423 }
424
Alexey Starikovskiybec5a1e2006-12-07 18:42:16 +0300425 value = acpi_ec_read_status(ec);
Alexey Starikovskiy5d0c2882006-12-07 18:42:16 +0300426 if ((value & ACPI_EC_FLAG_SCI) && !atomic_read(&ec->query_pending)) {
427 atomic_set(&ec->query_pending, 1);
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +0300428 status =
429 acpi_os_execute(OSL_EC_BURST_HANDLER, acpi_ec_gpe_query,
430 ec);
Len Brown50526df2005-08-11 17:32:05 -0400431 }
Alexey Starikovskiye41334c2006-12-07 18:42:16 +0300432
Dmitry Torokhov451566f2005-03-19 01:10:05 -0500433 return status == AE_OK ?
Len Brown50526df2005-08-11 17:32:05 -0400434 ACPI_INTERRUPT_HANDLED : ACPI_INTERRUPT_NOT_HANDLED;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700435}
436
437/* --------------------------------------------------------------------------
438 Address Space Management
439 -------------------------------------------------------------------------- */
440
441static acpi_status
Len Brown50526df2005-08-11 17:32:05 -0400442acpi_ec_space_setup(acpi_handle region_handle,
443 u32 function, void *handler_context, void **return_context)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700444{
445 /*
446 * The EC object is in the handler context and is needed
447 * when calling the acpi_ec_space_handler.
448 */
Len Brown50526df2005-08-11 17:32:05 -0400449 *return_context = (function != ACPI_REGION_DEACTIVATE) ?
450 handler_context : NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700451
452 return AE_OK;
453}
454
Linus Torvalds1da177e2005-04-16 15:20:36 -0700455static acpi_status
Alexey Starikovskiy5b7734b2007-05-29 16:42:52 +0400456acpi_ec_space_handler(u32 function, acpi_physical_address address,
457 u32 bits, acpi_integer *value,
Len Brown50526df2005-08-11 17:32:05 -0400458 void *handler_context, void *region_context)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700459{
Alexey Starikovskiy3d02b902007-03-07 22:28:00 +0300460 struct acpi_ec *ec = handler_context;
Alexey Starikovskiy5b7734b2007-05-29 16:42:52 +0400461 int result = 0, i = 0;
462 u8 temp = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700463
Linus Torvalds1da177e2005-04-16 15:20:36 -0700464 if ((address > 0xFF) || !value || !handler_context)
Patrick Mocheld550d982006-06-27 00:41:40 -0400465 return AE_BAD_PARAMETER;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700466
Alexey Starikovskiy5b7734b2007-05-29 16:42:52 +0400467 if (function != ACPI_READ && function != ACPI_WRITE)
Patrick Mocheld550d982006-06-27 00:41:40 -0400468 return AE_BAD_PARAMETER;
Alexey Starikovskiy5b7734b2007-05-29 16:42:52 +0400469
470 if (bits != 8 && acpi_strict)
471 return AE_BAD_PARAMETER;
472
473 while (bits - i > 0) {
474 if (function == ACPI_READ) {
475 result = acpi_ec_read(ec, address, &temp);
476 (*value) |= ((acpi_integer)temp) << i;
477 } else {
478 temp = 0xff & ((*value) >> i);
479 result = acpi_ec_write(ec, address, temp);
480 }
481 i += 8;
482 ++address;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700483 }
484
Linus Torvalds1da177e2005-04-16 15:20:36 -0700485 switch (result) {
486 case -EINVAL:
Patrick Mocheld550d982006-06-27 00:41:40 -0400487 return AE_BAD_PARAMETER;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700488 break;
489 case -ENODEV:
Patrick Mocheld550d982006-06-27 00:41:40 -0400490 return AE_NOT_FOUND;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700491 break;
492 case -ETIME:
Patrick Mocheld550d982006-06-27 00:41:40 -0400493 return AE_TIME;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700494 break;
495 default:
Patrick Mocheld550d982006-06-27 00:41:40 -0400496 return AE_OK;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700497 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700498}
499
Linus Torvalds1da177e2005-04-16 15:20:36 -0700500/* --------------------------------------------------------------------------
501 FS Interface (/proc)
502 -------------------------------------------------------------------------- */
503
Len Brown50526df2005-08-11 17:32:05 -0400504static struct proc_dir_entry *acpi_ec_dir;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700505
Len Brown50526df2005-08-11 17:32:05 -0400506static int acpi_ec_read_info(struct seq_file *seq, void *offset)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700507{
Alexey Starikovskiy3d02b902007-03-07 22:28:00 +0300508 struct acpi_ec *ec = seq->private;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700509
Linus Torvalds1da177e2005-04-16 15:20:36 -0700510 if (!ec)
511 goto end;
512
Alexey Starikovskiy01f22462007-03-07 22:28:00 +0300513 seq_printf(seq, "gpe:\t\t\t0x%02x\n", (u32) ec->gpe);
514 seq_printf(seq, "ports:\t\t\t0x%02x, 0x%02x\n",
515 (unsigned)ec->command_addr, (unsigned)ec->data_addr);
516 seq_printf(seq, "use global lock:\t%s\n",
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400517 ec->global_lock ? "yes" : "no");
Len Brown50526df2005-08-11 17:32:05 -0400518 end:
Patrick Mocheld550d982006-06-27 00:41:40 -0400519 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700520}
521
522static int acpi_ec_info_open_fs(struct inode *inode, struct file *file)
523{
524 return single_open(file, acpi_ec_read_info, PDE(inode)->data);
525}
526
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400527static struct file_operations acpi_ec_info_ops = {
Len Brown50526df2005-08-11 17:32:05 -0400528 .open = acpi_ec_info_open_fs,
529 .read = seq_read,
530 .llseek = seq_lseek,
531 .release = single_release,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700532 .owner = THIS_MODULE,
533};
534
Len Brown50526df2005-08-11 17:32:05 -0400535static int acpi_ec_add_fs(struct acpi_device *device)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700536{
Len Brown50526df2005-08-11 17:32:05 -0400537 struct proc_dir_entry *entry = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700538
Linus Torvalds1da177e2005-04-16 15:20:36 -0700539 if (!acpi_device_dir(device)) {
540 acpi_device_dir(device) = proc_mkdir(acpi_device_bid(device),
Len Brown50526df2005-08-11 17:32:05 -0400541 acpi_ec_dir);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700542 if (!acpi_device_dir(device))
Patrick Mocheld550d982006-06-27 00:41:40 -0400543 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700544 }
545
546 entry = create_proc_entry(ACPI_EC_FILE_INFO, S_IRUGO,
Len Brown50526df2005-08-11 17:32:05 -0400547 acpi_device_dir(device));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700548 if (!entry)
Patrick Mocheld550d982006-06-27 00:41:40 -0400549 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700550 else {
551 entry->proc_fops = &acpi_ec_info_ops;
552 entry->data = acpi_driver_data(device);
553 entry->owner = THIS_MODULE;
554 }
555
Patrick Mocheld550d982006-06-27 00:41:40 -0400556 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700557}
558
Len Brown50526df2005-08-11 17:32:05 -0400559static int acpi_ec_remove_fs(struct acpi_device *device)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700560{
Linus Torvalds1da177e2005-04-16 15:20:36 -0700561
562 if (acpi_device_dir(device)) {
563 remove_proc_entry(ACPI_EC_FILE_INFO, acpi_device_dir(device));
564 remove_proc_entry(acpi_device_bid(device), acpi_ec_dir);
565 acpi_device_dir(device) = NULL;
566 }
567
Patrick Mocheld550d982006-06-27 00:41:40 -0400568 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700569}
570
Linus Torvalds1da177e2005-04-16 15:20:36 -0700571/* --------------------------------------------------------------------------
572 Driver Interface
573 -------------------------------------------------------------------------- */
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300574static acpi_status
575ec_parse_io_ports(struct acpi_resource *resource, void *context);
576
577static acpi_status
578ec_parse_device(acpi_handle handle, u32 Level, void *context, void **retval);
579
580static struct acpi_ec *make_acpi_ec(void)
581{
582 struct acpi_ec *ec = kzalloc(sizeof(struct acpi_ec), GFP_KERNEL);
583 if (!ec)
584 return NULL;
585
Alexey Starikovskiy9fd9f8e2007-03-07 22:28:00 +0300586 atomic_set(&ec->query_pending, 1);
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300587 atomic_set(&ec->event_count, 1);
588 mutex_init(&ec->lock);
589 init_waitqueue_head(&ec->wait);
590
591 return ec;
592}
Linus Torvalds1da177e2005-04-16 15:20:36 -0700593
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400594static int acpi_ec_add(struct acpi_device *device)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700595{
Len Brown50526df2005-08-11 17:32:05 -0400596 acpi_status status = AE_OK;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400597 struct acpi_ec *ec = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700598
Linus Torvalds1da177e2005-04-16 15:20:36 -0700599 if (!device)
Patrick Mocheld550d982006-06-27 00:41:40 -0400600 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700601
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300602 strcpy(acpi_device_name(device), ACPI_EC_DEVICE_NAME);
603 strcpy(acpi_device_class(device), ACPI_EC_CLASS);
604
605 ec = make_acpi_ec();
Linus Torvalds1da177e2005-04-16 15:20:36 -0700606 if (!ec)
Patrick Mocheld550d982006-06-27 00:41:40 -0400607 return -ENOMEM;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700608
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300609 status = ec_parse_device(device->handle, 0, ec, NULL);
610 if (status != AE_CTRL_TERMINATE) {
611 kfree(ec);
612 return -EINVAL;
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400613 }
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300614
615 /* Check if we found the boot EC */
Alexey Starikovskiyd66d9692007-03-07 22:28:00 +0300616 if (boot_ec) {
617 if (boot_ec->gpe == ec->gpe) {
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300618 /* We might have incorrect info for GL at boot time */
Alexey Starikovskiyd66d9692007-03-07 22:28:00 +0300619 mutex_lock(&boot_ec->lock);
620 boot_ec->global_lock = ec->global_lock;
621 mutex_unlock(&boot_ec->lock);
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300622 kfree(ec);
Alexey Starikovskiyd66d9692007-03-07 22:28:00 +0300623 ec = boot_ec;
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300624 }
Alexey Starikovskiyd0338792007-03-07 22:28:00 +0300625 } else
626 first_ec = ec;
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300627 ec->handle = device->handle;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700628 acpi_driver_data(device) = ec;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700629
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300630 acpi_ec_add_fs(device);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700631
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300632 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700633}
634
Len Brown50526df2005-08-11 17:32:05 -0400635static int acpi_ec_remove(struct acpi_device *device, int type)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700636{
Alexey Starikovskiy01f22462007-03-07 22:28:00 +0300637 struct acpi_ec *ec;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700638
Linus Torvalds1da177e2005-04-16 15:20:36 -0700639 if (!device)
Patrick Mocheld550d982006-06-27 00:41:40 -0400640 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700641
642 ec = acpi_driver_data(device);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700643 acpi_ec_remove_fs(device);
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300644 acpi_driver_data(device) = NULL;
Alexey Starikovskiyd0338792007-03-07 22:28:00 +0300645 if (ec == first_ec)
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300646 first_ec = NULL;
647
648 /* Don't touch boot EC */
Alexey Starikovskiyd66d9692007-03-07 22:28:00 +0300649 if (boot_ec != ec)
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300650 kfree(ec);
Patrick Mocheld550d982006-06-27 00:41:40 -0400651 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700652}
653
Linus Torvalds1da177e2005-04-16 15:20:36 -0700654static acpi_status
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300655ec_parse_io_ports(struct acpi_resource *resource, void *context)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700656{
Alexey Starikovskiy3d02b902007-03-07 22:28:00 +0300657 struct acpi_ec *ec = context;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700658
Alexey Starikovskiy01f22462007-03-07 22:28:00 +0300659 if (resource->type != ACPI_RESOURCE_TYPE_IO)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700660 return AE_OK;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700661
662 /*
663 * The first address region returned is the data port, and
664 * the second address region returned is the status/command
665 * port.
666 */
Alexey Starikovskiy01f22462007-03-07 22:28:00 +0300667 if (ec->data_addr == 0)
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400668 ec->data_addr = resource->data.io.minimum;
Alexey Starikovskiy01f22462007-03-07 22:28:00 +0300669 else if (ec->command_addr == 0)
Denis M. Sadykov6ffb2212006-09-26 19:50:33 +0400670 ec->command_addr = resource->data.io.minimum;
Alexey Starikovskiy01f22462007-03-07 22:28:00 +0300671 else
Linus Torvalds1da177e2005-04-16 15:20:36 -0700672 return AE_CTRL_TERMINATE;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700673
Linus Torvalds1da177e2005-04-16 15:20:36 -0700674 return AE_OK;
675}
676
Alexey Starikovskiye8284322007-03-07 22:28:00 +0300677static int ec_install_handlers(struct acpi_ec *ec)
678{
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300679 acpi_status status;
680 status = acpi_install_gpe_handler(NULL, ec->gpe,
681 ACPI_GPE_EDGE_TRIGGERED,
682 &acpi_ec_gpe_handler, ec);
Alexey Starikovskiye8284322007-03-07 22:28:00 +0300683 if (ACPI_FAILURE(status))
684 return -ENODEV;
Alexey Starikovskiy01f22462007-03-07 22:28:00 +0300685
Alexey Starikovskiye8284322007-03-07 22:28:00 +0300686 acpi_set_gpe_type(NULL, ec->gpe, ACPI_GPE_TYPE_RUNTIME);
687 acpi_enable_gpe(NULL, ec->gpe, ACPI_NOT_ISR);
688
689 status = acpi_install_address_space_handler(ec->handle,
690 ACPI_ADR_SPACE_EC,
691 &acpi_ec_space_handler,
692 &acpi_ec_space_setup, ec);
693 if (ACPI_FAILURE(status)) {
694 acpi_remove_gpe_handler(NULL, ec->gpe, &acpi_ec_gpe_handler);
695 return -ENODEV;
696 }
697
Alexey Starikovskiy9fd9f8e2007-03-07 22:28:00 +0300698 /* EC is fully operational, allow queries */
699 atomic_set(&ec->query_pending, 0);
700
Alexey Starikovskiye8284322007-03-07 22:28:00 +0300701 return 0;
702}
703
Len Brown50526df2005-08-11 17:32:05 -0400704static int acpi_ec_start(struct acpi_device *device)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700705{
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300706 struct acpi_ec *ec;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700707
Linus Torvalds1da177e2005-04-16 15:20:36 -0700708 if (!device)
Patrick Mocheld550d982006-06-27 00:41:40 -0400709 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700710
711 ec = acpi_driver_data(device);
712
713 if (!ec)
Patrick Mocheld550d982006-06-27 00:41:40 -0400714 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700715
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300716 /* Boot EC is already working */
Alexey Starikovskiyd66d9692007-03-07 22:28:00 +0300717 if (ec == boot_ec)
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300718 return 0;
719
Alexey Starikovskiye8284322007-03-07 22:28:00 +0300720 return ec_install_handlers(ec);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700721}
722
Len Brown50526df2005-08-11 17:32:05 -0400723static int acpi_ec_stop(struct acpi_device *device, int type)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700724{
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300725 acpi_status status;
726 struct acpi_ec *ec;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700727
Linus Torvalds1da177e2005-04-16 15:20:36 -0700728 if (!device)
Patrick Mocheld550d982006-06-27 00:41:40 -0400729 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700730
731 ec = acpi_driver_data(device);
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300732 if (!ec)
733 return -EINVAL;
734
735 /* Don't touch boot EC */
Alexey Starikovskiyd66d9692007-03-07 22:28:00 +0300736 if (ec == boot_ec)
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300737 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700738
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400739 status = acpi_remove_address_space_handler(ec->handle,
Len Brown50526df2005-08-11 17:32:05 -0400740 ACPI_ADR_SPACE_EC,
741 &acpi_ec_space_handler);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700742 if (ACPI_FAILURE(status))
Patrick Mocheld550d982006-06-27 00:41:40 -0400743 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700744
Alexey Starikovskiy6ccedb12006-12-07 18:42:17 +0300745 status = acpi_remove_gpe_handler(NULL, ec->gpe, &acpi_ec_gpe_handler);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700746 if (ACPI_FAILURE(status))
Patrick Mocheld550d982006-06-27 00:41:40 -0400747 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700748
Patrick Mocheld550d982006-06-27 00:41:40 -0400749 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700750}
751
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300752static acpi_status
753ec_parse_device(acpi_handle handle, u32 Level, void *context, void **retval)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700754{
Len Brown50526df2005-08-11 17:32:05 -0400755 acpi_status status;
Luming Yu45bea152005-07-23 04:08:00 -0400756
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300757 struct acpi_ec *ec = context;
758 status = acpi_walk_resources(handle, METHOD_NAME__CRS,
759 ec_parse_io_ports, ec);
Luming Yu45bea152005-07-23 04:08:00 -0400760 if (ACPI_FAILURE(status))
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300761 return status;
Luming Yu45bea152005-07-23 04:08:00 -0400762
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300763 /* Get GPE bit assignment (EC events). */
764 /* TODO: Add support for _GPE returning a package */
765 status = acpi_evaluate_integer(handle, "_GPE", NULL, &ec->gpe);
766 if (ACPI_FAILURE(status))
767 return status;
Luming Yu45bea152005-07-23 04:08:00 -0400768
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300769 /* Use the global lock for all EC transactions? */
770 acpi_evaluate_integer(handle, "_GLK", NULL, &ec->global_lock);
Luming Yu45bea152005-07-23 04:08:00 -0400771
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300772 ec->handle = handle;
Luming Yu45bea152005-07-23 04:08:00 -0400773
Alexey Starikovskiy43509332007-05-29 16:42:57 +0400774 printk(KERN_INFO PREFIX "GPE = 0x%lx, I/O: command/status = 0x%lx, data = 0x%lx",
775 ec->gpe, ec->command_addr, ec->data_addr);
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300776
777 return AE_CTRL_TERMINATE;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700778}
779
Len Brown50526df2005-08-11 17:32:05 -0400780int __init acpi_ec_ecdt_probe(void)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700781{
Len Brown50526df2005-08-11 17:32:05 -0400782 int ret;
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300783 acpi_status status;
784 struct acpi_table_ecdt *ecdt_ptr;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700785
Alexey Starikovskiyd66d9692007-03-07 22:28:00 +0300786 boot_ec = make_acpi_ec();
787 if (!boot_ec)
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300788 return -ENOMEM;
789 /*
790 * Generate a boot ec context
791 */
792
793 status = acpi_get_table(ACPI_SIG_ECDT, 1,
794 (struct acpi_table_header **)&ecdt_ptr);
795 if (ACPI_FAILURE(status))
796 goto error;
797
Alexey Starikovskiy43509332007-05-29 16:42:57 +0400798 printk(KERN_INFO PREFIX "EC description table is found, configuring boot EC\n");
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300799
Alexey Starikovskiyd66d9692007-03-07 22:28:00 +0300800 boot_ec->command_addr = ecdt_ptr->control.address;
801 boot_ec->data_addr = ecdt_ptr->data.address;
802 boot_ec->gpe = ecdt_ptr->gpe;
Alexey Starikovskiyd66d9692007-03-07 22:28:00 +0300803 boot_ec->handle = ACPI_ROOT_OBJECT;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700804
Alexey Starikovskiyd66d9692007-03-07 22:28:00 +0300805 ret = ec_install_handlers(boot_ec);
Alexey Starikovskiyd0338792007-03-07 22:28:00 +0300806 if (!ret) {
807 first_ec = boot_ec;
Alexey Starikovskiye8284322007-03-07 22:28:00 +0300808 return 0;
Alexey Starikovskiyd0338792007-03-07 22:28:00 +0300809 }
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300810 error:
Alexey Starikovskiyd66d9692007-03-07 22:28:00 +0300811 kfree(boot_ec);
812 boot_ec = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700813
814 return -ENODEV;
815}
816
Len Brown50526df2005-08-11 17:32:05 -0400817static int __init acpi_ec_init(void)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700818{
Len Brown50526df2005-08-11 17:32:05 -0400819 int result = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700820
Linus Torvalds1da177e2005-04-16 15:20:36 -0700821 if (acpi_disabled)
Patrick Mocheld550d982006-06-27 00:41:40 -0400822 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700823
824 acpi_ec_dir = proc_mkdir(ACPI_EC_CLASS, acpi_root_dir);
825 if (!acpi_ec_dir)
Patrick Mocheld550d982006-06-27 00:41:40 -0400826 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700827
828 /* Now register the driver for the EC */
829 result = acpi_bus_register_driver(&acpi_ec_driver);
830 if (result < 0) {
831 remove_proc_entry(ACPI_EC_CLASS, acpi_root_dir);
Patrick Mocheld550d982006-06-27 00:41:40 -0400832 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700833 }
834
Patrick Mocheld550d982006-06-27 00:41:40 -0400835 return result;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700836}
837
838subsys_initcall(acpi_ec_init);
839
840/* EC driver currently not unloadable */
841#if 0
Len Brown50526df2005-08-11 17:32:05 -0400842static void __exit acpi_ec_exit(void)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700843{
Linus Torvalds1da177e2005-04-16 15:20:36 -0700844
845 acpi_bus_unregister_driver(&acpi_ec_driver);
846
847 remove_proc_entry(ACPI_EC_CLASS, acpi_root_dir);
848
Patrick Mocheld550d982006-06-27 00:41:40 -0400849 return;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700850}
Len Brown50526df2005-08-11 17:32:05 -0400851#endif /* 0 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700852
Len Brown02b28a32005-12-05 16:33:04 -0500853static int __init acpi_ec_set_intr_mode(char *str)
Luming Yu45bea152005-07-23 04:08:00 -0400854{
Len Brown02b28a32005-12-05 16:33:04 -0500855 int intr;
Luming Yu7b15f5e2005-08-03 17:38:04 -0400856
Len Brown02b28a32005-12-05 16:33:04 -0500857 if (!get_option(&str, &intr))
Luming Yu7b15f5e2005-08-03 17:38:04 -0400858 return 0;
859
Alexey Starikovskiyc0900c32007-03-07 22:28:00 +0300860 acpi_ec_mode = (intr) ? EC_INTR : EC_POLL;
861
Alexey Starikovskiy9e197212007-03-07 18:29:35 -0500862 printk(KERN_NOTICE PREFIX "%s mode.\n", intr ? "interrupt" : "polling");
Denis M. Sadykov703959d2006-09-26 19:50:33 +0400863
OGAWA Hirofumi9b410462006-03-31 02:30:33 -0800864 return 1;
Luming Yu45bea152005-07-23 04:08:00 -0400865}
Len Brown50526df2005-08-11 17:32:05 -0400866
Len Brown53f11d42005-12-05 16:46:36 -0500867__setup("ec_intr=", acpi_ec_set_intr_mode);