diff options
Diffstat (limited to 'drivers/platform/chrome/wilco_ec/mailbox.c')
-rw-r--r-- | drivers/platform/chrome/wilco_ec/mailbox.c | 237 |
1 files changed, 237 insertions, 0 deletions
diff --git a/drivers/platform/chrome/wilco_ec/mailbox.c b/drivers/platform/chrome/wilco_ec/mailbox.c new file mode 100644 index 000000000000..f6ff29a11f1a --- /dev/null +++ b/drivers/platform/chrome/wilco_ec/mailbox.c | |||
@@ -0,0 +1,237 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0 | ||
2 | /* | ||
3 | * Mailbox interface for Wilco Embedded Controller | ||
4 | * | ||
5 | * Copyright 2018 Google LLC | ||
6 | * | ||
7 | * The Wilco EC is similar to a typical ChromeOS embedded controller. | ||
8 | * It uses the same MEC based low-level communication and a similar | ||
9 | * protocol, but with some important differences. The EC firmware does | ||
10 | * not support the same mailbox commands so it is not registered as a | ||
11 | * cros_ec device type. | ||
12 | * | ||
13 | * Most messages follow a standard format, but there are some exceptions | ||
14 | * and an interface is provided to do direct/raw transactions that do not | ||
15 | * make assumptions about byte placement. | ||
16 | */ | ||
17 | |||
18 | #include <linux/delay.h> | ||
19 | #include <linux/device.h> | ||
20 | #include <linux/io.h> | ||
21 | #include <linux/platform_data/wilco-ec.h> | ||
22 | #include <linux/platform_device.h> | ||
23 | |||
24 | #include "../cros_ec_lpc_mec.h" | ||
25 | |||
26 | /* Version of mailbox interface */ | ||
27 | #define EC_MAILBOX_VERSION 0 | ||
28 | |||
29 | /* Command to start mailbox transaction */ | ||
30 | #define EC_MAILBOX_START_COMMAND 0xda | ||
31 | |||
32 | /* Version of EC protocol */ | ||
33 | #define EC_MAILBOX_PROTO_VERSION 3 | ||
34 | |||
35 | /* Number of header bytes to be counted as data bytes */ | ||
36 | #define EC_MAILBOX_DATA_EXTRA 2 | ||
37 | |||
38 | /* Maximum timeout */ | ||
39 | #define EC_MAILBOX_TIMEOUT HZ | ||
40 | |||
41 | /* EC response flags */ | ||
42 | #define EC_CMDR_DATA BIT(0) /* Data ready for host to read */ | ||
43 | #define EC_CMDR_PENDING BIT(1) /* Write pending to EC */ | ||
44 | #define EC_CMDR_BUSY BIT(2) /* EC is busy processing a command */ | ||
45 | #define EC_CMDR_CMD BIT(3) /* Last host write was a command */ | ||
46 | |||
47 | /** | ||
48 | * wilco_ec_response_timed_out() - Wait for EC response. | ||
49 | * @ec: EC device. | ||
50 | * | ||
51 | * Return: true if EC timed out, false if EC did not time out. | ||
52 | */ | ||
53 | static bool wilco_ec_response_timed_out(struct wilco_ec_device *ec) | ||
54 | { | ||
55 | unsigned long timeout = jiffies + EC_MAILBOX_TIMEOUT; | ||
56 | |||
57 | do { | ||
58 | if (!(inb(ec->io_command->start) & | ||
59 | (EC_CMDR_PENDING | EC_CMDR_BUSY))) | ||
60 | return false; | ||
61 | usleep_range(100, 200); | ||
62 | } while (time_before(jiffies, timeout)); | ||
63 | |||
64 | return true; | ||
65 | } | ||
66 | |||
67 | /** | ||
68 | * wilco_ec_checksum() - Compute 8-bit checksum over data range. | ||
69 | * @data: Data to checksum. | ||
70 | * @size: Number of bytes to checksum. | ||
71 | * | ||
72 | * Return: 8-bit checksum of provided data. | ||
73 | */ | ||
74 | static u8 wilco_ec_checksum(const void *data, size_t size) | ||
75 | { | ||
76 | u8 *data_bytes = (u8 *)data; | ||
77 | u8 checksum = 0; | ||
78 | size_t i; | ||
79 | |||
80 | for (i = 0; i < size; i++) | ||
81 | checksum += data_bytes[i]; | ||
82 | |||
83 | return checksum; | ||
84 | } | ||
85 | |||
86 | /** | ||
87 | * wilco_ec_prepare() - Prepare the request structure for the EC. | ||
88 | * @msg: EC message with request information. | ||
89 | * @rq: EC request structure to fill. | ||
90 | */ | ||
91 | static void wilco_ec_prepare(struct wilco_ec_message *msg, | ||
92 | struct wilco_ec_request *rq) | ||
93 | { | ||
94 | memset(rq, 0, sizeof(*rq)); | ||
95 | |||
96 | /* Handle messages without trimming bytes from the request */ | ||
97 | if (msg->request_size && msg->flags & WILCO_EC_FLAG_RAW_REQUEST) { | ||
98 | rq->reserved_raw = *(u8 *)msg->request_data; | ||
99 | msg->request_size--; | ||
100 | memmove(msg->request_data, msg->request_data + 1, | ||
101 | msg->request_size); | ||
102 | } | ||
103 | |||
104 | /* Fill in request packet */ | ||
105 | rq->struct_version = EC_MAILBOX_PROTO_VERSION; | ||
106 | rq->mailbox_id = msg->type; | ||
107 | rq->mailbox_version = EC_MAILBOX_VERSION; | ||
108 | rq->data_size = msg->request_size + EC_MAILBOX_DATA_EXTRA; | ||
109 | rq->command = msg->command; | ||
110 | |||
111 | /* Checksum header and data */ | ||
112 | rq->checksum = wilco_ec_checksum(rq, sizeof(*rq)); | ||
113 | rq->checksum += wilco_ec_checksum(msg->request_data, msg->request_size); | ||
114 | rq->checksum = -rq->checksum; | ||
115 | } | ||
116 | |||
117 | /** | ||
118 | * wilco_ec_transfer() - Perform actual data transfer. | ||
119 | * @ec: EC device. | ||
120 | * @msg: EC message data for request and response. | ||
121 | * @rq: Filled in request structure | ||
122 | * | ||
123 | * Context: ec->mailbox_lock should be held while using this function. | ||
124 | * Return: number of bytes received or negative error code on failure. | ||
125 | */ | ||
126 | static int wilco_ec_transfer(struct wilco_ec_device *ec, | ||
127 | struct wilco_ec_message *msg, | ||
128 | struct wilco_ec_request *rq) | ||
129 | { | ||
130 | struct wilco_ec_response *rs; | ||
131 | u8 checksum; | ||
132 | u8 flag; | ||
133 | size_t size; | ||
134 | |||
135 | /* Write request header, then data */ | ||
136 | cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, 0, sizeof(*rq), (u8 *)rq); | ||
137 | cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, sizeof(*rq), msg->request_size, | ||
138 | msg->request_data); | ||
139 | |||
140 | /* Start the command */ | ||
141 | outb(EC_MAILBOX_START_COMMAND, ec->io_command->start); | ||
142 | |||
143 | /* For some commands (eg shutdown) the EC will not respond, that's OK */ | ||
144 | if (msg->flags & WILCO_EC_FLAG_NO_RESPONSE) { | ||
145 | dev_dbg(ec->dev, "EC does not respond to this command\n"); | ||
146 | return 0; | ||
147 | } | ||
148 | |||
149 | /* Wait for it to complete */ | ||
150 | if (wilco_ec_response_timed_out(ec)) { | ||
151 | dev_dbg(ec->dev, "response timed out\n"); | ||
152 | return -ETIMEDOUT; | ||
153 | } | ||
154 | |||
155 | /* Check result */ | ||
156 | flag = inb(ec->io_data->start); | ||
157 | if (flag) { | ||
158 | dev_dbg(ec->dev, "bad response: 0x%02x\n", flag); | ||
159 | return -EIO; | ||
160 | } | ||
161 | |||
162 | if (msg->flags & WILCO_EC_FLAG_EXTENDED_DATA) | ||
163 | size = EC_MAILBOX_DATA_SIZE_EXTENDED; | ||
164 | else | ||
165 | size = EC_MAILBOX_DATA_SIZE; | ||
166 | |||
167 | /* Read back response */ | ||
168 | rs = ec->data_buffer; | ||
169 | checksum = cros_ec_lpc_io_bytes_mec(MEC_IO_READ, 0, | ||
170 | sizeof(*rs) + size, (u8 *)rs); | ||
171 | if (checksum) { | ||
172 | dev_dbg(ec->dev, "bad packet checksum 0x%02x\n", rs->checksum); | ||
173 | return -EBADMSG; | ||
174 | } | ||
175 | |||
176 | /* Check that the EC reported success */ | ||
177 | msg->result = rs->result; | ||
178 | if (msg->result) { | ||
179 | dev_dbg(ec->dev, "bad response: 0x%02x\n", msg->result); | ||
180 | return -EBADMSG; | ||
181 | } | ||
182 | |||
183 | /* Check the returned data size, skipping the header */ | ||
184 | if (rs->data_size != size) { | ||
185 | dev_dbg(ec->dev, "unexpected packet size (%u != %zu)", | ||
186 | rs->data_size, size); | ||
187 | return -EMSGSIZE; | ||
188 | } | ||
189 | |||
190 | /* Skip 1 response data byte unless specified */ | ||
191 | size = (msg->flags & WILCO_EC_FLAG_RAW_RESPONSE) ? 0 : 1; | ||
192 | if ((ssize_t) rs->data_size - size < msg->response_size) { | ||
193 | dev_dbg(ec->dev, "response data too short (%zd < %zu)", | ||
194 | (ssize_t) rs->data_size - size, msg->response_size); | ||
195 | return -EMSGSIZE; | ||
196 | } | ||
197 | |||
198 | /* Ignore response data bytes as requested */ | ||
199 | memcpy(msg->response_data, rs->data + size, msg->response_size); | ||
200 | |||
201 | /* Return actual amount of data received */ | ||
202 | return msg->response_size; | ||
203 | } | ||
204 | |||
205 | /** | ||
206 | * wilco_ec_mailbox() - Send EC request and receive EC response. | ||
207 | * @ec: EC device. | ||
208 | * @msg: EC message data for request and response. | ||
209 | * | ||
210 | * On entry msg->type, msg->flags, msg->command, msg->request_size, | ||
211 | * msg->response_size, and msg->request_data should all be filled in. | ||
212 | * | ||
213 | * On exit msg->result and msg->response_data will be filled. | ||
214 | * | ||
215 | * Return: number of bytes received or negative error code on failure. | ||
216 | */ | ||
217 | int wilco_ec_mailbox(struct wilco_ec_device *ec, struct wilco_ec_message *msg) | ||
218 | { | ||
219 | struct wilco_ec_request *rq; | ||
220 | int ret; | ||
221 | |||
222 | dev_dbg(ec->dev, "cmd=%02x type=%04x flags=%02x rslen=%zu rqlen=%zu\n", | ||
223 | msg->command, msg->type, msg->flags, msg->response_size, | ||
224 | msg->request_size); | ||
225 | |||
226 | /* Prepare request packet */ | ||
227 | rq = ec->data_buffer; | ||
228 | wilco_ec_prepare(msg, rq); | ||
229 | |||
230 | mutex_lock(&ec->mailbox_lock); | ||
231 | ret = wilco_ec_transfer(ec, msg, rq); | ||
232 | mutex_unlock(&ec->mailbox_lock); | ||
233 | |||
234 | return ret; | ||
235 | |||
236 | } | ||
237 | EXPORT_SYMBOL_GPL(wilco_ec_mailbox); | ||