/src/u-boot/drivers/misc/cros_ec_lpc.c
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0+ |
2 | | /* |
3 | | * Chromium OS cros_ec driver - LPC interface |
4 | | * |
5 | | * Copyright (c) 2012 The Chromium OS Authors. |
6 | | */ |
7 | | |
8 | | /* |
9 | | * The Matrix Keyboard Protocol driver handles talking to the keyboard |
10 | | * controller chip. Mostly this is for keyboard functions, but some other |
11 | | * things have slipped in, so we provide generic services to talk to the |
12 | | * KBC. |
13 | | */ |
14 | | |
15 | | #include <dm.h> |
16 | | #include <command.h> |
17 | | #include <cros_ec.h> |
18 | | #include <log.h> |
19 | | #include <time.h> |
20 | | #include <asm/io.h> |
21 | | |
22 | | #ifdef DEBUG_TRACE |
23 | | #define debug_trace(fmt, b...) debug(fmt, ##b) |
24 | | #else |
25 | | #define debug_trace(fmt, b...) |
26 | | #endif |
27 | | |
28 | | /* Timeout waiting for a flash erase command to complete */ |
29 | | static const int CROS_EC_CMD_TIMEOUT_MS = 5000; |
30 | | |
31 | | static int wait_for_sync(struct cros_ec_dev *dev) |
32 | 0 | { |
33 | 0 | unsigned long start; |
34 | |
|
35 | 0 | start = get_timer(0); |
36 | 0 | while (inb(EC_LPC_ADDR_HOST_CMD) & EC_LPC_STATUS_BUSY_MASK) { |
37 | 0 | if (get_timer(start) > CROS_EC_CMD_TIMEOUT_MS) { |
38 | 0 | debug("%s: Timeout waiting for CROS_EC sync\n", |
39 | 0 | __func__); |
40 | 0 | return -1; |
41 | 0 | } |
42 | 0 | } |
43 | | |
44 | 0 | return 0; |
45 | 0 | } |
46 | | |
47 | | int cros_ec_lpc_packet(struct udevice *udev, int out_bytes, int in_bytes) |
48 | 0 | { |
49 | 0 | struct cros_ec_dev *dev = dev_get_uclass_priv(udev); |
50 | 0 | uint8_t *d; |
51 | 0 | int i; |
52 | |
|
53 | 0 | if (out_bytes > EC_LPC_HOST_PACKET_SIZE) |
54 | 0 | return log_msg_ret("Cannot send that many bytes\n", -E2BIG); |
55 | | |
56 | 0 | if (in_bytes > EC_LPC_HOST_PACKET_SIZE) |
57 | 0 | return log_msg_ret("Cannot receive that many bytes\n", -E2BIG); |
58 | | |
59 | 0 | if (wait_for_sync(dev)) |
60 | 0 | return log_msg_ret("Timeout waiting ready\n", -ETIMEDOUT); |
61 | | |
62 | | /* Write data */ |
63 | 0 | for (i = 0, d = (uint8_t *)dev->dout; i < out_bytes; i++, d++) |
64 | 0 | outb(*d, EC_LPC_ADDR_HOST_PACKET + i); |
65 | | |
66 | | /* Start the command */ |
67 | 0 | outb(EC_COMMAND_PROTOCOL_3, EC_LPC_ADDR_HOST_CMD); |
68 | |
|
69 | 0 | if (wait_for_sync(dev)) |
70 | 0 | return log_msg_ret("Timeout waiting ready\n", -ETIMEDOUT); |
71 | | |
72 | | /* Read back args */ |
73 | 0 | for (i = 0, d = dev->din; i < in_bytes; i++, d++) |
74 | 0 | *d = inb(EC_LPC_ADDR_HOST_PACKET + i); |
75 | |
|
76 | 0 | return in_bytes; |
77 | 0 | } |
78 | | |
79 | | int cros_ec_lpc_command(struct udevice *udev, uint8_t cmd, int cmd_version, |
80 | | const uint8_t *dout, int dout_len, |
81 | | uint8_t **dinp, int din_len) |
82 | 0 | { |
83 | 0 | struct cros_ec_dev *dev = dev_get_uclass_priv(udev); |
84 | 0 | const int cmd_addr = EC_LPC_ADDR_HOST_CMD; |
85 | 0 | const int data_addr = EC_LPC_ADDR_HOST_DATA; |
86 | 0 | const int args_addr = EC_LPC_ADDR_HOST_ARGS; |
87 | 0 | const int param_addr = EC_LPC_ADDR_HOST_PARAM; |
88 | |
|
89 | 0 | struct ec_lpc_host_args args; |
90 | 0 | uint8_t *d; |
91 | 0 | int csum; |
92 | 0 | int i; |
93 | |
|
94 | 0 | if (dout_len > EC_PROTO2_MAX_PARAM_SIZE) { |
95 | 0 | debug("%s: Cannot send %d bytes\n", __func__, dout_len); |
96 | 0 | return -1; |
97 | 0 | } |
98 | | |
99 | | /* Fill in args */ |
100 | 0 | args.flags = EC_HOST_ARGS_FLAG_FROM_HOST; |
101 | 0 | args.command_version = cmd_version; |
102 | 0 | args.data_size = dout_len; |
103 | | |
104 | | /* Calculate checksum */ |
105 | 0 | csum = cmd + args.flags + args.command_version + args.data_size; |
106 | 0 | for (i = 0, d = (uint8_t *)dout; i < dout_len; i++, d++) |
107 | 0 | csum += *d; |
108 | |
|
109 | 0 | args.checksum = (uint8_t)csum; |
110 | |
|
111 | 0 | if (wait_for_sync(dev)) { |
112 | 0 | debug("%s: Timeout waiting ready\n", __func__); |
113 | 0 | return -1; |
114 | 0 | } |
115 | | |
116 | | /* Write args */ |
117 | 0 | for (i = 0, d = (uint8_t *)&args; i < sizeof(args); i++, d++) |
118 | 0 | outb(*d, args_addr + i); |
119 | | |
120 | | /* Write data, if any */ |
121 | 0 | debug_trace("cmd: %02x, ver: %02x", cmd, cmd_version); |
122 | 0 | for (i = 0, d = (uint8_t *)dout; i < dout_len; i++, d++) { |
123 | 0 | outb(*d, param_addr + i); |
124 | 0 | debug_trace("%02x ", *d); |
125 | 0 | } |
126 | |
|
127 | 0 | outb(cmd, cmd_addr); |
128 | 0 | debug_trace("\n"); |
129 | |
|
130 | 0 | if (wait_for_sync(dev)) { |
131 | 0 | debug("%s: Timeout waiting for response\n", __func__); |
132 | 0 | return -1; |
133 | 0 | } |
134 | | |
135 | | /* Check result */ |
136 | 0 | i = inb(data_addr); |
137 | 0 | if (i) { |
138 | 0 | debug("%s: CROS_EC result code %d\n", __func__, i); |
139 | 0 | return -i; |
140 | 0 | } |
141 | | |
142 | | /* Read back args */ |
143 | 0 | for (i = 0, d = (uint8_t *)&args; i < sizeof(args); i++, d++) |
144 | 0 | *d = inb(args_addr + i); |
145 | | |
146 | | /* |
147 | | * If EC didn't modify args flags, then somehow we sent a new-style |
148 | | * command to an old EC, which means it would have read its params |
149 | | * from the wrong place. |
150 | | */ |
151 | 0 | if (!(args.flags & EC_HOST_ARGS_FLAG_TO_HOST)) { |
152 | 0 | debug("%s: CROS_EC protocol mismatch\n", __func__); |
153 | 0 | return -EC_RES_INVALID_RESPONSE; |
154 | 0 | } |
155 | | |
156 | 0 | if (args.data_size > din_len) { |
157 | 0 | debug("%s: CROS_EC returned too much data %d > %d\n", |
158 | 0 | __func__, args.data_size, din_len); |
159 | 0 | return -EC_RES_INVALID_RESPONSE; |
160 | 0 | } |
161 | | |
162 | | /* Read data, if any */ |
163 | 0 | for (i = 0, d = (uint8_t *)dev->din; i < args.data_size; i++, d++) { |
164 | 0 | *d = inb(param_addr + i); |
165 | 0 | debug_trace("%02x ", *d); |
166 | 0 | } |
167 | 0 | debug_trace("\n"); |
168 | | |
169 | | /* Verify checksum */ |
170 | 0 | csum = cmd + args.flags + args.command_version + args.data_size; |
171 | 0 | for (i = 0, d = (uint8_t *)dev->din; i < args.data_size; i++, d++) |
172 | 0 | csum += *d; |
173 | |
|
174 | 0 | if (args.checksum != (uint8_t)csum) { |
175 | 0 | debug("%s: CROS_EC response has invalid checksum\n", __func__); |
176 | 0 | return -EC_RES_INVALID_CHECKSUM; |
177 | 0 | } |
178 | 0 | *dinp = dev->din; |
179 | | |
180 | | /* Return actual amount of data received */ |
181 | 0 | return args.data_size; |
182 | 0 | } |
183 | | |
184 | | /** |
185 | | * Initialize LPC protocol. |
186 | | * |
187 | | * @param dev CROS_EC device |
188 | | * @param blob Device tree blob |
189 | | * Return: 0 if ok, -1 on error |
190 | | */ |
191 | | int cros_ec_lpc_init(struct cros_ec_dev *dev, const void *blob) |
192 | 0 | { |
193 | 0 | int byte, i; |
194 | | |
195 | | /* See if we can find an EC at the other end */ |
196 | 0 | byte = 0xff; |
197 | 0 | byte &= inb(EC_LPC_ADDR_HOST_CMD); |
198 | 0 | byte &= inb(EC_LPC_ADDR_HOST_DATA); |
199 | 0 | for (i = 0; i < EC_PROTO2_MAX_PARAM_SIZE && (byte == 0xff); i++) |
200 | 0 | byte &= inb(EC_LPC_ADDR_HOST_PARAM + i); |
201 | 0 | if (byte == 0xff) { |
202 | 0 | debug("%s: CROS_EC device not found on LPC bus\n", |
203 | 0 | __func__); |
204 | 0 | return -1; |
205 | 0 | } |
206 | | |
207 | 0 | return 0; |
208 | 0 | } |
209 | | |
210 | | /* Return the byte of EC switch states */ |
211 | | static int cros_ec_lpc_get_switches(struct udevice *dev) |
212 | 0 | { |
213 | 0 | return inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_SWITCHES); |
214 | 0 | } |
215 | | |
216 | | /* |
217 | | * Test if LPC command args are supported. |
218 | | * |
219 | | * The cheapest way to do this is by looking for the memory-mapped |
220 | | * flag. This is faster than sending a new-style 'hello' command and |
221 | | * seeing whether the EC sets the EC_HOST_ARGS_FLAG_FROM_HOST flag |
222 | | * in args when it responds. |
223 | | */ |
224 | | static int cros_ec_lpc_check_version(struct udevice *dev) |
225 | 0 | { |
226 | 0 | if (inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID) == 'E' && |
227 | 0 | inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID + 1) |
228 | 0 | == 'C' && |
229 | 0 | (inb(EC_LPC_ADDR_MEMMAP + |
230 | 0 | EC_MEMMAP_HOST_CMD_FLAGS) & |
231 | 0 | EC_HOST_CMD_FLAG_LPC_ARGS_SUPPORTED)) { |
232 | 0 | return 0; |
233 | 0 | } |
234 | | |
235 | 0 | printf("%s: ERROR: old EC interface not supported\n", __func__); |
236 | 0 | return -1; |
237 | 0 | } |
238 | | |
239 | | static int cros_ec_probe(struct udevice *dev) |
240 | 0 | { |
241 | 0 | return cros_ec_register(dev); |
242 | 0 | } |
243 | | |
244 | | static struct dm_cros_ec_ops cros_ec_ops = { |
245 | | .packet = cros_ec_lpc_packet, |
246 | | .command = cros_ec_lpc_command, |
247 | | .check_version = cros_ec_lpc_check_version, |
248 | | .get_switches = cros_ec_lpc_get_switches, |
249 | | }; |
250 | | |
251 | | static const struct udevice_id cros_ec_ids[] = { |
252 | | { .compatible = "google,cros-ec-lpc" }, |
253 | | { } |
254 | | }; |
255 | | |
256 | | U_BOOT_DRIVER(google_cros_ec_lpc) = { |
257 | | .name = "google_cros_ec_lpc", |
258 | | .id = UCLASS_CROS_EC, |
259 | | .of_match = cros_ec_ids, |
260 | | .probe = cros_ec_probe, |
261 | | .ops = &cros_ec_ops, |
262 | | }; |