summaryrefslogtreecommitdiff
path: root/chip/g/spi_controller.c
blob: 4f7a5ee5123babe078ff0849d00b83b6e8c42f72 (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
/* Copyright 2015 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.
 */

#include "common.h"
#include "gpio.h"
#include "hooks.h"
#include "registers.h"
#include "spi.h"
#include "task.h"
#include "timer.h"
#include "util.h"

#ifdef CONFIG_STREAM_SIGNATURE
#include "signing.h"
#endif

/* Not defined in the hardware register spec, the RX and TX buffers are 128B. */
#define SPI_BUF_SIZE 0x80

/* This timeout should allow a full buffer transaction at the lowest SPI speed
 * by using the largest uint8_t clock divider of 256 (~235kHz). */
#define SPI_TRANSACTION_TIMEOUT_USEC (5 * MSEC)

/* There are two SPI controllers or ports on this chip. */
#define SPI_NUM_PORTS 2

static struct mutex spi_mutex[SPI_NUM_PORTS];
static enum spi_clock_mode clock_mode[SPI_NUM_PORTS];

/*
 * The Cr50 SPI controller is not DMA auto-fill/drain capable, so async and
 * flush are not defined on purpose.
 */
int spi_sub_transaction(const struct spi_device_t *spi_device,
			const uint8_t *txdata, int txlen, uint8_t *rxdata,
			int rxlen, bool deassert_cs)
{
	static bool cs_asserted;

	int port = spi_device->port;
	int rv = EC_SUCCESS;
	timestamp_t timeout;
	int transaction_size = 0;
	int rxoffset = 0;

	/*
	 * If SPI0's passthrough is enabled, SPI0 is not available unless the
	 * SPP's BUSY bit is set.
	 */
	if (port == 0) {
		if (GREAD_FIELD_I(SPI, port, CTRL, ENPASSTHRU) &&
		    !GREAD(SPS, EEPROM_BUSY_STATUS))
			return EC_ERROR_BUSY;
	}

	if (rxlen == SPI_READBACK_ALL) {
		/* Bidirectional SPI sends and receives a bit for each clock.
		 * We'll need to make sure the buffers for RX and TX are equal
		 * and return a bit received for every bit sent.
		 */
		if (txlen > SPI_BUF_SIZE)
			return EC_ERROR_INVAL;
		rxlen = txlen;
		transaction_size = txlen;
		rxoffset = 0;
	} else {
		/* Ensure it'll fit inside of the RX and TX buffers. Note that
		 * although the buffers are separate, the total transmission
		 * size must fit in the rx buffer.
		 */
		if (txlen + rxlen > SPI_BUF_SIZE)
			return EC_ERROR_INVAL;
		transaction_size = rxlen + txlen;
		rxoffset = txlen;
	}

	if (!cs_asserted)
		/* Grab the port's mutex. */
		mutex_lock(&spi_mutex[port]);

#ifdef CONFIG_STREAM_SIGNATURE
	/*
	 * This hook allows mn50 to sniff data written to target
	 * manufactured H1 devices.
	 */
	sig_append(stream_spiflash, txdata, txlen);
#endif

	/* Copy the txdata into the 128B Transmit Buffer. */
	memmove((uint8_t *)GREG32_ADDR_I(SPI, port, TX_DATA), txdata, txlen);

	if (!cs_asserted) {
#ifndef CONFIG_SPI_CONTROLLER_NO_CS_GPIOS
		/* Drive chip select low. */
		gpio_set_level(spi_device->gpio_cs, 0);
#endif
		cs_asserted = true;
	}

	/* Initiate the transaction. */
	GWRITE_FIELD_I(SPI, port, ISTATE_CLR, TXDONE, 1);
	GWRITE_FIELD_I(SPI, port, XACT, SIZE, transaction_size - 1);
	GWRITE_FIELD_I(SPI, port, XACT, START, 1);

	/* Wait for the SPI controller to finish the transaction. */
	timeout.val = get_time().val + SPI_TRANSACTION_TIMEOUT_USEC;
	while (!GREAD_FIELD_I(SPI, port, ISTATE, TXDONE)) {
		/* Give up if the deadline has been exceeded. */
		if (get_time().val > timeout.val) {
			/* Might have been pre-empted by other task.
			 * Check ISTATE.TXDONE again for legit timeout.
			 */
			if (GREAD_FIELD_I(SPI, port, ISTATE, TXDONE))
				break;
			rv = EC_ERROR_TIMEOUT;
			goto err_cs_high;
		}
	}
	GWRITE_FIELD_I(SPI, port, ISTATE_CLR, TXDONE, 1);

	/* Copy the result. */
	memmove(rxdata,
		&((uint8_t *)GREG32_ADDR_I(SPI, port, RX_DATA))[rxoffset],
		rxlen);

err_cs_high:
	if ((rv != EC_SUCCESS) || deassert_cs) {
#ifndef CONFIG_SPI_CONTROLLER_NO_CS_GPIOS
		/* Drive chip select high. */
		gpio_set_level(spi_device->gpio_cs, 1);
#endif  /* CONFIG_SPI_CONTROLLER_NO_CS_GPIOS */
		cs_asserted = false;
		/* Release the port's mutex. */
		mutex_unlock(&spi_mutex[port]);
	}
	return rv;
}

int spi_transaction(const struct spi_device_t *spi_device,
		    const uint8_t *txdata, int txlen, uint8_t *rxdata,
		    int rxlen)
{
	return spi_sub_transaction(spi_device, txdata, txlen, rxdata, rxlen,
				   true);
}

/*
 * Configure the SPI port's clock mode. The SPI port must be re-enabled after
 * changing the clocking mode.
 */
void set_spi_clock_mode(int port, enum spi_clock_mode mode)
{
	clock_mode[port] = mode;
}

/*
 * Configure the SPI0 controller's passthrough mode. Note:
 * 1) This must be called after the SPI port is enabled.
 * 2) Passthrough cannot be safely disabled while the SPI peripheral port is
 *    active and the SPI peripheral port's status register's BUSY bit is not
 *    set.
 */
void configure_spi0_passthrough(int enable)
{
	int port = 0;

	/* Grab the port's mutex. */
	mutex_lock(&spi_mutex[port]);

	GWRITE_FIELD_I(SPI, port, CTRL, ENPASSTHRU, enable);

	/* Release the port's mutex. */
	mutex_unlock(&spi_mutex[port]);
}

int spi_enable(int port, int enable)
{
	int i;

	if (enable) {
		int spi_device_found = 0;
		uint8_t max_div = 0;

#ifndef CONFIG_SPI_CONTROLLER_NO_CS_GPIOS
		gpio_config_module(MODULE_SPI, 1);
#endif  /* CONFIG_SPI_CONTROLLER_NO_CS_GPIOS */
		for (i = 0; i < spi_devices_used; i++) {
			if (spi_devices[i].port != port)
				continue;

			spi_device_found = 1;

#ifndef CONFIG_SPI_CONTROLLER_NO_CS_GPIOS
			/* Deassert CS# */
			gpio_set_flags(spi_devices[i].gpio_cs, GPIO_OUTPUT);
			gpio_set_level(spi_devices[i].gpio_cs, 1);
#endif  /* CONFIG_SPI_CONTROLLER_NO_CS_GPIOS */

			/* Find the port's largest DIV (lowest frequency). */
			if (spi_devices[i].div > max_div)
				max_div = spi_devices[i].div;
		}

		/* Ensure there is at least one device behind the SPI port. */
		if (!spi_device_found)
			return EC_ERROR_INVAL;

		/* configure the SPI clock mode */
		GWRITE_FIELD_I(SPI, port, CTRL, CPOL,
			       (clock_mode[port] == SPI_CLOCK_MODE2) ||
			       (clock_mode[port] == SPI_CLOCK_MODE3));
		GWRITE_FIELD_I(SPI, port, CTRL, CPHA,
			       (clock_mode[port] == SPI_CLOCK_MODE1) ||
			       (clock_mode[port] == SPI_CLOCK_MODE3));

		/* Enforce the default setup and hold times. */
		GWRITE_FIELD_I(SPI, port, CTRL, CSBSU, 0);
		GWRITE_FIELD_I(SPI, port, CTRL, CSBHLD, 0);

		/* Set the clock divider, where freq / (div + 1). */
		GWRITE_FIELD_I(SPI, port, CTRL, IDIV, max_div);

		/* Controller's CS is active low. */
		GWRITE_FIELD_I(SPI, port, CTRL, CSBPOL, 0);

		/* Byte 0 bit 7 is first in each double word in the buffers. */
		GWRITE_FIELD_I(SPI, port, CTRL, TXBITOR, 1);
		GWRITE_FIELD_I(SPI, port, CTRL, TXBYTOR, 0);
		GWRITE_FIELD_I(SPI, port, CTRL, RXBITOR, 1);
		GWRITE_FIELD_I(SPI, port, CTRL, RXBYTOR, 0);

		/* Disable passthrough by default. */
		if (port == 0)
			configure_spi0_passthrough(0);

		/* Disable the TXDONE interrupt, we'll busy poll instead. */
		GWRITE_FIELD_I(SPI, port, ICTRL, TXDONE, 0);

	} else {
		for (i = 0; i < spi_devices_used; i++) {
			if (spi_devices[i].port != port)
				continue;

#ifndef CONFIG_SPI_CONTROLLER_NO_CS_GPIOS
			/* Make sure CS# is deasserted and disabled. */
			gpio_set_level(spi_devices[i].gpio_cs, 1);
			gpio_set_flags(spi_devices[i].gpio_cs, GPIO_ODR_HIGH);
#endif  /* CONFIG_SPI_CONTROLLER_NO_CS_GPIOS */
		}

		/* Disable passthrough. */
		if (port == 0)
			configure_spi0_passthrough(0);

		gpio_config_module(MODULE_SPI, 1);
	}

	return EC_SUCCESS;
}

/******************************************************************************/
/* Hooks */

static void spi_init(void)
{
	size_t i;

#ifdef CONFIG_SPI_CONTROLLER_CONFIGURE_GPIOS
	/* Set SPI_MISO as an input */
	GWRITE_FIELD(PINMUX, DIOA11_CTL, IE, 1); /* SPI_MISO */
#endif

	for (i = 0; i < SPI_NUM_PORTS; i++) {
		/* Configure the SPI ports to default to mode0. */
		set_spi_clock_mode(i, SPI_CLOCK_MODE0);

		/*
		 * Ensure the SPI ports are disabled to prevent us from
		 * interfering with the main chipset when we're not explicitly
		 * using the SPI bus.
		 */
		spi_enable(i, 0);
	}
}
DECLARE_HOOK(HOOK_INIT, spi_init, HOOK_PRIO_DEFAULT);