source: rtems/bsps/arm/atsam/spi/atsam_spi_bus.c @ 276afd2b

Last change on this file since 276afd2b was 276afd2b, checked in by Sebastian Huber <sebastian.huber@…>, on Apr 23, 2018 at 7:48:52 AM

bsps: Move SPI drivers to bsps

This patch is a part of the BSP source reorganization.

Update #3285.

  • Property mode set to 100644
File size: 16.0 KB
Line 
1/* ---------------------------------------------------------------------------- */
2/*                  Atmel Microcontroller Software Support                      */
3/*                       SAM Software Package License                           */
4/* ---------------------------------------------------------------------------- */
5/* Copyright (c) 2015, Atmel Corporation                                        */
6/* Copyright (c) 2016, embedded brains GmbH                                     */
7/*                                                                              */
8/* All rights reserved.                                                         */
9/*                                                                              */
10/* Redistribution and use in source and binary forms, with or without           */
11/* modification, are permitted provided that the following condition is met:    */
12/*                                                                              */
13/* - Redistributions of source code must retain the above copyright notice,     */
14/* this list of conditions and the disclaimer below.                            */
15/*                                                                              */
16/* Atmel's name may not be used to endorse or promote products derived from     */
17/* this software without specific prior written permission.                     */
18/*                                                                              */
19/* DISCLAIMER:  THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR   */
20/* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */
21/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE   */
22/* DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,      */
23/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT */
24/* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,  */
25/* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF    */
26/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING         */
27/* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, */
28/* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                           */
29/* ---------------------------------------------------------------------------- */
30
31#include <bsp/atsam-clock-config.h>
32#include <bsp/atsam-spi.h>
33#include <bsp/iocopy.h>
34
35#include <dev/spi/spi.h>
36
37#include <string.h>
38
39#define MAX_SPI_FREQUENCY 50000000
40
41#define DMA_NR_DESC_PER_DIR 3
42#define DMA_DESC_ALLIGNMENT 4
43
44#define DMA_BUF_RX 0
45#define DMA_BUF_TX 1
46#define DMA_BUF_DIRS 2
47
48struct atsam_spi_xdma_buf {
49  LinkedListDescriporView0 desc[DMA_NR_DESC_PER_DIR];
50  uint8_t leadbuf[CPU_CACHE_LINE_BYTES];
51  uint8_t trailbuf[CPU_CACHE_LINE_BYTES];
52};
53
54typedef struct {
55  spi_bus base;
56  bool msg_cs_change;
57  const spi_ioc_transfer *msg_current;
58  const spi_ioc_transfer *msg_next;
59  uint32_t msg_todo;
60  int msg_error;
61  rtems_id msg_task;
62  Spid spi;
63  uint32_t dma_tx_channel;
64  uint32_t dma_rx_channel;
65  struct atsam_spi_xdma_buf *dma_bufs;
66  size_t leadbuf_rx_buffered_len;
67  size_t trailbuf_rx_buffered_len;
68  int transfer_in_progress;
69  bool chip_select_active;
70  bool chip_select_decode;
71} atsam_spi_bus;
72
73static void atsam_spi_wakeup_task(atsam_spi_bus *bus)
74{
75  rtems_status_code sc;
76
77  sc = rtems_event_transient_send(bus->msg_task);
78  assert(sc == RTEMS_SUCCESSFUL);
79}
80
81static uint8_t atsam_calculate_dlybcs(uint16_t delay_in_us)
82{
83  return (
84    (BOARD_MCK / delay_in_us) < 0xFF) ?
85    (BOARD_MCK / delay_in_us) : 0xFF;
86}
87
88static void atsam_set_phase_and_polarity(uint32_t mode, uint32_t *csr)
89{
90  uint32_t mode_mask = mode & SPI_MODE_3;
91
92  switch(mode_mask) {
93    case SPI_MODE_0:
94      *csr |= SPI_CSR_NCPHA;
95      break;
96    case SPI_MODE_1:
97      break;
98    case SPI_MODE_2:
99      *csr |= SPI_CSR_NCPHA;
100      *csr |= SPI_CSR_CPOL;
101      break;
102    case SPI_MODE_3:
103      *csr |= SPI_CSR_CPOL;
104      break;
105  }
106  *csr |= SPI_CSR_CSAAT;
107}
108
109static void atsam_configure_spi(atsam_spi_bus *bus)
110{
111  uint8_t delay_cs;
112  uint32_t csr = 0;
113  uint32_t mode = 0;
114  uint32_t cs = bus->base.cs;
115
116  delay_cs = atsam_calculate_dlybcs(bus->base.delay_usecs);
117
118  mode |= SPI_MR_DLYBCS(delay_cs);
119  mode |= SPI_MR_MSTR;
120  mode |= SPI_MR_MODFDIS;
121  if (bus->chip_select_decode) {
122    mode |= SPI_MR_PCS(bus->base.cs);
123    mode |= SPI_MR_PCSDEC;
124    cs /= 4;
125  } else {
126    mode |= SPI_PCS(bus->base.cs);
127  }
128
129  SPID_Configure(
130    &bus->spi,
131    bus->spi.pSpiHw,
132    bus->spi.spiId,
133    mode,
134    &XDMAD_Instance
135  );
136
137  csr =
138    SPI_DLYBCT(1000, BOARD_MCK) |
139    SPI_DLYBS(1000, BOARD_MCK) |
140    SPI_SCBR(bus->base.speed_hz, BOARD_MCK) |
141    SPI_CSR_BITS(bus->base.bits_per_word - 8);
142
143  atsam_set_phase_and_polarity(bus->base.mode, &csr);
144
145  SPI_ConfigureNPCS(bus->spi.pSpiHw, cs, csr);
146}
147
148static void atsam_spi_check_alignment_and_set_up_dma_descriptors(
149  atsam_spi_bus *bus,
150  struct atsam_spi_xdma_buf *buf,
151  const uint8_t *start,
152  size_t len,
153  bool tx
154)
155{
156  LinkedListDescriporView0 *curdesc = buf->desc;
157  size_t misaligned_begin;
158  size_t misaligned_end;
159  size_t len_main;
160  const uint8_t *start_main;
161  const uint8_t *start_trail;
162
163  /* Check alignments. */
164  if (len < CPU_CACHE_LINE_BYTES) {
165    misaligned_begin = len;
166    misaligned_end = 0;
167    len_main = 0;
168  } else {
169    misaligned_begin = ((uint32_t) start) % CPU_CACHE_LINE_BYTES;
170    misaligned_end = (((uint32_t) start) + len) % CPU_CACHE_LINE_BYTES;
171    len_main = len - misaligned_begin - misaligned_end;
172  }
173  start_main = start + misaligned_begin;
174  start_trail = start_main + len_main;
175
176  /* Store length for copying data back. */
177  if (!tx) {
178    bus->leadbuf_rx_buffered_len = misaligned_begin;
179    bus->trailbuf_rx_buffered_len = misaligned_end;
180  }
181
182  /* Handle misalignment on begin. */
183  if (misaligned_begin != 0) {
184    if (tx) {
185      atsam_copy_to_io(buf->leadbuf, start, misaligned_begin);
186    }
187    curdesc->mbr_nda = (uint32_t) (&curdesc[1]);
188    curdesc->mbr_ta = (uint32_t) buf->leadbuf;
189    curdesc->mbr_ubc = misaligned_begin;
190  }
191
192  /* Main part */
193  if (len_main > 0) {
194    curdesc->mbr_ubc |= tx ? XDMA_UBC_NSEN_UPDATED : XDMA_UBC_NDEN_UPDATED;
195    curdesc->mbr_ubc |= XDMA_UBC_NVIEW_NDV0;
196    curdesc->mbr_ubc |= XDMA_UBC_NDE_FETCH_EN;
197    ++curdesc;
198
199    curdesc->mbr_nda = (uint32_t) (&curdesc[1]);
200    curdesc->mbr_ta = (uint32_t) start_main;
201    curdesc->mbr_ubc = len_main;
202    if (tx) {
203      rtems_cache_flush_multiple_data_lines(start_main, len_main);
204    } else {
205      rtems_cache_invalidate_multiple_data_lines(start_main, len_main);
206    }
207  }
208
209  /* Handle misalignment on end */
210  if (misaligned_end != 0) {
211    curdesc->mbr_ubc |= tx ? XDMA_UBC_NSEN_UPDATED : XDMA_UBC_NDEN_UPDATED;
212    curdesc->mbr_ubc |= XDMA_UBC_NVIEW_NDV0;
213    curdesc->mbr_ubc |= XDMA_UBC_NDE_FETCH_EN;
214    ++curdesc;
215
216    if (tx) {
217      atsam_copy_to_io(buf->trailbuf, start_trail, misaligned_end);
218    }
219    curdesc->mbr_nda = 0;
220    curdesc->mbr_ta = (uint32_t) buf->trailbuf;
221    curdesc->mbr_ubc = misaligned_end;
222    curdesc->mbr_ubc |= XDMA_UBC_NDE_FETCH_DIS;
223  }
224}
225
226static void atsam_spi_copy_back_rx_after_dma_transfer(
227  atsam_spi_bus *bus
228)
229{
230  if (bus->leadbuf_rx_buffered_len != 0) {
231    atsam_copy_from_io(
232      bus->msg_current->rx_buf,
233      bus->dma_bufs[DMA_BUF_RX].leadbuf,
234      bus->leadbuf_rx_buffered_len
235    );
236  }
237  if (bus->trailbuf_rx_buffered_len != 0) {
238    atsam_copy_from_io(
239      bus->msg_current->rx_buf + bus->msg_current->len -
240        bus->trailbuf_rx_buffered_len,
241      bus->dma_bufs[DMA_BUF_RX].trailbuf,
242      bus->trailbuf_rx_buffered_len
243    );
244  }
245}
246
247static void atsam_spi_start_dma_transfer(
248  atsam_spi_bus *bus,
249  const spi_ioc_transfer *msg
250)
251{
252  Xdmac *pXdmac = XDMAC;
253  size_t i;
254
255  atsam_spi_check_alignment_and_set_up_dma_descriptors(
256    bus,
257    &bus->dma_bufs[DMA_BUF_RX],
258    msg->rx_buf,
259    msg->len,
260    false
261  );
262  atsam_spi_check_alignment_and_set_up_dma_descriptors(
263    bus,
264    &bus->dma_bufs[DMA_BUF_TX],
265    msg->tx_buf,
266    msg->len,
267    true
268  );
269
270  XDMAC_SetDescriptorAddr(
271    pXdmac,
272    bus->dma_rx_channel,
273    (uint32_t) bus->dma_bufs[DMA_BUF_RX].desc,
274    0
275  );
276  XDMAC_SetDescriptorControl(
277    pXdmac,
278    bus->dma_rx_channel,
279    XDMAC_CNDC_NDVIEW_NDV0 |
280    XDMAC_CNDC_NDDUP_DST_PARAMS_UPDATED |
281    XDMAC_CNDC_NDE_DSCR_FETCH_EN
282  );
283  XDMAC_SetDescriptorAddr(
284    pXdmac,
285    bus->dma_tx_channel,
286    (uint32_t) bus->dma_bufs[DMA_BUF_TX].desc,
287    0
288  );
289  XDMAC_SetDescriptorControl(
290    pXdmac,
291    bus->dma_tx_channel,
292    XDMAC_CNDC_NDVIEW_NDV0 |
293    XDMAC_CNDC_NDSUP_SRC_PARAMS_UPDATED |
294    XDMAC_CNDC_NDE_DSCR_FETCH_EN
295  );
296
297  XDMAC_StartTransfer(pXdmac, bus->dma_rx_channel);
298  XDMAC_StartTransfer(pXdmac, bus->dma_tx_channel);
299}
300
301static void atsam_spi_do_transfer(
302  atsam_spi_bus *bus,
303  const spi_ioc_transfer *msg
304)
305{
306  if (!bus->chip_select_active){
307    Spi *pSpiHw = bus->spi.pSpiHw;
308
309    bus->chip_select_active = true;
310
311    if (bus->chip_select_decode) {
312      pSpiHw->SPI_MR = (pSpiHw->SPI_MR & ~SPI_MR_PCS_Msk) | SPI_MR_PCS(msg->cs);
313    } else {
314      SPI_ChipSelect(pSpiHw, 1 << msg->cs);
315    }
316    SPI_Enable(pSpiHw);
317  }
318
319  atsam_spi_start_dma_transfer(bus, msg);
320}
321
322static int atsam_check_configure_spi(atsam_spi_bus *bus, const spi_ioc_transfer *msg)
323{
324  if (
325    msg->mode != bus->base.mode
326      || msg->speed_hz != bus->base.speed_hz
327      || msg->bits_per_word != bus->base.bits_per_word
328      || msg->cs != bus->base.cs
329      || msg->delay_usecs != bus->base.delay_usecs
330  ) {
331    if (
332      msg->bits_per_word < 8
333        || msg->bits_per_word > 16
334        || msg->mode > 3
335        || msg->speed_hz > bus->base.max_speed_hz
336    ) {
337      return -EINVAL;
338    }
339
340    bus->base.mode = msg->mode;
341    bus->base.speed_hz = msg->speed_hz;
342    bus->base.bits_per_word = msg->bits_per_word;
343    bus->base.cs = msg->cs;
344    bus->base.delay_usecs = msg->delay_usecs;
345    atsam_configure_spi(bus);
346  }
347
348  return 0;
349}
350
351static void atsam_spi_setup_transfer(atsam_spi_bus *bus)
352{
353  uint32_t msg_todo = bus->msg_todo;
354
355  bus->transfer_in_progress = 2;
356
357  if (bus->msg_cs_change) {
358    bus->chip_select_active = false;
359    SPI_ReleaseCS(bus->spi.pSpiHw);
360    SPI_Disable(bus->spi.pSpiHw);
361  }
362
363  if (msg_todo > 0) {
364    const spi_ioc_transfer *msg = bus->msg_next;
365    int error;
366
367    bus->msg_cs_change = msg->cs_change;
368    bus->msg_next = msg + 1;
369    bus->msg_current = msg;
370    bus->msg_todo = msg_todo - 1;
371
372    error = atsam_check_configure_spi(bus, msg);
373    if (error == 0) {
374      atsam_spi_do_transfer(bus, msg);
375    } else {
376      bus->msg_error = error;
377      atsam_spi_wakeup_task(bus);
378    }
379  } else {
380    atsam_spi_wakeup_task(bus);
381  }
382}
383
384static void atsam_spi_dma_callback(uint32_t channel, void *arg)
385{
386  atsam_spi_bus *bus = (atsam_spi_bus *)arg;
387
388  --bus->transfer_in_progress;
389
390  if (bus->transfer_in_progress == 0) {
391    atsam_spi_copy_back_rx_after_dma_transfer(bus);
392    atsam_spi_setup_transfer(bus);
393  }
394}
395
396static int atsam_spi_transfer(
397  spi_bus *base,
398  const spi_ioc_transfer *msgs,
399  uint32_t msg_count
400)
401{
402  rtems_status_code sc;
403  atsam_spi_bus *bus = (atsam_spi_bus *)base;
404
405  bus->msg_cs_change = false;
406  bus->msg_next = &msgs[0];
407  bus->msg_current = NULL;
408  bus->msg_todo = msg_count;
409  bus->msg_error = 0;
410  bus->msg_task = rtems_task_self();
411  atsam_spi_setup_transfer(bus);
412  sc = rtems_event_transient_receive(RTEMS_WAIT, RTEMS_NO_TIMEOUT);
413  assert(sc == RTEMS_SUCCESSFUL);
414  return bus->msg_error;
415}
416
417
418static void atsam_spi_destroy(spi_bus *base)
419{
420  atsam_spi_bus *bus = (atsam_spi_bus *)base;
421  eXdmadRC rc;
422
423  rc = XDMAD_SetCallback(
424    bus->spi.pXdmad,
425    bus->dma_rx_channel,
426    XDMAD_DoNothingCallback,
427    NULL
428  );
429  assert(rc == XDMAD_OK);
430
431  rc = XDMAD_SetCallback(
432    bus->spi.pXdmad,
433    bus->dma_tx_channel,
434    XDMAD_DoNothingCallback,
435    NULL
436  );
437  assert(rc == XDMAD_OK);
438
439  XDMAD_FreeChannel(bus->spi.pXdmad, bus->dma_rx_channel);
440  XDMAD_FreeChannel(bus->spi.pXdmad, bus->dma_tx_channel);
441
442  SPI_Disable(bus->spi.pSpiHw);
443  PMC_DisablePeripheral(bus->spi.spiId);
444
445  rtems_cache_coherent_free(bus->dma_bufs);
446
447  spi_bus_destroy_and_free(&bus->base);
448}
449
450static int atsam_spi_setup(spi_bus *base)
451{
452  atsam_spi_bus *bus = (atsam_spi_bus *)base;
453
454  if (
455    bus->base.speed_hz > MAX_SPI_FREQUENCY ||
456    bus->base.bits_per_word < 8 ||
457    bus->base.bits_per_word > 16
458  ) {
459      return -EINVAL;
460  }
461  atsam_configure_spi(bus);
462  return 0;
463}
464
465static void atsam_spi_init_xdma(atsam_spi_bus *bus)
466{
467  sXdmadCfg cfg;
468  uint32_t xdmaInt;
469  uint8_t channel;
470  eXdmadRC rc;
471  uint32_t xdma_cndc;
472
473  bus->dma_bufs = rtems_cache_coherent_allocate(
474    DMA_BUF_DIRS * sizeof(*(bus->dma_bufs)),
475    DMA_DESC_ALLIGNMENT,
476    0
477  );
478  assert(bus->dma_bufs != NULL);
479
480  bus->dma_tx_channel = XDMAD_AllocateChannel(
481    bus->spi.pXdmad,
482    XDMAD_TRANSFER_MEMORY,
483    bus->spi.spiId
484  );
485  assert(bus->dma_tx_channel != XDMAD_ALLOC_FAILED);
486
487  bus->dma_rx_channel = XDMAD_AllocateChannel(
488    bus->spi.pXdmad,
489    bus->spi.spiId,
490    XDMAD_TRANSFER_MEMORY
491  );
492  assert(bus->dma_rx_channel != XDMAD_ALLOC_FAILED);
493
494  rc = XDMAD_SetCallback(
495    bus->spi.pXdmad,
496    bus->dma_rx_channel,
497    atsam_spi_dma_callback,
498    bus
499  );
500  assert(rc == XDMAD_OK);
501
502  rc = XDMAD_SetCallback(
503    bus->spi.pXdmad,
504    bus->dma_tx_channel,
505    atsam_spi_dma_callback,
506    bus
507  );
508  assert(rc == XDMAD_OK);
509
510  rc = XDMAD_PrepareChannel(bus->spi.pXdmad, bus->dma_rx_channel);
511  assert(rc == XDMAD_OK);
512
513  rc = XDMAD_PrepareChannel(bus->spi.pXdmad, bus->dma_tx_channel);
514  assert(rc == XDMAD_OK);
515
516  /* Put all relevant interrupts on */
517  xdmaInt =  (
518    XDMAC_CIE_BIE |
519    XDMAC_CIE_DIE |
520    XDMAC_CIE_FIE |
521    XDMAC_CIE_RBIE |
522    XDMAC_CIE_WBIE |
523    XDMAC_CIE_ROIE);
524
525  /* Setup RX */
526  memset(&cfg, 0, sizeof(cfg));
527  channel = XDMAIF_Get_ChannelNumber(bus->spi.spiId, XDMAD_TRANSFER_RX);
528  cfg.mbr_sa = (uint32_t)&bus->spi.pSpiHw->SPI_RDR;
529  cfg.mbr_cfg =
530    XDMAC_CC_TYPE_PER_TRAN |
531    XDMAC_CC_MBSIZE_SINGLE |
532    XDMAC_CC_DSYNC_PER2MEM |
533    XDMAC_CC_CSIZE_CHK_1 |
534    XDMAC_CC_DWIDTH_BYTE |
535    XDMAC_CC_SIF_AHB_IF1 |
536    XDMAC_CC_DIF_AHB_IF1 |
537    XDMAC_CC_SAM_FIXED_AM |
538    XDMAC_CC_DAM_INCREMENTED_AM |
539    XDMAC_CC_PERID(channel);
540  xdma_cndc = XDMAC_CNDC_NDVIEW_NDV0 |
541    XDMAC_CNDC_NDE_DSCR_FETCH_EN |
542    XDMAC_CNDC_NDDUP_DST_PARAMS_UPDATED |
543    XDMAC_CNDC_NDSUP_SRC_PARAMS_UNCHANGED;
544  rc = XDMAD_ConfigureTransfer(
545    bus->spi.pXdmad,
546    bus->dma_rx_channel,
547    &cfg,
548    xdma_cndc,
549    (uint32_t) bus->dma_bufs[DMA_BUF_RX].desc,
550    xdmaInt
551  );
552  assert(rc == XDMAD_OK);
553
554  /* Setup TX  */
555  memset(&cfg, 0, sizeof(cfg));
556  channel = XDMAIF_Get_ChannelNumber(bus->spi.spiId, XDMAD_TRANSFER_TX);
557  cfg.mbr_da = (uint32_t)&bus->spi.pSpiHw->SPI_TDR;
558  cfg.mbr_cfg =
559    XDMAC_CC_TYPE_PER_TRAN |
560    XDMAC_CC_MBSIZE_SINGLE |
561    XDMAC_CC_DSYNC_MEM2PER |
562    XDMAC_CC_CSIZE_CHK_1 |
563    XDMAC_CC_DWIDTH_BYTE |
564    XDMAC_CC_SIF_AHB_IF1 |
565    XDMAC_CC_DIF_AHB_IF1 |
566    XDMAC_CC_SAM_INCREMENTED_AM |
567    XDMAC_CC_DAM_FIXED_AM |
568    XDMAC_CC_PERID(channel);
569  xdma_cndc = XDMAC_CNDC_NDVIEW_NDV0 |
570    XDMAC_CNDC_NDE_DSCR_FETCH_EN |
571    XDMAC_CNDC_NDDUP_DST_PARAMS_UNCHANGED |
572    XDMAC_CNDC_NDSUP_SRC_PARAMS_UPDATED;
573  rc = XDMAD_ConfigureTransfer(
574    bus->spi.pXdmad,
575    bus->dma_tx_channel,
576    &cfg,
577    xdma_cndc,
578    (uint32_t) bus->dma_bufs[DMA_BUF_TX].desc,
579    xdmaInt
580  );
581  assert(rc == XDMAD_OK);
582}
583
584int spi_bus_register_atsam(
585  const char *bus_path,
586  const atsam_spi_config *config
587)
588{
589  atsam_spi_bus *bus;
590
591  bus = (atsam_spi_bus *) spi_bus_alloc_and_init(sizeof(*bus));
592  if (bus == NULL) {
593    return -1;
594  }
595
596  bus->base.transfer = atsam_spi_transfer;
597  bus->base.destroy = atsam_spi_destroy;
598  bus->base.setup = atsam_spi_setup;
599  bus->base.max_speed_hz = MAX_SPI_FREQUENCY;
600  bus->base.bits_per_word = 8;
601  bus->base.speed_hz = bus->base.max_speed_hz;
602  bus->base.delay_usecs = 1;
603  bus->base.cs = 1;
604  bus->spi.spiId = config->spi_peripheral_id;
605  bus->spi.pSpiHw = config->spi_regs;
606  bus->chip_select_decode = config->chip_select_decode;
607
608  PIO_Configure(config->pins, config->pin_count);
609  PMC_EnablePeripheral(config->spi_peripheral_id);
610  atsam_configure_spi(bus);
611  atsam_spi_init_xdma(bus);
612
613  return spi_bus_register(&bus->base, bus_path);
614}
Note: See TracBrowser for help on using the repository browser.