summaryrefslogtreecommitdiff
path: root/chip/stm32/spi.c
blob: 4f65820d9aba78fd2b3dd4969ae10ecde1748da0 (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
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
/*
 * Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * SPI driver for Chrome EC.
 *
 * This uses DMA to handle transmission and reception.
 */

#include "console.h"
#include "dma.h"
#include "gpio.h"
#include "hooks.h"
#include "host_command.h"
#include "registers.h"
#include "spi.h"
#include "timer.h"
#include "util.h"

/* Console output macros */
#define CPUTS(outstr) cputs(CC_SPI, outstr)
#define CPRINTF(format, args...) cprintf(CC_SPI, format, ## args)

/* DMA channel option */
static const struct dma_option dma_tx_option = {
	STM32_DMAC_SPI1_TX, (void *)&STM32_SPI1_REGS->dr,
	STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_16_BIT
};

static const struct dma_option dma_rx_option = {
	STM32_DMAC_SPI1_RX, (void *)&STM32_SPI1_REGS->dr,
	STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_16_BIT
};

/*
 * Since message.c no longer supports our protocol, we must do it all here.
 *
 * We allow a preamble and a header byte so that SPI can function at all.
 * We also add an 8-bit length so that we can tell that we got the whole
 * message, since the master decides how many bytes to read.
 *
 * TODO: move these constants to ec_commands.h
 */
enum {
	/* The bytes which appear before the header in a message */
	SPI_MSG_PREAMBLE_BYTE	= 0xff,

	/* The header byte, which follows the preamble */
	SPI_MSG_HEADER_BYTE1	= 0xfe,
	SPI_MSG_HEADER_BYTE2	= 0xec,

	SPI_MSG_HEADER_LEN	= 4,
	SPI_MSG_TRAILER_LEN	= 2,
	SPI_MSG_PROTO_LEN	= SPI_MSG_HEADER_LEN + SPI_MSG_TRAILER_LEN,

	/*
	 * Timeout to wait for SPI command
	 * TODO(sjg@chromium.org): Support much slower SPI clocks. For 4096
	 * we have a delay of 4ms. For the largest message (68 bytes) this
	 * is 130KhZ, assuming that the master starts sending bytes as soon
	 * as it drops NSS. In practice, this timeout seems adequately high
	 * for a 1MHz clock which is as slow as we would reasonably want it.
	 */
	SPI_CMD_RX_TIMEOUT_US	= 8192,

	/*
	 * Max data size for request/response packet.  This is big enough
	 * to handle a request/respose header, flash write offset/size, and
	 * 512 bytes of flash data.
	 */
	SPI_MAX_REQUEST_SIZE = 0x220,
	SPI_MAX_RESPONSE_SIZE = 0x220,
};

/*
 * The AP blindly clocks back bytes over the SPI interface looking for the
 * header bytes.  So this preamble must always precede the actual response
 * packet.  The preamble must be 32-bit aligned so that the response buffer is
 * also 32-bit aligned.
 *
 * Really, only HEADER_BYTE2 matters, since the SPI driver ignores everything
 * until it sees that byte code.  Search for "spi-frame-header" in U-boot to
 * see how that's implemented.
 */
static const uint8_t out_preamble[4] = {
	SPI_MSG_PREAMBLE_BYTE,
	SPI_MSG_PREAMBLE_BYTE,
	SPI_MSG_HEADER_BYTE1,
	SPI_MSG_HEADER_BYTE2,  /* This is the byte which matters */
};

/*
 * Our input and output buffers. These must be large enough for our largest
 * message, including protocol overhead.
 */
static uint8_t out_msg[SPI_MAX_RESPONSE_SIZE + sizeof(out_preamble)];
static uint8_t in_msg[SPI_MAX_REQUEST_SIZE];
static uint8_t active;
static uint8_t enabled;
static struct host_cmd_handler_args args;
static struct host_packet spi_packet;

/**
 * Wait until we have received a certain number of bytes
 *
 * Watch the DMA receive channel until it has the required number of bytes,
 * or a timeout occurs
 *
 * We keep an eye on the NSS line - if this goes high then the transaction is
 * over so there is no point in trying to receive the bytes.
 *
 * @param rxdma		RX DMA channel to watch
 * @param needed	Number of bytes that are needed
 * @param nss_regs	GPIO register for NSS control line
 * @param nss_mask	Bit to check in GPIO register (when high, we abort)
 * @return 0 if bytes received, -1 if we hit a timeout or NSS went high
 */
static int wait_for_bytes(stm32_dma_chan_t *rxdma, int needed,
			  uint16_t *nss_reg, uint32_t nss_mask)
{
	timestamp_t deadline;

	ASSERT(needed <= sizeof(in_msg));
	deadline.val = 0;
	for (;;) {
		if (dma_bytes_done(rxdma, sizeof(in_msg)) >= needed)
			return 0;
		if (REG16(nss_reg) & nss_mask)
			return -1;
		if (!deadline.val) {
			deadline = get_time();
			deadline.val += SPI_CMD_RX_TIMEOUT_US;
		}
		if (timestamp_expired(deadline, NULL))
			return -1;
	}
}

/**
 * Send a reply on a given port.
 *
 * The format of a reply is as per the command interface, with a number of
 * preamble bytes before it.
 *
 * The format of a reply is a sequence of bytes:
 *
 * <hdr> <status> <len> <msg bytes> <sum> [<preamble byte>...]
 *
 * The hdr byte is just a tag to indicate that the real message follows. It
 * signals the end of any preamble required by the interface.
 *
 * The length is the entire packet size, including the header, length bytes,
 * message payload, checksum, and postamble byte.
 *
 * The preamble is at least 2 bytes, but can be longer if the STM takes ages
 * to react to the incoming message. Since we send our first byte as the AP
 * sends us the command, we clearly can't send anything sensible for that
 * byte. The second byte must be written to the output register just when the
 * command byte is ready (I think), so we can't do anything there either.
 * Any processing we do may increase this delay. That's the reason for the
 * preamble.
 *
 * It is interesting to note that it seems to be possible to run the SPI
 * interface faster than the CPU clock with this approach.
 *
 * We keep an eye on the NSS line - if this goes high then the transaction is
 * over so there is no point in trying to send the reply.
 *
 * @param txdma		TX DMA channel to send on
 * @param status	Status result to send
 * @param msg_ptr	Message payload to send, which normally starts
 *			SPI_MSG_HEADER_LEN bytes into out_msg
 * @param msg_len	Number of message bytes to send
 */
static void reply(stm32_dma_chan_t *txdma,
		  enum ec_status status, char *msg_ptr, int msg_len)
{
	char *msg;
	int sum, i;
	int copy;

	msg = out_msg;
	copy = msg_ptr != msg + SPI_MSG_HEADER_LEN;

	/* Add our header bytes - the first one might not actually be sent */
	msg[0] = SPI_MSG_HEADER_BYTE1;
	msg[1] = SPI_MSG_HEADER_BYTE2;
	msg[2] = status;
	msg[3] = msg_len & 0xff;
	sum = status + msg_len;

	/* Calculate the checksum */
	for (i = 0; i < msg_len; i++) {
		int ch;

		ch = msg_ptr[i];
		sum += ch;
		if (copy)
			msg[i + SPI_MSG_HEADER_LEN] = ch;
	}
	msg_len += SPI_MSG_PROTO_LEN;
	ASSERT(msg_len <= sizeof(out_msg));

	/* Add the checksum and get ready to send */
	msg[msg_len - 2] = sum & 0xff;
	msg[msg_len - 1] = SPI_MSG_PREAMBLE_BYTE;
	dma_prepare_tx(&dma_tx_option, msg_len, msg);

	/* Kick off the DMA to send the data */
	dma_go(txdma);
}

/**
 * Get ready to receive a message from the master.
 *
 * Set up our RX DMA and disable our TX DMA. Set up the data output so that
 * we will send preamble bytes.
 */
static void setup_for_transaction(void)
{
	stm32_spi_regs_t *spi = STM32_SPI1_REGS;
	int dmac __attribute__((unused));

	/* We are no longer actively processing a transaction */
	active = 0;

	/* write 0xfd which will be our default output value */
	spi->dr = 0xfd;
	dma_disable(STM32_DMAC_SPI1_TX);
	*in_msg = 0xff;

	/* read a byte in case there is one, and the rx dma gets it */
	dmac = spi->dr;
	dma_start_rx(&dma_rx_option, sizeof(in_msg), in_msg);
}

/**
 * Called for V2 protocol to indicate that a command has completed
 *
 * Some commands can continue for a while. This function is called by
 * host_command when it completes.
 *
 */
static void spi_send_response(struct host_cmd_handler_args *args)
{
	enum ec_status result = args->result;
	stm32_dma_chan_t *txdma;

	/* If we are too late, don't bother */
	if (!active)
		return;

	if (args->response_size > EC_HOST_PARAM_SIZE)
		result = EC_RES_INVALID_RESPONSE;

	if ((uint8_t *)args->response >= out_msg &&
			(uint8_t *)args->response < out_msg + sizeof(out_msg))
		ASSERT(args->response == out_msg + SPI_MSG_HEADER_LEN);

	/* Transmit the reply */
	txdma = dma_get_channel(STM32_DMAC_SPI1_TX);
	reply(txdma, result, args->response, args->response_size);
}

/**
 * Called to send a response back to the host.
 *
 * Some commands can continue for a while. This function is called by
 * host_command when it completes.
 *
 */
static void spi_send_response_packet(struct host_packet *pkt)
{
	stm32_dma_chan_t *txdma;

	/* If we are too late, don't bother */
	if (!active)
		return;

	/* Transmit the reply */
	txdma = dma_get_channel(STM32_DMAC_SPI1_TX);
	dma_prepare_tx(&dma_tx_option,
		       sizeof(out_preamble) + pkt->response_size, out_msg);
	dma_go(txdma);
}

/**
 * Handle an event on the NSS pin
 *
 * A falling edge of NSS indicates that the master is starting a new
 * transaction. A rising edge indicates that we have finsihed
 *
 * @param signal	GPIO signal for the NSS pin
 */
void spi_event(enum gpio_signal signal)
{
	stm32_dma_chan_t *rxdma;
	uint16_t *nss_reg;
	uint32_t nss_mask;

	/* If not enabled, ignore glitches on NSS */
	if (!enabled)
		return;

	/*
	 * If NSS is rising, we have finished the transaction, so prepare
	 * for the next.
	 */
	nss_reg = gpio_get_level_reg(GPIO_SPI1_NSS, &nss_mask);
	if (REG16(nss_reg) & nss_mask) {
		setup_for_transaction();
		return;
	}

	/* Otherwise, NSS is low and we're now inside a transaction */
	active = 1;
	rxdma = dma_get_channel(STM32_DMAC_SPI1_RX);

	/* Wait for version, command, length bytes */
	if (wait_for_bytes(rxdma, 3, nss_reg, nss_mask)) {
		setup_for_transaction();
		return;
	}

	if (in_msg[0] == EC_HOST_REQUEST_VERSION) {
		/* Protocol version 3 */
		struct ec_host_request *r = (struct ec_host_request *)in_msg;
		int pkt_size;

		/* Wait for the rest of the command header */
		if (wait_for_bytes(rxdma, sizeof(*r), nss_reg, nss_mask)) {
			setup_for_transaction();
			return;
		}

		/*
		 * Check how big the packet should be.  We can't just wait to
		 * see how much data the host sends, because it will keep
		 * sending dummy data until we respond.
		 */
		pkt_size = host_request_expected_size(r);
		if (pkt_size == 0 || pkt_size > sizeof(in_msg)) {
			setup_for_transaction();
			return;
		}

		/* Wait for the packet data */
		if (wait_for_bytes(rxdma, pkt_size, nss_reg, nss_mask)) {
			setup_for_transaction();
			return;
		}

		spi_packet.send_response = spi_send_response_packet;

		spi_packet.request = in_msg;
		spi_packet.request_temp = NULL;
		spi_packet.request_max = sizeof(in_msg);
		spi_packet.request_size = pkt_size;

		/* Response must start with the preamble */
		memcpy(out_msg, out_preamble, sizeof(out_preamble));
		spi_packet.response = out_msg + sizeof(out_preamble);
		spi_packet.response_max =
			sizeof(out_msg) - sizeof(out_preamble);
		spi_packet.response_size = 0;

		spi_packet.driver_result = EC_RES_SUCCESS;

		host_packet_receive(&spi_packet);
		return;

	} else if (in_msg[0] >= EC_CMD_VERSION0) {
		/*
		 * Protocol version 2
		 *
		 * TODO: remove once all systems upgraded to version 3 */
		args.version = in_msg[0] - EC_CMD_VERSION0;
		args.command = in_msg[1];
		args.params_size = in_msg[2];
		args.params = in_msg + 3;
	} else {
		/*
		 * Protocol version 1
		 *
		 * TODO: remove; nothing sends this.  Ignore this packet?
		 * Send back an error response?
		 */
		args.version = 0;
		args.command = in_msg[0];
		args.params = in_msg + 1;
		args.params_size = 0;
	}

	/* Wait for parameters */
	if (wait_for_bytes(rxdma, 3 + args.params_size, nss_reg, nss_mask)) {
		setup_for_transaction();
		return;
	}

	/* Process the command and send the reply */
	args.send_response = spi_send_response;

	/* Allow room for the header bytes */
	args.response = out_msg + SPI_MSG_HEADER_LEN;
	args.response_max = sizeof(out_msg) - SPI_MSG_PROTO_LEN;
	args.response_size = 0;
	args.result = EC_RES_SUCCESS;

	host_command_received(&args);
}

static void spi_init(void)
{
	stm32_spi_regs_t *spi = STM32_SPI1_REGS;

	/* 40 MHz pin speed */
	STM32_GPIO_OSPEEDR(GPIO_A) |= 0xff00;

	/* Enable clocks to SPI1 module */
	STM32_RCC_APB2ENR |= STM32_RCC_PB2_SPI1;

	/* Enable rx DMA and get ready to receive our first transaction */
	spi->cr2 = STM32_SPI_CR2_RXDMAEN | STM32_SPI_CR2_TXDMAEN;

	/* Enable the SPI peripheral */
	spi->cr1 |= STM32_SPI_CR1_SPE;

	gpio_enable_interrupt(GPIO_SPI1_NSS);
}
DECLARE_HOOK(HOOK_INIT, spi_init, HOOK_PRIO_DEFAULT);

static void spi_chipset_startup(void)
{
	/* Enable pullup and interrupts on NSS */
	gpio_set_flags(GPIO_SPI1_NSS, GPIO_INT_BOTH | GPIO_PULL_UP);

	/* Set SPI pins to alternate function */
	gpio_set_alternate_function(GPIO_A, 0xf0, GPIO_ALT_SPI);

	/* Set up for next transaction */
	setup_for_transaction();

	enabled = 1;
}
DECLARE_HOOK(HOOK_CHIPSET_STARTUP, spi_chipset_startup, HOOK_PRIO_DEFAULT);
DECLARE_HOOK(HOOK_CHIPSET_RESUME, spi_chipset_startup, HOOK_PRIO_DEFAULT);

static void spi_chipset_shutdown(void)
{
	enabled = active = 0;

	/* Disable pullup and interrupts on NSS */
	gpio_set_flags(GPIO_SPI1_NSS, 0);

	/* Set SPI pins to inputs so we don't leak power when AP is off */
	gpio_set_alternate_function(GPIO_A, 0xf0, -1);
}
DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, spi_chipset_shutdown, HOOK_PRIO_DEFAULT);
DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, spi_chipset_shutdown, HOOK_PRIO_DEFAULT);

/**
 * Get protocol information
 */
static int spi_get_protocol_info(struct host_cmd_handler_args *args)
{
	struct ec_response_get_protocol_info *r = args->response;

	memset(r, 0, sizeof(*r));
	r->protocol_versions = (1 << 2) | (1 << 3);
	r->max_request_packet_size = SPI_MAX_REQUEST_SIZE;
	r->max_response_packet_size = SPI_MAX_RESPONSE_SIZE;
	r->flags = EC_PROTOCOL_INFO_IN_PROGRESS_SUPPORTED;

	args->response_size = sizeof(*r);

	return EC_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_GET_PROTOCOL_INFO,
		     spi_get_protocol_info,
		     EC_VER_MASK(0));