1287 lines
37 KiB
C
1287 lines
37 KiB
C
/****************************************************************************
|
|
* arch/arm/src/samv7/sam_spi_slave.c
|
|
*
|
|
* Copyright (C) 2015 Gregory Nutt. All rights reserved.
|
|
* Authors: Gregory Nutt <gnutt@nuttx.org>
|
|
*
|
|
* 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 <nuttx/config.h>
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <semaphore.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
#include <debug.h>
|
|
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/irq.h>
|
|
#include <nuttx/spi/slave.h>
|
|
|
|
#include "up_arch.h"
|
|
|
|
#include "sam_config.h"
|
|
#include "sam_gpio.h"
|
|
#include "sam_periphclks.h"
|
|
#include "sam_spi.h"
|
|
|
|
#include "chip/sam_spi.h"
|
|
#include "chip/sam_pinmap.h"
|
|
#include <arch/board/board.h>
|
|
|
|
#ifdef CONFIG_SAMV7_SPI_SLAVE
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
/* Configuration ************************************************************/
|
|
|
|
#ifndef CONFIG_SAMV7_SPI_SLAVE_QSIZE
|
|
# define CONFIG_SAMV7_SPI_SLAVE_QSIZE 8
|
|
#endif
|
|
|
|
/* Debug *******************************************************************/
|
|
/* Check if SPI debug is enabled (non-standard.. no support in
|
|
* include/debug.h
|
|
*/
|
|
|
|
#ifndef CONFIG_DEBUG
|
|
# undef CONFIG_DEBUG_VERBOSE
|
|
# undef CONFIG_DEBUG_SPI
|
|
#endif
|
|
|
|
#ifdef CONFIG_DEBUG_SPI
|
|
# define spidbg lldbg
|
|
# ifdef CONFIG_DEBUG_VERBOSE
|
|
# define spivdbg lldbg
|
|
# else
|
|
# define spivdbg(x...)
|
|
# endif
|
|
#else
|
|
# define spidbg(x...)
|
|
# define spivdbg(x...)
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
/* The overall state of one SPI controller */
|
|
|
|
struct sam_spidev_s
|
|
{
|
|
struct spi_sctrlr_s sctrlr; /* Externally visible part of the SPI slave
|
|
* controller interface */
|
|
struct spi_sdev_s *sdev; /* Bound SPI slave device interface */
|
|
xcpt_t handler; /* SPI interrupt handler */
|
|
uint32_t base; /* SPI controller register base address */
|
|
sem_t spisem; /* Assures mutually exclusive access to SPI */
|
|
uint16_t outval; /* Default shift-out value */
|
|
uint16_t irq; /* SPI IRQ number */
|
|
uint8_t mode; /* Mode 0,1,2,3 */
|
|
uint8_t nbits; /* Width of word in bits (8 to 16) */
|
|
uint8_t spino; /* SPI controller number (0 or 1) */
|
|
bool initialized; /* True: Controller has been initialized */
|
|
bool nss; /* True: Chip selected */
|
|
|
|
/* Output queue */
|
|
|
|
uint8_t head; /* Location of next value */
|
|
uint8_t tail; /* Index of first value */
|
|
|
|
uint16_t outq[CONFIG_SAMV7_SPI_SLAVE_QSIZE];
|
|
|
|
/* Debug stuff */
|
|
|
|
#ifdef CONFIG_SAMV7_SPI_REGDEBUG
|
|
bool wrlast; /* Last was a write */
|
|
uint32_t addresslast; /* Last address */
|
|
uint32_t valuelast; /* Last value */
|
|
int ntimes; /* Number of times */
|
|
#endif
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
/* Helpers */
|
|
|
|
#ifdef CONFIG_SAMV7_SPI_REGDEBUG
|
|
static bool spi_checkreg(struct sam_spidev_s *priv, bool wr,
|
|
uint32_t value, uint32_t address);
|
|
#else
|
|
# define spi_checkreg(priv,wr,value,address) (false)
|
|
#endif
|
|
|
|
static uint32_t spi_getreg(struct sam_spidev_s *priv,
|
|
unsigned int offset);
|
|
static void spi_putreg(struct sam_spidev_s *priv, uint32_t value,
|
|
unsigned int offset);
|
|
|
|
#if defined(CONFIG_DEBUG_SPI) && defined(CONFIG_DEBUG_VERBOSE)
|
|
static void spi_dumpregs(struct sam_spidev_s *priv, const char *msg);
|
|
#else
|
|
# define spi_dumpregs(priv,msg)
|
|
#endif
|
|
|
|
static void spi_semtake(struct sam_spidev_s *priv);
|
|
#define spi_semgive(priv) (sem_post(&(priv)->spisem))
|
|
|
|
/* Interrupt Handling */
|
|
|
|
static int spi_interrupt(struct sam_spidev_s *priv);
|
|
#ifdef CONFIG_SAMV7_SPI0_SLAVE
|
|
static int spi0_interrupt(int irq, void *context);
|
|
#endif
|
|
#ifdef CONFIG_SAMV7_SPI1_SLAVE
|
|
static int spi1_interrupt(int irq, void *context);
|
|
#endif
|
|
|
|
/* SPI Helpers */
|
|
|
|
static uint16_t spi_dequeue(struct sam_spidev_s *priv);
|
|
static void spi_setmode(struct sam_spidev_s *priv,
|
|
enum spi_smode_e mode);
|
|
static void spi_setbits(struct sam_spidev_s *priv,
|
|
int nbits);
|
|
|
|
/* SPI slave controller methods */
|
|
|
|
static void spi_bind(struct spi_sctrlr_s *sctrlr,
|
|
struct spi_sdev_s *sdev, enum spi_smode_e mode,
|
|
int nbits);
|
|
static void spi_unbind(struct spi_sctrlr_s *sctrlr);
|
|
static int spi_enqueue(struct spi_sctrlr_s *sctrlr, uint16_t data);
|
|
static bool spi_qfull(struct spi_sctrlr_s *sctrlr);
|
|
static void spi_qflush(struct spi_sctrlr_s *sctrlr);
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
/* This array maps chip select numbers (0-3) to CSR register offsets */
|
|
|
|
static const uint8_t g_csroffset[4] =
|
|
{
|
|
SAM_SPI_CSR0_OFFSET, SAM_SPI_CSR1_OFFSET,
|
|
SAM_SPI_CSR2_OFFSET, SAM_SPI_CSR3_OFFSET
|
|
};
|
|
|
|
/* SPI slave controller driver operations */
|
|
|
|
static const struct spi_sctrlrops_s g_sctrlr_ops =
|
|
{
|
|
.bind = spi_bind,
|
|
.unbind = spi_unbind,
|
|
.enqueue = spi_enqueue,
|
|
.qfull = spi_qfull,
|
|
.qflush = spi_qflush,
|
|
};
|
|
|
|
#ifdef CONFIG_SAMV7_SPI0_SLAVE
|
|
/* This is the overall state of the SPI0 controller */
|
|
|
|
static struct sam_spidev_s g_spi0_sctrlr;
|
|
#endif
|
|
|
|
#ifdef CONFIG_SAMV7_SPI1_SLAVE
|
|
/* This is the overall state of the SPI0 controller */
|
|
|
|
static struct sam_spidev_s g_spi1_sctrlr;
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Public Data
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: spi_checkreg
|
|
*
|
|
* Description:
|
|
* Check if the current register access is a duplicate of the preceding.
|
|
*
|
|
* Input Parameters:
|
|
* value - The value to be written
|
|
* address - 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_SAMV7_SPI_REGDEBUG
|
|
static bool spi_checkreg(struct sam_spidev_s *priv, bool wr, uint32_t value,
|
|
uint32_t address)
|
|
{
|
|
if (wr == priv->wrlast && /* Same kind of access? */
|
|
value == priv->valuelast && /* Same value? */
|
|
address == priv->addresslast) /* 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 */
|
|
|
|
lldbg("...[Repeats %d times]...\n", priv->ntimes);
|
|
}
|
|
|
|
/* Save information about the new access */
|
|
|
|
priv->wrlast = wr;
|
|
priv->valuelast = value;
|
|
priv->addresslast = address;
|
|
priv->ntimes = 0;
|
|
}
|
|
|
|
/* Return true if this is the first time that we have done this operation */
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: spi_getreg
|
|
*
|
|
* Description:
|
|
* Read an SPI register
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint32_t spi_getreg(struct sam_spidev_s *priv, unsigned int offset)
|
|
{
|
|
uint32_t address = priv->base + offset;
|
|
uint32_t value = getreg32(address);
|
|
|
|
#ifdef CONFIG_SAMV7_SPI_REGDEBUG
|
|
if (spi_checkreg(priv, false, value, address))
|
|
{
|
|
lldbg("%08x->%08x\n", address, value);
|
|
}
|
|
#endif
|
|
|
|
return value;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spi_putreg
|
|
*
|
|
* Description:
|
|
* Write a value to an SPI register
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void spi_putreg(struct sam_spidev_s *priv, uint32_t value,
|
|
unsigned int offset)
|
|
{
|
|
uint32_t address = priv->base + offset;
|
|
|
|
#ifdef CONFIG_SAMV7_SPI_REGDEBUG
|
|
if (spi_checkreg(priv, true, value, address))
|
|
{
|
|
lldbg("%08x<-%08x\n", address, value);
|
|
}
|
|
#endif
|
|
|
|
putreg32(value, address);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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
|
|
*
|
|
****************************************************************************/
|
|
|
|
#if defined(CONFIG_DEBUG_SPI) && defined(CONFIG_DEBUG_VERBOSE)
|
|
static void spi_dumpregs(struct sam_spidev_s *priv, const char *msg)
|
|
{
|
|
spivdbg("%s:\n", msg);
|
|
spivdbg(" MR:%08x SR:%08x IMR:%08x\n",
|
|
getreg32(priv->base + SAM_SPI_MR_OFFSET),
|
|
getreg32(priv->base + SAM_SPI_SR_OFFSET),
|
|
getreg32(priv->base + SAM_SPI_IMR_OFFSET));
|
|
spivdbg(" CSR0:%08x CSR1:%08x CSR2:%08x CSR3:%08x\n",
|
|
getreg32(priv->base + SAM_SPI_CSR0_OFFSET),
|
|
getreg32(priv->base + SAM_SPI_CSR1_OFFSET),
|
|
getreg32(priv->base + SAM_SPI_CSR2_OFFSET),
|
|
getreg32(priv->base + SAM_SPI_CSR3_OFFSET));
|
|
spivdbg(" WPCR:%08x WPSR:%08x\n",
|
|
getreg32(priv->base + SAM_SPI_WPCR_OFFSET),
|
|
getreg32(priv->base + SAM_SPI_WPSR_OFFSET));
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: spi_semtake
|
|
*
|
|
* Description:
|
|
* Take the semaphore that enforces mutually exclusive access to SPI
|
|
* resources, handling any exceptional conditions
|
|
*
|
|
* Input Parameters:
|
|
* priv - A reference to the MCAN peripheral state
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void spi_semtake(struct sam_spidev_s *priv)
|
|
{
|
|
int ret;
|
|
|
|
/* Wait until we successfully get the semaphore. EINTR is the only
|
|
* expected 'failure' (meaning that the wait for the semaphore was
|
|
* interrupted by a signal.
|
|
*/
|
|
|
|
do
|
|
{
|
|
ret = sem_wait(&priv->spisem);
|
|
DEBUGASSERT(ret == 0 || errno == EINTR);
|
|
}
|
|
while (ret < 0);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spi_interrupt
|
|
*
|
|
* Description:
|
|
* Common SPI interrupt handler
|
|
*
|
|
* Input Parameters:
|
|
* priv - SPI controller CS state
|
|
*
|
|
* Returned Value:
|
|
* Standard interrupt return value.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int spi_interrupt(struct sam_spidev_s *priv)
|
|
{
|
|
uint32_t sr;
|
|
uint32_t imr;
|
|
uint32_t pending;
|
|
uint32_t regval;
|
|
|
|
/* We loop because the TDRE interrupt will probably immediately follow the
|
|
* RDRF interrupt and we might be able to catch it in this handler
|
|
* execution.
|
|
*/
|
|
|
|
for (;;)
|
|
{
|
|
/* Get the current set of pending/enabled interrupts */
|
|
|
|
sr = spi_getreg(priv, SAM_SPI_SR_OFFSET);
|
|
imr = spi_getreg(priv, SAM_SPI_IMR_OFFSET);
|
|
pending = sr & imr;
|
|
|
|
/* Return from the interrupt handler when all pending interrupts have
|
|
* been processed.
|
|
*/
|
|
|
|
if (pending == 0)
|
|
{
|
|
return OK;
|
|
}
|
|
|
|
/* TThe SPI waits until NSS goes active before receiving the serial
|
|
* clock from an external master. When NSS falls, the clock is
|
|
* validated and the data is loaded in the SPI_RDR depending on the
|
|
* BITS field configured in the SPI_CSR0. These bits are processed
|
|
* following a phase and a polarity defined respectively by the NCPHA
|
|
* and CPOL bits in the SPI_CSR0.
|
|
*
|
|
* When all bits are processed, the received data is transferred in
|
|
* the SPI_RDR and the RDRF bit rises. If the SPI_RDR has not been
|
|
* read before new data is received, the Overrun Error Status (OVRES)
|
|
* bit in the SPI_SR is set. As long as this flag is set, data is
|
|
* loaded in the SPI_RDR. The user must read SPI_SR to clear the OVRES
|
|
* bit.
|
|
*/
|
|
|
|
#ifdef CONFIG_DEBUG_SPI
|
|
/* Check the RX data overflow condition */
|
|
|
|
if ((pending & SPI_INT_OVRES) != 0)
|
|
{
|
|
/* If debug is enabled, report any overrun errors */
|
|
|
|
spidbg("Error: Overrun (OVRES): %08x\n", pending);
|
|
|
|
/* OVRES was cleared by the status read. */
|
|
}
|
|
#endif
|
|
|
|
/* Check for the availability of RX data */
|
|
|
|
if ((pending & SPI_INT_RDRF) != 0)
|
|
{
|
|
uint16_t data;
|
|
|
|
/* We get no indication of the falling edge of NSS. But if we are
|
|
* here then it must have fallen.
|
|
*/
|
|
|
|
if (priv->nss)
|
|
{
|
|
priv->nss = false;
|
|
SPI_SDEV_SELECT(priv->sdev, true);
|
|
}
|
|
|
|
/* Read the RDR to get the data and to clear the pending RDRF
|
|
* interrupt.
|
|
*/
|
|
|
|
regval = spi_getreg(priv, SAM_SPI_RDR_OFFSET);
|
|
data = (uint16_t)((regval & SPI_RDR_RD_MASK) >> SPI_RDR_RD_SHIFT);
|
|
|
|
/* Enable TXDR/OVRE interrupts */
|
|
|
|
regval = (SPI_INT_TDRE | SPI_INT_UNDES);
|
|
spi_putreg(priv, regval, SAM_SPI_IER_OFFSET);
|
|
|
|
/* Report the receipt of data to the SPI device driver */
|
|
|
|
SPI_SDEV_RECEIVE(priv->sdev, data);
|
|
}
|
|
|
|
/* When a transfer starts, the data shifted out is the data present
|
|
* in the Shift register. If no data has been written in the SPI_TDR,
|
|
* the last data received is transferred. If no data has been received
|
|
* since the last reset, all bits are transmitted low, as the Shift
|
|
* register resets to 0.
|
|
*
|
|
* When a first data is written in the SPI_TDR, it is transferred
|
|
* immediately in the Shift register and the TDRE flag rises. If new
|
|
* data is written, it remains in the SPI_TDR until a transfer occurs,
|
|
* i.e., NSS falls and there is a valid clock on the SPCK pin. When
|
|
* the transfer occurs, the last data written in the SPI_TDR is
|
|
* transferred in the Shift register and the TDRE flag rises. This
|
|
* enables frequent updates of critical variables with single transfers.
|
|
*
|
|
* Then, new data is loaded in the Shift register from the SPI_TDR. If
|
|
* no character is ready to be transmitted, i.e., no character has been
|
|
* written in the SPI_TDR since the last load from the SPI_TDR to the
|
|
* Shift register, the SPI_TDR is retransmitted. In this case the
|
|
* Underrun Error Status Flag (UNDES) is set in the SPI_SR.
|
|
*/
|
|
|
|
#ifdef CONFIG_DEBUG_SPI
|
|
/* Check the TX data underflow condition */
|
|
|
|
if ((pending & SPI_INT_UNDES) != 0)
|
|
{
|
|
/* If debug is enabled, report any overrun errors */
|
|
|
|
spidbg("Error: Underrun (UNDEX): %08x\n", pending);
|
|
|
|
/* UNDES was cleared by the status read. */
|
|
}
|
|
#endif
|
|
|
|
/* Output the next TX data */
|
|
|
|
if ((pending & SPI_INT_TDRE) != 0)
|
|
{
|
|
/* Get the next output value and write it to the TDR
|
|
* The TDRE interrupt is cleared by writing to the from RDR.
|
|
*/
|
|
|
|
regval = spi_dequeue(priv);
|
|
spi_putreg(priv, regval, SAM_SPI_TDR_OFFSET);
|
|
}
|
|
|
|
/* The SPI slave hardware provides only an event when NSS rises
|
|
* which may or many not happen at the end of a transfer. NSSR was
|
|
* cleared by the status read.
|
|
*/
|
|
|
|
if ((pending & SPI_INT_NSSR) != 0)
|
|
{
|
|
/* Disable further TXDR/OVRE interrupts */
|
|
|
|
regval = (SPI_INT_TDRE | SPI_INT_UNDES);
|
|
spi_putreg(priv, regval, SAM_SPI_IDR_OFFSET);
|
|
|
|
/* Report the state change to the SPI device driver */
|
|
|
|
priv->nss = true;
|
|
SPI_SDEV_SELECT(priv->sdev, false);
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spi0_interrupt
|
|
*
|
|
* Description:
|
|
* SPI0 interrupt handler
|
|
*
|
|
* Input Parameters:
|
|
* Standard interrupt input parameters
|
|
*
|
|
* Returned Value:
|
|
* Standard interrupt return value.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_SAMV7_SPI0_SLAVE
|
|
static int spi0_interrupt(int irq, void *context)
|
|
{
|
|
return spi_interrupt(&g_spi0_sctrlr);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: spi1_interrupt
|
|
*
|
|
* Description:
|
|
* SPI1 interrupt handler
|
|
*
|
|
* Input Parameters:
|
|
* Standard interrupt input parameters
|
|
*
|
|
* Returned Value:
|
|
* Standard interrupt return value.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_SAMV7_SPI1_SLAVE
|
|
static int spi1_interrupt(int irq, void *context)
|
|
{
|
|
return spi_interrupt(&g_spi1_sctrlr);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: spi_dequeue
|
|
*
|
|
* Description:
|
|
* Return the next queued output value. If nothing is in the output queue,
|
|
* then return the last value obtained from getdata();
|
|
*
|
|
* Input Parameters:
|
|
* priv - SPI controller CS state
|
|
*
|
|
* Assumptions:
|
|
* Called only from the SPI interrupt handler so all interrupts are
|
|
* disabled.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint16_t spi_dequeue(struct sam_spidev_s *priv)
|
|
{
|
|
uint32_t regval;
|
|
uint16_t ret;
|
|
int next;
|
|
|
|
/* Is the queue empty? */
|
|
|
|
if (priv->head != priv->tail)
|
|
{
|
|
/* No, take the oldest value from the tail of the cicular buffer */
|
|
|
|
ret = priv->outq[priv->tail];
|
|
|
|
/* Update the tail index, handling wraparound */
|
|
|
|
next = priv->tail + 1;
|
|
if (next >= CONFIG_SAMV7_SPI_SLAVE_QSIZE)
|
|
{
|
|
next = 0;
|
|
}
|
|
|
|
priv->tail = next;
|
|
|
|
/* If the queue is empty Disable further TXDR/OVRE interrupts until
|
|
* spi_enqueue() is called or until we received another command. We
|
|
* do this only for the case where NSS is non-functional (tied to
|
|
* ground) and we need to end transfers in some fashion.
|
|
*/
|
|
|
|
if (priv->head == next)
|
|
{
|
|
regval = (SPI_INT_TDRE | SPI_INT_UNDES);
|
|
spi_putreg(priv, regval, SAM_SPI_IDR_OFFSET);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Yes, return the last value we got from the getdata() method */
|
|
|
|
ret = priv->outval;
|
|
|
|
/* Disable further TXDR/OVRE interrupts until spi_enqueue() is called. */
|
|
|
|
regval = (SPI_INT_TDRE | SPI_INT_UNDES);
|
|
spi_putreg(priv, regval, SAM_SPI_IDR_OFFSET);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spi_setmode
|
|
*
|
|
* Description:
|
|
* Set the SPI mode. See enum spi_smode_e for mode definitions
|
|
*
|
|
* Input Parameters:
|
|
* priv - SPI device data structure
|
|
* mode - The SPI mode requested
|
|
*
|
|
* Returned Value:
|
|
* none
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void spi_setmode(struct sam_spidev_s *priv, enum spi_smode_e mode)
|
|
{
|
|
uint32_t regval;
|
|
|
|
spivdbg("mode=%d\n", mode);
|
|
|
|
/* Has the mode changed? */
|
|
|
|
if (mode != priv->mode)
|
|
{
|
|
/* Yes... Set the mode appropriately:
|
|
*
|
|
* SPI CPOL NCPHA
|
|
* MODE
|
|
* 0 0 1
|
|
* 1 0 0
|
|
* 2 1 1
|
|
* 3 1 0
|
|
*/
|
|
|
|
regval = spi_getreg(priv, SAM_SPI_CSR0_OFFSET);
|
|
regval &= ~(SPI_CSR_CPOL | SPI_CSR_NCPHA);
|
|
|
|
switch (mode)
|
|
{
|
|
case SPISLAVE_MODE0: /* CPOL=0; NCPHA=1 */
|
|
regval |= SPI_CSR_NCPHA;
|
|
break;
|
|
|
|
case SPISLAVE_MODE1: /* CPOL=0; NCPHA=0 */
|
|
break;
|
|
|
|
case SPISLAVE_MODE2: /* CPOL=1; NCPHA=1 */
|
|
regval |= (SPI_CSR_CPOL | SPI_CSR_NCPHA);
|
|
break;
|
|
|
|
case SPISLAVE_MODE3: /* CPOL=1; NCPHA=0 */
|
|
regval |= SPI_CSR_CPOL;
|
|
break;
|
|
|
|
default:
|
|
DEBUGASSERT(FALSE);
|
|
return;
|
|
}
|
|
|
|
spi_putreg(priv, regval, SAM_SPI_CSR0_OFFSET);
|
|
spivdbg("csr0=%08x\n", regval);
|
|
|
|
/* 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:
|
|
* priv - SPI device data structure
|
|
* nbits - The number of bits requests
|
|
*
|
|
* Returned Value:
|
|
* none
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void spi_setbits(struct sam_spidev_s *priv, int nbits)
|
|
{
|
|
uint32_t regval;
|
|
|
|
spivdbg("nbits=%d\n", nbits);
|
|
DEBUGASSERT(priv && nbits > 7 && nbits < 17);
|
|
|
|
/* Has the number of bits changed? */
|
|
|
|
if (nbits != priv->nbits)
|
|
{
|
|
/* Yes... Set number of bits appropriately */
|
|
|
|
regval = spi_getreg(priv, SAM_SPI_CSR0_OFFSET);
|
|
regval &= ~SPI_CSR_BITS_MASK;
|
|
regval |= SPI_CSR_BITS(nbits);
|
|
spi_putreg(priv, regval, SAM_SPI_CSR0_OFFSET);
|
|
|
|
spivdbg("csr0=%08x\n", regval);
|
|
|
|
/* Save the selection so the subsequence re-configurations will be faster */
|
|
|
|
priv->nbits = nbits;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spi_bind
|
|
*
|
|
* Description:
|
|
* Bind the SPI slave device interface to the SPI slave controller
|
|
* interface and configure the SPI interface. Upon return, the SPI
|
|
* slave controller driver is fully operational and ready to perform
|
|
* transfers.
|
|
*
|
|
* Input Parameters:
|
|
* sctrlr - SPI slave controller interface instance
|
|
* sdev - SPI slave device interface instance
|
|
* mode - The SPI mode requested
|
|
* nbits - The number of bits requests.
|
|
* If value is greater > 0 then it implies MSB first
|
|
* If value is below < 0, then it implies LSB first with -nbits
|
|
*
|
|
* Returned Value:
|
|
* none
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void spi_bind(struct spi_sctrlr_s *sctrlr,
|
|
struct spi_sdev_s *sdev, enum spi_smode_e mode,
|
|
int nbits)
|
|
{
|
|
struct sam_spidev_s *priv = (struct sam_spidev_s *)sctrlr;
|
|
uint32_t regval;
|
|
|
|
spivdbg("sdev=%p mode=%d nbits=%d\n", sdv, mode, nbits);
|
|
|
|
DEBUGASSERT(priv != NULL && priv->sdev == NULL && sdev != NULL);
|
|
|
|
/* Get exclusive access to the SPI device */
|
|
|
|
spi_semtake(priv);
|
|
|
|
/* Bind the SPI slave device interface instance to the SPI slave
|
|
* controller interface.
|
|
*/
|
|
|
|
priv->sdev = sdev;
|
|
|
|
/* Call the slaved device's select() and cmddata() methods to indicate
|
|
* the initial state of the chip select and command/data discretes.
|
|
*
|
|
* NOTE: Unless we reconfigure the NSS GPIO pin, it may not be possible
|
|
* to read the NSS pin value (I haven't actually tried just reading it).
|
|
* And, since the is no interrupt on the falling edge of NSS, we get no
|
|
* notification when we are selected... not until the arrival of data.
|
|
*
|
|
* REVISIT: A board-level interface would be required in order to support
|
|
* the Command/Data indication (not yet impklemented).
|
|
*/
|
|
|
|
SPI_SDEV_SELECT(sdev, false);
|
|
#warning Missing logic
|
|
SPI_SDEV_CMDDATA(sdev, false);
|
|
|
|
/* Discard any queued data */
|
|
|
|
priv->head = 0;
|
|
priv->tail = 0;
|
|
|
|
/* Call the slave device's getdata() method to get the value that will
|
|
* be shifted out the SPI clock is detected.
|
|
*/
|
|
|
|
priv->outval = SPI_SDEV_GETDATA(sdev);
|
|
spi_putreg(priv, priv->outval, SAM_SPI_TDR_OFFSET);
|
|
|
|
/* Setup to begin normal SPI operation */
|
|
|
|
spi_setmode(priv, mode);
|
|
spi_setbits(priv, nbits);
|
|
|
|
/* Clear pending interrupts by reading the SPI Status Register */
|
|
|
|
regval = spi_getreg(priv, SAM_SPI_SR_OFFSET);
|
|
UNUSED(regval);
|
|
|
|
/* Enable SPI interrupts (already enabled at the NVIC):
|
|
*
|
|
* Data Transfer:
|
|
* SPI_INT_RDRF - Receive Data Register Full Interrupt
|
|
* SPI_INT_TDRE - Transmit Data Register Empty Interrupt
|
|
* SPI_INT_NSSR - NSS Rising Interrupt
|
|
*
|
|
* Transfer Errors (for DEBUG purposes only):
|
|
* SPI_INT_OVRES - Overrun Error Interrupt
|
|
* SPI_INT_UNDES - Underrun Error Status Interrupt (slave)
|
|
*
|
|
* Not Used:
|
|
* SPI_INT_MODF - Mode Fault Error Interrupt
|
|
* SPI_INT_TXEMPTY - Transmission Registers Empty Interrupt
|
|
*
|
|
* TX interrupts (SPI_INT_TDRE and SPI_INT_UNDES) are not enabled until
|
|
* the transfer of data actually starts.
|
|
*/
|
|
|
|
regval = (SPI_INT_RDRF | SPI_INT_NSSR);
|
|
#ifdef CONFIG_DEBUG_SPI
|
|
regval |= SPI_INT_OVRES;
|
|
#endif
|
|
|
|
spi_putreg(priv, regval, SAM_SPI_IER_OFFSET);
|
|
|
|
spi_semgive(priv);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spi_unbind
|
|
*
|
|
* Description:
|
|
* Un-bind the SPI slave device interface from the SPI slave controller
|
|
* interface. Reset the SPI interface and restore the SPI slave
|
|
* controller driver to its initial state,
|
|
*
|
|
* Input Parameters:
|
|
* sctrlr - SPI slave controller interface instance
|
|
*
|
|
* Returned Value:
|
|
* none
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void spi_unbind(struct spi_sctrlr_s *sctrlr)
|
|
{
|
|
struct sam_spidev_s *priv = (struct sam_spidev_s *)sctrlr;
|
|
|
|
DEBUGASSERT(priv != NULL);
|
|
spivdbg("Unbinding %p\n", priv->sdev);
|
|
|
|
DEBUGASSERT(priv->sdev != NULL);
|
|
|
|
/* Get exclusive access to the SPI device */
|
|
|
|
spi_semtake(priv);
|
|
|
|
/* Disable SPI interrupts (still enabled at the NVIC) */
|
|
|
|
spi_putreg(priv, SPI_INT_ALL, SAM_SPI_IDR_OFFSET);
|
|
|
|
/* Unbind the SPI slave interface */
|
|
|
|
priv->sdev = NULL;
|
|
|
|
/* Disable the SPI peripheral */
|
|
|
|
spi_putreg(priv, SPI_CR_SPIDIS, SAM_SPI_CR_OFFSET);
|
|
|
|
/* Execute a software reset of the SPI (twice) */
|
|
|
|
spi_putreg(priv, SPI_CR_SWRST, SAM_SPI_CR_OFFSET);
|
|
spi_putreg(priv, SPI_CR_SWRST, SAM_SPI_CR_OFFSET);
|
|
|
|
spi_semgive(priv);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spi_enqueue
|
|
*
|
|
* Description:
|
|
* Enqueue the next value to be shifted out from the interface. This adds
|
|
* the word the controller driver for a subsequent transfer but has no
|
|
* effect on anyin-process or currently "committed" transfers
|
|
*
|
|
* Input Parameters:
|
|
* sctrlr - SPI slave controller interface instance
|
|
* data - Command/data mode data value to be shifted out. The width of
|
|
* the data must be the same as the nbits parameter previously
|
|
* provided to the bind() methods.
|
|
*
|
|
* Returned Value:
|
|
* Zero if the word was successfully queue; A negated errno valid is
|
|
* returned on any failure to enqueue the word (such as if the queue is
|
|
* full).
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int spi_enqueue(struct spi_sctrlr_s *sctrlr, uint16_t data)
|
|
{
|
|
struct sam_spidev_s *priv = (struct sam_spidev_s *)sctrlr;
|
|
irqstate_t flags;
|
|
uint32_t regval;
|
|
int next;
|
|
int ret;
|
|
|
|
spivdbg("data=%04x\n", data);
|
|
DEBUGASSERT(priv != NULL && priv->sdev != NULL);
|
|
|
|
/* Get exclusive access to the SPI device */
|
|
|
|
spi_semtake(priv);
|
|
|
|
/* Check if this word would overflow the circular buffer
|
|
*
|
|
* Interrupts are disabled briefly.
|
|
*/
|
|
|
|
flags = irqsave();
|
|
next = priv->head + 1;
|
|
if (next >= CONFIG_SAMV7_SPI_SLAVE_QSIZE)
|
|
{
|
|
next = 0;
|
|
}
|
|
|
|
if (next == priv->tail)
|
|
{
|
|
ret = -ENOSPC;
|
|
}
|
|
else
|
|
{
|
|
/* Save this new word as the next word to shifted out. The current
|
|
* word written to the TX data registers is "committed" and will not
|
|
* be overwritten.
|
|
*/
|
|
|
|
priv->outq[priv->head] = data;
|
|
priv->head = next;
|
|
ret = OK;
|
|
|
|
/* Enable TX interrupts if we have begun the transfer */
|
|
|
|
if (!priv->nss)
|
|
{
|
|
/* Enable TXDR/OVRE interrupts */
|
|
|
|
regval = (SPI_INT_TDRE | SPI_INT_UNDES);
|
|
spi_putreg(priv, regval, SAM_SPI_IER_OFFSET);
|
|
}
|
|
}
|
|
|
|
irqrestore(flags);
|
|
spi_semgive(priv);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spi_qfull
|
|
*
|
|
* Description:
|
|
* Return true if the queue is full or false if there is space to add an
|
|
* additional word to the queue.
|
|
*
|
|
* Input Parameters:
|
|
* sctrlr - SPI slave controller interface instance
|
|
*
|
|
* Returned Value:
|
|
* true if the output wueue is full
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool spi_qfull(struct spi_sctrlr_s *sctrlr)
|
|
{
|
|
struct sam_spidev_s *priv = (struct sam_spidev_s *)sctrlr;
|
|
irqstate_t flags;
|
|
int next;
|
|
bool ret;
|
|
|
|
DEBUGASSERT(priv != NULL && priv->sdev != NULL);
|
|
|
|
/* Get exclusive access to the SPI device */
|
|
|
|
spi_semtake(priv);
|
|
|
|
/* Check if another word would overflow the circular buffer
|
|
*
|
|
* Interrupts are disabled briefly.
|
|
*/
|
|
|
|
flags = irqsave();
|
|
next = priv->head + 1;
|
|
if (next >= CONFIG_SAMV7_SPI_SLAVE_QSIZE)
|
|
{
|
|
next = 0;
|
|
}
|
|
|
|
ret = (next == priv->tail);
|
|
irqrestore(flags);
|
|
spi_semgive(priv);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spi_qflush
|
|
*
|
|
* Description:
|
|
* Discard all saved values in the output queue. On return from this
|
|
* function the output queue will be empty. Any in-progress or otherwise
|
|
* "committed" output values may not be flushed.
|
|
*
|
|
* Input Parameters:
|
|
* sctrlr - SPI slave controller interface instance
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void spi_qflush(struct spi_sctrlr_s *sctrlr)
|
|
{
|
|
struct sam_spidev_s *priv = (struct sam_spidev_s *)sctrlr;
|
|
irqstate_t flags;
|
|
|
|
spivdbg("data=%04x\n", data);
|
|
|
|
DEBUGASSERT(priv != NULL && priv->sdev != NULL);
|
|
|
|
/* Get exclusive access to the SPI device */
|
|
|
|
spi_semtake(priv);
|
|
|
|
/* Mark the buffer empty, momentarily disabling interrupts */
|
|
|
|
flags = irqsave();
|
|
priv->head = 0;
|
|
priv->tail = 0;
|
|
irqrestore(flags);
|
|
spi_semgive(priv);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: sam_spi_slave_initialize
|
|
*
|
|
* Description:
|
|
* Initialize the selected SPI port in slave mode.
|
|
*
|
|
* Input Parameter:
|
|
* port - Chip select number identifying the "logical" SPI port. Includes
|
|
* encoded port and chip select information.
|
|
*
|
|
* Returned Value:
|
|
* Valid SPI device structure reference on success; a NULL on failure
|
|
*
|
|
****************************************************************************/
|
|
|
|
struct spi_sctrlr_s *sam_spi_slave_initialize(int port)
|
|
{
|
|
struct sam_spidev_s *priv;
|
|
int spino = (port & __SPI_SPI_MASK) >> __SPI_SPI_SHIFT;
|
|
irqstate_t flags;
|
|
uint32_t regval;
|
|
|
|
/* The support SAM parts have only a single SPI port */
|
|
|
|
spivdbg("port: %d spino: %d\n", port, spino);
|
|
|
|
#if defined(CONFIG_SAMV7_SPI0_SLAVE) && defined(CONFIG_SAMV7_SPI1_SLAVE)
|
|
DEBUGASSERT(spino >= 0 && spino <= 1);
|
|
#elif defined(CONFIG_SAMV7_SPI0_SLAVE)
|
|
DEBUGASSERT(spino == 0);
|
|
#else
|
|
DEBUGASSERT(spino == 1);
|
|
#endif
|
|
|
|
/* Allocate a new state structure for this chip select. NOTE that there
|
|
* is no protection if the same chip select is used in two different
|
|
* chip select structures.
|
|
*/
|
|
|
|
priv = (struct sam_spidev_s *)zalloc(sizeof(struct sam_spidev_s));
|
|
if (!priv)
|
|
{
|
|
spidbg("ERROR: Failed to allocate a chip select structure\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* Set up the initial state for this chip select structure. Other fields
|
|
* were zeroed by zalloc().
|
|
*/
|
|
|
|
/* Initialize the SPI operations */
|
|
|
|
priv->sctrlr.ops = &g_sctrlr_ops;
|
|
|
|
/* Save the SPI controller number */
|
|
|
|
priv->spino = spino;
|
|
|
|
/* Has the SPI hardware been initialized? */
|
|
|
|
if (!priv->initialized)
|
|
{
|
|
/* Enable clocking to the SPI block */
|
|
|
|
flags = irqsave();
|
|
#if defined(CONFIG_SAMV7_SPI0_SLAVE) && defined(CONFIG_SAMV7_SPI1_SLAVE)
|
|
if (spino == 0)
|
|
#endif
|
|
#if defined(CONFIG_SAMV7_SPI0_SLAVE)
|
|
{
|
|
/* Set the SPI0 register base address and interrupt information */
|
|
|
|
priv->base = SAM_SPI0_BASE,
|
|
priv->irq = SAM_IRQ_SPI0;
|
|
priv->handler = spi0_interrupt;
|
|
|
|
/* Enable peripheral clocking to SPI0 */
|
|
|
|
sam_spi0_enableclk();
|
|
|
|
/* Configure multiplexed pins as connected on the board. */
|
|
|
|
sam_configgpio(GPIO_SPI0_MISO); /* Output */
|
|
sam_configgpio(GPIO_SPI0_MOSI); /* Input */
|
|
sam_configgpio(GPIO_SPI0_SPCK); /* Drives slave */
|
|
sam_configgpio(GPIO_SPI0_NSS); /* aka NPCS0 */
|
|
}
|
|
#endif
|
|
#if defined(CONFIG_SAMV7_SPI0_SLAVE) && defined(CONFIG_SAMV7_SPI1_SLAVE)
|
|
else
|
|
#endif
|
|
#if defined(CONFIG_SAMV7_SPI1_SLAVE)
|
|
{
|
|
/* Set the SPI1 register base address and interrupt information */
|
|
|
|
priv->base = SAM_SPI1_BASE,
|
|
priv->irq = SAM_IRQ_SPI1;
|
|
priv->handler = spi1_interrupt;
|
|
|
|
/* Enable peripheral clocking to SPI1 */
|
|
|
|
sam_spi1_enableclk();
|
|
|
|
/* Configure multiplexed pins as connected on the board. */
|
|
|
|
sam_configgpio(GPIO_SPI1_MISO); /* Output */
|
|
sam_configgpio(GPIO_SPI1_MOSI); /* Input */
|
|
sam_configgpio(GPIO_SPI1_SPCK); /* Drives slave */
|
|
sam_configgpio(GPIO_SPI0_NSS); /* aka NPCS0 */
|
|
}
|
|
#endif
|
|
|
|
/* Disable the SPI peripheral */
|
|
|
|
spi_putreg(priv, SPI_CR_SPIDIS, SAM_SPI_CR_OFFSET);
|
|
|
|
/* Execute a software reset of the SPI (twice) */
|
|
|
|
spi_putreg(priv, SPI_CR_SWRST, SAM_SPI_CR_OFFSET);
|
|
spi_putreg(priv, SPI_CR_SWRST, SAM_SPI_CR_OFFSET);
|
|
irqrestore(flags);
|
|
|
|
/* Configure the SPI mode register */
|
|
|
|
spi_putreg(priv, SPI_MR_SLAVE | SPI_MR_MODFDIS, SAM_SPI_MR_OFFSET);
|
|
|
|
/* And enable the SPI */
|
|
|
|
spi_putreg(priv, SPI_CR_SPIEN, SAM_SPI_CR_OFFSET);
|
|
up_mdelay(20);
|
|
|
|
/* Flush any pending interrupts/transfers */
|
|
|
|
(void)spi_getreg(priv, SAM_SPI_SR_OFFSET);
|
|
(void)spi_getreg(priv, SAM_SPI_RDR_OFFSET);
|
|
|
|
/* Initialize the SPI semaphore that enforces mutually exclusive
|
|
* access to the SPI registers.
|
|
*/
|
|
|
|
sem_init(&priv->spisem, 0, 1);
|
|
priv->nss = true;
|
|
priv->initialized = true;
|
|
|
|
/* Disable all SPI interrupts at the SPI peripheral */
|
|
|
|
spi_putreg(priv, SPI_INT_ALL, SAM_SPI_IDR_OFFSET);
|
|
|
|
/* Attach and enable interrupts at the NVIC */
|
|
|
|
DEBUGVERIFY(irq_attach(priv->irq, priv->handler));
|
|
up_enable_irq(priv->irq);
|
|
|
|
spi_dumpregs(priv, "After initialization");
|
|
}
|
|
|
|
/* Set to mode=0 and nbits=8 */
|
|
|
|
regval = spi_getreg(priv, SAM_SPI_CSR0_OFFSET);
|
|
regval &= ~(SPI_CSR_CPOL | SPI_CSR_NCPHA | SPI_CSR_BITS_MASK);
|
|
regval |= (SPI_CSR_NCPHA | SPI_CSR_BITS(8));
|
|
spi_putreg(priv, regval, SAM_SPI_CSR0_OFFSET);
|
|
|
|
priv->nbits = 8;
|
|
spivdbg("csr[offset=%02x]=%08x\n", offset, regval);
|
|
|
|
return &priv->sctrlr;
|
|
}
|
|
#endif /* CONFIG_SAMV7_SPI_SLAVE */
|