summaryrefslogtreecommitdiff
path: root/chip/stm32/spi.c
blob: 5b5920831778df4cc74fe15ab1a8d4038cd27c5a (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
/* Copyright (c) 2012 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 although not in an optimal way yet.
 */

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


/* Status register flags that we use */
enum {
	SR_RXNE		= 1 << 0,
	SR_TXE		= 1 << 1,
	SR_BSY		= 1 << 7,

	CR1_SPE		= 1 << 6,

	CR2_RXDMAEN	= 1 << 0,
	CR2_TXDMAEN	= 1 << 1,
	CR2_RXNEIE	= 1 << 6,
};

/*
 * 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 a 16-bit length so that we can tell that we got the whole
 * message, since the master decides how many bytes to read.
 */
enum {
	/* The bytes which appear before the header in a message */
	SPI_MSG_PREAMBLE	= 0xff,

	/* The header byte, which follows the preamble */
	SPI_MSG_HEADER		= 0xec,

	SPI_MSG_HEADER_BYTES	= 3,
	SPI_MSG_TRAILER_BYTES	= 2,
	SPI_MSG_PROTO_BYTES	= SPI_MSG_HEADER_BYTES + SPI_MSG_TRAILER_BYTES,
};

/*
 * Our input and output buffers. These must be large enough for our largest
 * message, including protocol overhead.
 */
static char out_msg[32];
static char in_msg[32];

/**
 * Monitor the SPI bus
 *
 * At present this function is very simple - it hangs the system until we
 * have sent the response, then clears things away. This is equivalent to
 * not using DMA at all.
 *
 * TODO(sjg): Use an interrupt on NSS to triggler this function.
 *
 */
void spi_task(void)
{
	int port = STM32_SPI1_PORT;

	while (1) {
		task_wait_event(-1);

		/* Wait for the master to let go of our slave select */
		while (!gpio_get_level(GPIO_SPI1_NSS))
			;

		/* Transfer is now complete, so reset everything */
		dma_disable(DMA_CHANNEL_FOR_SPI_RX(port));
		dma_disable(DMA_CHANNEL_FOR_SPI_TX(port));
		STM32_SPI_CR2(port) &= ~CR2_TXDMAEN;
		STM32_SPI_DR(port) = 0xff;
	}
}

/**
 * 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> <len_lo> <len_hi> <msg bytes> <sum> <preamble bytes>
 *
 * 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 16-bit length is the entire packet size, including the header, length
 * bytes, message payload, checksum, and postamble byte.
 *
 * The preamble is typically 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.
 *
 * @param port		Port to send reply back on (STM32_SPI0/1_PORT)
 * @param msg		Message to send, which starts SPI_MSG_HEADER_BYTES
 *			bytes into the buffer
 * @param msg_len	Length of message in bytes, including checksum
 */
static void reply(int port, char *msg, int msg_len)
{
	int sum, i;
	int dmac;

	/* Add our header bytes */
	msg_len += SPI_MSG_HEADER_BYTES + SPI_MSG_TRAILER_BYTES;
	msg[0] = SPI_MSG_HEADER;
	msg[1] = msg_len & 0xff;
	msg[2] = (msg_len >> 8) & 0xff;

	/* Calculate the checksum */
	for (i = sum = 0; i < msg_len - 2; i++)
		sum += msg[i];
	msg[msg_len - 2] = sum & 0xff;
	msg[msg_len - 1] = SPI_MSG_PREAMBLE;

	/*
	 * This method is not really suitable for very large messages. If
	 * we need these, we should set up a second DMA transfer to do
	 * the message, and then a third to do the trailer, rather than
	 * copying the message around.
	 */
	STM32_SPI_CR2(port) |= CR2_TXDMAEN;
	dmac = DMA_CHANNEL_FOR_SPI_TX(port);
	dma_start_tx(dmac, msg_len, (void *)&STM32_SPI_DR(port), out_msg);
}

/* dummy handler for SPI - will be filled in later */
static void spi_send_response(struct host_cmd_handler_args *args)
{
}

/**
 * Handles an interrupt on the specified port.
 *
 * This signals the start of a transfer. We read the command byte (which is
 * the first byte), star the RX DMA and set up our reply accordingly.
 *
 * We will not get interrupts on subsequent bytes since the DMA will handle
 * the incoming data.
 *
 * @param port	Port that the interrupt came in on (STM32_SPI0/1_PORT)
 */
static void spi_interrupt(int port)
{
	struct host_cmd_handler_args args;
	enum ec_status status;
	int msg_len;
	int dmac;
	int cmd;

	/* Make sure there is a byte available */
	if (!(STM32_SPI_SR(port) & SR_RXNE))
		return;

	/* Get the command byte */
	cmd = STM32_SPI_DR(port);

	/* Read the rest of the message - for now we do nothing with it */
	dmac = DMA_CHANNEL_FOR_SPI_RX(port);
	dma_start_rx(dmac, sizeof(in_msg), (void *)&STM32_SPI_DR(port),
		     in_msg);

	/*
	 * Process the command and send the reply.
	 *
	 * This is kind of ugly, because the host command interface can
	 * only call host_send_response() for one host bus, but stm32 could
	 * potentially have both I2C and SPI active at the same time on the
	 * current devel board.
	 */
	args.command = cmd;
	args.result = EC_RES_SUCCESS;
	args.send_response = spi_send_response;
	args.version = 0;
	args.params = out_msg + SPI_MSG_HEADER_BYTES + 1;
	args.params_size = sizeof(out_msg) - SPI_MSG_PROTO_BYTES;
	/* TODO: use a different initial buffer for params vs. response */
	args.response = args.params;
	args.response_max = sizeof(out_msg) - SPI_MSG_PROTO_BYTES;
	args.response_size = 0;

	status = host_command_process(&args);

	if (args.response_size < 0 || args.response_size > EC_PARAM_SIZE)
		status = EC_RES_INVALID_RESPONSE;
	else if (args.response != args.params)
		memcpy(args.response, args.params, args.response_size);

	out_msg[SPI_MSG_HEADER_BYTES] = status;
	reply(port, out_msg, args.response_size);

	/* Wake up the task that watches for end of the incoming message */
	task_wake(TASK_ID_SPI);
}

/* The interrupt code cannot pass a parameters, so handle this here */
static void spi1_interrupt(void) { spi_interrupt(STM32_SPI1_PORT); };

DECLARE_IRQ(STM32_IRQ_SPI1, spi1_interrupt, 2);

static int spi_init(void)
{
	int port;

#if defined(BOARD_daisy) || defined(BOARD_snow)
	/**
	 * SPI1
	 * PA7: SPI1_MOSI
	 * PA6: SPI1_MISO
	 * PA5: SPI1_SCK
	 * PA4: SPI1_NSS
	 *
	 * 8-bit data, master mode, full-duplex, clock is fpclk / 2
	 */
	port = STM32_SPI1_PORT;

	/* enable rx buffer not empty interrupt, and rx DMA */
	STM32_SPI_CR2(port) |= CR2_RXNEIE | CR2_RXDMAEN;

	/* set up an interrupt when we get the first byte of a packet */
	task_enable_irq(STM32_IRQ_SPI1);

	/* write 0xff which will be our default output value */
	STM32_SPI_DR(port) = 0xff;

	/* enable the SPI peripheral */
	STM32_SPI_CR1(port) |= CR1_SPE;
#else
#error "Need to know how to set up SPI for this board"
#endif
	return EC_SUCCESS;
}
DECLARE_HOOK(HOOK_INIT, spi_init, HOOK_PRIO_DEFAULT);