/****************************************************************************** * drivers/wireless/spirit/lib/spirit_spi.c * NuttX SPIRIT SPI driver interface. * * Copyright (C) 2017 Gregory Nutt. All rights reserved. * Authors: Gregory Nutt * * Derives loosely from simlilarly licensed, platform-specific, example * implementations from STMicro: * * Copyright(c) 2015 STMicroelectronics * Author: VMA division - AMS * Version 3.2.2 08-July-2015 * * 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 NuttX nor the names of its 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. * ******************************************************************************/ /****************************************************************************** * Included Files ******************************************************************************/ #include #include #include #include #include #include #include "spirit_regs.h" #include "spirit_types.h" #include "spirit_spi.h" /****************************************************************************** * Pre-processor Definitions ******************************************************************************/ #ifndef CONFIG_WL_SPIRIT_SPIFREQUENCY # define CONFIG_WL_SPIRIT_SPIFREQUENCY (10000000) #endif /****************************************************************************** * Private Functions ******************************************************************************/ /****************************************************************************** * Name: spirit_dump_buffer * * Description: * Dump a data buffer to the SYSLOG. * * Input Parameters: * buffer - The buffer to be dumped * buflen - The length of the buffer in bytes. * * Returned Value: * None * ******************************************************************************/ #if defined(CONFIG_WL_SPIRIT_REGDEBUG) || defined(CONFIG_WL_SPIRIT_FIFODUMP) static void spirit_dump_buffer(FAR const uint8_t *buffer, unsigned int buflen) { char outbuf[16 * 3 + 3]; /* 16 hex bytes + 2 space separator + NUL termination */ FAR char *ptr; unsigned int i; unsigned int j; unsigned int maxj; for (i = 0; i < buflen; i += 16) { maxj = 16; if (i + maxj > buflen) { maxj = buflen - i; } ptr = outbuf; for (j = 0; j < maxj; j++) { if (j == 8) { *ptr++ = ' '; *ptr++ = ' '; } sprintf(ptr, "%02x ", *buffer++); ptr += 3; } *ptr = '\0'; wlinfo(" %s\n", ptr); } } #endif /****************************************************************************** * Name: spirit_regdebug * * Description: * Dump a register access. * * Input Parameters: * msg - Describes the access * header - Two byte header before the access * buffer - The buffer to be dumped * buflen - The length of the buffer in bytes. * * Returned Value: * None * ******************************************************************************/ #ifdef CONFIG_WL_SPIRIT_REGDEBUG static void spirit_regdebug(FAR const char *msg, FAR uint8_t *header, FAR const uint8_t *buffer, unsigned int buflen) { wlinfo("%-8s: %02x %02x\n", header[0], header[1]); spirit_dump_buffer(buffer, buflen); } #else # define spirit_regdebug(msg,header,buffer,buflen) #endif /****************************************************************************** * Name: spirit_fifodebug * * Description: * Dump a FIFO access. * * Input Parameters: * msg - Describes the access * header - Two byte header before the access * buffer - The buffer to be dumped * buflen - The length of the buffer in bytes. * * Returned Value: * None * ******************************************************************************/ #ifdef CONFIG_WL_SPIRIT_FIFODUMP static void spirit_fifodebug(FAR const char *msg, FAR uint8_t *header, FAR const uint8_t *buffer, unsigned int buflen) { wlinfo("%-8s: %02x %02x\n", header[0], header[1]); spirit_dump_buffer(buffer, buflen); } #else # define spirit_fifodebug(msg,header,buffer,buflen) #endif /****************************************************************************** * Name: spirit_lock * * Description: * Lock the SPI bus before each transfer, setting the correct SPI * characteristics for the Spirit device. These must be set if there are * multiple devices on the bus because while the bus was unlocked, another * device may have re-configured the SPIO. * * Input Parameters: * spi - Reference to the SPI driver structure * * Returned Value: * None * * Assumptions: * ******************************************************************************/ static void spirit_lock(FAR struct spi_dev_s *spi) { /* Lock the SPI bus because there are multiple devices competing for the * SPI bus */ SPI_LOCK(spi, true); /* We have the lock. Now make sure that the SPI bus is configured for the * Spirit (it might have gotten configured for a different device while * unlocked) * * NOTES: * - The SPIRIT1 SPI is mode 0 (CPOL=0, CPHA=0) * - Data word is 8-bits * - Maximum clock frequency is 10MHz * - Transfers are MS bit first */ SPI_SETMODE(spi, SPIDEV_MODE0); SPI_SETBITS(spi, 8); SPI_HWFEATURES(spi, 0); SPI_SETFREQUENCY(spi, CONFIG_WL_SPIRIT_SPIFREQUENCY); } /****************************************************************************** * Name: spirit_unlock * * Description: * Un-lock the SPI bus after each transfer, possibly losing the current * configuration if we are sharing the SPI bus with other devices. * * Input Parameters: * spi - Reference to the SPI driver structure * * Returned Value: * None * * Assumptions: * ******************************************************************************/ static void spirit_unlock(FAR struct spi_dev_s *spi) { /* Relinquish the SPI bus. */ SPI_LOCK(spi, false); } /****************************************************************************** * Public Functions ******************************************************************************/ /****************************************************************************** * Name: spirit_reg_read * * Description: * Read single or multiple SPIRIT1 register * * Input Parameters: * * regaddr: Base register's address to be read * buffer: Pointer to the buffer of registers' values to be read * buflen: Number of register values to be read * * Returned Value: * Zero (OK) is returned on success. A negated errno value is returned on * any failure. On success, spirit->state is updated. * ******************************************************************************/ int spirit_reg_read(FAR struct spirit_library_s *spirit, uint8_t regaddr, FAR uint8_t *buffer, unsigned int buflen) { uint8_t header[2]; uint8_t status[2]; /* Setup the header bytes */ header[0] = READ_HEADER; header[1] = regaddr; /* Lock the SPI bus and select the Spirit device */ spirit_lock(spirit->spi); SPI_SELECT(spirit->spi, SPIDEV_WIRELESS(0), true); /* Write the header bytes and read the SPIRIT1 status bytes */ SPI_EXCHANGE(spirit->spi, header, status, 2); /* Update Spirit status. 16-bit status is returned MS byte first */ spirit->u.u16 = ((uint16_t)status[0] << 8) | (uint16_t)status[1]; /* Read the register values */ SPI_RECVBLOCK(spirit->spi, buffer, buflen); /* Deselect the Spirit device and return the result */ SPI_SELECT(spirit->spi, SPIDEV_WIRELESS(0), false); spirit_unlock(spirit->spi); spirit_regdebug("READ", header, buffer, buflen); return OK; } /****************************************************************************** * Name: spirit_reg_write * * Description: * Read single or multiple SPIRIT1 register. * * Input Parameters: * spirit - Reference to an instance of the driver state structure. * regaddr - Base register's address to write * buffer - Pointer to the buffer of register values to write * buflen - Number of registers values to be written. * * Returned Value: * Zero (OK) is returned on success. A negated errno value is returned on * any failure. On success, spirit->state is updated. * ******************************************************************************/ int spirit_reg_write(FAR struct spirit_library_s *spirit, uint8_t regaddr, FAR const uint8_t *buffer, unsigned int buflen) { uint8_t header[2]; uint8_t status[2]; /* Setup the header bytes */ header[0] = WRITE_HEADER; header[1] = regaddr; spirit_regdebug("WRITE", header, buffer, buflen); /* Lock the SPI bus and select the Spirit device */ spirit_lock(spirit->spi); SPI_SELECT(spirit->spi, SPIDEV_WIRELESS(0), true); /* Write the header bytes and read the SPIRIT1 status bytes */ SPI_EXCHANGE(spirit->spi, header, status, 2); /* Update Spirit status. 16-bit status is returned MS byte first */ spirit->u.u16 = ((uint16_t)status[0] << 8) | (uint16_t)status[1]; /* Write the register values */ SPI_SNDBLOCK(spirit->spi, buffer, buflen); /* Deselect the Spirit device and return the result */ SPI_SELECT(spirit->spi, SPIDEV_WIRELESS(0), false); spirit_unlock(spirit->spi); return OK; } /****************************************************************************** * Name: spirit_reg_modify * * Description: * Perform atomic read/modify/write on a single SPIRIT1 register. * * Input Parameters: * spirit - Reference to an instance of the driver state structure. * regaddr - Base register's address to write * clrbits - Bits to clear in the register * setbits - Bits to set in the register * * Returned Value: * Zero (OK) is returned on success. A negated errno value is returned on * any failure. On success, spirit->state is updated. * ******************************************************************************/ int spirit_reg_modify(FAR struct spirit_library_s *spirit, uint8_t regaddr, uint8_t setbits, uint8_t clrbits) { uint8_t header[2]; uint8_t status[2]; uint8_t regval; /* Setup the header byte to read the register */ header[0] = READ_HEADER; header[1] = regaddr; /* Lock the SPI bus and select the Spirit device */ spirit_lock(spirit->spi); SPI_SELECT(spirit->spi, SPIDEV_WIRELESS(0), true); /* Write the header bytes (ignoring the returned SPIRIT1 status bytes) */ SPI_SNDBLOCK(spirit->spi, header, 2); /* Read the register value */ regval = SPI_SEND(spirit->spi, 0xff); spirit_regdebug("READ", header, ®val, 1); /* Modify the register value */ regval &= ~clrbits; regval |= setbits; /* Setup the header byte for the write operation */ header[0] = WRITE_HEADER; header[1] = regaddr; spirit_regdebug("WRITE", header, ®val, 1); /* Toggle Chip select so that we get the correct status */ SPI_SELECT(spirit->spi, SPIDEV_WIRELESS(0), false); SPI_SELECT(spirit->spi, SPIDEV_WIRELESS(0), true); /* Write the header bytes and read the SPIRIT1 status bytes */ SPI_EXCHANGE(spirit->spi, header, status, 2); /* Update Spirit status. 16-bit status is returned MS byte first */ spirit->u.u16 = ((uint16_t)status[0] << 8) | (uint16_t)status[1]; /* Write the register value */ SPI_SEND(spirit->spi, regval); /* Deselect the Spirit device and return the result */ SPI_SELECT(spirit->spi, SPIDEV_WIRELESS(0), false); spirit_unlock(spirit->spi); return OK; } /****************************************************************************** * Name: spirit_command * * Description: * Send a command * * Input Parameters: * spirit - Reference to an instance of the driver state structure. * cmd - Command code to be sent * * Returned Value: * Zero (OK) is returned on success. A negated errno value is returned on * any failure. On success, spirit->state is updated. * ******************************************************************************/ int spirit_command(FAR struct spirit_library_s *spirit, uint8_t cmd) { uint8_t header[2]; uint8_t status[2]; /* Setup the header bytes */ header[0] = COMMAND_HEADER; header[1] = cmd; spirit_regdebug("CMD", header, NULL, 0); /* Lock the SPI bus and select the Spirit device */ spirit_lock(spirit->spi); SPI_SELECT(spirit->spi, SPIDEV_WIRELESS(0), true); /* Write the header bytes and read the SPIRIT1 status bytes */ SPI_EXCHANGE(spirit->spi, header, status, 2); /* Update Spirit status. 16-bit status is returned MS byte first */ spirit->u.u16 = ((uint16_t)status[0] << 8) | (uint16_t)status[1]; /* Deselect the Spirit device and return the result */ SPI_SELECT(spirit->spi, SPIDEV_WIRELESS(0), false); spirit_unlock(spirit->spi); return OK; } /****************************************************************************** * Name: spirit_fifo_read * * Description: * Read data from RX FIFO * * Input Parameters: * spirit - Reference to an instance of the driver state structure. * buffer - Pointer to the buffer of data values to write * buflen - Number of bytes to be written * * Returned Value: * Zero (OK) is returned on success. A negated errno value is returned on * any failure. On success, spirit->state is updated. * ******************************************************************************/ int spirit_fifo_read(FAR struct spirit_library_s *spirit, FAR uint8_t *buffer, unsigned int buflen) { uint8_t header[2]; uint8_t status[2]; /* Setup the header bytes */ header[0] = READ_HEADER; header[1] = LINEAR_FIFO_ADDRESS; /* Lock the SPI bus and select the Spirit device */ spirit_lock(spirit->spi); SPI_SELECT(spirit->spi, SPIDEV_WIRELESS(0), true); /* Write the header bytes and read the SPIRIT1 status bytes */ SPI_EXCHANGE(spirit->spi, header, status, 2); /* Update Spirit status. 16-bit status is returned MS byte first */ spirit->u.u16 = ((uint16_t)status[0] << 8) | (uint16_t)status[1]; /* Read the register values */ SPI_RECVBLOCK(spirit->spi, buffer, buflen); /* Deselect the Spirit device and return the result */ SPI_SELECT(spirit->spi, SPIDEV_WIRELESS(0), false); spirit_unlock(spirit->spi); spirit_fifodebug("FIFO IN", header, buffer, buflen); return OK; } /****************************************************************************** * Name: spirit_fifo_write * * Description: * Write data into TX FIFO. * * Input Parameters: * spirit - Reference to an instance of the driver state structure. * buffer - Pointer to the buffer of data values to write * buflen - Number of data values to be written. * * Returned Value: * Zero (OK) is returned on success. A negated errno value is returned on * any failure. On success, spirit->state is updated. * ******************************************************************************/ int spirit_fifo_write(FAR struct spirit_library_s *spirit, FAR const uint8_t *buffer, unsigned int buflen) { uint8_t header[2]; uint8_t status[2]; /* Setup the header bytes */ header[0] = WRITE_HEADER; header[1] = LINEAR_FIFO_ADDRESS; spirit_fifodebug("FIFO OUT", header, buffer, buflen); /* Lock the SPI bus and select the Spirit device */ spirit_lock(spirit->spi); SPI_SELECT(spirit->spi, SPIDEV_WIRELESS(0), true); /* Write the header bytes and read the SPIRIT1 status bytes */ SPI_EXCHANGE(spirit->spi, header, status, 2); /* Update Spirit status. 16-bit status is returned MS byte first */ spirit->u.u16 = ((uint16_t)status[0] << 8) | (uint16_t)status[1]; /* Write the fifo values */ SPI_SNDBLOCK(spirit->spi, buffer, buflen); /* Deselect the Spirit device and return the result */ SPI_SELECT(spirit->spi, SPIDEV_WIRELESS(0), false); spirit_unlock(spirit->spi); return OK; } /****************************************************************************** * Name: spirit_update_status * * Description: * Updates the state field in the driver instance, reading the MC_STATE * register of SPIRIT. * * Input Parameters: * spirit - Reference to an instance of the driver state structure. * * Returned Value: * Zero (OK) is returned on success. A negated errno value is returned on * any failure. On success, spirit->state is updated. * ******************************************************************************/ int spirit_update_status(FAR struct spirit_library_s *spirit) { uint8_t regval; DEBUGASSERT(spirit != NULL); /* Reads the MC_STATUS register to update the spirit->state */ return spirit_reg_read(spirit, MC_STATE1_BASE, ®val, 1); } /**************************************************************************** * Name: spirit_waitstatus * * Description: * Poll until the Spirit status is the requested value or until a timeout * occurs. * * Input Parameters: * spirit - Reference to a Spirit library state structure instance * state - That that we are waiting for. * msec - Timeout in millisedonds * * Returned Value: * OK on success; a negated errno on a timeout * * Assumptions: * We have exclusive access to the driver state and to the spirit library. * ****************************************************************************/ int spirit_waitstatus(FAR struct spirit_library_s *spirit, enum spirit_state_e state, unsigned int msec) { clock_t start; clock_t ticks; clock_t elapsed; int ret; #ifdef CONFIG_DEBUG_SPI_INFO /* If the SPI logic is generating debug output, then a longer timeout is * probably needed. */ msec <<= 4; #endif /* Convert the MSEC timedelay to clock ticks, making sure that the * resulting delay in ticks is greater than or equal to the requested time * in MSEC. * * REVIST: If USEC_PER_TICK and 'msec' are large, then the second * computation may overflow! */ #if (MSEC_PER_TICK * USEC_PER_MSEC) == USEC_PER_TICK ticks = (msec + (MSEC_PER_TICK - 1)) / MSEC_PER_TICK; #else ticks = ((clock_t)msec * USEC_PER_MSEC + (USEC_PER_TICK - 1)) / USEC_PER_TICK; #endif /* The time that we started the wait */ start = clock_systimer(); /* Loop until the status change occurs (or the wait times out) */ do { ret = spirit_update_status(spirit); if (ret < 0) { return ret; } elapsed = clock_systimer() - start; } while (spirit->u.state.MC_STATE != state && elapsed <= ticks); if (spirit->u.state.MC_STATE == state) { return OK; } /* This is probably not an error. In a busy radio environment, there * are many race conditions. Most typically, just when the driver is * setting up to perform a transmission, the hardware commits to a * reception. The symptom is that the the above loop times out out * waiting to go into the TX state (because it is in the RX state). * * Complaining with too much debug output just aggravates the problem. */ wlinfo("Timed out: %lu > %lu (%u msec)\n", (unsigned long)elapsed, (unsigned long)ticks, msec); wlinfo("with MC status: current=%02x desired=%02x\n", spirit->u.state.MC_STATE, state); return -ETIMEDOUT; }