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: 70ebf1d46858522f3b81509b2f607f60ab9ec198 (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
/*
 * 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		0		// 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);
}

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);
}

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

static bool spi_dma_check_rx_complete()
{
	return true;
}

static void spi_tx_dma_setup(const TransactionBuffer *buf, uint32_t maxTransmitLength)
{
	pdc_packet_t pdc_spi_packet;
	pdc_spi_packet.ul_addr = reinterpret_cast<uint32_t>(buf);
	pdc_spi_packet.ul_size = buf->PacketLength() * 4;			// need length in bytes
	pdc_tx_init(spi_pdc, &pdc_spi_packet, NULL);
}

static void spi_rx_dma_setup(const TransactionBuffer *buf)
{
	pdc_packet_t pdc_spi_packet;
	pdc_spi_packet.ul_addr = reinterpret_cast<uint32_t>(buf);
	pdc_spi_packet.ul_size = TransactionBuffer::MaxReceiveBytes;
	pdc_rx_init(spi_pdc, &pdc_spi_packet, NULL);
}

#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();
}

#if 0
/**
 * \brief Set SPI slave transfer.
 */
static void spi_slave_dma_setup(bool dataToSend, bool allowReceive)
{
#if USE_PDC
	pdc_disable_transfer(spi_pdc, PERIPH_PTCR_TXTDIS | PERIPH_PTCR_RXTDIS);

	TransactionBuffer *outBufPointer = (dataToSend) ? &outBuffer : reinterpret_cast<TransactionBuffer*>(&dummyOutBuffer);
	spi_tx_dma_setup(outBufPointer);
	if (allowReceive)
	{
		outBufPointer->SetDataTaken();
		spi_rx_dma_setup(&inBuffer);
		pdc_enable_transfer(spi_pdc, PERIPH_PTCR_TXTEN | PERIPH_PTCR_RXTEN);
	}
	else
	{
		outBufPointer->ClearDataTaken();
		pdc_enable_transfer(spi_pdc, PERIPH_PTCR_TXTEN);
	}
#endif

#if USE_DMAC
	spi_dma_disable();

	TransactionBuffer *outBufPointer = (dataToSend) ? &outBuffer : reinterpret_cast<TransactionBuffer*>(&dummyOutBuffer);
	if (allowReceive)
	{
		spi_rx_dma_setup(&inBuffer);
		spi_rx_dma_enable();
//		outBufPointer->SetDataTaken();
	}
	else
	{
//		outBufPointer->ClearDataTaken();
	}

//	spi_tx_dma_setup(outBufPointer, (dataToSend) ? TransactionBuffer::MaxTransferBytes : 4 * TransactionBuffer::headerDwords);
	spi_tx_dma_enable();
#endif
}
#endif

#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 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]);
#if 1
		pinMode(APIN_SPI_SS0, OUTPUT_HIGH);					// use manual SS control for now
#else
		ConfigurePin(g_APinDescription[APIN_SPI_SS0]);
#endif

		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
	}

	// Wait for transmitter ready 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 | SPI_TDR_LASTXFER;
			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 | SPI_TDR_LASTXFER;
		if (!waitForTxReady())
		{
			SPI->SPI_TDR = dOut;
		}
	}

	spi_status_t ReadBurst(uint8_t* rx_data, size_t len)
	{
		if (waitForTxEmpty())
		{
			return SPI_ERROR_TIMEOUT;
		}

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

		for (size_t i = 0; i < len; ++i)
		{
			const uint32_t dOut = ((i + 1) == len) ? 0x000000FF | SPI_TDR_LASTXFER : 0x000000FF;
			if (waitForTxReady())
			{
				return SPI_ERROR_TIMEOUT;
			}

			// Write to transmit register
			SPI->SPI_TDR = dOut;

			// Wait for receive register
			if (waitForRxReady())
			{
				return SPI_ERROR_TIMEOUT;
			}

			// Get data from receive register
			*rx_data++ = (uint8_t)SPI->SPI_RDR;
		}

		return SPI_OK;
	}

	spi_status_t SendBurst(const uint8_t* tx_data, size_t len)
	{
		for (uint32_t i = 0; i < len; ++i)
		{
			uint32_t dOut = (uint32_t)*tx_data++;
			if (i + 1 == len)
			{
				dOut |= SPI_TDR_LASTXFER;
			}

			if (waitForTxReady())
			{
				return SPI_ERROR_TIMEOUT;
			}

			// Write to transmit register
			SPI->SPI_TDR = dOut;
			// Wait for receive register
			if (waitForRxReady())
			{
				return SPI_ERROR_TIMEOUT;
			}

			// Get data from receive register
			(void)SPI->SPI_RDR;
		}

		return SPI_OK;
	}

}	// end namespace

// End