aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorLan Tianyu <tianyu.lan@intel.com>2014-05-20 08:59:23 -0400
committerWolfram Sang <wsa@the-dreams.de>2014-06-27 08:50:40 -0400
commit5d98e61d337c181f199a6cb864569cc4e116ef4c (patch)
tree8768f805e13e68c0f9662cf288d2c7179fe01002 /drivers
parenta497c3ba1d97fc69c1e78e7b96435ba8c2cb42ee (diff)
I2C/ACPI: Add i2c ACPI operation region support
ACPI 5.0 spec(5.5.2.4.5) defines GenericSerialBus(i2c, spi, uart) operation region. It allows ACPI aml code able to access such kind of devices to implement some ACPI standard method. ACPI Spec defines some access attribute to associate with i2c protocol. AttribQuick Read/Write Quick Protocol AttribSendReceive Send/Receive Byte Protocol AttribByte Read/Write Byte Protocol AttribWord Read/Write Word Protocol AttribBlock Read/Write Block Protocol AttribBytes Read/Write N-Bytes Protocol AttribProcessCall Process Call Protocol AttribBlockProcessCall Write Block-Read Block Process Call Protocol AttribRawBytes Raw Read/Write N-BytesProtocol AttribRawProcessBytes Raw Process Call Protocol On the Asus T100TA, Bios use GenericSerialBus operation region to access i2c device to get battery info. Sample code From Asus T100TA Scope (_SB.I2C1) { Name (UMPC, ResourceTemplate () { I2cSerialBus (0x0066, ControllerInitiated, 0x00061A80, AddressingMode7Bit, "\\_SB.I2C1", 0x00, ResourceConsumer, , ) }) ... OperationRegion (DVUM, GenericSerialBus, Zero, 0x0100) Field (DVUM, BufferAcc, NoLock, Preserve) { Connection (UMPC), Offset (0x81), AccessAs (BufferAcc, AttribBytes (0x3E)), FGC0, 8 } ... } Device (BATC) { Name (_HID, EisaId ("PNP0C0A")) // _HID: Hardware ID Name (_UID, One) // _UID: Unique ID ... Method (_BST, 0, NotSerialized) // _BST: Battery Status { If (LEqual (AVBL, One)) { Store (FGC0, BFFG) If (LNotEqual (STAT, One)) { ShiftRight (CHST, 0x04, Local0) And (Local0, 0x03, Local0) If (LOr (LEqual (Local0, One), LEqual (Local0, 0x02))) { Store (0x02, Local1) } ... } The i2c operation region is defined under I2C1 scope. _BST method under battery device BATC read battery status from the field "FCG0". The request would be sent to i2c operation region handler. This patch is to add i2c ACPI operation region support. Due to there are only "Byte" and "Bytes" protocol access on the Asus T100TA, other protocols have not been tested. About RawBytes and RawProcessBytes protocol, they needs specific drivers to interpret reference data from AML code according ACPI 5.0 SPEC(5.5.2.4.5.3.9 and 5.5.2.4.5.3.10). So far, not found such case and will add when find real case. Signed-off-by: Lan Tianyu <tianyu.lan@intel.com> Reviewed-by: Mika Westerberg <mika.westerberg@linux.intel.com> Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/i2c/Makefile5
-rw-r--r--drivers/i2c/i2c-acpi.c273
-rw-r--r--drivers/i2c/i2c-core.c2
3 files changed, 279 insertions, 1 deletions
diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile
index 1722f50f2473..80db3073aa84 100644
--- a/drivers/i2c/Makefile
+++ b/drivers/i2c/Makefile
@@ -2,8 +2,11 @@
2# Makefile for the i2c core. 2# Makefile for the i2c core.
3# 3#
4 4
5i2ccore-y := i2c-core.o
6i2ccore-$(CONFIG_ACPI) += i2c-acpi.o
7
5obj-$(CONFIG_I2C_BOARDINFO) += i2c-boardinfo.o 8obj-$(CONFIG_I2C_BOARDINFO) += i2c-boardinfo.o
6obj-$(CONFIG_I2C) += i2c-core.o 9obj-$(CONFIG_I2C) += i2ccore.o
7obj-$(CONFIG_I2C_SMBUS) += i2c-smbus.o 10obj-$(CONFIG_I2C_SMBUS) += i2c-smbus.o
8obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o 11obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o
9obj-$(CONFIG_I2C_MUX) += i2c-mux.o 12obj-$(CONFIG_I2C_MUX) += i2c-mux.o
diff --git a/drivers/i2c/i2c-acpi.c b/drivers/i2c/i2c-acpi.c
new file mode 100644
index 000000000000..f7f4c89c09b3
--- /dev/null
+++ b/drivers/i2c/i2c-acpi.c
@@ -0,0 +1,273 @@
1/*
2 * I2C ACPI code
3 *
4 * Copyright (C) 2014 Intel Corp
5 *
6 * Author: Lan Tianyu <tianyu.lan@intel.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2 as
10 * published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 * for more details.
16 */
17#define pr_fmt(fmt) "I2C/ACPI : " fmt
18
19#include <linux/kernel.h>
20#include <linux/errno.h>
21#include <linux/err.h>
22#include <linux/i2c.h>
23#include <linux/acpi.h>
24
25struct acpi_i2c_handler_data {
26 struct acpi_connection_info info;
27 struct i2c_adapter *adapter;
28};
29
30struct gsb_buffer {
31 u8 status;
32 u8 len;
33 union {
34 u16 wdata;
35 u8 bdata;
36 u8 data[0];
37 };
38} __packed;
39
40static int acpi_gsb_i2c_read_bytes(struct i2c_client *client,
41 u8 cmd, u8 *data, u8 data_len)
42{
43
44 struct i2c_msg msgs[2];
45 int ret;
46 u8 *buffer;
47
48 buffer = kzalloc(data_len, GFP_KERNEL);
49 if (!buffer)
50 return AE_NO_MEMORY;
51
52 msgs[0].addr = client->addr;
53 msgs[0].flags = client->flags;
54 msgs[0].len = 1;
55 msgs[0].buf = &cmd;
56
57 msgs[1].addr = client->addr;
58 msgs[1].flags = client->flags | I2C_M_RD;
59 msgs[1].len = data_len;
60 msgs[1].buf = buffer;
61
62 ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
63 if (ret < 0)
64 dev_err(&client->adapter->dev, "i2c read failed\n");
65 else
66 memcpy(data, buffer, data_len);
67
68 kfree(buffer);
69 return ret;
70}
71
72static int acpi_gsb_i2c_write_bytes(struct i2c_client *client,
73 u8 cmd, u8 *data, u8 data_len)
74{
75
76 struct i2c_msg msgs[1];
77 u8 *buffer;
78 int ret = AE_OK;
79
80 buffer = kzalloc(data_len + 1, GFP_KERNEL);
81 if (!buffer)
82 return AE_NO_MEMORY;
83
84 buffer[0] = cmd;
85 memcpy(buffer + 1, data, data_len);
86
87 msgs[0].addr = client->addr;
88 msgs[0].flags = client->flags;
89 msgs[0].len = data_len + 1;
90 msgs[0].buf = buffer;
91
92 ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
93 if (ret < 0)
94 dev_err(&client->adapter->dev, "i2c write failed\n");
95
96 kfree(buffer);
97 return ret;
98}
99
100static acpi_status
101acpi_i2c_space_handler(u32 function, acpi_physical_address command,
102 u32 bits, u64 *value64,
103 void *handler_context, void *region_context)
104{
105 struct gsb_buffer *gsb = (struct gsb_buffer *)value64;
106 struct acpi_i2c_handler_data *data = handler_context;
107 struct acpi_connection_info *info = &data->info;
108 struct acpi_resource_i2c_serialbus *sb;
109 struct i2c_adapter *adapter = data->adapter;
110 struct i2c_client client;
111 struct acpi_resource *ares;
112 u32 accessor_type = function >> 16;
113 u8 action = function & ACPI_IO_MASK;
114 acpi_status ret = AE_OK;
115 int status;
116
117 ret = acpi_buffer_to_resource(info->connection, info->length, &ares);
118 if (ACPI_FAILURE(ret))
119 return ret;
120
121 if (!value64 || ares->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) {
122 ret = AE_BAD_PARAMETER;
123 goto err;
124 }
125
126 sb = &ares->data.i2c_serial_bus;
127 if (sb->type != ACPI_RESOURCE_SERIAL_TYPE_I2C) {
128 ret = AE_BAD_PARAMETER;
129 goto err;
130 }
131
132 memset(&client, 0, sizeof(client));
133 client.adapter = adapter;
134 client.addr = sb->slave_address;
135 client.flags = 0;
136
137 if (sb->access_mode == ACPI_I2C_10BIT_MODE)
138 client.flags |= I2C_CLIENT_TEN;
139
140 switch (accessor_type) {
141 case ACPI_GSB_ACCESS_ATTRIB_SEND_RCV:
142 if (action == ACPI_READ) {
143 status = i2c_smbus_read_byte(&client);
144 if (status >= 0) {
145 gsb->bdata = status;
146 status = 0;
147 }
148 } else {
149 status = i2c_smbus_write_byte(&client, gsb->bdata);
150 }
151 break;
152
153 case ACPI_GSB_ACCESS_ATTRIB_BYTE:
154 if (action == ACPI_READ) {
155 status = i2c_smbus_read_byte_data(&client, command);
156 if (status >= 0) {
157 gsb->bdata = status;
158 status = 0;
159 }
160 } else {
161 status = i2c_smbus_write_byte_data(&client, command,
162 gsb->bdata);
163 }
164 break;
165
166 case ACPI_GSB_ACCESS_ATTRIB_WORD:
167 if (action == ACPI_READ) {
168 status = i2c_smbus_read_word_data(&client, command);
169 if (status >= 0) {
170 gsb->wdata = status;
171 status = 0;
172 }
173 } else {
174 status = i2c_smbus_write_word_data(&client, command,
175 gsb->wdata);
176 }
177 break;
178
179 case ACPI_GSB_ACCESS_ATTRIB_BLOCK:
180 if (action == ACPI_READ) {
181 status = i2c_smbus_read_block_data(&client, command,
182 gsb->data);
183 if (status >= 0) {
184 gsb->len = status;
185 status = 0;
186 }
187 } else {
188 status = i2c_smbus_write_block_data(&client, command,
189 gsb->len, gsb->data);
190 }
191 break;
192
193 case ACPI_GSB_ACCESS_ATTRIB_MULTIBYTE:
194 if (action == ACPI_READ) {
195 status = acpi_gsb_i2c_read_bytes(&client, command,
196 gsb->data, info->access_length);
197 if (status > 0)
198 status = 0;
199 } else {
200 status = acpi_gsb_i2c_write_bytes(&client, command,
201 gsb->data, info->access_length);
202 }
203 break;
204
205 default:
206 pr_info("protocol(0x%02x) is not supported.\n", accessor_type);
207 ret = AE_BAD_PARAMETER;
208 goto err;
209 }
210
211 gsb->status = status;
212
213 err:
214 ACPI_FREE(ares);
215 return ret;
216}
217
218
219int acpi_i2c_install_space_handler(struct i2c_adapter *adapter)
220{
221 acpi_handle handle = ACPI_HANDLE(adapter->dev.parent);
222 struct acpi_i2c_handler_data *data;
223 acpi_status status;
224
225 if (!handle)
226 return -ENODEV;
227
228 data = kzalloc(sizeof(struct acpi_i2c_handler_data),
229 GFP_KERNEL);
230 if (!data)
231 return -ENOMEM;
232
233 data->adapter = adapter;
234 status = acpi_bus_attach_private_data(handle, (void *)data);
235 if (ACPI_FAILURE(status)) {
236 kfree(data);
237 return -ENOMEM;
238 }
239
240 status = acpi_install_address_space_handler(handle,
241 ACPI_ADR_SPACE_GSBUS,
242 &acpi_i2c_space_handler,
243 NULL,
244 data);
245 if (ACPI_FAILURE(status)) {
246 dev_err(&adapter->dev, "Error installing i2c space handler\n");
247 acpi_bus_detach_private_data(handle);
248 kfree(data);
249 return -ENOMEM;
250 }
251
252 return 0;
253}
254
255void acpi_i2c_remove_space_handler(struct i2c_adapter *adapter)
256{
257 acpi_handle handle = ACPI_HANDLE(adapter->dev.parent);
258 struct acpi_i2c_handler_data *data;
259 acpi_status status;
260
261 if (!handle)
262 return;
263
264 acpi_remove_address_space_handler(handle,
265 ACPI_ADR_SPACE_GSBUS,
266 &acpi_i2c_space_handler);
267
268 status = acpi_bus_get_private_data(handle, (void **)&data);
269 if (ACPI_SUCCESS(status))
270 kfree(data);
271
272 acpi_bus_detach_private_data(handle);
273}
diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c
index 7c7f4b856bad..e25cb84cb297 100644
--- a/drivers/i2c/i2c-core.c
+++ b/drivers/i2c/i2c-core.c
@@ -1293,6 +1293,7 @@ exit_recovery:
1293 /* create pre-declared device nodes */ 1293 /* create pre-declared device nodes */
1294 of_i2c_register_devices(adap); 1294 of_i2c_register_devices(adap);
1295 acpi_i2c_register_devices(adap); 1295 acpi_i2c_register_devices(adap);
1296 acpi_i2c_install_space_handler(adap);
1296 1297
1297 if (adap->nr < __i2c_first_dynamic_bus_num) 1298 if (adap->nr < __i2c_first_dynamic_bus_num)
1298 i2c_scan_static_board_info(adap); 1299 i2c_scan_static_board_info(adap);
@@ -1466,6 +1467,7 @@ void i2c_del_adapter(struct i2c_adapter *adap)
1466 return; 1467 return;
1467 } 1468 }
1468 1469
1470 acpi_i2c_remove_space_handler(adap);
1469 /* Tell drivers about this removal */ 1471 /* Tell drivers about this removal */
1470 mutex_lock(&core_lock); 1472 mutex_lock(&core_lock);
1471 bus_for_each_drv(&i2c_bus_type, NULL, adap, 1473 bus_for_each_drv(&i2c_bus_type, NULL, adap,