Commit | Line | Data |
---|---|---|
062476f2 JMC |
1 | /* |
2 | * ChromeOS EC communication protocol helper functions | |
3 | * | |
4 | * Copyright (C) 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 | */ | |
16 | ||
17 | #include <linux/mfd/cros_ec.h> | |
18 | #include <linux/delay.h> | |
19 | #include <linux/device.h> | |
20 | #include <linux/module.h> | |
21 | #include <linux/slab.h> | |
22 | ||
23 | #define EC_COMMAND_RETRIES 50 | |
24 | ||
2c7589af SB |
25 | static int prepare_packet(struct cros_ec_device *ec_dev, |
26 | struct cros_ec_command *msg) | |
27 | { | |
28 | struct ec_host_request *request; | |
29 | u8 *out; | |
30 | int i; | |
31 | u8 csum = 0; | |
32 | ||
33 | BUG_ON(ec_dev->proto_version != EC_HOST_REQUEST_VERSION); | |
34 | BUG_ON(msg->outsize + sizeof(*request) > ec_dev->dout_size); | |
35 | ||
36 | out = ec_dev->dout; | |
37 | request = (struct ec_host_request *)out; | |
38 | request->struct_version = EC_HOST_REQUEST_VERSION; | |
39 | request->checksum = 0; | |
40 | request->command = msg->command; | |
41 | request->command_version = msg->version; | |
42 | request->reserved = 0; | |
43 | request->data_len = msg->outsize; | |
44 | ||
45 | for (i = 0; i < sizeof(*request); i++) | |
46 | csum += out[i]; | |
47 | ||
48 | /* Copy data and update checksum */ | |
49 | memcpy(out + sizeof(*request), msg->data, msg->outsize); | |
50 | for (i = 0; i < msg->outsize; i++) | |
51 | csum += msg->data[i]; | |
52 | ||
53 | request->checksum = -csum; | |
54 | ||
55 | return sizeof(*request) + msg->outsize; | |
56 | } | |
57 | ||
58 | static int send_command(struct cros_ec_device *ec_dev, | |
59 | struct cros_ec_command *msg) | |
60 | { | |
61 | int ret; | |
62 | ||
63 | if (ec_dev->proto_version > 2) | |
64 | ret = ec_dev->pkt_xfer(ec_dev, msg); | |
65 | else | |
66 | ret = ec_dev->cmd_xfer(ec_dev, msg); | |
67 | ||
68 | if (msg->result == EC_RES_IN_PROGRESS) { | |
69 | int i; | |
70 | struct cros_ec_command *status_msg; | |
71 | struct ec_response_get_comms_status *status; | |
72 | ||
73 | status_msg = kmalloc(sizeof(*status_msg) + sizeof(*status), | |
74 | GFP_KERNEL); | |
75 | if (!status_msg) | |
76 | return -ENOMEM; | |
77 | ||
78 | status_msg->version = 0; | |
79 | status_msg->command = EC_CMD_GET_COMMS_STATUS; | |
80 | status_msg->insize = sizeof(*status); | |
81 | status_msg->outsize = 0; | |
82 | ||
83 | /* | |
84 | * Query the EC's status until it's no longer busy or | |
85 | * we encounter an error. | |
86 | */ | |
87 | for (i = 0; i < EC_COMMAND_RETRIES; i++) { | |
88 | usleep_range(10000, 11000); | |
89 | ||
90 | ret = ec_dev->cmd_xfer(ec_dev, status_msg); | |
91 | if (ret < 0) | |
92 | break; | |
93 | ||
94 | msg->result = status_msg->result; | |
95 | if (status_msg->result != EC_RES_SUCCESS) | |
96 | break; | |
97 | ||
98 | status = (struct ec_response_get_comms_status *) | |
99 | status_msg->data; | |
100 | if (!(status->flags & EC_COMMS_STATUS_PROCESSING)) | |
101 | break; | |
102 | } | |
103 | ||
104 | kfree(status_msg); | |
105 | } | |
106 | ||
107 | return ret; | |
108 | } | |
109 | ||
062476f2 JMC |
110 | int cros_ec_prepare_tx(struct cros_ec_device *ec_dev, |
111 | struct cros_ec_command *msg) | |
112 | { | |
2c7589af SB |
113 | u8 *out; |
114 | u8 csum; | |
115 | int i; | |
116 | ||
117 | if (ec_dev->proto_version > 2) | |
118 | return prepare_packet(ec_dev, msg); | |
062476f2 JMC |
119 | |
120 | BUG_ON(msg->outsize > EC_PROTO2_MAX_PARAM_SIZE); | |
121 | out = ec_dev->dout; | |
122 | out[0] = EC_CMD_VERSION0 + msg->version; | |
123 | out[1] = msg->command; | |
124 | out[2] = msg->outsize; | |
125 | csum = out[0] + out[1] + out[2]; | |
126 | for (i = 0; i < msg->outsize; i++) | |
127 | csum += out[EC_MSG_TX_HEADER_BYTES + i] = msg->data[i]; | |
2c7589af | 128 | out[EC_MSG_TX_HEADER_BYTES + msg->outsize] = csum; |
062476f2 JMC |
129 | |
130 | return EC_MSG_TX_PROTO_BYTES + msg->outsize; | |
131 | } | |
132 | EXPORT_SYMBOL(cros_ec_prepare_tx); | |
133 | ||
134 | int cros_ec_check_result(struct cros_ec_device *ec_dev, | |
135 | struct cros_ec_command *msg) | |
136 | { | |
137 | switch (msg->result) { | |
138 | case EC_RES_SUCCESS: | |
139 | return 0; | |
140 | case EC_RES_IN_PROGRESS: | |
141 | dev_dbg(ec_dev->dev, "command 0x%02x in progress\n", | |
142 | msg->command); | |
143 | return -EAGAIN; | |
144 | default: | |
145 | dev_dbg(ec_dev->dev, "command 0x%02x returned %d\n", | |
146 | msg->command, msg->result); | |
147 | return 0; | |
148 | } | |
149 | } | |
150 | EXPORT_SYMBOL(cros_ec_check_result); | |
151 | ||
2c7589af SB |
152 | static int cros_ec_host_command_proto_query(struct cros_ec_device *ec_dev, |
153 | int devidx, | |
154 | struct cros_ec_command *msg) | |
062476f2 | 155 | { |
2c7589af SB |
156 | /* |
157 | * Try using v3+ to query for supported protocols. If this | |
158 | * command fails, fall back to v2. Returns the highest protocol | |
159 | * supported by the EC. | |
160 | * Also sets the max request/response/passthru size. | |
161 | */ | |
062476f2 JMC |
162 | int ret; |
163 | ||
2c7589af SB |
164 | if (!ec_dev->pkt_xfer) |
165 | return -EPROTONOSUPPORT; | |
062476f2 | 166 | |
2c7589af SB |
167 | memset(msg, 0, sizeof(*msg)); |
168 | msg->command = EC_CMD_PASSTHRU_OFFSET(devidx) | EC_CMD_GET_PROTOCOL_INFO; | |
169 | msg->insize = sizeof(struct ec_response_get_protocol_info); | |
062476f2 | 170 | |
2c7589af SB |
171 | ret = send_command(ec_dev, msg); |
172 | ||
173 | if (ret < 0) { | |
174 | dev_dbg(ec_dev->dev, | |
175 | "failed to check for EC[%d] protocol version: %d\n", | |
176 | devidx, ret); | |
177 | return ret; | |
178 | } | |
179 | ||
180 | if (devidx > 0 && msg->result == EC_RES_INVALID_COMMAND) | |
181 | return -ENODEV; | |
182 | else if (msg->result != EC_RES_SUCCESS) | |
183 | return msg->result; | |
184 | ||
185 | return 0; | |
186 | } | |
187 | ||
188 | static int cros_ec_host_command_proto_query_v2(struct cros_ec_device *ec_dev) | |
189 | { | |
190 | struct cros_ec_command *msg; | |
191 | struct ec_params_hello *hello_params; | |
192 | struct ec_response_hello *hello_response; | |
193 | int ret; | |
194 | int len = max(sizeof(*hello_params), sizeof(*hello_response)); | |
195 | ||
196 | msg = kmalloc(sizeof(*msg) + len, GFP_KERNEL); | |
197 | if (!msg) | |
198 | return -ENOMEM; | |
199 | ||
200 | msg->version = 0; | |
201 | msg->command = EC_CMD_HELLO; | |
202 | hello_params = (struct ec_params_hello *)msg->data; | |
203 | msg->outsize = sizeof(*hello_params); | |
204 | hello_response = (struct ec_response_hello *)msg->data; | |
205 | msg->insize = sizeof(*hello_response); | |
206 | ||
207 | hello_params->in_data = 0xa0b0c0d0; | |
208 | ||
209 | ret = send_command(ec_dev, msg); | |
210 | ||
211 | if (ret < 0) { | |
212 | dev_dbg(ec_dev->dev, | |
213 | "EC failed to respond to v2 hello: %d\n", | |
214 | ret); | |
215 | goto exit; | |
216 | } else if (msg->result != EC_RES_SUCCESS) { | |
217 | dev_err(ec_dev->dev, | |
218 | "EC responded to v2 hello with error: %d\n", | |
219 | msg->result); | |
220 | ret = msg->result; | |
221 | goto exit; | |
222 | } else if (hello_response->out_data != 0xa1b2c3d4) { | |
223 | dev_err(ec_dev->dev, | |
224 | "EC responded to v2 hello with bad result: %u\n", | |
225 | hello_response->out_data); | |
226 | ret = -EBADMSG; | |
227 | goto exit; | |
228 | } | |
229 | ||
230 | ret = 0; | |
231 | ||
232 | exit: | |
233 | kfree(msg); | |
234 | return ret; | |
235 | } | |
236 | ||
237 | int cros_ec_query_all(struct cros_ec_device *ec_dev) | |
238 | { | |
239 | struct device *dev = ec_dev->dev; | |
240 | struct cros_ec_command *proto_msg; | |
241 | struct ec_response_get_protocol_info *proto_info; | |
242 | int ret; | |
243 | ||
244 | proto_msg = kzalloc(sizeof(*proto_msg) + sizeof(*proto_info), | |
245 | GFP_KERNEL); | |
246 | if (!proto_msg) | |
247 | return -ENOMEM; | |
248 | ||
249 | /* First try sending with proto v3. */ | |
250 | ec_dev->proto_version = 3; | |
251 | ret = cros_ec_host_command_proto_query(ec_dev, 0, proto_msg); | |
252 | ||
253 | if (ret == 0) { | |
254 | proto_info = (struct ec_response_get_protocol_info *) | |
255 | proto_msg->data; | |
256 | ec_dev->max_request = proto_info->max_request_packet_size - | |
257 | sizeof(struct ec_host_request); | |
258 | ec_dev->max_response = proto_info->max_response_packet_size - | |
259 | sizeof(struct ec_host_response); | |
260 | ec_dev->proto_version = | |
261 | min(EC_HOST_REQUEST_VERSION, | |
262 | fls(proto_info->protocol_versions) - 1); | |
263 | dev_dbg(ec_dev->dev, | |
264 | "using proto v%u\n", | |
265 | ec_dev->proto_version); | |
266 | ||
267 | ec_dev->din_size = ec_dev->max_response + | |
268 | sizeof(struct ec_host_response) + | |
269 | EC_MAX_RESPONSE_OVERHEAD; | |
270 | ec_dev->dout_size = ec_dev->max_request + | |
271 | sizeof(struct ec_host_request) + | |
272 | EC_MAX_REQUEST_OVERHEAD; | |
062476f2 JMC |
273 | |
274 | /* | |
2c7589af | 275 | * Check for PD |
062476f2 | 276 | */ |
2c7589af | 277 | ret = cros_ec_host_command_proto_query(ec_dev, 1, proto_msg); |
062476f2 | 278 | |
2c7589af SB |
279 | if (ret) { |
280 | dev_dbg(ec_dev->dev, "no PD chip found: %d\n", ret); | |
281 | ec_dev->max_passthru = 0; | |
282 | } else { | |
283 | dev_dbg(ec_dev->dev, "found PD chip\n"); | |
284 | ec_dev->max_passthru = | |
285 | proto_info->max_request_packet_size - | |
286 | sizeof(struct ec_host_request); | |
287 | } | |
288 | } else { | |
289 | /* Try querying with a v2 hello message. */ | |
290 | ec_dev->proto_version = 2; | |
291 | ret = cros_ec_host_command_proto_query_v2(ec_dev); | |
062476f2 | 292 | |
2c7589af SB |
293 | if (ret == 0) { |
294 | /* V2 hello succeeded. */ | |
295 | dev_dbg(ec_dev->dev, "falling back to proto v2\n"); | |
062476f2 | 296 | |
2c7589af SB |
297 | ec_dev->max_request = EC_PROTO2_MAX_PARAM_SIZE; |
298 | ec_dev->max_response = EC_PROTO2_MAX_PARAM_SIZE; | |
299 | ec_dev->max_passthru = 0; | |
300 | ec_dev->pkt_xfer = NULL; | |
5d749d0b GG |
301 | ec_dev->din_size = EC_PROTO2_MSG_BYTES; |
302 | ec_dev->dout_size = EC_PROTO2_MSG_BYTES; | |
2c7589af SB |
303 | } else { |
304 | /* | |
305 | * It's possible for a test to occur too early when | |
306 | * the EC isn't listening. If this happens, we'll | |
307 | * test later when the first command is run. | |
308 | */ | |
309 | ec_dev->proto_version = EC_PROTO_VERSION_UNKNOWN; | |
310 | dev_dbg(ec_dev->dev, "EC query failed: %d\n", ret); | |
311 | goto exit; | |
062476f2 | 312 | } |
2c7589af | 313 | } |
062476f2 | 314 | |
2c7589af SB |
315 | devm_kfree(dev, ec_dev->din); |
316 | devm_kfree(dev, ec_dev->dout); | |
317 | ||
318 | ec_dev->din = devm_kzalloc(dev, ec_dev->din_size, GFP_KERNEL); | |
319 | if (!ec_dev->din) { | |
320 | ret = -ENOMEM; | |
321 | goto exit; | |
062476f2 | 322 | } |
2c7589af SB |
323 | |
324 | ec_dev->dout = devm_kzalloc(dev, ec_dev->dout_size, GFP_KERNEL); | |
325 | if (!ec_dev->dout) { | |
326 | devm_kfree(dev, ec_dev->din); | |
327 | ret = -ENOMEM; | |
328 | goto exit; | |
329 | } | |
330 | ||
062476f2 | 331 | exit: |
2c7589af SB |
332 | kfree(proto_msg); |
333 | return ret; | |
334 | } | |
335 | EXPORT_SYMBOL(cros_ec_query_all); | |
336 | ||
337 | int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, | |
338 | struct cros_ec_command *msg) | |
339 | { | |
340 | int ret; | |
341 | ||
342 | mutex_lock(&ec_dev->lock); | |
343 | if (ec_dev->proto_version == EC_PROTO_VERSION_UNKNOWN) { | |
344 | ret = cros_ec_query_all(ec_dev); | |
345 | if (ret) { | |
346 | dev_err(ec_dev->dev, | |
347 | "EC version unknown and query failed; aborting command\n"); | |
348 | mutex_unlock(&ec_dev->lock); | |
349 | return ret; | |
350 | } | |
351 | } | |
352 | ||
353 | if (msg->insize > ec_dev->max_response) { | |
354 | dev_dbg(ec_dev->dev, "clamping message receive buffer\n"); | |
355 | msg->insize = ec_dev->max_response; | |
356 | } | |
357 | ||
358 | if (msg->command < EC_CMD_PASSTHRU_OFFSET(1)) { | |
359 | if (msg->outsize > ec_dev->max_request) { | |
360 | dev_err(ec_dev->dev, | |
361 | "request of size %u is too big (max: %u)\n", | |
362 | msg->outsize, | |
363 | ec_dev->max_request); | |
364 | mutex_unlock(&ec_dev->lock); | |
365 | return -EMSGSIZE; | |
366 | } | |
367 | } else { | |
368 | if (msg->outsize > ec_dev->max_passthru) { | |
369 | dev_err(ec_dev->dev, | |
370 | "passthru rq of size %u is too big (max: %u)\n", | |
371 | msg->outsize, | |
372 | ec_dev->max_passthru); | |
373 | mutex_unlock(&ec_dev->lock); | |
374 | return -EMSGSIZE; | |
375 | } | |
376 | } | |
377 | ret = send_command(ec_dev, msg); | |
062476f2 JMC |
378 | mutex_unlock(&ec_dev->lock); |
379 | ||
380 | return ret; | |
381 | } | |
382 | EXPORT_SYMBOL(cros_ec_cmd_xfer); |