source: rtems/c/src/lib/libbsp/arm/atsam/spi/atsam_spi_bus.c @ 3fbaaa8b

5
Last change on this file since 3fbaaa8b was 3fbaaa8b, checked in by Christian Mauderer <Christian.Mauderer@…>, on 07/31/17 at 14:18:37

bsp/atsam: Make clock application configurable.

  • Property mode set to 100644
File size: 11.1 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
34#include <dev/spi/spi.h>
35
36#include <string.h>
37
38#define MAX_SPI_FREQUENCY 50000000
39
40typedef struct {
41  spi_bus base;
42  bool msg_cs_change;
43  const spi_ioc_transfer *msg_current;
44  uint32_t msg_todo;
45  int msg_error;
46  rtems_id msg_task;
47  Spid spi;
48  uint32_t dma_tx_channel;
49  uint32_t dma_rx_channel;
50  int transfer_in_progress;
51  bool chip_select_active;
52} atsam_spi_bus;
53
54static void atsam_spi_wakeup_task(atsam_spi_bus *bus)
55{
56  rtems_status_code sc;
57
58  sc = rtems_event_transient_send(bus->msg_task);
59  assert(sc == RTEMS_SUCCESSFUL);
60}
61
62static uint8_t atsam_calculate_dlybcs(uint16_t delay_in_us)
63{
64  return (
65    (BOARD_MCK / delay_in_us) < 0xFF) ?
66    (BOARD_MCK / delay_in_us) : 0xFF;
67}
68
69static void atsam_set_phase_and_polarity(uint32_t mode, uint32_t *csr)
70{
71  uint32_t mode_mask = mode & SPI_MODE_3;
72
73  switch(mode_mask) {
74    case SPI_MODE_0:
75      *csr |= SPI_CSR_NCPHA;
76      break;
77    case SPI_MODE_1:
78      break;
79    case SPI_MODE_2:
80      *csr |= SPI_CSR_NCPHA;
81      *csr |= SPI_CSR_CPOL;
82      break;
83    case SPI_MODE_3:
84      *csr |= SPI_CSR_CPOL;
85      break;
86  }
87  *csr |= SPI_CSR_CSAAT;
88}
89
90static void atsam_configure_spi(atsam_spi_bus *bus)
91{
92  uint8_t delay_cs;
93  uint32_t csr = 0;
94
95  delay_cs = atsam_calculate_dlybcs(bus->base.delay_usecs);
96
97  SPID_Configure(
98    &bus->spi,
99    bus->spi.pSpiHw,
100    bus->spi.spiId,
101    (SPI_MR_DLYBCS(delay_cs) |
102      SPI_MR_MSTR |
103      SPI_MR_MODFDIS |
104      SPI_PCS(bus->base.cs)),
105    &XDMAD_Instance
106  );
107
108  csr =
109    SPI_DLYBCT(1000, BOARD_MCK) |
110    SPI_DLYBS(1000, BOARD_MCK) |
111    SPI_SCBR(bus->base.speed_hz, BOARD_MCK) |
112    SPI_CSR_BITS(bus->base.bits_per_word - 8);
113
114  atsam_set_phase_and_polarity(bus->base.mode, &csr);
115
116  SPI_ConfigureNPCS(bus->spi.pSpiHw, bus->base.cs, csr);
117}
118
119static void atsam_spi_start_dma_transfer(
120  atsam_spi_bus *bus,
121  const spi_ioc_transfer *msg
122)
123{
124  Xdmac *pXdmac = XDMAC;
125
126  XDMAC_SetDestinationAddr(pXdmac, bus->dma_rx_channel, (uint32_t)msg->rx_buf);
127  XDMAC_SetSourceAddr(pXdmac, bus->dma_tx_channel, (uint32_t)msg->tx_buf);
128  XDMAC_SetMicroblockControl(pXdmac, bus->dma_rx_channel, msg->len);
129  XDMAC_SetMicroblockControl(pXdmac, bus->dma_tx_channel, msg->len);
130  XDMAC_StartTransfer(pXdmac, bus->dma_rx_channel);
131  XDMAC_StartTransfer(pXdmac, bus->dma_tx_channel);
132}
133
134static void atsam_spi_do_transfer(
135  atsam_spi_bus *bus,
136  const spi_ioc_transfer *msg
137)
138{
139  if (!bus->chip_select_active){
140    Spi *pSpiHw = bus->spi.pSpiHw;
141
142    bus->chip_select_active = true;
143
144    SPI_ChipSelect(pSpiHw, 1 << msg->cs);
145    SPI_Enable(pSpiHw);
146  }
147
148  atsam_spi_start_dma_transfer(bus, msg);
149}
150
151static int atsam_check_configure_spi(atsam_spi_bus *bus, const spi_ioc_transfer *msg)
152{
153  if (
154    msg->mode != bus->base.mode
155      || msg->speed_hz != bus->base.speed_hz
156      || msg->bits_per_word != bus->base.bits_per_word
157      || msg->cs != bus->base.cs
158      || msg->delay_usecs != bus->base.delay_usecs
159  ) {
160    if (
161      msg->bits_per_word < 8
162        || msg->bits_per_word > 16
163        || msg->mode > 3
164        || msg->speed_hz > bus->base.max_speed_hz
165    ) {
166      return -EINVAL;
167    }
168
169    bus->base.mode = msg->mode;
170    bus->base.speed_hz = msg->speed_hz;
171    bus->base.bits_per_word = msg->bits_per_word;
172    bus->base.cs = msg->cs;
173    bus->base.delay_usecs = msg->delay_usecs;
174    atsam_configure_spi(bus);
175  }
176
177  return 0;
178}
179
180static void atsam_spi_setup_transfer(atsam_spi_bus *bus)
181{
182  uint32_t msg_todo = bus->msg_todo;
183
184  bus->transfer_in_progress = 2;
185
186  if (bus->msg_cs_change) {
187    bus->chip_select_active = false;
188    SPI_ReleaseCS(bus->spi.pSpiHw);
189    SPI_Disable(bus->spi.pSpiHw);
190  }
191
192  if (msg_todo > 0) {
193    const spi_ioc_transfer *msg = bus->msg_current;
194    int error;
195
196    bus->msg_cs_change = msg->cs_change;
197    bus->msg_current = msg + 1;
198    bus->msg_todo = msg_todo - 1;
199
200    error = atsam_check_configure_spi(bus, msg);
201    if (error == 0) {
202      atsam_spi_do_transfer(bus, msg);
203    } else {
204      bus->msg_error = error;
205      atsam_spi_wakeup_task(bus);
206    }
207  } else {
208    atsam_spi_wakeup_task(bus);
209  }
210}
211
212static void atsam_spi_dma_callback(uint32_t channel, void *arg)
213{
214  atsam_spi_bus *bus = (atsam_spi_bus *)arg;
215
216  --bus->transfer_in_progress;
217
218  if (bus->transfer_in_progress == 0) {
219    atsam_spi_setup_transfer(bus);
220  }
221}
222
223static int atsam_spi_transfer(
224  spi_bus *base,
225  const spi_ioc_transfer *msgs,
226  uint32_t msg_count
227)
228{
229  atsam_spi_bus *bus = (atsam_spi_bus *)base;
230
231  bus->msg_cs_change = false;
232  bus->msg_current = &msgs[0];
233  bus->msg_todo = msg_count;
234  bus->msg_error = 0;
235  bus->msg_task = rtems_task_self();
236  atsam_spi_setup_transfer(bus);
237  rtems_event_transient_receive(RTEMS_WAIT, RTEMS_NO_TIMEOUT);
238  return bus->msg_error;
239}
240
241
242static void atsam_spi_destroy(spi_bus *base)
243{
244  atsam_spi_bus *bus = (atsam_spi_bus *)base;
245  eXdmadRC rc;
246
247  rc = XDMAD_SetCallback(
248    bus->spi.pXdmad,
249    bus->dma_rx_channel,
250    XDMAD_DoNothingCallback,
251    NULL
252  );
253  assert(rc == XDMAD_OK);
254
255  rc = XDMAD_SetCallback(
256    bus->spi.pXdmad,
257    bus->dma_tx_channel,
258    XDMAD_DoNothingCallback,
259    NULL
260  );
261  assert(rc == XDMAD_OK);
262
263  XDMAD_FreeChannel(bus->spi.pXdmad, bus->dma_rx_channel);
264  XDMAD_FreeChannel(bus->spi.pXdmad, bus->dma_tx_channel);
265
266  SPI_Disable(bus->spi.pSpiHw);
267  PMC_DisablePeripheral(bus->spi.spiId);
268
269  spi_bus_destroy_and_free(&bus->base);
270}
271
272static int atsam_spi_setup(spi_bus *base)
273{
274  atsam_spi_bus *bus = (atsam_spi_bus *)base;
275
276  if (
277    bus->base.speed_hz > MAX_SPI_FREQUENCY ||
278    bus->base.bits_per_word < 8 ||
279    bus->base.bits_per_word > 16
280  ) {
281      return -EINVAL;
282  }
283  atsam_configure_spi(bus);
284  return 0;
285}
286
287static void atsam_spi_init_xdma(atsam_spi_bus *bus)
288{
289  sXdmadCfg cfg;
290  uint32_t xdmaInt;
291  uint8_t channel;
292  eXdmadRC rc;
293
294  bus->dma_tx_channel = XDMAD_AllocateChannel(
295    bus->spi.pXdmad,
296    XDMAD_TRANSFER_MEMORY,
297    bus->spi.spiId
298  );
299  assert(bus->dma_tx_channel != XDMAD_ALLOC_FAILED);
300
301  bus->dma_rx_channel = XDMAD_AllocateChannel(
302    bus->spi.pXdmad,
303    bus->spi.spiId,
304    XDMAD_TRANSFER_MEMORY
305  );
306  assert(bus->dma_rx_channel != XDMAD_ALLOC_FAILED);
307
308  rc = XDMAD_SetCallback(
309    bus->spi.pXdmad,
310    bus->dma_rx_channel,
311    atsam_spi_dma_callback,
312    bus
313  );
314  assert(rc == XDMAD_OK);
315
316  rc = XDMAD_SetCallback(
317    bus->spi.pXdmad,
318    bus->dma_tx_channel,
319    atsam_spi_dma_callback,
320    bus
321  );
322  assert(rc == XDMAD_OK);
323
324  rc = XDMAD_PrepareChannel(bus->spi.pXdmad, bus->dma_rx_channel);
325  assert(rc == XDMAD_OK);
326
327  rc = XDMAD_PrepareChannel(bus->spi.pXdmad, bus->dma_tx_channel);
328  assert(rc == XDMAD_OK);
329
330  /* Put all interrupts on for non LLI list setup of DMA */
331  xdmaInt =  (
332    XDMAC_CIE_BIE |
333    XDMAC_CIE_DIE |
334    XDMAC_CIE_FIE |
335    XDMAC_CIE_RBIE |
336    XDMAC_CIE_WBIE |
337    XDMAC_CIE_ROIE);
338
339  /* Setup RX */
340  memset(&cfg, 0, sizeof(cfg));
341  channel = XDMAIF_Get_ChannelNumber(bus->spi.spiId, XDMAD_TRANSFER_RX);
342  cfg.mbr_sa = (uint32_t)&bus->spi.pSpiHw->SPI_RDR;
343  cfg.mbr_cfg =
344    XDMAC_CC_TYPE_PER_TRAN |
345    XDMAC_CC_MBSIZE_SINGLE |
346    XDMAC_CC_DSYNC_PER2MEM |
347    XDMAC_CC_CSIZE_CHK_1 |
348    XDMAC_CC_DWIDTH_BYTE |
349    XDMAC_CC_SIF_AHB_IF1 |
350    XDMAC_CC_DIF_AHB_IF1 |
351    XDMAC_CC_SAM_FIXED_AM |
352    XDMAC_CC_DAM_INCREMENTED_AM |
353    XDMAC_CC_PERID(channel);
354  rc = XDMAD_ConfigureTransfer(
355    bus->spi.pXdmad,
356    bus->dma_rx_channel,
357    &cfg,
358    0,
359    0,
360    xdmaInt
361  );
362  assert(rc == XDMAD_OK);
363
364  /* Setup TX  */
365  memset(&cfg, 0, sizeof(cfg));
366  channel = XDMAIF_Get_ChannelNumber(bus->spi.spiId, XDMAD_TRANSFER_TX);
367  cfg.mbr_da = (uint32_t)&bus->spi.pSpiHw->SPI_TDR;
368  cfg.mbr_cfg =
369    XDMAC_CC_TYPE_PER_TRAN |
370    XDMAC_CC_MBSIZE_SINGLE |
371    XDMAC_CC_DSYNC_MEM2PER |
372    XDMAC_CC_CSIZE_CHK_1 |
373    XDMAC_CC_DWIDTH_BYTE |
374    XDMAC_CC_SIF_AHB_IF1 |
375    XDMAC_CC_DIF_AHB_IF1 |
376    XDMAC_CC_SAM_INCREMENTED_AM |
377    XDMAC_CC_DAM_FIXED_AM |
378    XDMAC_CC_PERID(channel);
379  rc = XDMAD_ConfigureTransfer(
380    bus->spi.pXdmad,
381    bus->dma_tx_channel,
382    &cfg,
383    0,
384    0,
385    xdmaInt
386  );
387  assert(rc == XDMAD_OK);
388}
389
390int spi_bus_register_atsam(
391  const char *bus_path,
392  uint8_t     spi_peripheral_id,
393  Spi        *spi_regs,
394  const Pin  *pins,
395  size_t      pin_count
396)
397{
398  atsam_spi_bus *bus;
399
400  bus = (atsam_spi_bus *) spi_bus_alloc_and_init(sizeof(*bus));
401  if (bus == NULL) {
402    return -1;
403  }
404
405  bus->base.transfer = atsam_spi_transfer;
406  bus->base.destroy = atsam_spi_destroy;
407  bus->base.setup = atsam_spi_setup;
408  bus->base.max_speed_hz = MAX_SPI_FREQUENCY;
409  bus->base.bits_per_word = 8;
410  bus->base.speed_hz = bus->base.max_speed_hz;
411  bus->base.delay_usecs = 1;
412  bus->base.cs = 1;
413  bus->spi.spiId = spi_peripheral_id;
414  bus->spi.pSpiHw = spi_regs;
415
416  PIO_Configure(pins, pin_count);
417  PMC_EnablePeripheral(spi_peripheral_id);
418  atsam_configure_spi(bus);
419  atsam_spi_init_xdma(bus);
420
421  return spi_bus_register(&bus->base, bus_path);
422}
Note: See TracBrowser for help on using the repository browser.