Welcome to mirror list, hosted at ThFree Co, Russian Federation.

WizSpi.cpp « Ethernet « Wiznet « DuetEthernet « DuetNG « src - github.com/Duet3D/RepRapFirmware.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: b45e99017a6ec61f41f0e84aa51addc2087eb10f (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
/*
 * WizSpi.cpp
 *
 *  Created on: 16 Dec 2016
 *      Author: David
 */

#include "WizSpi.h"
#include "variant.h"
#include "Pins.h"

// Define exactly one of the following as 1, the other as zero
// The PDC seems to be too slow to work reliably without getting transmit underruns, so we use the DMAC now.
#define USE_PDC		1		// use peripheral DMA controller
#define USE_DMAC	0		// use general DMA controller

#if USE_PDC
#include "pdc.h"
#endif

#if USE_DMAC
#include "dmac.h"
#endif

#include "matrix.h"

// Functions called by the W5500 module to transfer data to/from the W5500 via SPI
const uint32_t SpiClockFrequency = 40000000;
const unsigned int SpiPeripheralChannelId = 0;			// we use NPCS0 as the slave select signal

#if USE_PDC
static Pdc *spi_pdc;

static inline void spi_rx_dma_enable()
{
	pdc_enable_transfer(spi_pdc, PERIPH_PTCR_RXTEN);
	pdc_enable_transfer(spi_pdc, PERIPH_PTCR_TXTEN);				// we have to transmit in order to receive
}

static inline void spi_tx_dma_enable()
{
	pdc_enable_transfer(spi_pdc, PERIPH_PTCR_TXTEN);
}

static inline void spi_rx_dma_disable()
{
	pdc_disable_transfer(spi_pdc, PERIPH_PTCR_RXTDIS);
	pdc_disable_transfer(spi_pdc, PERIPH_PTCR_TXTDIS);				// we have to transmit in order to receive
}

static inline void spi_tx_dma_disable()
{
	pdc_disable_transfer(spi_pdc, PERIPH_PTCR_TXTDIS);
}

static bool spi_dma_check_rx_complete()
{
	return pdc_read_rx_counter(spi_pdc) == 0;
}

static void spi_tx_dma_setup(const uint8_t *buf, uint32_t length)
{
	pdc_packet_t pdc_spi_packet;
	pdc_spi_packet.ul_addr = reinterpret_cast<uint32_t>(buf);
	pdc_spi_packet.ul_size = length;
	pdc_tx_init(spi_pdc, &pdc_spi_packet, NULL);
}

static void spi_rx_dma_setup(uint8_t *buf, uint32_t length)
{
	pdc_packet_t pdc_spi_packet;
	pdc_spi_packet.ul_addr = reinterpret_cast<uint32_t>(buf);
	pdc_spi_packet.ul_size = length;
	pdc_rx_init(spi_pdc, &pdc_spi_packet, NULL);
	pdc_tx_init(spi_pdc, &pdc_spi_packet, NULL);					// we have to transmit in order to receive
}

#endif

#if USE_DMAC

// Our choice of DMA channels to use
const uint32_t CONF_SPI_DMAC_TX_CH = 1;
const uint32_t CONF_SPI_DMAC_RX_CH = 2;

// Hardware IDs of the SPI transmit and receive DMA interfaces. See atsam datasheet.
const uint32_t DMA_HW_ID_SPI_TX = 1;
const uint32_t DMA_HW_ID_SPI_RX = 2;

static inline void spi_rx_dma_enable()
{
	dmac_channel_enable(DMAC, CONF_SPI_DMAC_RX_CH);
}

static inline void spi_tx_dma_enable()
{
	dmac_channel_enable(DMAC, CONF_SPI_DMAC_TX_CH);
}

static inline void spi_rx_dma_disable()
{
	dmac_channel_disable(DMAC, CONF_SPI_DMAC_RX_CH);
}

static inline void spi_tx_dma_disable()
{
	dmac_channel_disable(DMAC, CONF_SPI_DMAC_TX_CH);
}

static bool spi_dma_check_rx_complete()
{
	uint32_t status = DMAC->DMAC_CHSR;
	if (   ((status & (DMAC_CHSR_ENA0 << CONF_SPI_DMAC_RX_CH)) == 0)	// controller is not enabled, perhaps because it finished a full buffer transfer
		|| ((status & (DMAC_CHSR_EMPT0 << CONF_SPI_DMAC_RX_CH)) != 0)	// controller is enabled, probably suspended, and the FIFO is empty
	   )
	{
		// Disable the channel.
		// We also need to set the resume bit, otherwise it remains suspended when we re-enable it.
		DMAC->DMAC_CHDR = (DMAC_CHDR_DIS0 << CONF_SPI_DMAC_RX_CH) | (DMAC_CHDR_RES0 << CONF_SPI_DMAC_RX_CH);
		return true;
	}
	return false;
}

static void spi_tx_dma_setup(const TransactionBuffer *buf, uint32_t maxTransmitLength)
{
	DMAC->DMAC_EBCISR;		// clear any pending interrupts

	dmac_channel_set_source_addr(DMAC, CONF_SPI_DMAC_TX_CH, reinterpret_cast<uint32_t>(buf));
	dmac_channel_set_destination_addr(DMAC, CONF_SPI_DMAC_TX_CH, reinterpret_cast<uint32_t>(& SPI->SPI_TDR));
	dmac_channel_set_descriptor_addr(DMAC, CONF_SPI_DMAC_TX_CH, 0);
	dmac_channel_set_ctrlA(DMAC, CONF_SPI_DMAC_TX_CH, maxTransmitLength | DMAC_CTRLA_SRC_WIDTH_WORD | DMAC_CTRLA_DST_WIDTH_BYTE);
	dmac_channel_set_ctrlB(DMAC, CONF_SPI_DMAC_TX_CH,
		DMAC_CTRLB_SRC_DSCR | DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_MEM2PER_DMA_FC | DMAC_CTRLB_SRC_INCR_INCREMENTING | DMAC_CTRLB_DST_INCR_FIXED);
}

static void spi_rx_dma_setup(const TransactionBuffer *buf)
{
	DMAC->DMAC_EBCISR;		// clear any pending interrupts

	dmac_channel_set_source_addr(DMAC, CONF_SPI_DMAC_RX_CH, reinterpret_cast<uint32_t>(& SPI->SPI_RDR));
	dmac_channel_set_destination_addr(DMAC, CONF_SPI_DMAC_RX_CH, reinterpret_cast<uint32_t>(buf));
	dmac_channel_set_descriptor_addr(DMAC, CONF_SPI_DMAC_RX_CH, 0);
//	dmac_channel_set_ctrlA(DMAC, CONF_SPI_DMAC_RX_CH, TransactionBuffer::MaxTransferBytes | DMAC_CTRLA_SRC_WIDTH_BYTE | DMAC_CTRLA_DST_WIDTH_WORD);
	dmac_channel_set_ctrlB(DMAC, CONF_SPI_DMAC_RX_CH,
		DMAC_CTRLB_SRC_DSCR | DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_PER2MEM_DMA_FC | DMAC_CTRLB_SRC_INCR_FIXED | DMAC_CTRLB_DST_INCR_INCREMENTING);
}

#endif

#if USE_PDC || USE_DMAC

static void spi_dma_disable()
{
	spi_tx_dma_disable();
	spi_rx_dma_disable();
}

#endif

namespace WizSpi
{
	// Initialise the SPI interface
	void Init()
	{
#if USE_PDC
		spi_pdc = spi_get_pdc_base(SPI);
		// The PDCs are masters 2 and 3 and the SRAM is slave 0. Give the receive PDCs the highest priority.
		matrix_set_master_burst_type(0, MATRIX_ULBT_8_BEAT_BURST);
		matrix_set_slave_default_master_type(0, MATRIX_DEFMSTR_LAST_DEFAULT_MASTER);
		matrix_set_slave_priority(0, (3 << MATRIX_PRAS0_M2PR_Pos) | (3 << MATRIX_PRAS0_M3PR_Pos));
		matrix_set_slave_slot_cycle(0, 8);
#endif

#if USE_DMAC
		pmc_enable_periph_clk(ID_DMAC);
		dmac_init(DMAC);
		dmac_set_priority_mode(DMAC, DMAC_PRIORITY_ROUND_ROBIN);
		dmac_enable(DMAC);
		// The DMAC is master 4 and the SRAM is slave 0. Give the DMAC the highest priority.
		matrix_set_slave_default_master_type(0, MATRIX_DEFMSTR_LAST_DEFAULT_MASTER);
		matrix_set_slave_priority(0, (3 << MATRIX_PRAS0_M4PR_Pos));
		// Set the slave slot cycle limit.
		// If we leave it at the default value of 511 clock cycles, we get transmit underruns due to the HSMCI using the bus for too long.
		// A value of 8 seems to work. I haven't tried other values yet.
		matrix_set_slave_slot_cycle(0, 8);
#endif

		// Set up the SPI pins
		ConfigurePin(g_APinDescription[APIN_SPI_SCK]);
		ConfigurePin(g_APinDescription[APIN_SPI_MOSI]);
		ConfigurePin(g_APinDescription[APIN_SPI_MISO]);
		pinMode(APIN_SPI_SS0, OUTPUT_HIGH);					// use manual SS control

		pmc_enable_periph_clk(ID_SPI);

#if USE_PDC || USE_DMAC
		spi_dma_disable();
#endif

		spi_reset(SPI);										// this clears the transmit and receive registers and puts the SPI into slave mode
		SPI->SPI_MR = SPI_MR_MSTR							// master mode
						| SPI_MR_MODFDIS					// disable fault detection
						| SPI_MR_PCS(SpiPeripheralChannelId); // fixed peripheral select

		// Set SPI mode, clock frequency, CS active after transfer, delay between transfers
		const uint16_t baud_div = (uint16_t)spi_calc_baudrate_div(SpiClockFrequency, SystemCoreClock);
		const uint32_t csr = SPI_CSR_SCBR(baud_div)			// Baud rate
						| SPI_CSR_BITS_8_BIT				// Transfer bit width
						| SPI_CSR_DLYBCT(0)      			// Transfer delay
						| SPI_CSR_CSAAT						// Keep CS low after transfer in case we are slow in writing the next byte
						| SPI_CSR_NCPHA;					// Data is captured on the leading edge of the clock (SPI mode 0)
		SPI->SPI_CSR[SpiPeripheralChannelId] = csr;
		spi_enable(SPI);

#if USE_DMAC
		// Configure DMA RX channel
		dmac_channel_set_configuration(DMAC, CONF_SPI_DMAC_RX_CH,
				DMAC_CFG_SRC_PER(DMA_HW_ID_SPI_RX) | DMAC_CFG_SRC_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ASAP_CFG);

		// Configure DMA TX channel
		dmac_channel_set_configuration(DMAC, CONF_SPI_DMAC_TX_CH,
				DMAC_CFG_DST_PER(DMA_HW_ID_SPI_TX) | DMAC_CFG_DST_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ASAP_CFG);
#endif
	}

	void Stop()
	{
		NVIC_DisableIRQ(SPI_IRQn);
		spi_disable(SPI);
#if USE_PDC || USE_DMA
		spi_dma_check_rx_complete();
		spi_dma_disable();
#endif
	}

// The remaining functions are speed-critical, so use full optimisation
#pragma GCC optimize ("O3")

	// Wait for transmit buffer empty, returning true if timed out
	static inline bool waitForTxReady()
	{
		uint32_t timeout = SPI_TIMEOUT;
		while ((SPI->SPI_SR & SPI_SR_TDRE) == 0)
		{
			if (--timeout == 0)
			{
				return true;
			}
		}
		return false;
	}

	// Wait for transmitter empty, returning true if timed out
	static inline bool waitForTxEmpty()
	{
		uint32_t timeout = SPI_TIMEOUT;
		while ((SPI->SPI_SR & SPI_SR_TXEMPTY) == 0)
		{
			if (!timeout--)
			{
				return true;
			}
		}
		return false;
	}

	// Wait for receive data available, returning true if timed out
	static inline bool waitForRxReady()
	{
		uint32_t timeout = SPI_TIMEOUT;
		while ((SPI->SPI_SR & SPI_SR_RDRF) == 0)
		{
			if (--timeout == 0)
			{
				return true;
			}
		}
		return false;
	}

	// Set the SS pin low to address the W5500
	void AssertSS()
	{
		spi_set_peripheral_chip_select_value(SPI, spi_get_pcs(SpiPeripheralChannelId));
		digitalWrite(SamCsPin, LOW);
		(void)SPI->SPI_RDR;						// clear receive register
	}

	// Set the SS pin high again
	void ReleaseSS()
	{
		waitForTxEmpty();
		digitalWrite(SamCsPin, HIGH);
	}

	// Send the 3-byte address and control bits. On return there may be data still being received.
	void SendAddress(uint32_t addr)
	{
		uint32_t dout = (addr >> 16) & 0x000000FF;
		if (!waitForTxReady())
		{
			SPI->SPI_TDR = dout;
			(void)SPI->SPI_RDR;
			dout = (addr >> 8) & 0x000000FF;
			if (!waitForTxReady())
			{
				SPI->SPI_TDR = dout;
				(void)SPI->SPI_RDR;
				dout = addr & 0x000000FF;
				if (!waitForTxReady())
				{
					SPI->SPI_TDR = dout;
					(void)SPI->SPI_RDR;
				}
			}
		}
	}

	// Read a single byte. Called after sending the 3-byte address.
	uint8_t ReadByte()
	{
		(void)SPI->SPI_RDR;
		if (!waitForTxEmpty())
		{
			while ((SPI->SPI_SR & SPI_SR_RDRF) != 0)
			{
				(void)SPI->SPI_RDR;						// clear previous data
			}
			SPI->SPI_TDR = 0x000000FF;
			if (!waitForRxReady())
			{
				return (uint8_t)SPI->SPI_RDR;
			}
		}
		return 0;
	}

	// Write a single byte. Called after sending the address.
	void WriteByte(uint8_t b)
	{
		const uint32_t dOut = b;
		if (!waitForTxReady())
		{
			SPI->SPI_TDR = dOut;
		}
	}

	// Read some data
	spi_status_t ReadBurst(uint8_t* rx_data, size_t len)
	{
		if (len != 0)
		{
			if (waitForTxEmpty())
			{
				return SPI_ERROR_TIMEOUT;
			}

			while ((SPI->SPI_SR & SPI_SR_RDRF) != 0)
			{
				(void)SPI->SPI_RDR;						// clear previous data
			}

#if USE_PDC
			spi_rx_dma_setup(rx_data, len);
			spi_rx_dma_enable();
			while (pdc_read_tx_counter(spi_pdc) != 0) { }
			uint32_t timeout = SPI_TIMEOUT;
			while (!spi_dma_check_rx_complete() && timeout != 0)
			{
				--timeout;
			}
			spi_rx_dma_disable();
#else
			const uint32_t dOut = 0x000000FF;
			SPI->SPI_TDR = dOut;						// send first byte
			while (--len != 0)
			{
				// Wait for receive data available and transmit buffer empty
				uint32_t timeout = SPI_TIMEOUT + 1;
				do
				{
					if (--timeout == 0)
					{
						return SPI_ERROR_TIMEOUT;
					}
				}
				while ((SPI->SPI_SR & (SPI_SR_RDRF | SPI_SR_TDRE)) != (SPI_SR_RDRF | SPI_SR_TDRE));

				const uint32_t din = SPI->SPI_RDR;		// get data from receive register
				SPI->SPI_TDR = dOut;					// write to transmit register immediately
				*rx_data++ = (uint8_t)din;
			}

			if (waitForRxReady())
			{
				return SPI_ERROR_TIMEOUT;
			}

			*rx_data++ = (uint8_t)SPI->SPI_RDR;			// Get last byte from receive register
#endif
		}
		return SPI_OK;
	}

	// Send some data
	spi_status_t SendBurst(const uint8_t* tx_data, size_t len)
	{
#if USE_PDC
		spi_tx_dma_setup(tx_data, len);
		spi_tx_dma_enable();
		while (pdc_read_tx_counter(spi_pdc) != 0) { }
		spi_tx_dma_disable();
#else
		for (uint32_t i = 0; i < len; ++i)
		{
			uint32_t dOut = (uint32_t)*tx_data++;
			if (waitForTxReady())
			{
				return SPI_ERROR_TIMEOUT;
			}

			SPI->SPI_TDR = dOut;						// write to transmit register
			(void)SPI->SPI_RDR;
		}
#endif
		return SPI_OK;
	}

}	// end namespace

// End