summaryrefslogtreecommitdiff
path: root/src/ec/google/wilco/mailbox.c
blob: c592d81f760c34f77c22884ebb0815edf1207d1a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
/* SPDX-License-Identifier: GPL-2.0-only */

#include <arch/io.h>
#include <console/console.h>
#include <delay.h>
#include <ec/google/common/mec.h>
#include <stdint.h>
#include <string.h>
#include <timer.h>
#include <types.h>

#include "ec.h"

/* Mailbox ID */
#define EC_MAILBOX_ID			0x00f0

/* Version of mailbox interface */
#define EC_MAILBOX_VERSION		0

/* Command to start mailbox transaction */
#define EC_MAILBOX_START_COMMAND	0xda

/* Version of EC protocol */
#define EC_MAILBOX_PROTO_VERSION	3

/* Max number of bytes in protocol data payload */
#define EC_MAILBOX_DATA_SIZE		32

/* Number of header bytes to be counted as data bytes */
#define EC_MAILBOX_DATA_EXTRA		2

/* Maximum timeout */
#define EC_MAILBOX_TIMEOUT_MS		MSECS_PER_SEC

/* EC response flags */
#define EC_CMDR_DATA		BIT(0)	/* Data ready for host to read */
#define EC_CMDR_PENDING		BIT(1)	/* Write pending to EC */
#define EC_CMDR_BUSY		BIT(2)	/* EC is busy processing a command */
#define EC_CMDR_CMD		BIT(3)	/* Last host write was a command */

/* Request to EC */
struct wilco_ec_request {
	uint8_t struct_version;		/* version (=3) */
	uint8_t checksum;		/* sum of all bytes must be 0 */
	uint16_t mailbox_id;		/* mailbox identifier */
	uint8_t mailbox_version;	/* mailbox version (=0) */
	uint8_t reserved1;		/* unused (=0) */
	uint16_t data_size;		/* length (data + 2 bytes of header) */
	uint8_t command;		/* mailbox command */
	uint8_t reserved2;		/* unused (=0) */
} __packed;

/* Response from EC */
struct wilco_ec_response {
	uint8_t struct_version;		/* version (=3) */
	uint8_t checksum;		/* sum of all bytes must be 0 */
	uint16_t result;		/* result code */
	uint16_t data_size;		/* length of data buffer (always 32) */
	uint8_t reserved[3];		/* unused (=0) */
	uint8_t data[EC_MAILBOX_DATA_SIZE];
} __packed;

struct wilco_ec_message {
	uint8_t command;		/* mailbox command code */
	uint8_t result;			/* request result */
	size_t request_size;		/* bytes to send to the EC */
	size_t response_size;		/* bytes expected from the EC */
	enum wilco_ec_msg_type type;	/* message type */
	/*
	 * This data buffer will contain the request data when passed to
	 * wilco_ec_message() and will contain the response data on return.
	 */
	uint8_t data[EC_MAILBOX_DATA_SIZE];
};

static bool wilco_ec_response_timed_out(void)
{
	uint8_t mask = EC_CMDR_PENDING | EC_CMDR_BUSY;
	struct stopwatch sw;

	stopwatch_init_msecs_expire(&sw, EC_MAILBOX_TIMEOUT_MS);

	while (inb(CONFIG_EC_BASE_HOST_COMMAND) & mask) {
		if (stopwatch_expired(&sw)) {
			printk(BIOS_ERR, "%s: Command timeout\n", __func__);
			return true; /* Timed out */
		}
		mdelay(1);
	}

	return false; /* Did not time out */
}

static uint8_t wilco_ec_checksum(void *data, size_t size)
{
	uint8_t *data_bytes = (uint8_t *)data;
	uint8_t checksum = 0;
	size_t i;

	for (i = 0; i < size; i++)
		checksum += data_bytes[i];

	return checksum;
}

static void wilco_ec_prepare(struct wilco_ec_message *msg,
			     struct wilco_ec_request *rq)
{
	memset(rq, 0, sizeof(*rq));

	/* Fill in request packet */
	rq->struct_version = EC_MAILBOX_PROTO_VERSION;
	rq->mailbox_id = EC_MAILBOX_ID;
	rq->mailbox_version = EC_MAILBOX_VERSION;
	rq->data_size = msg->request_size + EC_MAILBOX_DATA_EXTRA;
	rq->command = msg->command;

