aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/platform/chrome/cros_ec_lpc.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2015-04-26 16:36:02 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2015-04-26 16:36:02 -0400
commit36a8032d77649430f5ef11fbf0df2bb026be0b04 (patch)
tree18e386a957bde5f71e9efd2cddf1d8aadafe48e4 /drivers/platform/chrome/cros_ec_lpc.c
parent7f9f44308c8993c9ab8078d174dad34bea3e82d7 (diff)
parent96cba9b00e297303774bec59e192064d20adeb3d (diff)
Merge tag 'chrome-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/olof/chrome-platform
Pull chrome platform updates from Olof Johansson: "Here's a set of updates to the Chrome OS platform drivers for this merge window. Main new things this cycle is: - Driver changes to expose the lightbar to users. With this, you can make your own blinkenlights on Chromebook Pixels. - Changes in the way that the atmel_mxt trackpads are probed. The laptop driver is trying to be smart and not instantiate the devices that don't answer to probe. For the trackpad that can come up in two modes (bootloader or regular), this gets complicated since the driver already knows how to handle the two modes including the actual addresses used. So now the laptop driver needs to know more too, instantiating the regular address even if the bootloader one is the probe that passed. - mfd driver improvements by Javier Martines Canillas, and a few bugfixes from him, kbuild and myself" * tag 'chrome-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/olof/chrome-platform: platform/chrome: chromeos_laptop - instantiate Atmel at primary address platform/chrome: cros_ec_lpc - Depend on X86 || COMPILE_TEST platform/chrome: cros_ec_lpc - Include linux/io.h header file platform/chrome: fix platform_no_drv_owner.cocci warnings platform/chrome: cros_ec_lightbar - fix duplicate const warning platform/chrome: cros_ec_dev - fix Unknown escape '%' warning platform/chrome: Expose Chrome OS Lightbar to users platform/chrome: Create sysfs attributes for the ChromeOS EC mfd: cros_ec: Instantiate ChromeOS EC character device platform/chrome: Add Chrome OS EC userspace device interface platform/chrome: Add cros_ec_lpc driver for x86 devices mfd: cros_ec: Add char dev and virtual dev pointers mfd: cros_ec: Use fixed size arrays to transfer data with the EC
Diffstat (limited to 'drivers/platform/chrome/cros_ec_lpc.c')
-rw-r--r--drivers/platform/chrome/cros_ec_lpc.c319
1 files changed, 319 insertions, 0 deletions
diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c
new file mode 100644
index 000000000000..8f9ac4d7bbd0
--- /dev/null
+++ b/drivers/platform/chrome/cros_ec_lpc.c
@@ -0,0 +1,319 @@
1/*
2 * cros_ec_lpc - LPC access to the Chrome OS Embedded Controller
3 *
4 * Copyright (C) 2012-2015 Google, Inc
5 *
6 * This software is licensed under the terms of the GNU General Public
7 * License version 2, as published by the Free Software Foundation, and
8 * may be copied, distributed, and modified under those terms.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * This driver uses the Chrome OS EC byte-level message-based protocol for
16 * communicating the keyboard state (which keys are pressed) from a keyboard EC
17 * to the AP over some bus (such as i2c, lpc, spi). The EC does debouncing,
18 * but everything else (including deghosting) is done here. The main
19 * motivation for this is to keep the EC firmware as simple as possible, since
20 * it cannot be easily upgraded and EC flash/IRAM space is relatively
21 * expensive.
22 */
23
24#include <linux/dmi.h>
25#include <linux/delay.h>
26#include <linux/io.h>
27#include <linux/mfd/cros_ec.h>
28#include <linux/mfd/cros_ec_commands.h>
29#include <linux/module.h>
30#include <linux/platform_device.h>
31#include <linux/printk.h>
32
33#define DRV_NAME "cros_ec_lpc"
34
35static int ec_response_timed_out(void)
36{
37 unsigned long one_second = jiffies + HZ;
38
39 usleep_range(200, 300);
40 do {
41 if (!(inb(EC_LPC_ADDR_HOST_CMD) & EC_LPC_STATUS_BUSY_MASK))
42 return 0;
43 usleep_range(100, 200);
44 } while (time_before(jiffies, one_second));
45
46 return 1;
47}
48
49static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec,
50 struct cros_ec_command *msg)
51{
52 struct ec_lpc_host_args args;
53 int csum;
54 int i;
55 int ret = 0;
56
57 if (msg->outsize > EC_PROTO2_MAX_PARAM_SIZE ||
58 msg->insize > EC_PROTO2_MAX_PARAM_SIZE) {
59 dev_err(ec->dev,
60 "invalid buffer sizes (out %d, in %d)\n",
61 msg->outsize, msg->insize);
62 return -EINVAL;
63 }
64
65 /* Now actually send the command to the EC and get the result */
66 args.flags = EC_HOST_ARGS_FLAG_FROM_HOST;
67 args.command_version = msg->version;
68 args.data_size = msg->outsize;
69
70 /* Initialize checksum */
71 csum = msg->command + args.flags +
72 args.command_version + args.data_size;
73
74 /* Copy data and update checksum */
75 for (i = 0; i < msg->outsize; i++) {
76 outb(msg->outdata[i], EC_LPC_ADDR_HOST_PARAM + i);
77 csum += msg->outdata[i];
78 }
79
80 /* Finalize checksum and write args */
81 args.checksum = csum & 0xFF;
82 outb(args.flags, EC_LPC_ADDR_HOST_ARGS);
83 outb(args.command_version, EC_LPC_ADDR_HOST_ARGS + 1);
84 outb(args.data_size, EC_LPC_ADDR_HOST_ARGS + 2);
85 outb(args.checksum, EC_LPC_ADDR_HOST_ARGS + 3);
86
87 /* Here we go */
88 outb(msg->command, EC_LPC_ADDR_HOST_CMD);
89
90 if (ec_response_timed_out()) {
91 dev_warn(ec->dev, "EC responsed timed out\n");
92 ret = -EIO;
93 goto done;
94 }
95
96 /* Check result */
97 msg->result = inb(EC_LPC_ADDR_HOST_DATA);
98
99 switch (msg->result) {
100 case EC_RES_SUCCESS:
101 break;
102 case EC_RES_IN_PROGRESS:
103 ret = -EAGAIN;
104 dev_dbg(ec->dev, "command 0x%02x in progress\n",
105 msg->command);
106 goto done;
107 default:
108 dev_dbg(ec->dev, "command 0x%02x returned %d\n",
109 msg->command, msg->result);
110 }
111
112 /* Read back args */
113 args.flags = inb(EC_LPC_ADDR_HOST_ARGS);
114 args.command_version = inb(EC_LPC_ADDR_HOST_ARGS + 1);
115 args.data_size = inb(EC_LPC_ADDR_HOST_ARGS + 2);
116 args.checksum = inb(EC_LPC_ADDR_HOST_ARGS + 3);
117
118 if (args.data_size > msg->insize) {
119 dev_err(ec->dev,
120 "packet too long (%d bytes, expected %d)",
121 args.data_size, msg->insize);
122 ret = -ENOSPC;
123 goto done;
124 }
125
126 /* Start calculating response checksum */
127 csum = msg->command + args.flags +
128 args.command_version + args.data_size;
129
130 /* Read response and update checksum */
131 for (i = 0; i < args.data_size; i++) {
132 msg->indata[i] = inb(EC_LPC_ADDR_HOST_PARAM + i);
133 csum += msg->indata[i];
134 }
135
136 /* Verify checksum */
137 if (args.checksum != (csum & 0xFF)) {
138 dev_err(ec->dev,
139 "bad packet checksum, expected %02x, got %02x\n",
140 args.checksum, csum & 0xFF);
141 ret = -EBADMSG;
142 goto done;
143 }
144
145 /* Return actual amount of data received */
146 ret = args.data_size;
147done:
148 return ret;
149}
150
151/* Returns num bytes read, or negative on error. Doesn't need locking. */
152static int cros_ec_lpc_readmem(struct cros_ec_device *ec, unsigned int offset,
153 unsigned int bytes, void *dest)
154{
155 int i = offset;
156 char *s = dest;
157 int cnt = 0;
158
159 if (offset >= EC_MEMMAP_SIZE - bytes)
160 return -EINVAL;
161
162 /* fixed length */
163 if (bytes) {
164 for (; cnt < bytes; i++, s++, cnt++)
165 *s = inb(EC_LPC_ADDR_MEMMAP + i);
166 return cnt;
167 }
168
169 /* string */
170 for (; i < EC_MEMMAP_SIZE; i++, s++) {
171 *s = inb(EC_LPC_ADDR_MEMMAP + i);
172 cnt++;
173 if (!*s)
174 break;
175 }
176
177 return cnt;
178}
179
180static int cros_ec_lpc_probe(struct platform_device *pdev)
181{
182 struct device *dev = &pdev->dev;
183 struct cros_ec_device *ec_dev;
184 int ret;
185
186 if (!devm_request_region(dev, EC_LPC_ADDR_MEMMAP, EC_MEMMAP_SIZE,
187 dev_name(dev))) {
188 dev_err(dev, "couldn't reserve memmap region\n");
189 return -EBUSY;
190 }
191
192 if ((inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID) != 'E') ||
193 (inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID + 1) != 'C')) {
194 dev_err(dev, "EC ID not detected\n");
195 return -ENODEV;
196 }
197
198 if (!devm_request_region(dev, EC_HOST_CMD_REGION0,
199 EC_HOST_CMD_REGION_SIZE, dev_name(dev))) {
200 dev_err(dev, "couldn't reserve region0\n");
201 return -EBUSY;
202 }
203 if (!devm_request_region(dev, EC_HOST_CMD_REGION1,
204 EC_HOST_CMD_REGION_SIZE, dev_name(dev))) {
205 dev_err(dev, "couldn't reserve region1\n");
206 return -EBUSY;
207 }
208
209 ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL);
210 if (!ec_dev)
211 return -ENOMEM;
212
213 platform_set_drvdata(pdev, ec_dev);
214 ec_dev->dev = dev;
215 ec_dev->ec_name = pdev->name;
216 ec_dev->phys_name = dev_name(dev);
217 ec_dev->parent = dev;
218 ec_dev->cmd_xfer = cros_ec_cmd_xfer_lpc;
219 ec_dev->cmd_readmem = cros_ec_lpc_readmem;
220
221 ret = cros_ec_register(ec_dev);
222 if (ret) {
223 dev_err(dev, "couldn't register ec_dev (%d)\n", ret);
224 return ret;
225 }
226
227 return 0;
228}
229
230static int cros_ec_lpc_remove(struct platform_device *pdev)
231{
232 struct cros_ec_device *ec_dev;
233
234 ec_dev = platform_get_drvdata(pdev);
235 cros_ec_remove(ec_dev);
236
237 return 0;
238}
239
240static struct dmi_system_id cros_ec_lpc_dmi_table[] __initdata = {
241 {
242 /*
243 * Today all Chromebooks/boxes ship with Google_* as version and
244 * coreboot as bios vendor. No other systems with this
245 * combination are known to date.
246 */
247 .matches = {
248 DMI_MATCH(DMI_BIOS_VENDOR, "coreboot"),
249 DMI_MATCH(DMI_BIOS_VERSION, "Google_"),
250 },
251 },
252 {
253 /* x86-link, the Chromebook Pixel. */
254 .matches = {
255 DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"),
256 DMI_MATCH(DMI_PRODUCT_NAME, "Link"),
257 },
258 },
259 {
260 /* x86-peppy, the Acer C720 Chromebook. */
261 .matches = {
262 DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
263 DMI_MATCH(DMI_PRODUCT_NAME, "Peppy"),
264 },
265 },
266 { /* sentinel */ }
267};
268MODULE_DEVICE_TABLE(dmi, cros_ec_lpc_dmi_table);
269
270static struct platform_driver cros_ec_lpc_driver = {
271 .driver = {
272 .name = DRV_NAME,
273 },
274 .probe = cros_ec_lpc_probe,
275 .remove = cros_ec_lpc_remove,
276};
277
278static struct platform_device cros_ec_lpc_device = {
279 .name = DRV_NAME
280};
281
282static int __init cros_ec_lpc_init(void)
283{
284 int ret;
285
286 if (!dmi_check_system(cros_ec_lpc_dmi_table)) {
287 pr_err(DRV_NAME ": unsupported system.\n");
288 return -ENODEV;
289 }
290
291 /* Register the driver */
292 ret = platform_driver_register(&cros_ec_lpc_driver);
293 if (ret) {
294 pr_err(DRV_NAME ": can't register driver: %d\n", ret);
295 return ret;
296 }
297
298 /* Register the device, and it'll get hooked up automatically */
299 ret = platform_device_register(&cros_ec_lpc_device);
300 if (ret) {
301 pr_err(DRV_NAME ": can't register device: %d\n", ret);
302 platform_driver_unregister(&cros_ec_lpc_driver);
303 return ret;
304 }
305
306 return 0;
307}
308
309static void __exit cros_ec_lpc_exit(void)
310{
311 platform_device_unregister(&cros_ec_lpc_device);
312 platform_driver_unregister(&cros_ec_lpc_driver);
313}
314
315module_init(cros_ec_lpc_init);
316module_exit(cros_ec_lpc_exit);
317
318MODULE_LICENSE("GPL");
319MODULE_DESCRIPTION("ChromeOS EC LPC driver");