aboutsummaryrefslogtreecommitdiff
path: root/Marlin/src/HAL/LPC1768/HAL_SPI.cpp
blob: dbc89a33f547d3796ae0f4100032bf95799e81bb (plain) (blame)
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
/**
 * Marlin 3D Printer Firmware
 * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
 *
 * Based on Sprinter and grbl.
 * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

/**
 * Software SPI functions originally from Arduino Sd2Card Library
 * Copyright (c) 2009 by William Greiman
 */

/**
 * For TARGET_LPC1768
 */

/**
 * Hardware SPI and Software SPI implementations are included in this file.
 * The hardware SPI runs faster and has higher throughput but is not compatible
 * with some LCD interfaces/adapters.
 *
 * Control of the slave select pin(s) is handled by the calling routines.
 *
 * Some of the LCD interfaces/adapters result in the LCD SPI and the SD card
 * SPI sharing pins. The SCK, MOSI & MISO pins can NOT be set/cleared with
 * WRITE nor digitalWrite when the hardware SPI module within the LPC17xx is
 * active. If any of these pins are shared then the software SPI must be used.
 *
 * A more sophisticated hardware SPI can be found at the following link.
 * This implementation has not been fully debugged.
 * https://github.com/MarlinFirmware/Marlin/tree/071c7a78f27078fd4aee9a3ef365fcf5e143531e
 */

#ifdef TARGET_LPC1768

#include "../../inc/MarlinConfig.h"
#include <SPI.h>

// Hardware SPI and SPIClass
#include <lpc17xx_pinsel.h>
#include <lpc17xx_clkpwr.h>

#include "../shared/HAL_SPI.h"

// ------------------------
// Public functions
// ------------------------
#if ENABLED(LPC_SOFTWARE_SPI)

  // Software SPI

  #include <SoftwareSPI.h>

  #ifndef HAL_SPI_SPEED
    #define HAL_SPI_SPEED SPI_FULL_SPEED
  #endif

  static uint8_t SPI_speed = HAL_SPI_SPEED;

  static uint8_t spiTransfer(uint8_t b) {
    return swSpiTransfer(b, SPI_speed, SD_SCK_PIN, SD_MISO_PIN, SD_MOSI_PIN);
  }

  void spiBegin() {
    swSpiBegin(SD_SCK_PIN, SD_MISO_PIN, SD_MOSI_PIN);
  }

  void spiInit(uint8_t spiRate) {
    SPI_speed = swSpiInit(spiRate, SD_SCK_PIN, SD_MOSI_PIN);
  }

  uint8_t spiRec() { return spiTransfer(0xFF); }

  void spiRead(uint8_t*buf, uint16_t nbyte) {
    for (int i = 0; i < nbyte; i++)
      buf[i] = spiTransfer(0xFF);
  }

  void spiSend(uint8_t b) { (void)spiTransfer(b); }

  void spiSend(const uint8_t* buf, size_t nbyte) {
    for (uint16_t i = 0; i < nbyte; i++)
      (void)spiTransfer(buf[i]);
  }

  void spiSendBlock(uint8_t token, const uint8_t* buf) {
    (void)spiTransfer(token);
    for (uint16_t i = 0; i < 512; i++)
      (void)spiTransfer(buf[i]);
  }

