summaryrefslogtreecommitdiff
path: root/common/ec_ec_comm_client.c
blob: 193d1213437ffbe30911eb8dfa4e1e3336a87b35 (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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
/* Copyright 2017 The ChromiumOS Authors
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * EC-EC communication, functions and definitions for client.
 */

#include "battery.h"
#include "common.h"
#include "console.h"
#include "crc8.h"
#include "ec_commands.h"
#include "ec_ec_comm_client.h"
#include "timer.h"
#include "uart.h"
#include "util.h"

/* Console output macros */
#define CPRINTF(format, args...) cprintf(CC_CHARGER, format, ##args)

/*
 * TODO(b:65697962): The packed structures below do not play well if we force EC
 * host commands structures to be aligned on 32-bit boundary. There are ways to
 * fix that, possibly requiring copying data around, or modifying
 * uart_alt_pad_write_read API to write the actual server response to a separate
 * buffer.
 */
#ifdef CONFIG_HOSTCMD_ALIGNED
#error "Cannot define CONFIG_HOSTCMD_ALIGNED with EC-EC communication client."
#endif

#define EC_EC_HOSTCMD_VERSION 4

/* Print extra debugging information */
#undef EXTRA_DEBUG

/*
 * During early debugging, we would like to check that the error rate does
 * grow out of control.
 */
#define DEBUG_EC_COMM_STATS
#ifdef DEBUG_EC_COMM_STATS
struct {
	int total;
	int errtimeout;
	int errbusy;
	int errunknown;
	int errdatacrc;
	int errcrc;
	int errinval;
} comm_stats;

#define INCR_COMM_STATS(var) (comm_stats.var++)
#else
#define INCR_COMM_STATS(var)
#endif

/**
 * Write a command on the EC-EC communication UART channel.
 *
 * @param command	One of EC_CMD_*.
 * @param data		Packed structure with this layout:
 * struct {
 *	struct {
 *		struct ec_host_request4 head;
 *		struct ec_params_* param;
 *		uint8_t crc8;
 *	} req;
 *	struct {
 *		struct ec_host_response4 head;
 *		struct ec_response_* info;
 *		uint8_t crc8;
 *	} resp;
 * } __packed data;
 *
 * Where req is the request to be transmitted (head and crc8 are computed by
 * this function), and resp is the response to be received (head integrity and
 * crc8 are verified by this function).
 *
 * This format is required as the EC-EC UART is half-duplex, and all the
 * transmitted data is received back, i.e. the client writes req, then reads
 * req, followed by resp.
 *
 * When a command does not take parameters, param/crc8 must be omitted in
 * tx structure. The same applies to rx structure if the response does not
 * include a payload: info/crc8 must be omitted.
 *
 * @param req_len	size of req.param (0 if no parameter is passed).
 * @param resp_len	size of resp.info (0 if no information is returned).
 * @param timeout_us	timeout in microseconds for the transaction to complete.
 *
 * @return
 *  - EC_SUCCESS on success.
 *  - EC_ERROR_TIMEOUT when remote end times out replying.
 *  - EC_ERROR_BUSY when UART is busy and cannot transmit currently.
 *  - EC_ERROR_CRC when the header or data CRC is invalid.
 *  - EC_ERROR_INVAL when the received header is invalid.
 *  - EC_ERROR_UNKNOWN on other error.
 */
static int write_command(uint16_t command, uint8_t *data, int req_len,
			 int resp_len, int timeout_us)
{
	/* Sequence number. */
	static uint8_t cur_seq;
	int ret;
	int hascrc, response_seq;

	struct ec_host_request4 *request_header = (void *)data;
	/* Request (TX) length is header + (data + crc8), response follows. */
	int tx_length =
		sizeof(*request_header) + ((req_len > 0) ? (req_len + 1) : 0);

	struct ec_host_response4 *response_header = (void *)&data[tx_length];
	/* RX length is TX length + response from server. */
	int rx_length = tx_length + sizeof(*request_header) +
			((resp_len > 0) ? (resp_len + 1) : 0);

	/*
	 * Make sure there is a gap between each command, so that the server
	 * can recover its state machine after each command.
	 *
	 * TODO(b:65697962): We can be much smarter than this, and record the
	 * last transaction time instead of just sleeping blindly.
	 */
	usleep(10 * MSEC);

#ifdef DEBUG_EC_COMM_STATS
	if ((comm_stats.total % 128) == 0) {
		CPRINTF("UART %d (T%dB%d,U%dC%dD%dI%d)\n", comm_stats.total,
			comm_stats.errtimeout, comm_stats.errbusy,
			comm_stats.errunknown, comm_stats.errcrc,
			comm_stats.errdatacrc, comm_stats.errinval);
	}
#endif

	cur_seq = (cur_seq + 1) &
		  (EC_PACKET4_0_SEQ_NUM_MASK >> EC_PACKET4_0_SEQ_NUM_SHIFT);

	memset(request_header, 0, sizeof(*request_header));
	/* fields0: leave seq_dup and is_response as 0. */
	request_header->fields0 =
		EC_EC_HOSTCMD_VERSION | /* version */
		(cur_seq << EC_PACKET4_0_SEQ_NUM_SHIFT); /* seq_num
							  */
	/* fields1: leave command_version as 0. */
	if (req_len > 0)
		request_header->fields1 |= EC_PACKET4_1_DATA_CRC_PRESENT_MASK;
	request_header->command = command;
	request_header->data_len = req_len;
	request_header->header_crc = cros_crc8((uint8_t *)request_header,
					       sizeof(*request_header) - 1);
	if (req_len > 0)
		data[sizeof(*request_header) + req_len] =
			cros_crc8(&data[sizeof(*request_header)], req_len);

	ret = uart_alt_pad_write_read((void *)data, tx_length, (void *)data,
				      rx_length, timeout_us);

	INCR_COMM_STATS(total);

#ifdef EXTRA_DEBUG
	CPRINTF("EC-EC ret=%d/%d\n", ret, rx_length);
#endif

	if (ret != rx_length) {
		if (ret == -EC_ERROR_TIMEOUT) {
			INCR_COMM_STATS(errtimeout);
			return EC_ERROR_TIMEOUT;
		}

		if (ret == -EC_ERROR_BUSY) {
			INCR_COMM_STATS(errbusy);
			return EC_ERROR_BUSY;
		}

		INCR_COMM_STATS(errunknown);
		return EC_ERROR_UNKNOWN;
	}

	if (response_header->header_crc !=
	    cros_crc8((uint8_t *)response_header,
		      sizeof(*response_header) - 1)) {
		INCR_COMM_STATS(errcrc);
		return EC_ERROR_CRC;
	}

	hascrc = response_header->fields1 & EC_PACKET4_1_DATA_CRC_PRESENT_MASK;
	response_seq = (response_header->fields0 & EC_PACKET4_0_SEQ_NUM_MASK) >>
		       EC_PACKET4_0_SEQ_NUM_SHIFT;

	/*
	 * Validate received header.
	 * Note that we _require_ data crc to be present if there is data to be
	 * read back, else we would not know how many bytes to read exactly.
	 */
	if ((response_header->fields0 & EC_PACKET4_0_STRUCT_VERSION_MASK) !=
		    EC_EC_HOSTCMD_VERSION ||
	    !(response_header->fields0 & EC_PACKET4_0_IS_RESPONSE_MASK) ||
	    response_seq != cur_seq ||
	    (response_header->data_len > 0 && !hascrc) ||
	    response_header->data_len != resp_len) {
		INCR_COMM_STATS(errinval);
		return EC_ERROR_INVAL;
	}

	/* Check data CRC. */
	if (hascrc &&
	    data[rx_length - 1] !=
		    cros_crc8(&data[tx_length + sizeof(*request_header)],
			      resp_len)) {
		INCR_COMM_STATS(errdatacrc);
		return EC_ERROR_CRC;
	}

	return EC_SUCCESS;
}

/**
 * handle error from write_command
 *
 * @param ret is return value from write_command
 * @param request_result is data.resp.head.result (response result value)
 *
 * @return EC_RES_ERROR if ret is not EC_SUCCESS, else request_result.
 */
static int handle_error(const char *func, int ret, int request_result)
{
	if (ret != EC_SUCCESS) {
		/* Do not print busy errors as they just spam the console. */
		if (ret != EC_ERROR_BUSY)
			CPRINTF("%s: tx error %d\n", func, ret);
		return EC_RES_ERROR;
	}

	if (request_result != EC_RES_SUCCESS)
		CPRINTF("%s: cmd error %d\n", func, ret);

	return request_result;
}

#ifdef CONFIG_EC_EC_COMM_BATTERY
int ec_ec_client_base_get_dynamic_info(void)
{
	int ret;
	struct {
		struct {
			struct ec_host_request4 head;
			struct ec_params_battery_dynamic_info param;
			uint8_t crc8;
		} req;
		struct {
			struct ec_host_response4 head;
			struct ec_response_battery_dynamic_info info;
			uint8_t crc8;
		} resp;
	} __packed data;

	data.req.param.index = 0;

	ret = write_command(EC_CMD_BATTERY_GET_DYNAMIC, (void *)&data,
			    sizeof(data.req.param), sizeof(data.resp.info),
			    15 * MSEC);
	ret = handle_error(__func__, ret, data.resp.head.result);
	if (ret != EC_RES_SUCCESS)
		return ret;

#ifdef EXTRA_DEBUG
	CPRINTF("V:          %d mV\n", data.resp.info.actual_voltage);
	CPRINTF("I:          %d mA\n", data.resp.info.actual_current);
	CPRINTF("Remaining:  %d mAh\n", data.resp.info.remaining_capacity);
	CPRINTF("Cap-full:   %d mAh\n", data.resp.info.full_capacity);
	CPRINTF("Flags:      %04x\n", data.resp.info.flags);
	CPRINTF("V-desired:  %d mV\n", data.resp.info.desired_voltage);
	CPRINTF("I-desired:  %d mA\n", data.resp.info.desired_current);
#endif

	memcpy(&battery_dynamic[BATT_IDX_BASE], &data.resp.info,
	       sizeof(battery_dynamic[BATT_IDX_BASE]));
	return EC_RES_SUCCESS;
}

int ec_ec_client_base_get_static_info(void)
{
	int ret;
	struct {
		struct {
			struct ec_host_request4 head;
			struct ec_params_battery_static_info param;
			uint8_t crc8;
		} req;
		struct {
			struct ec_host_response4 head;
			struct ec_response_battery_static_info info;
			uint8_t crc8;
		} resp;
	} __packed data;
	const struct ec_response_battery_static_info *info = &data.resp.info;
	struct battery_static_info *bs = &battery_static[BATT_IDX_BASE];

	data.req.param.index = 0;

	ret = write_command(EC_CMD_BATTERY_GET_STATIC, (void *)&data,
			    sizeof(data.req.param), sizeof(data.resp.info),
			    15 * MSEC);
	ret = handle_error(__func__, ret, data.resp.head.result);
	if (ret != EC_RES_SUCCESS)
		return ret;

#ifdef EXTRA_DEBUG
	CPRINTF("Cap-design: %d mAh\n", data.resp.info.design_capacity);
	CPRINTF("V-design:   %d mV\n", data.resp.info.design_voltage);
	CPRINTF("Manuf:      %s\n", data.resp.info.manufacturer);
	CPRINTF("Model:      %s\n", data.resp.info.model);
	CPRINTF("Serial:     %s\n", data.resp.info.serial);
	CPRINTF("Type:       %s\n", data.resp.info.type);
	CPRINTF("C-count:    %d\n", data.resp.info.cycle_count);
#endif

	bs->design_capacity = info->design_capacity;
	bs->design_voltage = info->design_voltage;
	bs->cycle_count = info->cycle_count;
	strncpy(bs->manufacturer_ext, info->manufacturer,
		sizeof(bs->manufacturer_ext));
	strncpy(bs->model_ext, info->model, sizeof(bs->model_ext));
	strncpy(bs->serial_ext, info->serial, sizeof(bs->serial_ext));
	strncpy(bs->type_ext, info->type, sizeof(bs->type_ext));

	return EC_RES_SUCCESS;
}

int ec_ec_client_base_charge_control(int max_current, int otg_voltage,
				     int allow_charging)
{
	int ret;
	struct {
		struct {
			struct ec_host_request4 head;
			struct ec_params_charger_control ctrl;
			uint8_t crc8;
		} req;
		struct {
			struct ec_host_response4 head;
		} resp;
	} __packed data;

	data.req.ctrl.allow_charging = allow_charging;
	data.req.ctrl.max_current = max_current;
	data.req.ctrl.otg_voltage = otg_voltage;

	ret = write_command(EC_CMD_CHARGER_CONTROL, (void *)&data,
			    sizeof(data.req.ctrl), 0, 30 * MSEC);

	return handle_error(__func__, ret, data.resp.head.result);
}

int ec_ec_client_hibernate(void)
{
	int ret;
	struct {
		struct {
			struct ec_host_request4 head;
			struct ec_params_reboot_ec param;
		} req;
		struct {
			struct ec_host_response4 head;
		} resp;
	} __packed data;

	data.req.param.cmd = EC_REBOOT_HIBERNATE;
	data.req.param.flags = 0;

	ret = write_command(EC_CMD_REBOOT_EC, (void *)&data,
			    sizeof(data.req.param), 0, 30 * MSEC);

	return handle_error(__func__, ret, data.resp.head.result);
}
#endif /* CONFIG_EC_EC_COMM_BATTERY */