diff options
author | Stephen Barber <smbarber@chromium.org> | 2015-06-09 07:04:45 -0400 |
---|---|---|
committer | Lee Jones <lee.jones@linaro.org> | 2015-06-15 08:18:21 -0400 |
commit | 2c7589af3c4dee844e6a4174f2aa8996cf837604 (patch) | |
tree | daa21a14a2033a96f755af7a487c20b558267393 /drivers/platform | |
parent | 062476f24aa7cf714169342cc50626fd9bbb93da (diff) |
mfd: cros_ec: add proto v3 skeleton
Add support in cros_ec.c to handle EC host command protocol v3.
For v3+, probe for maximum shared protocol version and max
request, response, and passthrough sizes. For now, this will
always fall back to v2, since there is no bus-specific code
for handling proto v3 packets.
Signed-off-by: Stephen Barber <smbarber@chromium.org>
Signed-off-by: Javier Martinez Canillas <javier.martinez@collabora.co.uk>
Reviewed-by: Gwendal Grignou <gwendal@chromium.org>
Tested-by: Gwendal Grignou <gwendal@chromium.org>
Tested-by: Heiko Stuebner <heiko@sntech.de>
Acked-by: Lee Jones <lee.jones@linaro.org>
Acked-by: Olof Johansson <olof@lixom.net>
Signed-off-by: Lee Jones <lee.jones@linaro.org>
Diffstat (limited to 'drivers/platform')
-rw-r--r-- | drivers/platform/chrome/cros_ec_lpc.c | 4 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_proto.c | 339 |
2 files changed, 307 insertions, 36 deletions
diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c index 4c7f0df33bf8..05aeb559275f 100644 --- a/drivers/platform/chrome/cros_ec_lpc.c +++ b/drivers/platform/chrome/cros_ec_lpc.c | |||
@@ -215,7 +215,11 @@ static int cros_ec_lpc_probe(struct platform_device *pdev) | |||
215 | ec_dev->ec_name = pdev->name; | 215 | ec_dev->ec_name = pdev->name; |
216 | ec_dev->phys_name = dev_name(dev); | 216 | ec_dev->phys_name = dev_name(dev); |
217 | ec_dev->cmd_xfer = cros_ec_cmd_xfer_lpc; | 217 | ec_dev->cmd_xfer = cros_ec_cmd_xfer_lpc; |
218 | ec_dev->pkt_xfer = NULL; | ||
218 | ec_dev->cmd_readmem = cros_ec_lpc_readmem; | 219 | ec_dev->cmd_readmem = cros_ec_lpc_readmem; |
220 | ec_dev->din_size = sizeof(struct ec_host_response) + | ||
221 | sizeof(struct ec_response_get_protocol_info); | ||
222 | ec_dev->dout_size = sizeof(struct ec_host_request); | ||
219 | 223 | ||
220 | ret = cros_ec_register(ec_dev); | 224 | ret = cros_ec_register(ec_dev); |
221 | if (ret) { | 225 | if (ret) { |
diff --git a/drivers/platform/chrome/cros_ec_proto.c b/drivers/platform/chrome/cros_ec_proto.c index 58e98a24fd08..990308ca384f 100644 --- a/drivers/platform/chrome/cros_ec_proto.c +++ b/drivers/platform/chrome/cros_ec_proto.c | |||
@@ -22,11 +22,100 @@ | |||
22 | 22 | ||
23 | #define EC_COMMAND_RETRIES 50 | 23 | #define EC_COMMAND_RETRIES 50 |
24 | 24 | ||
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 | |||
25 | int cros_ec_prepare_tx(struct cros_ec_device *ec_dev, | 110 | int cros_ec_prepare_tx(struct cros_ec_device *ec_dev, |
26 | struct cros_ec_command *msg) | 111 | struct cros_ec_command *msg) |
27 | { | 112 | { |
28 | uint8_t *out; | 113 | u8 *out; |
29 | int csum, i; | 114 | u8 csum; |
115 | int i; | ||
116 | |||
117 | if (ec_dev->proto_version > 2) | ||
118 | return prepare_packet(ec_dev, msg); | ||
30 | 119 | ||
31 | BUG_ON(msg->outsize > EC_PROTO2_MAX_PARAM_SIZE); | 120 | BUG_ON(msg->outsize > EC_PROTO2_MAX_PARAM_SIZE); |
32 | out = ec_dev->dout; | 121 | out = ec_dev->dout; |
@@ -36,7 +125,7 @@ int cros_ec_prepare_tx(struct cros_ec_device *ec_dev, | |||
36 | csum = out[0] + out[1] + out[2]; | 125 | csum = out[0] + out[1] + out[2]; |
37 | for (i = 0; i < msg->outsize; i++) | 126 | for (i = 0; i < msg->outsize; i++) |
38 | csum += out[EC_MSG_TX_HEADER_BYTES + i] = msg->data[i]; | 127 | csum += out[EC_MSG_TX_HEADER_BYTES + i] = msg->data[i]; |
39 | out[EC_MSG_TX_HEADER_BYTES + msg->outsize] = (uint8_t)(csum & 0xff); | 128 | out[EC_MSG_TX_HEADER_BYTES + msg->outsize] = csum; |
40 | 129 | ||
41 | return EC_MSG_TX_PROTO_BYTES + msg->outsize; | 130 | return EC_MSG_TX_PROTO_BYTES + msg->outsize; |
42 | } | 131 | } |
@@ -60,54 +149,232 @@ int cros_ec_check_result(struct cros_ec_device *ec_dev, | |||
60 | } | 149 | } |
61 | EXPORT_SYMBOL(cros_ec_check_result); | 150 | EXPORT_SYMBOL(cros_ec_check_result); |
62 | 151 | ||
63 | int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, | 152 | static int cros_ec_host_command_proto_query(struct cros_ec_device *ec_dev, |
64 | struct cros_ec_command *msg) | 153 | int devidx, |
154 | struct cros_ec_command *msg) | ||
65 | { | 155 | { |
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 | */ | ||
66 | int ret; | 162 | int ret; |
67 | 163 | ||
68 | mutex_lock(&ec_dev->lock); | 164 | if (!ec_dev->pkt_xfer) |
69 | ret = ec_dev->cmd_xfer(ec_dev, msg); | 165 | return -EPROTONOSUPPORT; |
70 | if (msg->result == EC_RES_IN_PROGRESS) { | ||
71 | int i; | ||
72 | struct cros_ec_command *status_msg; | ||
73 | struct ec_response_get_comms_status *status; | ||
74 | 166 | ||
75 | status_msg = kmalloc(sizeof(*status_msg) + sizeof(*status), | 167 | memset(msg, 0, sizeof(*msg)); |
76 | GFP_KERNEL); | 168 | msg->command = EC_CMD_PASSTHRU_OFFSET(devidx) | EC_CMD_GET_PROTOCOL_INFO; |
77 | if (!status_msg) { | 169 | msg->insize = sizeof(struct ec_response_get_protocol_info); |
78 | ret = -ENOMEM; | ||
79 | goto exit; | ||
80 | } | ||
81 | 170 | ||
82 | status_msg->version = 0; | 171 | ret = send_command(ec_dev, msg); |
83 | status_msg->command = EC_CMD_GET_COMMS_STATUS; | 172 | |
84 | status_msg->insize = sizeof(*status); | 173 | if (ret < 0) { |
85 | status_msg->outsize = 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; | ||
86 | 273 | ||
87 | /* | 274 | /* |
88 | * Query the EC's status until it's no longer busy or | 275 | * Check for PD |
89 | * we encounter an error. | ||
90 | */ | 276 | */ |
91 | for (i = 0; i < EC_COMMAND_RETRIES; i++) { | 277 | ret = cros_ec_host_command_proto_query(ec_dev, 1, proto_msg); |
92 | usleep_range(10000, 11000); | ||
93 | 278 | ||
94 | ret = ec_dev->cmd_xfer(ec_dev, status_msg); | 279 | if (ret) { |
95 | if (ret < 0) | 280 | dev_dbg(ec_dev->dev, "no PD chip found: %d\n", ret); |
96 | break; | 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); | ||
97 | 292 | ||
98 | msg->result = status_msg->result; | 293 | if (ret == 0) { |
99 | if (status_msg->result != EC_RES_SUCCESS) | 294 | /* V2 hello succeeded. */ |
100 | break; | 295 | dev_dbg(ec_dev->dev, "falling back to proto v2\n"); |
101 | 296 | ||
102 | status = (struct ec_response_get_comms_status *) | 297 | ec_dev->max_request = EC_PROTO2_MAX_PARAM_SIZE; |
103 | status_msg->data; | 298 | ec_dev->max_response = EC_PROTO2_MAX_PARAM_SIZE; |
104 | if (!(status->flags & EC_COMMS_STATUS_PROCESSING)) | 299 | ec_dev->max_passthru = 0; |
105 | break; | 300 | ec_dev->pkt_xfer = NULL; |
301 | ec_dev->din_size = EC_MSG_BYTES; | ||
302 | ec_dev->dout_size = EC_MSG_BYTES; | ||
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; | ||
106 | } | 312 | } |
313 | } | ||
107 | 314 | ||
108 | kfree(status_msg); | 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; | ||
109 | } | 322 | } |
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 | |||
110 | exit: | 331 | exit: |
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); | ||
111 | mutex_unlock(&ec_dev->lock); | 378 | mutex_unlock(&ec_dev->lock); |
112 | 379 | ||
113 | return ret; | 380 | return ret; |