#else

  #ifndef HAL_SPI_SPEED
    #ifdef SD_SPI_SPEED
      #define HAL_SPI_SPEED SD_SPI_SPEED
    #else
      #define HAL_SPI_SPEED SPI_FULL_SPEED
    #endif
  #endif

  void spiBegin() { spiInit(HAL_SPI_SPEED); } // Set up SCK, MOSI & MISO pins for SSP0

  void spiInit(uint8_t spiRate) {
    #if SD_MISO_PIN == BOARD_SPI1_MISO_PIN
      SPI.setModule(1);
    #elif SD_MISO_PIN == BOARD_SPI2_MISO_PIN
      SPI.setModule(2);
    #endif
    SPI.setDataSize(DATA_SIZE_8BIT);
    SPI.setDataMode(SPI_MODE0);

    SPI.setClock(SPISettings::spiRate2Clock(spiRate));
    SPI.begin();
  }

  static uint8_t doio(uint8_t b) {
    return SPI.transfer(b & 0x00FF) & 0x00FF;
  }

  void spiSend(uint8_t b) { doio(b); }

  void spiSend(const uint8_t* buf, size_t nbyte) {
    for (uint16_t i = 0; i < nbyte; i++) doio(buf[i]);
  }

  void spiSend(uint32_t chan, byte b) {}

  void spiSend(uint32_t chan, const uint8_t* buf, size_t nbyte) {}

  // Read single byte from SPI
  uint8_t spiRec() { return doio(0xFF); }

  uint8_t spiRec(uint32_t chan) { return 0; }

  // Read from SPI into buffer
  void spiRead(uint8_t *buf, uint16_t nbyte) {
    for (uint16_t i = 0; i < nbyte; i++) buf[i] = doio(0xFF);
  }

  uint8_t spiTransfer(uint8_t b) { return doio(b); }

  // Write from buffer to SPI
  void spiSendBlock(uint8_t token, const uint8_t* buf) {
   (void)spiTransfer(token);
    for (uint16_t i = 0; i < 512; i++)
      (void)spiTransfer(buf[i]);
  }

  // Begin SPI transaction, set clock, bit order, data mode
  void spiBeginTransaction(uint32_t spiClock, uint8_t bitOrder, uint8_t dataMode) {
    // TODO: Implement this method
  }

#endif // LPC_SOFTWARE_SPI

/**
 * @brief Wait until TXE (tx empty) flag is set and BSY (busy) flag unset.
 */
static inline void waitSpiTxEnd(LPC_SSP_TypeDef *spi_d) {
  while (SSP_GetStatus(spi_d, SSP_STAT_TXFIFO_EMPTY) == RESET) { /* nada */ } // wait until TXE=1
  while (SSP_GetStatus(spi_d, SSP_STAT_BUSY) == SET) { /* nada */ }     // wait until BSY=0
}

// Retain the pin init state of the SPI, to avoid init more than once,
// even if more instances of SPIClass exist
static bool spiInitialised[BOARD_NR_SPI] = { false };

SPIClass::SPIClass(uint8_t device) {
  // Init things specific to each SPI device
  // clock divider setup is a bit of hack, and needs to be improved at a later date.

  #if BOARD_NR_SPI >= 1
    _settings[0].spi_d = LPC_SSP0;
    _settings[0].dataMode = SPI_MODE0;
    _settings[0].dataSize = DATA_SIZE_8BIT;
    _settings[0].clock = SPI_CLOCK_MAX;
    //_settings[0].clockDivider = determine_baud_rate(_settings[0].spi_d, _settings[0].clock);
  #endif

  #if BOARD_NR_SPI >= 2
    _settings[1].spi_d = LPC_SSP1;
    _settings[1].dataMode = SPI_MODE0;
    _settings[1].dataSize = DATA_SIZE_8BIT;
    _settings[1].clock = SPI_CLOCK_MAX;
    //_settings[1].clockDivider = determine_baud_rate(_settings[1].spi_d, _settings[1].clock);
  #endif

  setModule(device);

  // Init the GPDMA controller
  // TODO: call once in the constructor? or each time?
  GPDMA_Init();
}

SPIClass::SPIClass(pin_t mosi, pin_t miso, pin_t sclk, pin_t ssel) {
  #if BOARD_NR_SPI >= 1
    if (mosi == BOARD_SPI1_MOSI_PIN) SPIClass(1);
  #endif
  #if BOARD_NR_SPI >= 2
    if (mosi == BOARD_SPI2_MOSI_PIN) SPIClass(2);
  #endif
}

