/**************************************************************************** * arch/arm/src/samd5e5/sam_spi.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "up_internal.h" #include "up_arch.h" #include "hardware/sam_pinmap.h" #include "sam_gclk.h" #include "sam_port.h" #include "sam_sercom.h" #include "sam_spi.h" #ifdef CONFIG_SAMD5E5SPI_DMA # include "sam_dmac.h" #endif #include #ifdef SAMD5E5_HAVE_SPI /**************************************************************************** * Pre-process Definitions ****************************************************************************/ #ifndef CONFIG_DEBUG_SPI_INFO # undef CONFIG_SAMD5E5SPI_REGDEBUG #endif /**************************************************************************** * Private Types ****************************************************************************/ /* The state of the one SPI chip select */ struct sam_spidev_s { const struct spi_ops_s *ops; /* Externally visible part of the SPI interface */ /* Fixed configuration */ uint8_t sercom; /* Identifies the SERCOM peripheral */ #if 0 /* Not used */ uint8_t irq; /* SERCOM IRQ number */ #endif uint8_t coregen; /* Source GCLK generator */ uint8_t slowgen; /* Slow GCLK generator */ port_pinset_t pad0; /* Pin configuration for PAD0 */ port_pinset_t pad1; /* Pin configuration for PAD1 */ port_pinset_t pad2; /* Pin configuration for PAD2 */ port_pinset_t pad3; /* Pin configuration for PAD3 */ uint32_t muxconfig; /* Pad multiplexing configuration */ uint32_t srcfreq; /* Source clock frequency */ uintptr_t base; /* SERCOM base address */ #if 0 /* Not used */ xcpt_t handler; /* SERCOM interrupt handler */ #endif /* Dynamic configuration */ sem_t spilock; /* Used to managed exclusive access to the bus */ uint32_t frequency; /* Requested clock frequency */ uint32_t actual; /* Actual clock frequency */ uint8_t mode; /* Mode 0,1,2,3 */ uint8_t nbits; /* Width of word in bits (8 to 16) */ #ifdef CONFIG_SAMD5E5SPI_DMA /* DMA */ uint8_t dma_tx_trig; /* DMA TX trigger source to use */ uint8_t dma_rx_trig; /* DMA RX trigger source to use */ DMA_HANDLE dma_tx; /* DMA TX channel handle */ DMA_HANDLE dma_rx; /* DMA RX channel handle */ sem_t dmasem; /* Transfer wait semaphore */ #endif /* Debug stuff */ #ifdef CONFIG_SAMD5E5SPI_REGDEBUG bool wr; /* Last was a write */ uint32_t regaddr; /* Last address */ uint32_t regval; /* Last value */ int ntimes; /* Number of times */ #endif }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* Helpers */ #ifdef CONFIG_SAMD5E5SPI_REGDEBUG static bool spi_checkreg(struct sam_spidev_s *priv, bool wr, uint32_t regval, uint32_t regaddr); #else # define spi_checkreg(priv,wr,regval,regaddr) (false) #endif static uint8_t spi_getreg8(struct sam_spidev_s *priv, unsigned int offset); static void spi_putreg8(struct sam_spidev_s *priv, uint8_t regval, unsigned int offset); static uint16_t spi_getreg16(struct sam_spidev_s *priv, unsigned int offset); static void spi_putreg16(struct sam_spidev_s *priv, uint16_t regval, unsigned int offset); static uint32_t spi_getreg32(struct sam_spidev_s *priv, unsigned int offset); static void spi_putreg32(struct sam_spidev_s *priv, uint32_t regval, unsigned int offset); #ifdef CONFIG_SAMD5E5SPI_DMA static void spi_dma_setup(struct sam_spidev_s *priv); #endif #ifdef CONFIG_DEBUG_SPI_INFO static void spi_dumpregs(struct sam_spidev_s *priv, const char *msg); #else # define spi_dumpregs(priv,msg) #endif /* Interrupt handling */ #if 0 /* Not used */ static int spi_interrupt(int irq, void *context, FAR void *arg); #endif /* SPI methods */ static int spi_lock(struct spi_dev_s *dev, bool lock); static uint32_t spi_setfrequency(struct spi_dev_s *dev, uint32_t frequency); static void spi_setmode(struct spi_dev_s *dev, enum spi_mode_e mode); static void spi_setbits(struct spi_dev_s *dev, int nbits); static uint32_t spi_send(struct spi_dev_s *dev, uint32_t ch); static void spi_exchange(struct spi_dev_s *dev, const void *txbuffer, void *rxbuffer, size_t nwords); #ifndef CONFIG_SPI_EXCHANGE static void spi_sndblock(struct spi_dev_s *dev, const void *buffer, size_t nwords); static void spi_recvblock(struct spi_dev_s *dev, void *buffer, size_t nwords); #endif /* Initialization */ static void spi_wait_synchronization(struct sam_spidev_s *priv); static void spi_pad_configure(struct sam_spidev_s *priv); /**************************************************************************** * Private Data ****************************************************************************/ #ifdef SAMD5E5_HAVE_SPI0 /* SPI0 driver operations */ static const struct spi_ops_s g_spi0ops = { .lock = spi_lock, .select = sam_spi0select, .setfrequency = spi_setfrequency, .setmode = spi_setmode, .setbits = spi_setbits, #ifdef CONFIG_SPI_HWFEATURES .hwfeatures = 0, /* Not supported */ #endif .status = sam_spi0status, #ifdef CONFIG_SPI_CMDDATA .cmddata = sam_spi0cmddata, #endif .send = spi_send, #ifdef CONFIG_SPI_EXCHANGE .exchange = spi_exchange, #else .sndblock = spi_sndblock, .recvblock = spi_recvblock, #endif .registercallback = 0, /* Not implemented */ }; /* This is the overall state of the SPI0 controller */ static struct sam_spidev_s g_spi0dev = { .ops = &g_spi0ops, .sercom = 0, #if 0 /* Not used */ .irq = SAM_IRQ_SERCOM0, #endif .coregen = BOARD_SERCOM0_GCLKGEN, .slowgen = BOARD_SERCOM0_SLOW_GCLKGEN, .pad0 = BOARD_SERCOM0_PINMAP_PAD0, .pad1 = BOARD_SERCOM0_PINMAP_PAD1, .pad2 = BOARD_SERCOM0_PINMAP_PAD2, .pad3 = BOARD_SERCOM0_PINMAP_PAD3, .muxconfig = BOARD_SERCOM0_MUXCONFIG, .srcfreq = BOARD_SERCOM0_FREQUENCY, .base = SAM_SERCOM0_BASE, .spilock = SEM_INITIALIZER(1), #ifdef CONFIG_SAMD5E5SPI_DMA .dma_tx_trig = DMAC_TRIGSRC_SERCOM0_TX, .dma_rx_trig = DMAC_TRIGSRC_SERCOM0_RX, #endif }; #endif #ifdef SAMD5E5_HAVE_SPI1 /* SPI1 driver operations */ static const struct spi_ops_s g_spi1ops = { .lock = spi_lock, .select = sam_spi1select, .setfrequency = spi_setfrequency, .setmode = spi_setmode, .setbits = spi_setbits, .status = sam_spi1status, #ifdef CONFIG_SPI_CMDDATA .cmddata = sam_spi1cmddata, #endif .send = spi_send, #ifdef CONFIG_SPI_EXCHANGE .exchange = spi_exchange, #else .sndblock = spi_sndblock, .recvblock = spi_recvblock, #endif .registercallback = 0, /* Not implemented */ }; /* This is the overall state of the SPI1 controller */ static struct sam_spidev_s g_spi1dev = { .ops = &g_spi1ops, .sercom = 1, #if 0 /* Not used */ .irq = SAM_IRQ_SERCOM1, #endif .coregen = BOARD_SERCOM1_GCLKGEN, .slowgen = BOARD_SERCOM1_SLOW_GCLKGEN, .pad0 = BOARD_SERCOM1_PINMAP_PAD0, .pad1 = BOARD_SERCOM1_PINMAP_PAD1, .pad2 = BOARD_SERCOM1_PINMAP_PAD2, .pad3 = BOARD_SERCOM1_PINMAP_PAD3, .muxconfig = BOARD_SERCOM1_MUXCONFIG, .srcfreq = BOARD_SERCOM1_FREQUENCY, .base = SAM_SERCOM1_BASE, .spilock = SEM_INITIALIZER(1), #ifdef CONFIG_SAMD5E5SPI_DMA .dma_tx_trig = DMAC_TRIGSRC_SERCOM1_TX, .dma_rx_trig = DMAC_TRIGSRC_SERCOM1_RX, #endif }; #endif #ifdef SAMD5E5_HAVE_SPI2 /* SPI2 driver operations */ static const struct spi_ops_s g_spi2ops = { .lock = spi_lock, .select = sam_spi2select, .setfrequency = spi_setfrequency, .setmode = spi_setmode, .setbits = spi_setbits, .status = sam_spi2status, #ifdef CONFIG_SPI_CMDDATA .cmddata = sam_spi2cmddata, #endif .send = spi_send, #ifdef CONFIG_SPI_EXCHANGE .exchange = spi_exchange, #else .sndblock = spi_sndblock, .recvblock = spi_recvblock, #endif .registercallback = 0, /* Not implemented */ }; /* This is the overall state of the SPI2 controller */ static struct sam_spidev_s g_spi2dev = { .ops = &g_spi2ops, .sercom = 2, #if 0 /* Not used */ .irq = SAM_IRQ_SERCOM2, #endif .coregen = BOARD_SERCOM2_GCLKGEN, .slowgen = BOARD_SERCOM2_SLOW_GCLKGEN, .pad0 = BOARD_SERCOM2_PINMAP_PAD0, .pad1 = BOARD_SERCOM2_PINMAP_PAD1, .pad2 = BOARD_SERCOM2_PINMAP_PAD2, .pad3 = BOARD_SERCOM2_PINMAP_PAD3, .muxconfig = BOARD_SERCOM2_MUXCONFIG, .srcfreq = BOARD_SERCOM2_FREQUENCY, .base = SAM_SERCOM2_BASE, .spilock = SEM_INITIALIZER(1), #ifdef CONFIG_SAMD5E5SPI_DMA .dma_tx_trig = DMAC_TRIGSRC_SERCOM2_TX, .dma_rx_trig = DMAC_TRIGSRC_SERCOM2_RX, #endif }; #endif #ifdef SAMD5E5_HAVE_SPI3 /* SPI3 driver operations */ static const struct spi_ops_s g_spi3ops = { .lock = spi_lock, .select = sam_spi3select, .setfrequency = spi_setfrequency, .setmode = spi_setmode, .setbits = spi_setbits, .status = sam_spi3status, #ifdef CONFIG_SPI_CMDDATA .cmddata = sam_spi3cmddata, #endif .send = spi_send, #ifdef CONFIG_SPI_EXCHANGE .exchange = spi_exchange, #else .sndblock = spi_sndblock, .recvblock = spi_recvblock, #endif .registercallback = 0, /* Not implemented */ }; /* This is the overall state of the SPI3 controller */ static struct sam_spidev_s g_spi3dev = { .ops = &g_spi3ops, .sercom = 3, #if 0 /* Not used */ .irq = SAM_IRQ_SERCOM3, #endif .coregen = BOARD_SERCOM3_GCLKGEN, .slowgen = BOARD_SERCOM3_SLOW_GCLKGEN, .pad0 = BOARD_SERCOM3_PINMAP_PAD0, .pad1 = BOARD_SERCOM3_PINMAP_PAD1, .pad2 = BOARD_SERCOM3_PINMAP_PAD2, .pad3 = BOARD_SERCOM3_PINMAP_PAD3, .muxconfig = BOARD_SERCOM3_MUXCONFIG, .srcfreq = BOARD_SERCOM3_FREQUENCY, .base = SAM_SERCOM3_BASE, .spilock = SEM_INITIALIZER(1), #ifdef CONFIG_SAMD5E5SPI_DMA .dma_tx_trig = DMAC_TRIGSRC_SERCOM3_TX, .dma_rx_trig = DMAC_TRIGSRC_SERCOM3_RX, #endif }; #endif #ifdef SAMD5E5_HAVE_SPI4 /* SPI4 driver operations */ static const struct spi_ops_s g_spi4ops = { .lock = spi_lock, .select = sam_spi4select, .setfrequency = spi_setfrequency, .setmode = spi_setmode, .setbits = spi_setbits, .status = sam_spi4status, #ifdef CONFIG_SPI_CMDDATA .cmddata = sam_spi4cmddata, #endif .send = spi_send, #ifdef CONFIG_SPI_EXCHANGE .exchange = spi_exchange, #else .sndblock = spi_sndblock, .recvblock = spi_recvblock, #endif .registercallback = 0, /* Not implemented */ }; /* This is the overall state of the SPI4 controller */ static struct sam_spidev_s g_spi4dev = { .ops = &g_spi4ops, .sercom = 4, #if 0 /* Not used */ .irq = SAM_IRQ_SERCOM4, #endif .coregen = BOARD_SERCOM4_GCLKGEN, .slowgen = BOARD_SERCOM4_SLOW_GCLKGEN, .pad0 = BOARD_SERCOM4_PINMAP_PAD0, .pad1 = BOARD_SERCOM4_PINMAP_PAD1, .pad2 = BOARD_SERCOM4_PINMAP_PAD2, .pad3 = BOARD_SERCOM4_PINMAP_PAD3, .muxconfig = BOARD_SERCOM4_MUXCONFIG, .srcfreq = BOARD_SERCOM4_FREQUENCY, .base = SAM_SERCOM4_BASE, .spilock = SEM_INITIALIZER(1), #ifdef CONFIG_SAMD5E5SPI_DMA .dma_tx_trig = DMAC_TRIGSRC_SERCOM4_TX, .dma_rx_trig = DMAC_TRIGSRC_SERCOM4_RX, #endif }; #endif #ifdef SAMD5E5_HAVE_SPI5 /* SPI5 driver operations */ static const struct spi_ops_s g_spi5ops = { .lock = spi_lock, .select = sam_spi5select, .setfrequency = spi_setfrequency, .setmode = spi_setmode, .setbits = spi_setbits, .status = sam_spi5status, #ifdef CONFIG_SPI_CMDDATA .cmddata = sam_spi5cmddata, #endif .send = spi_send, #ifdef CONFIG_SPI_EXCHANGE .exchange = spi_exchange, #else .sndblock = spi_sndblock, .recvblock = spi_recvblock, #endif .registercallback = 0, /* Not implemented */ }; /* This is the overall state of the SPI5 controller */ static struct sam_spidev_s g_spi5dev = { .ops = &g_spi5ops, .sercom = 5, #if 0 /* Not used */ .irq = SAM_IRQ_SERCOM5, #endif .coregen = BOARD_SERCOM5_GCLKGEN, .slowgen = BOARD_SERCOM5_SLOW_GCLKGEN, .pad0 = BOARD_SERCOM5_PINMAP_PAD0, .pad1 = BOARD_SERCOM5_PINMAP_PAD1, .pad2 = BOARD_SERCOM5_PINMAP_PAD2, .pad3 = BOARD_SERCOM5_PINMAP_PAD3, .muxconfig = BOARD_SERCOM5_MUXCONFIG, .srcfreq = BOARD_SERCOM5_FREQUENCY, .base = SAM_SERCOM5_BASE, .spilock = SEM_INITIALIZER(1), #ifdef CONFIG_SAMD5E5SPI_DMA .dma_tx_trig = DMAC_TRIGSRC_SERCOM5_TX, .dma_rx_trig = DMAC_TRIGSRC_SERCOM5_RX, #endif }; #endif #ifdef SAMD5E5_HAVE_SPI6 /* SPI6 driver operations */ static const struct spi_ops_s g_spi6ops = { .lock = spi_lock, .select = sam_spi6select, .setfrequency = spi_setfrequency, .setmode = spi_setmode, .setbits = spi_setbits, .status = sam_spi6status, #ifdef CONFIG_SPI_CMDDATA .cmddata = sam_spi6cmddata, #endif .send = spi_send, #ifdef CONFIG_SPI_EXCHANGE .exchange = spi_exchange, #else .sndblock = spi_sndblock, .recvblock = spi_recvblock, #endif .registercallback = 0, /* Not implemented */ }; /* This is the overall state of the SPI6 controller */ static struct sam_spidev_s g_spi6dev = { .ops = &g_spi6ops, .sercom = 6, #if 0 /* Not used */ .irq = SAM_IRQ_SERCOM6, #endif .coregen = BOARD_SERCOM6_GCLKGEN, .slowgen = BOARD_SERCOM6_SLOW_GCLKGEN, .pad0 = BOARD_SERCOM6_PINMAP_PAD0, .pad1 = BOARD_SERCOM6_PINMAP_PAD1, .pad2 = BOARD_SERCOM6_PINMAP_PAD2, .pad3 = BOARD_SERCOM6_PINMAP_PAD3, .muxconfig = BOARD_SERCOM6_MUXCONFIG, .srcfreq = BOARD_SERCOM6_FREQUENCY, .base = SAM_SERCOM6_BASE, .spilock = SEM_INITIALIZER(1), #ifdef CONFIG_SAMD5E5SPI_DMA .dma_tx_trig = DMAC_TRIGSRC_SERCOM6_TX, .dma_rx_trig = DMAC_TRIGSRC_SERCOM6_RX, #endif }; #endif #ifdef SAMD5E5_HAVE_SPI7 /* SPI7 driver operations */ static const struct spi_ops_s g_spi7ops = { .lock = spi_lock, .select = sam_spi7select, .setfrequency = spi_setfrequency, .setmode = spi_setmode, .setbits = spi_setbits, .status = sam_spi7status, #ifdef CONFIG_SPI_CMDDATA .cmddata = sam_spi7cmddata, #endif .send = spi_send, #ifdef CONFIG_SPI_EXCHANGE .exchange = spi_exchange, #else .sndblock = spi_sndblock, .recvblock = spi_recvblock, #endif .registercallback = 0, /* Not implemented */ }; /* This is the overall state of the SPI7 controller */ static struct sam_spidev_s g_spi7dev = { .ops = &g_spi7ops, .sercom = 7, #if 0 /* Not used */ .irq = SAM_IRQ_SERCOM7, #endif .coregen = BOARD_SERCOM7_GCLKGEN, .slowgen = BOARD_SERCOM7_SLOW_GCLKGEN, .pad0 = BOARD_SERCOM7_PINMAP_PAD0, .pad1 = BOARD_SERCOM7_PINMAP_PAD1, .pad2 = BOARD_SERCOM7_PINMAP_PAD2, .pad3 = BOARD_SERCOM7_PINMAP_PAD3, .muxconfig = BOARD_SERCOM7_MUXCONFIG, .srcfreq = BOARD_SERCOM7_FREQUENCY, .base = SAM_SERCOM7_BASE, .spilock = SEM_INITIALIZER(1), #ifdef CONFIG_SAMD5E5SPI_DMA .dma_tx_trig = DMAC_TRIGSRC_SERCOM7_TX, .dma_rx_trig = DMAC_TRIGSRC_SERCOM7_RX, #endif }; #endif /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: spi_checkreg * * Description: * Check if the current register access is a duplicate of the preceding. * * Input Parameters: * regval - The value to be written * regaddr - The address of the register to write to * * Returned Value: * true: This is the first register access of this type. * flase: This is the same as the preceding register access. * ****************************************************************************/ #ifdef CONFIG_SAMD5E5SPI_REGDEBUG static bool spi_checkreg(struct sam_spidev_s *priv, bool wr, uint32_t regval, uint32_t regaddr) { if (wr == priv->wr && /* Same kind of access? */ regval == priv->regval && /* Same value? */ regaddr == priv->regaddr) /* Same address? */ { /* Yes, then just keep a count of the number of times we did this. */ priv->ntimes++; return false; } else { /* Did we do the previous operation more than once? */ if (priv->ntimes > 0) { /* Yes... show how many times we did it */ spiinfo("...[Repeats %d times]...\n", priv->ntimes); } /* Save information about the new access */ priv->wr = wr; priv->regval = regval; priv->regaddr = regaddr; priv->ntimes = 0; } /* Return true if this is the first time that we have done this operation */ return true; } #endif /**************************************************************************** * Name: spi_getreg8 * * Description: * Read an SPI register * ****************************************************************************/ static uint8_t spi_getreg8(struct sam_spidev_s *priv, unsigned int offset) { uintptr_t regaddr = priv->base + offset; uint8_t regval = getreg8(regaddr); #ifdef CONFIG_SAMD5E5SPI_REGDEBUG if (spi_checkreg(priv, false, (uint32_t)regval, regaddr)) { spiinfo("%08x->%02x\n", regaddr, regval); } #endif return regval; } /**************************************************************************** * Name: spi_putreg8 * * Description: * Write a value to an SPI register * ****************************************************************************/ static void spi_putreg8(struct sam_spidev_s *priv, uint8_t regval, unsigned int offset) { uintptr_t regaddr = priv->base + offset; #ifdef CONFIG_SAMD5E5SPI_REGDEBUG if (spi_checkreg(priv, true, (uint32_t)regval, regaddr)) { spiinfo("%08x<-%02x\n", regaddr, regval); } #endif putreg8(regval, regaddr); } /**************************************************************************** * Name: spi_getreg16 * * Description: * Read an SPI register * ****************************************************************************/ static uint16_t spi_getreg16(struct sam_spidev_s *priv, unsigned int offset) { uintptr_t regaddr = priv->base + offset; uint16_t regval = getreg16(regaddr); #ifdef CONFIG_SAMD5E5SPI_REGDEBUG if (spi_checkreg(priv, false, (uint32_t)regval, regaddr)) { spiinfo("%08x->%04x\n", regaddr, regval); } #endif return regval; } /**************************************************************************** * Name: spi_putreg16 * * Description: * Write a value to an SPI register * ****************************************************************************/ static void spi_putreg16(struct sam_spidev_s *priv, uint16_t regval, unsigned int offset) { uintptr_t regaddr = priv->base + offset; #ifdef CONFIG_SAMD5E5SPI_REGDEBUG if (spi_checkreg(priv, true, (uint32_t)regval, regaddr)) { spiinfo("%08x<-%04x\n", regaddr, regval); } #endif putreg16(regval, regaddr); } /**************************************************************************** * Name: spi_getreg32 * * Description: * Read an SPI register * ****************************************************************************/ static uint32_t spi_getreg32(struct sam_spidev_s *priv, unsigned int offset) { uintptr_t regaddr = priv->base + offset; uint32_t regval = getreg32(regaddr); #ifdef CONFIG_SAMD5E5SPI_REGDEBUG if (spi_checkreg(priv, false, regval, regaddr)) { spiinfo("%08x->%08x\n", regaddr, regval); } #endif return regval; } /**************************************************************************** * Name: spi_putreg32 * * Description: * Write a value to an SPI register * ****************************************************************************/ static void spi_putreg32(struct sam_spidev_s *priv, uint32_t regval, unsigned int offset) { uintptr_t regaddr = priv->base + offset; #ifdef CONFIG_SAMD5E5SPI_REGDEBUG if (spi_checkreg(priv, true, regval, regaddr)) { spiinfo("%08x<-%08x\n", regaddr, regval); } #endif putreg32(regval, regaddr); } /**************************************************************************** * Name: spi_dumpregs * * Description: * Dump the contents of all SPI registers * * Input Parameters: * priv - The SPI controller to dump * msg - Message to print before the register data * * Returned Value: * None * ****************************************************************************/ #ifdef CONFIG_DEBUG_SPI_INFO static void spi_dumpregs(struct sam_spidev_s *priv, const char *msg) { spiinfo("%s:\n", msg); spiinfo(" CTRLA:%08x CTRLB:%08x DBGCTRL:%02x\n", getreg32(priv->base + SAM_SPI_CTRLA_OFFSET), getreg32(priv->base + SAM_SPI_CTRLB_OFFSET), getreg8(priv->base + SAM_SPI_DBGCTRL_OFFSET)); spiinfo(" BAUD:%02x INTEN:%02x INTFLAG:%02x\n", getreg8(priv->base + SAM_SPI_BAUD_OFFSET), getreg8(priv->base + SAM_SPI_INTENCLR_OFFSET), getreg8(priv->base + SAM_SPI_INTFLAG_OFFSET)); spiinfo(" STATUS:%04x ADDR:%08x\n", getreg16(priv->base + SAM_SPI_STATUS_OFFSET), getreg32(priv->base + SAM_SPI_ADDR_OFFSET)); } #endif /**************************************************************************** * Name: spi_interrupt * * Description: * This is the SPI interrupt handler. It will be invoked when an * interrupt received on the 'irq' indicating either that the DATA * register is available for the next transmission (DRE) or that the * DATA register holds a new incoming work. * ****************************************************************************/ #if 0 /* Not used */ static int spi_interrupt(int irq, void *context, FAR void *arg) { struct sam_dev_s *priv = (struct sam_dev_s *)arg uint8_t pending; uint8_t intflag; uint8_t inten; DEBUGASSERT(priv != NULL); /* Get the set of pending SPI interrupts (we are only interested in the * unmasked interrupts). */ intflag = sam_getreg8(priv, SAM_SPI_INTFLAG_OFFSET); inten = sam_getreg8(priv, SAM_SPI_INTENCLR_OFFSET); pending = intflag & inten; /* Handle an incoming, receive byte. The RXC flag is set when there is * unread data in DATA register. This flag is cleared by reading the DATA * register (or by disabling the receiver). */ if ((pending & SPI_INT_RXC) != 0) { /* Received data ready... process incoming SPI ata */ #warning Missing logic } /* Handle outgoing, transmit bytes. The DRE flag is set when the DATA * register is empty and ready to be written. This flag is cleared by * writing new data to the DATA register. If there is no further data to * be transmitted, the serial driver will disable TX interrupts, prohibit * further interrupts until TX interrupts are re-enabled. */ if ((pending & SPI_INT_DRE) != 0) { /* Transmit data register empty ... process outgoing bytes */ #warning Missing logic } return OK; } #endif /**************************************************************************** * Name: spi_lock * * Description: * On SPI buses where there are multiple devices, it will be necessary to * lock SPI to have exclusive access to the buses for a sequence of * transfers. The bus should be locked before the chip is selected. After * locking the SPI bus, the caller should then also call the setfrequency, * setbits, and setmode methods to make sure that the SPI is properly * configured for the device. If the SPI bus is being shared, then it * may have been left in an incompatible state. * * Input Parameters: * dev - Device-specific state data * lock - true: Lock priv bus, false: unlock SPI bus * * Returned Value: * None * ****************************************************************************/ static int spi_lock(struct spi_dev_s *dev, bool lock) { struct sam_spidev_s *priv = (struct sam_spidev_s *)dev; int ret; spiinfo("lock=%d\n", lock); if (lock) { ret = nxsem_wait_uninterruptible(&priv->spilock); } else { ret = nxsem_post(&priv->spilock); } return ret; } /**************************************************************************** * Name: spi_setfrequency * * Description: * Set the SPI frequency. * * Input Parameters: * dev - Device-specific state data * frequency - The SPI frequency requested * * Returned Value: * Returns the actual frequency selected * ****************************************************************************/ static uint32_t spi_setfrequency(struct spi_dev_s *dev, uint32_t frequency) { struct sam_spidev_s *priv = (struct sam_spidev_s *)dev; uint32_t maxfreq; uint32_t actual; uint32_t baud; uint32_t ctrla; spiinfo("sercom=%d frequency=%d\n", priv->sercom, frequency); /* Check if the configured BAUD is within the valid range */ maxfreq = (priv->srcfreq >> 1); if (frequency > maxfreq) { /* Set the frequency to the maximum */ spierr("ERROR: Cannot realize frequency: %ld\n", (long)frequency); frequency = maxfreq; } /* Check if the requested frequency is the same as the frequency selection */ if (priv->frequency == frequency) { /* We are already at this frequency. Return the actual. */ return priv->actual; } /* For synchronous mode, the BAUAD rate (Fbaud) is generated from the * source clock frequency (Fref) as follows: * * Fbaud = Fref / (2 * (BAUD + 1)) * * Or * * BAUD = (Fref / (2 * Fbaud)) - 1 * * Where BAUD <= 255 */ baud = ((priv->srcfreq + frequency) / (frequency << 1)) - 1; /* Verify that the resulting if BAUD divisor is within range */ if (baud > 255) { spierr("ERROR: BAUD is out of range: %ld\n", (long)baud); baud = 255; } /* Momentarily disable SPI while we apply the new BAUD setting (if it was * previously enabled) */ ctrla = spi_getreg32(priv, SAM_SPI_CTRLA_OFFSET); if ((ctrla & SPI_CTRLA_ENABLE) != 0) { /* Disable SPI.. waiting for synchronization */ spi_putreg32(priv, ctrla & ~SPI_CTRLA_ENABLE, SAM_SPI_CTRLA_OFFSET); spi_wait_synchronization(priv); /* Set the new BAUD value */ spi_putreg8(priv, (uint8_t)baud, SAM_SPI_BAUD_OFFSET); /* Re-enable SPI.. waiting for synchronization */ spi_putreg32(priv, ctrla, SAM_SPI_CTRLA_OFFSET); spi_wait_synchronization(priv); } else { /* Set the new BAUD when the SPI is already disabled */ spi_putreg8(priv, (uint8_t)baud, SAM_SPI_BAUD_OFFSET); } /* Calculate the new actual frequency */ actual = priv->srcfreq / ((baud + 1) << 1); /* Save the frequency setting */ priv->frequency = frequency; priv->actual = actual; spiinfo("Frequency %d->%d\n", frequency, actual); return actual; } /**************************************************************************** * Name: spi_setmode * * Description: * Set the SPI mode. Optional. See enum spi_mode_e for mode definitions * * Input Parameters: * dev - Device-specific state data * mode - The SPI mode requested * * Returned Value: * none * ****************************************************************************/ static void spi_setmode(struct spi_dev_s *dev, enum spi_mode_e mode) { struct sam_spidev_s *priv = (struct sam_spidev_s *)dev; uint32_t regval; spiinfo("sercom=%d mode=%d\n", priv->sercom, mode); /* Has the mode changed? */ if (mode != priv->mode) { /* Yes... Set the mode appropriately */ /* First we need to disable SPI while we change the mode */ regval = spi_getreg32(priv, SAM_SPI_CTRLA_OFFSET); spi_putreg32(priv, regval & ~SPI_CTRLA_ENABLE, SAM_SPI_CTRLA_OFFSET); spi_wait_synchronization(priv); regval &= ~(SPI_CTRLA_CPOL | SPI_CTRLA_CPHA); switch (mode) { case SPIDEV_MODE0: /* CPOL=0; CPHA=0 */ break; case SPIDEV_MODE1: /* CPOL=0; CPHA=1 */ regval |= SPI_CTRLA_CPHA; break; case SPIDEV_MODE2: /* CPOL=1; CPHA=0 */ regval |= SPI_CTRLA_CPOL; break; case SPIDEV_MODE3: /* CPOL=1; CPHA=1 */ regval |= (SPI_CTRLA_CPOL | SPI_CTRLA_CPHA); break; default: DEBUGASSERT(FALSE); return; } spi_putreg32(priv, regval | SPI_CTRLA_ENABLE, SAM_SPI_CTRLA_OFFSET); /* Save the mode so that subsequent re-configurations will be faster */ priv->mode = mode; } } /**************************************************************************** * Name: spi_setbits * * Description: * Set the number if bits per word. * * Input Parameters: * dev - Device-specific state data * nbits - The number of bits requests * * Returned Value: * none * ****************************************************************************/ static void spi_setbits(struct spi_dev_s *dev, int nbits) { struct sam_spidev_s *priv = (struct sam_spidev_s *)dev; uint32_t regval; spiinfo("sercom=%d nbits=%d\n", priv->sercom, nbits); DEBUGASSERT(priv && nbits > 7 && nbits < 10); /* Has the number of bits changed? */ if (nbits != priv->nbits) { /* Yes... Set number of bits appropriately */ regval = spi_getreg32(priv, SAM_SPI_CTRLB_OFFSET); regval &= ~SPI_CTRLB_CHSIZE_MASK; if (nbits == 9) { regval |= SPI_CTRLB_CHSIZE_9BITS; } spi_putreg32(priv, regval, SAM_SPI_CTRLB_OFFSET); /* Save the selection so the subsequence re-configurations will be faster */ priv->nbits = nbits; } } /**************************************************************************** * Name: spi_send * * Description: * Exchange one word on SPI * * Input Parameters: * dev - Device-specific state data * wd - The word to send. the size of the data is determined by the * number of bits selected for the SPI interface. * * Returned Value: * response * ****************************************************************************/ static uint32_t spi_send(struct spi_dev_s *dev, uint32_t wd) { uint8_t txbyte; uint8_t rxbyte; /* spi_exchange can do this. Note: right now, this only deals with 8-bit * words. If the SPI interface were configured for words of other sizes, * this would fail. */ txbyte = (uint8_t)wd; rxbyte = (uint8_t)0; spi_exchange(dev, &txbyte, &rxbyte, 1); spiinfo("Sent %02x received %02x\n", txbyte, rxbyte); return (uint32_t)rxbyte; } /**************************************************************************** * Name: spi_dma_callback * * Description: * DMA completion callback * * Input Parameters: * dma - Allocate DMA handle * arg - User argument provided with callback * result - The result of the DMA operation * * Returned Value: * None * ****************************************************************************/ #ifdef CONFIG_SAMD5E5SPI_DMA static void spi_dma_callback(DMA_HANDLE dma, void *arg, int result) { struct sam_spidev_s *priv = (struct sam_spidev_s *)arg; if (dma == priv->dma_rx) { /* Notify the blocked spi_exchange() call that the transaction * has completed by posting to the semaphore */ nxsem_post(&priv->dmasem); } else if (dma == priv->dma_tx) { if (result != OK) { spierr("ERROR: DMA transmission failed: %d\n", result); } } } #endif /**************************************************************************** * Name: spi_exchange * * Description: * Exchange a block of data from SPI. * * Input Parameters: * dev - Device-specific state data * txbuffer - A pointer to the buffer of data to be sent * rxbuffer - A pointer to the buffer in which to receive data * nwords - the length of data that to be exchanged in units of words. * The wordsize is determined by the number of bits-per-word * selected for the SPI interface. If nbits <= 8, the data is * packed into uint8_t's; if nbits >8, the data is packed into * uint16_t's * * Returned Value: * None * * Assumptions/Limitations: * Data must be 16-bit aligned in 9-bit data transfer mode. * ****************************************************************************/ static void spi_exchange(struct spi_dev_s *dev, const void *txbuffer, void *rxbuffer, size_t nwords) { struct sam_spidev_s *priv = (struct sam_spidev_s *)dev; #ifdef CONFIG_SAMD5E5SPI_DMA uint32_t regval; spiinfo("txbuffer=%p rxbuffer=%p nwords=%d\n", txbuffer, rxbuffer, nwords); /* Disable SPI while we configure new DMA descriptors */ regval = spi_getreg32(priv, SAM_SPI_CTRLA_OFFSET); regval &= ~SPI_CTRLA_ENABLE; spi_putreg32(priv, regval, SAM_SPI_CTRLA_OFFSET); spi_wait_synchronization(priv); /* Setup RX and TX DMA channels */ sam_dmatxsetup(priv->dma_tx, priv->base + SAM_SPI_DATA_OFFSET, (uint32_t)txbuffer, nwords); sam_dmarxsetup(priv->dma_rx, priv->base + SAM_SPI_DATA_OFFSET, (uint32_t)rxbuffer, nwords); /* Start RX and TX DMA channels */ sam_dmastart(priv->dma_tx, spi_dma_callback, (void *)priv); sam_dmastart(priv->dma_rx, spi_dma_callback, (void *)priv); /* Enable SPI to trigger the TX DMA channel */ regval = spi_getreg32(priv, SAM_SPI_CTRLA_OFFSET); regval |= SPI_CTRLA_ENABLE; spi_putreg32(priv, regval, SAM_SPI_CTRLA_OFFSET); spi_wait_synchronization(priv); /* Wait for the DMA callback to notify us that the transfer is complete */ nxsem_wait_uninterruptible(&priv->dmasem); #else const uint16_t *ptx16; const uint8_t *ptx8; uint16_t *prx16; uint8_t *prx8; uint16_t data; spiinfo("txbuffer=%p rxbuffer=%p nwords=%d\n", txbuffer, rxbuffer, nwords); /* Set up data receive and transmit pointers */ if (priv->nbits > 8) { ptx8 = NULL; prx8 = NULL; ptx16 = (const uint16_t *)txbuffer; prx16 = (uint16_t *)rxbuffer; } else { ptx8 = (const uint8_t *)txbuffer; prx8 = (uint8_t *)rxbuffer; ptx16 = NULL; prx16 = NULL; } /* Loop, sending each word in the user-provided data buffer. * * Note 1: Right now, this only deals with 8-bit words. If the SPI * interface were configured for words of other sizes, this * would fail. * Note 2: This loop might be made more efficient. Would logic * like the following improve the throughput? Or would it * just add the risk of overruns? * * Get word 1; * Send word 1; Now word 1 is "in flight" * nwords--; * for (; nwords > 0; nwords--) * { * Get word N. * Wait for DRE:: meaning that word N-1 has moved to the shift * register. * Disable interrupts to keep the following atomic * Send word N. Now both work N-1 and N are "in flight" * Wait for RXC: meaning that word N-1 is available * Read word N-1. * Re-enable interrupts. * Save word N-1. * } * Wait for RXC: meaning that the final word is available * Read the final word. * Save the final word. */ for (; nwords > 0; nwords--) { /* Get the data to send (0xff if there is no data source) */ if (ptx8) { data = (uint16_t)*ptx8++; } else if (ptx16) { data = *ptx16++; } else { data = 0x01ff; } /* Wait for any previous data written to the DATA register to be * transferred to the serializer. */ while ((spi_getreg8(priv, SAM_SPI_INTFLAG_OFFSET) & SPI_INT_DRE) == 0); /* Write the data to transmitted to the DATA Register (TDR) */ spi_putreg16(priv, data, SAM_SPI_DATA_OFFSET); /* Wait for the read data to be available in the DATA register. */ while ((spi_getreg8(priv, SAM_SPI_INTFLAG_OFFSET) & SPI_INT_RXC) == 0); /* Check for data overflow. The BUFOVF bit provides the status of the * next DATA to be read. On buffer overflow, the corresponding DATA * will be 0. */ data = spi_getreg16(priv, SAM_SPI_STATUS_OFFSET); if ((data & SPI_STATUS_BUFOVF) != 0) { spierr("ERROR: Buffer overflow!\n"); /* Clear the buffer overflow flag */ spi_putreg16(priv, data, SAM_SPI_STATUS_OFFSET); } /* Read the received data from the SPI DATA Register.. * TODO: The following only works if nbits <= 8. */ data = spi_getreg16(priv, SAM_SPI_DATA_OFFSET); if (prx8) { *prx8++ = (uint8_t)data; } else if (prx16) { *prx16++ = (uint16_t)data; } } #endif } /**************************************************************************** * Name: spi_sndblock * * Description: * Send a block of data on SPI * * Input Parameters: * dev - Device-specific state data * buffer - A pointer to the buffer of data to be sent * nwords - the length of data to send from the buffer in number of words. * The wordsize is determined by the number of bits-per-word * selected for the SPI interface. If nbits <= 8, the data is * packed into uint8_t's; if nbits >8, the data is packed into * uint16_t's * * Returned Value: * None * ****************************************************************************/ #ifndef CONFIG_SPI_EXCHANGE static void spi_sndblock(struct spi_dev_s *dev, const void *buffer, size_t nwords) { /* spi_exchange can do this. */ spi_exchange(dev, buffer, NULL, nwords); } #endif /**************************************************************************** * Name: spi_recvblock * * Description: * Revice a block of data from SPI * * Input Parameters: * dev - Device-specific state data * buffer - A pointer to the buffer in which to receive data * nwords - the length of data that can be received in the buffer in number * of words. The wordsize is determined by the number of * bits-per-word selected for the SPI interface. If nbits <= 8, * the data is packed into uint8_t's; if nbits >8, the data is * packed into uint16_t's * * Returned Value: * None * ****************************************************************************/ #ifndef CONFIG_SPI_EXCHANGE static void spi_recvblock(struct spi_dev_s *dev, void *buffer, size_t nwords) { /* spi_exchange can do this. */ spi_exchange(dev, NULL, buffer, nwords); } #endif /**************************************************************************** * Name: spi_wait_synchronization * * Description: * Wait until the SERCOM SPI reports that it is synchronized. * ****************************************************************************/ static void spi_wait_synchronization(struct sam_spidev_s *priv) { while ((spi_getreg16(priv, SAM_SPI_SYNCBUSY_OFFSET) & SPI_SYNCBUSY_ALL) != 0); } /**************************************************************************** * Name: spi_pad_configure * * Description: * Configure the SERCOM SPI pads. * ****************************************************************************/ static void spi_pad_configure(struct sam_spidev_s *priv) { /* Configure SERCOM pads */ if (priv->pad0 != 0) { sam_portconfig(priv->pad0); } if (priv->pad1 != 0) { sam_portconfig(priv->pad1); } if (priv->pad2 != 0) { sam_portconfig(priv->pad2); } if (priv->pad3 != 0) { sam_portconfig(priv->pad3); } } /**************************************************************************** * Name: spi_dma_setup * * Description: * Configure the SPI DMA operation. * ****************************************************************************/ #ifdef CONFIG_SAMD5E5SPI_DMA static void spi_dma_setup(struct sam_spidev_s *priv) { /* Allocate a pair of DMA channels */ priv->dma_rx = sam_dmachannel(DMACH_FLAG_BEATSIZE_BYTE | DMACH_FLAG_MEM_INCREMENT | DMACH_FLAG_PERIPH_RXTRIG(priv->dma_rx_trig)); priv->dma_tx = sam_dmachannel(DMACH_FLAG_BEATSIZE_BYTE | DMACH_FLAG_MEM_INCREMENT | DMACH_FLAG_PERIPH_TXTRIG(priv->dma_tx_trig)); /* Initialize the semaphore used to notify when DMA is complete */ nxsem_init(&priv->dmasem, 0, 0); nxsem_setprotocol(&priv->dmasem, SEM_PRIO_NONE); } #endif /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: sam_spibus_initialize * * Description: * Initialize the selected SPI port * * Input Parameters: * port - SPI "port" number (i.e., SERCOM number) * * Returned Value: * Valid SPI device structure reference on success; a NULL on failure * ****************************************************************************/ struct spi_dev_s *sam_spibus_initialize(int port) { struct sam_spidev_s *priv; irqstate_t flags; uint32_t regval; /* Get the port state structure */ spiinfo("port: %d \n", port); #ifdef SAMD5E5_HAVE_SPI0 if (port == 0) { priv = &g_spi0dev; } else #endif #ifdef SAMD5E5_HAVE_SPI1 if (port == 1) { priv = &g_spi1dev; } else #endif #ifdef SAMD5E5_HAVE_SPI2 if (port == 2) { priv = &g_spi2dev; } else #endif #ifdef SAMD5E5_HAVE_SPI3 if (port == 3) { priv = &g_spi3dev; } else #endif #ifdef SAMD5E5_HAVE_SPI4 if (port == 4) { priv = &g_spi4dev; } else #endif #ifdef SAMD5E5_HAVE_SPI5 if (port == 5) { priv = &g_spi5dev; } else #endif #ifdef SAMD5E5_HAVE_SPI6 if (port == 6) { priv = &g_spi6dev; } else #endif #ifdef SAMD5E5_HAVE_SPI7 if (port == 7) { priv = &g_spi7dev; } else #endif { spierr("ERROR: Unsupported port: %d\n", port); return NULL; } #ifdef CONFIG_SAMD5E5SPI_DMA spi_dma_setup(priv); #endif /* Enable clocking to the SERCOM module in PM */ flags = enter_critical_section(); sercom_enable(priv->sercom); /* Configure the GCLKs for the SERCOM module */ sercom_coreclk_configure(priv->sercom, priv->coregen, false); sercom_slowclk_configure(priv->sercom, priv->slowgen); /* Set the SERCOM in SPI master mode (no address) */ regval = spi_getreg32(priv, SAM_SPI_CTRLA_OFFSET); regval &= ~(SPI_CTRLA_MODE_MASK | SPI_CTRLA_FORM_MASK); regval |= (SPI_CTRLA_MODE_MASTER | SPI_CTRLA_FORM_SPI); spi_putreg32(priv, regval, SAM_SPI_CTRLA_OFFSET); /* Configure pads */ spi_pad_configure(priv); /* Set an initial baud value. This will be changed by the upper-half * driver as soon as it starts. */ spi_setfrequency((struct spi_dev_s *)priv, 400000); /* Set MSB first data order and the configured pad mux setting. * SPI mode 0 is assumed initially (CPOL=0 and CPHA=0). */ regval &= ~(SPI_CTRLA_DOPO_MASK | SPI_CTRLA_DIPO_MASK); regval &= ~(SPI_CTRLA_CPHA | SPI_CTRLA_CPOL); regval |= (SPI_CTRLA_MSBFIRST | priv->muxconfig); spi_putreg32(priv, regval, SAM_SPI_CTRLA_OFFSET); /* Enable the receiver. Note that 8-bit data width is assumed initially */ regval = (SPI_CTRLB_RXEN | SPI_CTRLB_CHSIZE_8BITS); spi_putreg32(priv, regval, SAM_SPI_CTRLB_OFFSET); spi_wait_synchronization(priv); priv->nbits = 8; /* Enable SPI */ regval = spi_getreg32(priv, SAM_SPI_CTRLA_OFFSET); regval |= SPI_CTRLA_ENABLE; spi_putreg32(priv, regval, SAM_SPI_CTRLA_OFFSET); spi_wait_synchronization(priv); /* Disable all interrupts at the SPI source and clear all pending * status that we can. */ spi_putreg8(priv, SPI_INT_ALL, SAM_SPI_INTENCLR_OFFSET); spi_putreg8(priv, SPI_INT_ALL, SAM_SPI_INTFLAG_OFFSET); spi_putreg16(priv, SPI_STATUS_CLRALL, SAM_SPI_STATUS_OFFSET); #if 0 /* Not used */ /* Attach and enable the SERCOM interrupt handler */ ret = irq_attach(priv->irq, spi_interrupt, priv); if (ret < 0) { spierr("ERROR: Failed to attach interrupt: %d\n", irq); return NULL; } /* Enable SERCOM interrupts at the NVIC */ up_enable_irq(priv->irq); #endif spi_dumpregs(priv, "After initialization"); leave_critical_section(flags); return (struct spi_dev_s *)priv; } #endif /* SAMD5E5_HAVE_SPI */