summaryrefslogtreecommitdiff
path: root/chip/g/spi_master.c
blob: aa9f9707b85ccdf840107e51da768d4455bf1c90 (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
/* 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"

/* 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 masters 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 master is not DMA auto-fill/drain capable, so async and flush
 * are not defined on purpose. */
int spi_transaction(const struct spi_device_t *spi_device,
		    const uint8_t *txdata, int txlen,
		    uint8_t *rxdata, int rxlen)
{
	int port = spi_device->port;
	int rv = EC_SUCCESS;
	timestamp_t timeout;

	/* If SPI0's passthrough is enabled, SPI0 is not available unless the
	 * SPS'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;
	}

	/* 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;

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


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

#ifndef CONFIG_SPI_MASTER_NO_CS_GPIOS
	/* Drive chip select low. */
	gpio_set_level(spi_device->gpio_cs, 0);
#endif  /* CONFIG_SPI_MASTER_NO_CS_GPIOS */

	/* Initiate the transaction. */
	GWRITE_FIELD_I(SPI, port, XACT, SIZE, rxlen + txlen - 1);
	GWRITE_FIELD_I(SPI, port, XACT, START, 1);

	/* Wait for the SPI master 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) {
			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))[txlen],
		rxlen);

err_cs_high:
#ifndef CONFIG_SPI_MASTER_NO_CS_GPIOS
	/* Drive chip select high. */
	gpio_set_level(spi_device->gpio_cs, 1);
#endif  /* CONFIG_SPI_MASTER_NO_CS_GPIOS */

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

/*
 * 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 master's passthrough mode. Note:
 * 1) This must be called after the SPI port is enabled.
 * 2) Passthrough cannot be safely disabled while the SPI slave port is active
 *    and the SPI slave 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_MASTER_NO_CS_GPIOS
		gpio_config_module(MODULE_SPI, 1);
#endif  /* CONFIG_SPI_MASTER_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_MASTER_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_MASTER_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);

		/* Master'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_MASTER_NO_CS_GPIOS
			/* Make sure CS# is deaserted 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_MASTER_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_MASTER_CONFIGURE_GPIOS
	/* Set SPI_MISO as an input */
	GWRITE_FIELD(PINMUX, DIOA11_CTL, IE, 1); /* SPS_MISO */
	/* Set SPI_CS to be an internal pull up */
	GWRITE_FIELD(PINMUX, DIOA14_CTL, PU, 1);
#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);