/* * Copyright (C) 2010 by Ulrich Prinz (uprinz2@netscape.net) * Copyright (C) 2010 by Nikolaj Zamotaev. All rights reserved. * Copyright (C) 2014 by Uwe Bonnes(bon@elektron.ikp.physik.tu-darmstadt.de). * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * For additional information see http://www.ethernut.de/ * */ /* * \file arch/cm3/stm/stm32_spi.c * \brief STM32 SPI handling. Only include from devices specific file * stm32_spiX.c. * * The SPI IP has no interrupt for SPI getting idle, so it takes some effort * to find the right moment to switch SPI and CS off. To do so, we always * count the received byte even if we transmit only. * DMA_Mode handles this case per se. * \verbatim * $Id: stm32_spi.c 5717 2014-05-23 14:55:51Z u_bonnes $ * \endverbatim */ #include #include #include #include #include #include #include #include #include #if !defined(SPI_SCK_GPIO_AF) #define SPI_SCK_GPIO_AF SPI_GPIO_AF #endif #if !defined(SPI_MOSI_GPIO_AF) #define SPI_MOSI_GPIO_AF SPI_GPIO_AF #endif #if !defined(SPI_MISO_GPIO_AF) #define SPI_MISO_GPIO_AF SPI_GPIO_AF #endif /* Keep SPIBUS_CSx_INIT() undefined when PORT/PIN is not available*/ #if defined(SPIBUS_CS0_PORT) && defined(SPIBUS_CS0_PIN) #define SPIBUS_CS0_INIT(x) GpioPinConfigSet(SPIBUS_CS0_PORT, SPIBUS_CS0_PIN, GPIO_CFG_OUTPUT | x) #define SPIBUS_CS0_SET() GpioPinSetHigh(SPIBUS_CS0_PORT, SPIBUS_CS0_PIN) #define SPIBUS_CS0_CLR() GpioPinSetLow (SPIBUS_CS0_PORT, SPIBUS_CS0_PIN) #else #define SPIBUS_CS0_SET() #define SPIBUS_CS0_CLR() #endif #if defined(SPIBUS_CS1_PORT) && defined(SPIBUS_CS1_PIN) #define SPIBUS_CS1_INIT(x) GpioPinConfigSet(SPIBUS_CS1_PORT, SPIBUS_CS1_PIN, GPIO_CFG_OUTPUT | x) #define SPIBUS_CS1_SET() GpioPinSetHigh(SPIBUS_CS1_PORT, SPIBUS_CS1_PIN) #define SPIBUS_CS1_CLR() GpioPinSetLow (SPIBUS_CS1_PORT, SPIBUS_CS1_PIN) #else #define SPIBUS_CS1_SET() #define SPIBUS_CS1_CLR() #endif #if defined(SPIBUS_CS2_PORT) && defined(SPIBUS_CS2_PIN) #define SPIBUS_CS2_INIT(x) GpioPinConfigSet(SPIBUS_CS2_PORT, SPIBUS_CS2_PIN, GPIO_CFG_OUTPUT | x) #define SPIBUS_CS2_SET() GpioPinSetHigh(SPIBUS_CS2_PORT, SPIBUS_CS2_PIN) #define SPIBUS_CS2_CLR() GpioPinSetLow (SPIBUS_CS2_PORT, SPIBUS_CS2_PIN) #else #define SPIBUS_CS2_SET() #define SPIBUS_CS2_CLR() #endif #if defined(SPIBUS_CS3_PORT) && defined(SPIBUS_CS3_PIN) #define SPIBUS_CS3_INIT(x) GpioPinConfigSet(SPIBUS_CS3_PORT, SPIBUS_CS3_PIN, GPIO_CFG_OUTPUT | x) #define SPIBUS_CS3_SET() GpioPinSetHigh(SPIBUS_CS3_PORT, SPIBUS_CS3_PIN) #define SPIBUS_CS3_CLR() GpioPinSetLow (SPIBUS_CS3_PORT, SPIBUS_CS3_PIN) #else #define SPIBUS_CS3_SET() #define SPIBUS_CS3_CLR() #endif #if SPIBUS_MODE == POLLING_MODE #elif SPIBUS_MODE == IRQ_MODE static uint8_t * volatile spi_txp; static uint8_t * volatile spi_rxp; static volatile size_t spi_rx_len; static volatile size_t spi_tx_len; static volatile size_t spi_len; static void Stm32SpiBusInterrupt(void *arg) { uint8_t b; SPI_TypeDef *spi = (SPI_TypeDef *)SPI_BASE; if(CM3BBGET(SPI_BASE, SPI_TypeDef, SR, _BI32(SPI_SR_RXNE))) { b = spi->DR; spi_len --; if (spi_rx_len) { if (spi_rxp) { *spi_rxp = b; spi_rxp++; spi_rx_len--; if (spi_rx_len == 1) { if (spi->CR1 & (SPI_CR1_BIDIMODE| SPI_CR1_RXONLY)) { /* Follow "Disabling the SPI" */ while( GpioPinGet(SPIBUS_SCK_PORT, SPIBUS_SCK_PIN)); while(!GpioPinGet(SPIBUS_SCK_PORT, SPIBUS_SCK_PIN)); CM3BBCLR(SPI_BASE, SPI_TypeDef, CR1, _BI32(SPI_CR1_SPE)); } } } } /* Terminate when the requested number of bytes have been received * even so perhaps we didn't need to store them. * That way we can make sure we don't deassert CS while SCK is still running. */ if (spi_len == 0){ spi->CR2 &= ~(SPI_CR2_TXEIE | SPI_CR2_RXNEIE); NutEventPostFromIrq((void **)arg); } } if (CM3BBGET(SPI_BASE, SPI_TypeDef, SR, _BI32(SPI_SR_TXE))) { if (spi_tx_len) { b = *spi_txp; /* After sending the last byte we need to wait for the last * receive interrupt, but we are not interested in the transmitter * empty interrupt */ if (spi_tx_len == 1) { CM3BBSET(SPI_BASE, SPI_TypeDef, CR2, _BI32(SPI_CR2_RXNEIE)); CM3BBCLR(SPI_BASE, SPI_TypeDef, CR2, _BI32(SPI_CR2_TXEIE)); } /* Half word access via "spi->DR = b" shifts out two frames * on the F373! */ *(uint8_t *) &spi->DR = b; spi_txp++; spi_tx_len --; } } } #else #include static void Stm32SpiBusDMAInterrupt(void *arg) { CM3BBCLR(SPI_BASE, SPI_TypeDef, CR1, _BI32(SPI_CR1_SPE)); DMA_ClearFlag(SPI_DMA_RX_CHANNEL, DMA_TCIF); NutEventPostFromIrq((void **)arg); } #endif /*! * \brief Set the specified chip select to a given level. */ static int Stm32SpiChipSelect(NUTSPINODE *node, int assert) { int res; int hi; if (node->node_mode & SPI_MODE_CSHIGH) hi = assert; else hi = !assert; switch (node->node_cs) { case 0: if(hi) SPIBUS_CS0_SET(); else SPIBUS_CS0_CLR(); res = 0; break; #if SPIBUS_CS1_SET !=0 case 1: if(hi) SPIBUS_CS1_SET(); else SPIBUS_CS1_CLR(); res = 0; break; #endif #if SPIBUS_CS2_SET !=0 case 2: if(hi) SPIBUS_CS2_SET(); else SPIBUS_CS2_CLR(); res = 0; break; #endif #if SPIBUS_CS3_SET !=0 case 2: if(hi) SPIBUS_CS3_SET(); else SPIBUS_CS3_CLR(); res = 0; break; #endif default: res = -1; } return res; } /*! \brief Deselect a device on the SPI bus. * * Deactivates the chip select and unlocks the bus. * * \param node Specifies the SPI bus node. * * \return Always 0. */ static int Stm32SpiBusDeselect(NUTSPINODE * node) { /* Sanity check. */ NUTASSERT(node != NULL); NUTASSERT(node->node_bus != NULL); NutSpiBusWait(node, NUT_WAIT_INFINITE); /* Deactivate the node's chip select. */ Stm32SpiChipSelect(node, 0); /* Release the bus. */ NutEventPostAsync(&node->node_bus->bus_mutex); return 0; } /*! \brief Select a device on the SPI bus. * * Locks and activates the bus for the specified node. * * \param node Specifies the SPI bus node. * \param tmo Timeout in milliseconds. To disable timeout, set this * parameter to NUT_WAIT_INFINITE. * * \return 0 on success. In case of an error, -1 is returned and the bus * is not locked. */ static int Stm32SpiBusSelect(NUTSPINODE * node, uint32_t tmo) { int rc; SPI_TypeDef* base; /* Sanity check. */ NUTASSERT(node != NULL); NUTASSERT(node->node_bus != NULL); NUTASSERT(node->node_stat != NULL); base=(SPI_TypeDef*)(node->node_bus->bus_base); /* Allocate the bus. */ rc = NutEventWait(&node->node_bus->bus_mutex, tmo); if (rc) { errno = EIO; } else { STM32SPIREG *spireg = node->node_stat; /* If the mode update bit is set, then update our shadow registers. */ if (node->node_mode & SPI_MODE_UPDATE) { Stm32SpiSetup(node); } /* Set SPI mode. */ base->CR1 = spireg->CR1; base->CR1 |= SPI_CR1_SSI|SPI_CR1_MSTR; base->CR2 = spireg->CR2; base->I2SCFGR=spireg->I2SCFGR; base->I2SPR=spireg->I2SPR; /* Finally activate the node's chip select. */ rc = Stm32SpiChipSelect(node, 1); if (rc) { /* Release the bus in case of an error. */ NutEventPost(&node->node_bus->bus_mutex); } } return rc; } /*! * \brief Update SPI shadow registers. * * \param node Specifies the SPI bus node. * * \return Always 0. */ static int Stm32SpiSetup(NUTSPINODE * node) { uint32_t clk; uint32_t clkdiv; SPI_TypeDef *spireg; NUTASSERT(node != NULL); NUTASSERT(node->node_stat != NULL); NUTASSERT(node->node_bus != NULL); NUTASSERT(node->node_bus->bus_base != 0); spireg = node->node_stat; #if defined(SPI_CR2_DS) spireg->CR1 &= ~( SPI_CR1_CPOL | SPI_CR1_CPHA | SPI_CR1_BR); spireg->CR2 &= ~SPI_CR2_DS; if((node->node_bits > 6) && (node->node_bits <= 16)) spireg->CR2 |= (node->node_bits -1) <<8; #else spireg->CR1 &= ~(SPI_CR1_DFF | SPI_CR1_CPOL | SPI_CR1_CPHA | SPI_CR1_BR); switch(node->node_bits){ case 8: spireg->CR1 &= ~(SPI_CR1_DFF); break; case 16: spireg->CR1 |= SPI_CR1_DFF; break; default: break; }; #endif if (node->node_mode & SPI_MODE_CPOL) { spireg->CR1 |= SPI_CR1_CPOL; } if (node->node_mode & SPI_MODE_CPHA) { spireg->CR1 |= SPI_CR1_CPHA; } /* Query peripheral clock. */ #if defined (MCU_STM32F0) clk = NutClockGet(NUT_HWCLK_PCLK1); #else if (node->node_bus->bus_base < APB2PERIPH_BASE) clk = NutClockGet(NUT_HWCLK_PCLK1); else clk = NutClockGet(NUT_HWCLK_PCLK2); #endif /* Calculate the SPI clock divider. Avoid rounding errors. */ clkdiv = (clk + node->node_rate - 1) / node->node_rate; /* Choose the actual clock rate not greater than the requested rate */ if (clkdiv > 128) clkdiv = 7; else if (clkdiv > 64) clkdiv = 6; else if (clkdiv > 32) clkdiv = 5; else if (clkdiv > 16) clkdiv = 4; else if (clkdiv > 8) clkdiv = 3; else if (clkdiv > 4) clkdiv = 2; else if (clkdiv > 2) clkdiv = 1; else clkdiv = 0; spireg->CR1 |= (clkdiv * SPI_CR1_BR_0); /* Update interface parameters. */ node->node_rate = clk / (1 << (clkdiv + 1)) ; node->node_mode &= ~SPI_MODE_UPDATE; return 0; } /*! * \brief Initialize an SPI bus node. * * This routine is called for each SPI node, which is registered via * NutRegisterSpiDevice(). * * \param node Specifies the SPI bus node. * * \return 0 on success or -1 if there is no valid chip select. */ static int Stm32SpiBusNodeInit(NUTSPINODE * node) { int rc = 0; uint32_t init_flag; /* Sanity check. */ NUTASSERT(node != NULL); NUTASSERT(node->node_bus != NULL); if ((node->node_mode & SPI_MODE_CSHIGH) == 0) init_flag = GPIO_CFG_INIT_HIGH; else init_flag = GPIO_CFG_INIT_LOW; (void) init_flag; switch (node->node_cs) { case 0: /* If CS0 is undefined, we assume permanent selection. */ #if defined(SPIBUS_CS0_INIT) SPIBUS_CS0_INIT(init_flag); #endif break; #if defined(SPIBUS_CS1_INIT) case 1: SPIBUS_CS1_INIT(init_flag); break; #endif #if defined(SPIBUS_CS2_INIT) case 2: SPIBUS_CS2_INIT(init_flag); break; #endif #if defined(SPIBUS_CS3_INIT) case 3: SPIBUS_CS3_INIT(init_flag); break; #endif default: return -1; } /* Deactivate the node's chip select when initializing the port */ rc = Stm32SpiChipSelect(node, 0); /* Test if the SPI Bus is already initialized*/ if (SPI_ENABLE_CLK_GET() == 0) { /* Initialize Hardware */ GpioPinConfigSet (SPIBUS_SCK_PORT, SPIBUS_SCK_PIN, GPIO_CFG_PERIPHAL | GPIO_CFG_OUTPUT |GPIO_CFG_INIT_LOW);//SCK GpioPinConfigSet (SPIBUS_MISO_PORT, SPIBUS_MISO_PIN, GPIO_CFG_PERIPHAL);//MISO GpioPinConfigSet (SPIBUS_MOSI_PORT, SPIBUS_MOSI_PIN, GPIO_CFG_PERIPHAL | GPIO_CFG_OUTPUT |GPIO_CFG_INIT_LOW);//MOSI #if defined(STM32F10X_CL) #if defined(SPIBUS_REMAP_BB) SPIBUS_REMAP_BB(); #endif #else GPIO_PinAFConfig ((GPIO_TypeDef*)SPIBUS_SCK_PORT, SPIBUS_SCK_PIN, SPI_SCK_GPIO_AF); GPIO_PinAFConfig ((GPIO_TypeDef*)SPIBUS_MISO_PORT, SPIBUS_MISO_PIN, SPI_MISO_GPIO_AF); GPIO_PinAFConfig ((GPIO_TypeDef*)SPIBUS_MOSI_PORT, SPIBUS_MOSI_PIN, SPI_MOSI_GPIO_AF); #endif SPI_ENABLE_CLK_SET(); } /* It should not hurt us being called more than once. Thus, we ** check wether any initialization had been taken place already. */ if (rc == 0 && node->node_stat == NULL) { /* Allocate and set our shadow registers. */ STM32SPIREG *spireg = malloc(sizeof(STM32SPIREG)); if (spireg) { /* Set interface defaults. */ spireg->CR1 = SPI_CR1_SSM | SPI_CR1_MSTR; /* FIXME: Check values needed*/ #if defined(SPI_CR2_FRXTH) spireg->CR2 = SPI_CR2_FRXTH | 0x700; #else spireg->CR2 = 0; #endif spireg->I2SCFGR=0; spireg->I2SPR=2; /* Update with node's defaults. */ node->node_stat = (void *)spireg; Stm32SpiSetup(node); #if SPIBUS_MODE == IRQ_MODE NUTSPIBUS *bus; bus = node->node_bus; NutRegisterIrqHandler(&sig_SPI, Stm32SpiBusInterrupt, &bus->bus_ready); NutIrqEnable(&sig_SPI); #elif SPIBUS_MODE == DMA_MODE DMA_Init(); #endif } else { /* Out of memory? */ rc = -1; } } return rc; } /*! * \brief Transfer data on the SPI bus using single buffered interrupt mode. * * A device must have been selected by calling At91SpiSelect(). * * \param node Specifies the SPI bus node. * \param txbuf Pointer to the transmit buffer. If NULL, undetermined * byte values are transmitted. * \param rxbuf Pointer to the receive buffer. If NULL, then incoming * data is discarded. * \param xlen Number of bytes to transfer. * * \return Always 0. */ static int Stm32SpiBusTransfer (NUTSPINODE * node, const void *txbuf, void *rxbuf, int xlen) { SPI_TypeDef* base; uint8_t *tx = (uint8_t *)txbuf; uint8_t *rx = (uint8_t *)rxbuf; int tx_only; int rx_only; /* Sanity check. */ if (xlen == 0) return 0; NUTASSERT(node != NULL); NUTASSERT(node->node_bus != NULL); NUTASSERT(node->node_bus->bus_base != 0); base = (SPI_TypeDef*)node->node_bus->bus_base; tx_only = txbuf && !rxbuf; rx_only = (!txbuf || node->node_mode & SPI_MODE_HALFDUPLEX); /* Remove any remainders in DR */ while (CM3BBGET(SPI_BASE, SPI_TypeDef, SR, _BI32(SPI_SR_RXNE))) (void) base->DR; /* Empty DR */ #if SPIBUS_MODE == POLLING_MODE if (tx_only) { base->CR1 |= SPI_CR1_SPE; while( xlen > 0) { /* Half word access via "spi->DR = b" shifts out two frames * on the F373! */ *(uint8_t *)&base->DR = *tx; xlen--; tx++; while ( (base->SR & SPI_SR_TXE ) == 0 ); /* Wait till TXE = 1*/ } while (base->SR & SPI_SR_BSY); /* Wait till BSY = 0 */ } else if (rx_only) { (void) base->DR; /* Empty DR */ if (node->node_mode & SPI_MODE_HALFDUPLEX) base->CR1 |= SPI_CR1_BIDIMODE; else base->CR1 |= SPI_CR1_RXONLY; base->CR1 |= SPI_CR1_SPE; while( xlen > 0) { if(xlen < 2) { /* Follow procedure "Disabling the SPI" */ while(!(GpioPinGet(SPIBUS_SCK_PORT,SPIBUS_SCK_PIN))); while(GpioPinGet(SPIBUS_SCK_PORT,SPIBUS_SCK_PIN)); base->CR1 &= ~SPI_CR1_SPE; } xlen--; while ((base->SR & SPI_SR_RXNE) == 0 ); /* Wait till RXNE = 1*/ if (rxbuf) { *rx = base->DR; rx++; } } } else { base->CR1 |= SPI_CR1_SPE; *(uint8_t *)&base->DR = *tx; /* Write first item */ while( xlen > 0){ tx++; xlen --; while ((base->SR & SPI_SR_TXE) == 0 ); /* Wait till TXE = 1*/ if (xlen > 0) *(uint8_t *)&base->DR = *tx; while ((base->SR & SPI_SR_RXNE) == 0 );/* Wait till RXNE = 1*/ *rx = base->DR; rx++; } while ((base->SR & SPI_SR_TXE) == 0 ); /* Wait till TXE = 1*/ while (base->SR & SPI_SR_BSY); /* Wait till BSY = 0 */ } #elif SPIBUS_MODE == IRQ_MODE spi_len = xlen; if (rx_only) { spi_rxp = rx; spi_rx_len = xlen; spi_tx_len = 0; if (node->node_mode & SPI_MODE_HALFDUPLEX) CM3BBSET(SPI_BASE, SPI_TypeDef, CR1, _BI32(SPI_CR1_BIDIMODE)); else CM3BBSET(SPI_BASE, SPI_TypeDef, CR1, _BI32(SPI_CR1_RXONLY)); CM3BBSET(SPI_BASE, SPI_TypeDef, CR2, _BI32(SPI_CR2_RXNEIE)); CM3BBSET(SPI_BASE, SPI_TypeDef, CR1, _BI32(SPI_CR1_SPE)); } else { uint8_t b = *tx++; spi_tx_len = xlen -1; spi_txp = tx; CM3BBSET(SPI_BASE, SPI_TypeDef, CR1, _BI32(SPI_CR1_SPE)); if (tx_only) { spi_rx_len = 0; CM3BBSET(SPI_BASE, SPI_TypeDef, CR2, _BI32(SPI_CR2_TXEIE)); } else { spi_rx_len = xlen; spi_rxp = rx; base->CR2 |= (SPI_CR2_RXNEIE | SPI_CR2_TXEIE); } *(uint8_t *)&base->DR = b; } NutEventWait(&node->node_bus->bus_ready, NUT_WAIT_INFINITE); #else (void) rx_only; (void) rx; NUTSPIBUS *bus; bus = node->node_bus; if (rx_only) { if (node->node_mode & SPI_MODE_HALFDUPLEX) CM3BBSET(SPI_BASE, SPI_TypeDef, CR1, _BI32(SPI_CR1_BIDIMODE)); else CM3BBSET(SPI_BASE, SPI_TypeDef, CR1, _BI32(SPI_CR1_RXONLY)); DMA_Setup( SPI_DMA_RX_CHANNEL, rx, (void*)(&base->DR), xlen , DMA_MINC); DMA_Enable( SPI_DMA_RX_CHANNEL); } else { DMA_Setup( SPI_DMA_TX_CHANNEL, (void*)(&base->DR), tx, xlen, DMA_MINC); DMA_Enable( SPI_DMA_TX_CHANNEL); if (tx_only) { uint32_t dummy; DMA_Setup( SPI_DMA_RX_CHANNEL, &dummy, (void*)(&base->DR), xlen, 0); } else DMA_Setup( SPI_DMA_RX_CHANNEL, rx, (void*)(&base->DR), xlen, DMA_MINC); CM3BBSET(SPI_BASE, SPI_TypeDef, CR2, _BI32(SPI_CR2_TXDMAEN)); } /* Register the DMA interrupt after we have acquired the DMA channel. * Otherwise we may disrupt an on-going transaction to an other device on the same * channel. */ NutRegisterIrqHandler(&sig_SPI_DMA_RX, Stm32SpiBusDMAInterrupt, &bus->bus_ready); NutIrqEnable(&sig_SPI_DMA_RX); DMA_Enable( SPI_DMA_RX_CHANNEL); DMA_IrqMask(SPI_DMA_RX_CHANNEL, DMA_TCIF, 1); CM3BBSET(SPI_BASE, SPI_TypeDef, CR2, _BI32(SPI_CR2_RXDMAEN)); CM3BBSET(SPI_BASE, SPI_TypeDef, CR1, _BI32(SPI_CR1_SPE)); NutEventWait(&node->node_bus->bus_ready, NUT_WAIT_INFINITE); #endif base->CR1 &= ~(SPI_CR1_SPE | SPI_CR1_RXONLY | SPI_CR1_BIDIMODE | SPI_CR2_TXDMAEN | SPI_CR2_RXDMAEN); return 0; }