	/* Checksum header and data */
	rq->checksum = wilco_ec_checksum(rq, sizeof(*rq));
	rq->checksum += wilco_ec_checksum(msg->data, msg->request_size);
	rq->checksum = -rq->checksum;
}

static int wilco_ec_transfer(struct wilco_ec_message *msg)
{
	struct wilco_ec_request rq;
	struct wilco_ec_response rs;
	uint8_t checksum;
	size_t skip_size;

	/* Prepare request packet */
	wilco_ec_prepare(msg, &rq);

	/* Write request header */
	mec_io_bytes(MEC_IO_WRITE, CONFIG_EC_BASE_PACKET, 0, &rq, sizeof(rq));

	/* Write request data */
	mec_io_bytes(MEC_IO_WRITE, CONFIG_EC_BASE_PACKET, sizeof(rq),
		     msg->data, msg->request_size);

	/* Start the command */
	outb(EC_MAILBOX_START_COMMAND, CONFIG_EC_BASE_HOST_COMMAND);

	/* Some commands will put the EC into a state where it cannot respond */
	if (msg->type == WILCO_EC_MSG_NO_RESPONSE) {
		printk(BIOS_DEBUG, "%s: EC does not respond to this command\n",
		       __func__);
		return 0;
	}

	/* Wait for it to complete */
	if (wilco_ec_response_timed_out()) {
		printk(BIOS_ERR, "%s: response timed out\n", __func__);
		return -1;
	}

	/* Check result */
	msg->result = inb(CONFIG_EC_BASE_HOST_DATA);
	if (msg->result != 0) {
		printk(BIOS_ERR, "%s: bad response: 0x%02x\n",
		       __func__, msg->result);
		return -1;
	}

	/* Read back response */
	checksum = mec_io_bytes(MEC_IO_READ, CONFIG_EC_BASE_PACKET, 0,
				&rs, sizeof(rs));
	if (checksum) {
		printk(BIOS_ERR, "%s: bad checksum %02x\n", __func__, checksum);
		return -1;
	}
	msg->result = rs.result;

	/* EC always returns EC_MAILBOX_DATA_SIZE bytes */
	if (rs.data_size > EC_MAILBOX_DATA_SIZE) {
		printk(BIOS_ERR, "%s: packet too long (%d bytes, expected %d)",
		       __func__, rs.data_size, EC_MAILBOX_DATA_SIZE);
		return -1;
	}

	/* Skip response data bytes as requested */
	skip_size = (msg->type == WILCO_EC_MSG_DEFAULT) ? 1 : 0;

	if (msg->response_size > rs.data_size - skip_size) {
		printk(BIOS_ERR, "%s: data too short (%lu bytes, expected %zu)",
		       __func__, rs.data_size - skip_size, msg->response_size);
		return -1;
	}

	memcpy(msg->data, rs.data + skip_size, msg->response_size);

	/* Return actual amount of data received */
	return msg->response_size;
}

int wilco_ec_mailbox(enum wilco_ec_msg_type type, uint8_t command,
		     const void *request_data, size_t request_size,
		     void *response_data, size_t response_size)
{
	struct wilco_ec_message msg = {
		.command = command,
		.request_size = request_size,
		.response_size = response_size,
		.type = type,
	};
	int ret;

	if (request_size > EC_MAILBOX_DATA_SIZE) {
		printk(BIOS_ERR, "%s: provided request data too large: %zu\n",
		       __func__, request_size);
		return -1;
	}
	if (response_size > EC_MAILBOX_DATA_SIZE) {
		printk(BIOS_ERR, "%s: expected response data too large: %zu\n",
		       __func__, response_size);
		return -1;
	}
	if (request_size && !request_data) {
		printk(BIOS_ERR, "%s: request data missing\n", __func__);
		return -1;
	}
	if (response_size && !response_data) {
		printk(BIOS_ERR, "%s: request data missing\n", __func__);
		return -1;
	}

	/* Copy request data if present */
	if (request_size)
		memcpy(msg.data, request_data, request_size);

	/* Do the EC transfer */
	ret = wilco_ec_transfer(&msg);

	/* Copy response data if present */
	if (ret > 0 && response_size)
		memcpy(response_data, msg.data, response_size);

	/* Return error if message result is non-zero */
	if (ret >= 0 && msg.result)
		ret = -1;

	return ret;
}