void SPIClass::begin() {
  // Init the SPI pins in the first begin call
  if ((_currentSetting->spi_d == LPC_SSP0 && spiInitialised[0] == false) ||
      (_currentSetting->spi_d == LPC_SSP1 && spiInitialised[1] == false)) {
    pin_t sck, miso, mosi;
    if (_currentSetting->spi_d == LPC_SSP0) {
      sck = BOARD_SPI1_SCK_PIN;
      miso = BOARD_SPI1_MISO_PIN;
      mosi = BOARD_SPI1_MOSI_PIN;
      spiInitialised[0] = true;
    }
    else if (_currentSetting->spi_d == LPC_SSP1) {
      sck = BOARD_SPI2_SCK_PIN;
      miso = BOARD_SPI2_MISO_PIN;
      mosi = BOARD_SPI2_MOSI_PIN;
      spiInitialised[1] = true;
    }
    PINSEL_CFG_Type PinCfg;  // data structure to hold init values
    PinCfg.Funcnum = 2;
    PinCfg.OpenDrain = 0;
    PinCfg.Pinmode = 0;
    PinCfg.Pinnum = LPC176x::pin_bit(sck);
    PinCfg.Portnum = LPC176x::pin_port(sck);
    PINSEL_ConfigPin(&PinCfg);
    SET_OUTPUT(sck);

    PinCfg.Pinnum = LPC176x::pin_bit(miso);
    PinCfg.Portnum = LPC176x::pin_port(miso);
    PINSEL_ConfigPin(&PinCfg);
    SET_INPUT(miso);

    PinCfg.Pinnum = LPC176x::pin_bit(mosi);
    PinCfg.Portnum = LPC176x::pin_port(mosi);
    PINSEL_ConfigPin(&PinCfg);
    SET_OUTPUT(mosi);
  }

  updateSettings();
  SSP_Cmd(_currentSetting->spi_d, ENABLE);  // start SSP running
}

void SPIClass::beginTransaction(const SPISettings &cfg) {
  setBitOrder(cfg.bitOrder);
  setDataMode(cfg.dataMode);
  setDataSize(cfg.dataSize);
  //setClockDivider(determine_baud_rate(_currentSetting->spi_d, settings.clock));
  begin();
}

uint8_t SPIClass::transfer(const uint16_t b) {
  // Send and receive a single byte
  SSP_ReceiveData(_currentSetting->spi_d); // read any previous data
  SSP_SendData(_currentSetting->spi_d, b);
  waitSpiTxEnd(_currentSetting->spi_d);  // wait for it to finish
  return SSP_ReceiveData(_currentSetting->spi_d);
}

uint16_t SPIClass::transfer16(const uint16_t data) {
  return (transfer((data >> 8) & 0xFF) << 8) | (transfer(data & 0xFF) & 0xFF);
}

void SPIClass::end() {
  // Neither is needed for Marlin
  //SSP_Cmd(_currentSetting->spi_d, DISABLE);
  //SSP_DeInit(_currentSetting->spi_d);
}

void SPIClass::send(uint8_t data) {
  SSP_SendData(_currentSetting->spi_d, data);
}

void SPIClass::dmaSend(void *buf, uint16_t length, bool minc) {
  //TODO: LPC dma can only write 0xFFF bytes at once.
  GPDMA_Channel_CFG_Type GPDMACfg;

  /* Configure GPDMA channel 0 -------------------------------------------------------------*/
  /* DMA Channel 0 */
  GPDMACfg.ChannelNum = 0;
  // Source memory
  GPDMACfg.SrcMemAddr = (uint32_t)buf;
  // Destination memory - Not used
  GPDMACfg.DstMemAddr = 0;
  // Transfer size
  GPDMACfg.TransferSize = length;
  // Transfer width
  GPDMACfg.TransferWidth = (_currentSetting->dataSize == DATA_SIZE_16BIT) ? GPDMA_WIDTH_HALFWORD : GPDMA_WIDTH_BYTE;
  // Transfer type
  GPDMACfg.TransferType = GPDMA_TRANSFERTYPE_M2P;
  // Source connection - unused
  GPDMACfg.SrcConn = 0;
  // Destination connection
  GPDMACfg.DstConn = (_currentSetting->spi_d == LPC_SSP0) ? GPDMA_CONN_SSP0_Tx : GPDMA_CONN_SSP1_Tx;

  GPDMACfg.DMALLI = 0;

  // Enable dma on SPI
  SSP_DMACmd(_currentSetting->spi_d, SSP_DMA_TX, ENABLE);

  // Only increase memory if minc is true
  GPDMACfg.MemoryIncrease = (minc ? GPDMA_DMACCxControl_SI : 0);

  // Setup channel with given parameter
  GPDMA_Setup(&GPDMACfg);

  // Enable DMA
  GPDMA_ChannelCmd(0, ENABLE);

  // Wait for data transfer
  while (!GPDMA_IntGetStatus(GPDMA_STAT_RAWINTTC, 0) && !GPDMA_IntGetStatus(GPDMA_STAT_RAWINTERR, 0)) { }

  // Clear err and int
  GPDMA_ClearIntPending (GPDMA_STATCLR_INTTC, 0);
  GPDMA_ClearIntPending (GPDMA_STATCLR_INTERR, 0);

  // Disable DMA
  GPDMA_ChannelCmd(0, DISABLE);

  waitSpiTxEnd(_currentSetting->spi_d);

  SSP_DMACmd(_currentSetting->spi_d, SSP_DMA_TX, DISABLE);
}

