/* * Copyright (C) 2008-2009 by egnite GmbH * * All rights reserved. * * 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/avr/dev/spibus0avr.c * \brief AVR SPI bus 0 driver. * * May be configured as an interrupt driven or polling driver. The * interrupt driven version may use single or double buffering. * * Single buffer interrupt mode is the default. * * Polling mode requires about 300 bytes less code and is roughly 10% * faster. However, it blocks the CPU during the whole transfer. * * Double buffering additionally requires about 200 bytes of code space * and is only slightly faster (less than 1%). * * \verbatim * $Id: spibus0avr.c 4937 2013-01-22 11:38:42Z haraldkipp $ * \endverbatim */ #include #include #include #include #include #include #include #include #include #if defined(SPI0_CS0_PIO_BIT) #if defined(SPI0_CS0_PIO_ID) #undef GPIO_ID #define GPIO_ID SPI0_CS0_PIO_ID #include static INLINE void SPI0_CS0_LO(void) { GPIO_SET_LO(SPI0_CS0_PIO_BIT); } static INLINE void SPI0_CS0_HI(void) { GPIO_SET_HI(SPI0_CS0_PIO_BIT); } static INLINE void SPI0_CS0_SO(void) { GPIO_OUTPUT(SPI0_CS0_PIO_BIT); } #else #define SPI0_CS0_LO() #define SPI0_CS0_HI() #define SPI0_CS0_SO() #endif #endif #if defined(SPI0_CS1_PIO_BIT) #if defined(SPI0_CS1_PIO_ID) #undef GPIO_ID #define GPIO_ID SPI0_CS1_PIO_ID #include static INLINE void SPI0_CS1_LO(void) { GPIO_SET_LO(SPI0_CS1_PIO_BIT); } static INLINE void SPI0_CS1_HI(void) { GPIO_SET_HI(SPI0_CS1_PIO_BIT); } static INLINE void SPI0_CS1_SO(void) { GPIO_OUTPUT(SPI0_CS1_PIO_BIT); } #else #define SPI0_CS1_LO() #define SPI0_CS1_HI() #define SPI0_CS1_SO() #endif #endif #if defined(SPI0_CS2_PIO_BIT) #if defined(SPI0_CS2_PIO_ID) #undef GPIO_ID #define GPIO_ID SPI0_CS2_PIO_ID #include static INLINE void SPI0_CS2_LO(void) { GPIO_SET_LO(SPI0_CS2_PIO_BIT); } static INLINE void SPI0_CS2_HI(void) { GPIO_SET_HI(SPI0_CS2_PIO_BIT); } static INLINE void SPI0_CS2_SO(void) { GPIO_OUTPUT(SPI0_CS2_PIO_BIT); } #else #define SPI0_CS2_LO() #define SPI0_CS2_HI() #define SPI0_CS2_SO() #endif #endif #if defined(SPI0_CS3_PIO_BIT) #if defined(SPI0_CS3_PIO_ID) #undef GPIO_ID #define GPIO_ID SPI0_CS3_PIO_ID #include static INLINE void SPI0_CS3_LO(void) { GPIO_SET_LO(SPI0_CS3_PIO_BIT); } static INLINE void SPI0_CS3_HI(void) { GPIO_SET_HI(SPI0_CS3_PIO_BIT); } static INLINE void SPI0_CS3_SO(void) { GPIO_OUTPUT(SPI0_CS3_PIO_BIT); } #else #define SPI0_CS3_LO() #define SPI0_CS3_HI() #define SPI0_CS4_SO() #endif #endif /*! * \brief Set the specified chip select to a given level. */ static int AvrSpi0ChipSelect(uint_fast8_t cs, uint_fast8_t hi) { int rc = 0; switch (cs) { #if defined(SPI0_CS0_PIO_BIT) case 0: if (hi) { SPI0_CS0_HI(); } else { SPI0_CS0_LO(); } SPI0_CS0_SO(); break; #endif #if defined(SPI0_CS1_PIO_BIT) case 1: if (hi) { SPI0_CS1_HI(); } else { SPI0_CS1_LO(); } SPI0_CS1_SO(); break; #endif #if defined(SPI0_CS2_PIO_BIT) case 2: if (hi) { SPI0_CS2_HI(); } else { SPI0_CS2_LO(); } SPI0_CS2_SO(); break; #endif #if defined(SPI0_CS3_PIO_BIT) case 3: if (hi) { SPI0_CS3_HI(); } else { SPI0_CS3_LO(); } SPI0_CS3_SO(); break; #endif default: errno = EIO; rc = -1; break; } return rc; } static uint8_t * volatile spi0_txp; static uint8_t * volatile spi0_rxp; #ifndef SPIBUS0_POLLING_MODE static HANDLE spi0_que; static volatile size_t spi0_xc; #ifdef SPIBUS0_DOUBLE_BUFFER /* Buffers used for double buffering. */ static uint8_t * volatile spi0_ntxp; static uint8_t * volatile spi0_nrxp; static volatile size_t spi0_nxc; #endif /*! * \brief AVR SPI interrupt handler. */ static void AvrSpi0Interrupt(void *arg) { uint8_t b; /* Get the received byte. */ b = inb(SPDR); if (spi0_xc) { if (spi0_rxp) { *spi0_rxp++ = b; } spi0_xc--; } if (spi0_xc == 0) { #ifdef SPIBUS0_DOUBLE_BUFFER if (spi0_nxc) { /* More data in secondary buffer. */ spi0_txp = spi0_ntxp; spi0_rxp = spi0_nrxp; spi0_xc = spi0_nxc; spi0_nxc = 0; } #endif NutEventPostFromIrq(&spi0_que); } if (spi0_xc) { if (spi0_txp) { b = *spi0_txp++; } outb(SPDR, b); } } #endif /* SPIBUS0_POLLING_MODE */ /*! * \brief Transfer data on the SPI bus. * * A device must have been selected by calling AvrSpi0Select(). * * Depending on the configuration, this routine implemets polling or * interrupt mode. For the latter either single or double buffering * may have been selected. * * When using double buffered interrupt mode, then the transfer may * be still in progress when returning from this function. * * \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. */ int AvrSpiBus0Transfer(NUTSPINODE * node, const void *txbuf, void *rxbuf, int xlen) { uint8_t b = 0xff; /* Sanity check. */ NUTASSERT(node != NULL); #ifdef SPIBUS0_POLLING_MODE /* * Polling mode. */ spi0_txp = (uint8_t *) txbuf; spi0_rxp = (uint8_t *) rxbuf; while (xlen--) { if (spi0_txp) { b = *spi0_txp++; } outb(SPDR, b); while ((inb(SPSR) & 0x80) == 0); b = inb(SPDR); if (spi0_rxp) { *spi0_rxp++ = b; } } #else /* SPIBUS0_POLLING_MODE */ if (xlen) { #ifdef SPIBUS0_DOUBLE_BUFFER /* * Double buffer interrupt mode. */ cbi(SPCR, SPIE); /* Wait until secondary buffer is available. */ while (spi0_nxc) { sbi(SPCR, SPIE); NutEventWait(&spi0_que, NUT_WAIT_INFINITE); cbi(SPCR, SPIE); } if (spi0_xc) { /* Primary buffer in use. Prepare secondary buffer. */ spi0_ntxp = (uint8_t *) txbuf; spi0_nrxp = (uint8_t *) rxbuf; spi0_nxc = (size_t) xlen; sbi(SPCR, SPIE); } else { spi0_txp = (uint8_t *) txbuf; spi0_rxp = (uint8_t *) rxbuf; spi0_xc = (size_t) xlen; if (spi0_txp) { b = *spi0_txp++; } /* Enable and kick interrupts. */ sbi(SPCR, SPIE); outb(SPDR, b); } #else /* SPIBUS0_DOUBLE_BUFFER */ /* * Single buffer interrupt mode. */ spi0_txp = (uint8_t *) txbuf; spi0_rxp = (uint8_t *) rxbuf; spi0_xc = (size_t) xlen; if (spi0_txp) { b = *spi0_txp++; } /* Enable and kick interrupts. */ sbi(SPCR, SPIE); outb(SPDR, b); /* Wait until transfer has finished. */ NutEventWait(&spi0_que, NUT_WAIT_INFINITE); cbi(SPCR, SPIE); #endif /* SPIBUS0_DOUBLE_BUFFER */ } #endif /* SPIBUS0_POLLING_MODE */ return 0; } #ifdef SPIBUS0_DOUBLE_BUFFER /*! * \brief Wait until all SPI bus transfers are done. * * \param node Specifies the SPI bus node. * \param tmo Timeout in milliseconds. To disable timeout, set this * parameter to NUT_WAIT_INFINITE. * * \return Always 0. */ int AvrSpiBus0Wait(NUTSPINODE * node, uint32_t tmo) { cbi(SPCR, SPIE); while (spi0_xc) { sbi(SPCR, SPIE); if (NutEventWait(&spi0_que, tmo)) { return -1; } cbi(SPCR, SPIE); } return 0; } #endif /* SPIBUS0_DOUBLE_BUFFER */ /*! * \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. */ int AvrSpiBus0NodeInit(NUTSPINODE * node) { int rc; /* Sanity check. */ NUTASSERT(node != NULL); /* Try to deactivate the node's chip select. */ rc = AvrSpi0ChipSelect(node->node_cs, (node->node_mode & SPI_MODE_CSHIGH) == 0); /* 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 our shadow registers. */ node->node_stat = malloc(sizeof(AVRSPIREG)); if (node->node_stat) { AvrSpiSetup(node); #ifndef SPIBUS0_POLLING_MODE /* Register and enable SPI interrupt handler. */ NutRegisterIrqHandler(node->node_bus->bus_sig, AvrSpi0Interrupt, NULL); #endif } else { /* Out of memory? */ rc = -1; } } return rc; } /*! \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. */ int AvrSpiBus0Select(NUTSPINODE * node, uint32_t tmo) { int rc; /* Sanity check. */ NUTASSERT(node != NULL); NUTASSERT(node->node_stat != NULL); /* Allocate the bus. */ rc = NutEventWait(&node->node_bus->bus_mutex, tmo); if (rc) { errno = EIO; } else { AVRSPIREG *spireg = node->node_stat; /* If the mode update bit is set, then update our shadow registers. */ if (node->node_mode & SPI_MODE_UPDATE) { AvrSpiSetup(node); } /* Even when set to master mode, the SCK pin is not automatically ** switched to output. Do it manually, maintaining the polarity. */ if (spireg->avrspi_spcr & _BV(CPOL)) { cbi(PORTB, 1); } else { sbi(PORTB, 1); } sbi(DDRB, 1); /* Also MOSI requires manual setting. */ cbi(PORTB, 2); sbi(DDRB, 2); /* When SS is configured as input, we may be forced into slave ** mode if this pin goes low. Enable the pull-up. */ if (bit_is_clear(DDRB, 0)) { sbi(PORTB, 0); } /* Enable MISO pull-up to avoid floating. */ sbi(PORTB, 3); /* Set SPI mode for this device, using the shadow registers. */ outb(SPCR, spireg->avrspi_spcr); #if defined(SPI2X) outb(SPSR, spireg->avrspi_spsr); #endif /* Clean-up the status. */ inb(SPSR); inb(SPDR); /* Finally activate the node's chip select. */ rc = AvrSpi0ChipSelect(node->node_cs, (node->node_mode & SPI_MODE_CSHIGH) != 0); if (rc) { /* Release the bus in case of an error. */ NutEventPost(&node->node_bus->bus_mutex); } } return rc; } /*! \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. */ int AvrSpiBus0Deselect(NUTSPINODE * node) { /* Sanity check. */ NUTASSERT(node != NULL); NUTASSERT(node->node_bus != NULL); #ifdef SPIBUS0_DOUBLE_BUFFER AvrSpiBus0Wait(node, NUT_WAIT_INFINITE); #endif /* Deactivate the node's chip select. */ AvrSpi0ChipSelect(node->node_cs, (node->node_mode & SPI_MODE_CSHIGH) == 0); /* Release the bus. */ NutEventPost(&node->node_bus->bus_mutex); return 0; } /*! * \brief AVR SPI bus driver implementation structure. */ NUTSPIBUS spiBus0Avr = { NULL, /*!< Bus mutex semaphore (bus_mutex). */ NULL, /*!< Bus ready signal (bus_ready). */ 0, /*!< Unused bus base address (bus_base). */ &sig_SPI, /*!< Bus interrupt handler (bus_sig). */ AvrSpiBus0NodeInit, /*!< Initialize the bus (bus_initnode). */ AvrSpiBus0Select, /*!< Select the specified device (bus_alloc). */ AvrSpiBus0Deselect, /*!< Deselect the specified device (bus_release). */ AvrSpiBus0Transfer, /*!< Transfer data to and from a specified device (bus_transfer). */ #ifdef SPIBUS0_DOUBLE_BUFFER AvrSpiBus0Wait, #else NutSpiBusWait, /*!< Wait for bus transfer ready (bus_wait). */ #endif NutSpiBusSetMode, /*!< Set SPI mode of a specified device (bus_set_mode). */ NutSpiBusSetRate, /*!< Set clock rate of a specified device (bus_set_rate). */ NutSpiBusSetBits /*!< Set number of data bits of a specified device (bus_set_bits). */ };