uint16_t SPIClass::read() {
  return SSP_ReceiveData(_currentSetting->spi_d);
}

void SPIClass::read(uint8_t *buf, uint32_t len) {
  for (uint16_t i = 0; i < len; i++) buf[i] = transfer(0xFF);
}

void SPIClass::setClock(uint32_t clock) { _currentSetting->clock = clock; }

void SPIClass::setModule(uint8_t device) { _currentSetting = &_settings[device - 1]; } // SPI channels are called 1, 2, and 3 but the array is zero-indexed

void SPIClass::setBitOrder(uint8_t bitOrder) { _currentSetting->bitOrder = bitOrder; }

void SPIClass::setDataMode(uint8_t dataMode) { _currentSetting->dataMode = dataMode; }

void SPIClass::setDataSize(uint32_t dataSize) { _currentSetting->dataSize = dataSize; }

/**
 * Set up/tear down
 */
void SPIClass::updateSettings() {
  //SSP_DeInit(_currentSetting->spi_d); //todo: need force de init?!

  // Divide PCLK by 2 for SSP0
  //CLKPWR_SetPCLKDiv(_currentSetting->spi_d == LPC_SSP0 ? CLKPWR_PCLKSEL_SSP0 : CLKPWR_PCLKSEL_SSP1, CLKPWR_PCLKSEL_CCLK_DIV_2);

  SSP_CFG_Type HW_SPI_init; // data structure to hold init values
  SSP_ConfigStructInit(&HW_SPI_init);  // set values for SPI mode
  HW_SPI_init.ClockRate = _currentSetting->clock;
  HW_SPI_init.Databit = _currentSetting->dataSize;

  /**
   * SPI Mode  CPOL  CPHA  Shift SCK-edge  Capture SCK-edge
   * 0       0     0     Falling     Rising
   * 1       0     1     Rising      Falling
   * 2       1     0     Rising      Falling
   * 3       1     1     Falling     Rising
   */
  switch (_currentSetting->dataMode) {
    case SPI_MODE0:
      HW_SPI_init.CPHA = SSP_CPHA_FIRST;
      HW_SPI_init.CPOL = SSP_CPOL_HI;
      break;
    case SPI_MODE1:
      HW_SPI_init.CPHA = SSP_CPHA_SECOND;
      HW_SPI_init.CPOL = SSP_CPOL_HI;
      break;
    case SPI_MODE2:
      HW_SPI_init.CPHA = SSP_CPHA_FIRST;
      HW_SPI_init.CPOL = SSP_CPOL_LO;
      break;
    case SPI_MODE3:
      HW_SPI_init.CPHA = SSP_CPHA_SECOND;
      HW_SPI_init.CPOL = SSP_CPOL_LO;
      break;
    default:
      break;
  }

  // TODO: handle bitOrder
  SSP_Init(_currentSetting->spi_d, &HW_SPI_init);  // puts the values into the proper bits in the SSP0 registers
}

#if SD_MISO_PIN == BOARD_SPI1_MISO_PIN
  SPIClass SPI(1);
#elif SD_MISO_PIN == BOARD_SPI2_MISO_PIN
  SPIClass SPI(2);
#endif

#endif // TARGET_LPC1768