nuttx/arch/arm/src/samdl/sam_spi.c

1608 lines
43 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/****************************************************************************
* arch/arm/src/samdl/sam_spi.c
*
* Copyright (C) 2014-2015 Gregory Nutt. All rights reserved.
* Authors: Gregory Nutt <gnutt@nuttx.org>
*
* References:
* 1. "Atmel SAM D20J / SAM D20G / SAM D20E ARM-Based Microcontroller
* Datasheet", 42129JSAM12/2013
*
* 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 <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <semaphore.h>
#include <errno.h>
#include <assert.h>
#include <debug.h>
#include <nuttx/arch.h>
#include <nuttx/wdog.h>
#include <nuttx/clock.h>
#include <nuttx/spi/spi.h>
#include "up_internal.h"
#include "up_arch.h"
#include "chip.h"
#include "sam_pinmap.h"
#include "sam_gclk.h"
#include "sam_port.h"
#include "sam_sercom.h"
#include "sam_spi.h"
#include <arch/board/board.h>
#ifdef SAMDL_HAVE_SPI
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Clocking *****************************************************************/
/* 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
# undef CONFIG_SAMDL_SPI_REGDEBUG
#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 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 gclkgen; /* 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 */
#ifndef CONFIG_SPI_OWNBUS
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 */
#endif
uint8_t nbits; /* Width of word in bits (8 to 16) */
/* Debug stuff */
#ifdef CONFIG_SAMDL_SPI_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_SAMDL_SPI_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);
#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
/* Interrupt handling */
#if 0 /* Not used */
static int spi_interrupt(struct sam_spidev_s *dev);
#ifdef SAMDL_HAVE_SPI0
static int spi0_interrupt(int irq, void *context);
#endif
#ifdef SAMDL_HAVE_SPI1
static int spi1_interrupt(int irq, void *context);
#endif
#ifdef SAMDL_HAVE_SPI2
static int spi2_interrupt(int irq, void *context);
#endif
#ifdef SAMDL_HAVE_SPI3
static int spi3_interrupt(int irq, void *context);
#endif
#ifdef SAMDL_HAVE_SPI4
static int spi4_interrupt(int irq, void *context);
#endif
#ifdef SAMDL_HAVE_SPI5
static int spi5_interrupt(int irq, void *context);
#endif
#endif
/* SPI methods */
#ifndef CONFIG_SPI_OWNBUS
static int spi_lock(struct spi_dev_s *dev, bool lock);
#endif
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 uint16_t spi_send(struct spi_dev_s *dev, uint16_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 SAMDL_HAVE_SPI0
/* SPI0 driver operations */
static const struct spi_ops_s g_spi0ops =
{
#ifndef CONFIG_SPI_OWNBUS
.lock = spi_lock,
#endif
.select = sam_spi0select,
.setfrequency = spi_setfrequency,
.setmode = spi_setmode,
.setbits = spi_setbits,
.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
.gclkgen = 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,
#if 0 /* Not used */
.handler = spi0_interrupt,
#endif
#ifndef CONFIG_SPI_OWNBUS
.spilock = SEM_INITIALIZER(1),
#endif
};
#endif
#ifdef SAMDL_HAVE_SPI1
/* SPI1 driver operations */
static const struct spi_ops_s g_spi1ops =
{
#ifndef CONFIG_SPI_OWNBUS
.lock = spi_lock,
#endif
.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
.gclkgen = 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,
#if 0 /* Not used */
.handler = spi1_interrupt,
#endif
#ifndef CONFIG_SPI_OWNBUS
.spilock = SEM_INITIALIZER(1),
#endif
};
#endif
#ifdef SAMDL_HAVE_SPI2
/* SPI2 driver operations */
static const struct spi_ops_s g_spi2ops =
{
#ifndef CONFIG_SPI_OWNBUS
.lock = spi_lock,
#endif
.select = sam_spi0select,
.setfrequency = spi_setfrequency,
.setmode = spi_setmode,
.setbits = spi_setbits,
.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 SPI2 controller */
static struct sam_spidev_s g_spi2dev =
{
.ops = &g_spi1ops,
.sercom = 2,
#if 0 /* Not used */
.irq = SAM_IRQ_SERCOM2,
#endif
.gclkgen = 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,
#if 0 /* Not used */
.handler = spi2_interrupt,
#endif
#ifndef CONFIG_SPI_OWNBUS
.spilock = SEM_INITIALIZER(1),
#endif
};
#endif
#ifdef SAMDL_HAVE_SPI3
/* SPI3 driver operations */
static const struct spi_ops_s g_spi3ops =
{
#ifndef CONFIG_SPI_OWNBUS
.lock = spi_lock,
#endif
.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
.gclkgen = 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,
#if 0 /* Not used */
.handler = spi3_interrupt,
#endif
#ifndef CONFIG_SPI_OWNBUS
.spilock = SEM_INITIALIZER(1),
#endif
};
#endif
#ifdef SAMDL_HAVE_SPI4
/* SPI4 driver operations */
static const struct spi_ops_s g_spi4ops =
{
#ifndef CONFIG_SPI_OWNBUS
.lock = spi_lock,
#endif
.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
.gclkgen = 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,
#if 0 /* Not used */
.handler = spi4_interrupt,
#endif
#ifndef CONFIG_SPI_OWNBUS
.spilock = SEM_INITIALIZER(1),
#endif
};
#endif
#ifdef SAMDL_HAVE_SPI5
/* SPI5 driver operations */
static const struct spi_ops_s g_spi5ops =
{
#ifndef CONFIG_SPI_OWNBUS
.lock = spi_lock,
#endif
.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
.gclkgen = 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,
#if 0 /* Not used */
.handler = spi5_interrupt,
#endif
#ifndef CONFIG_SPI_OWNBUS
.spilock = SEM_INITIALIZER(1),
#endif
};
#endif
/****************************************************************************
* Public Data
****************************************************************************/
/****************************************************************************
* 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_SAMDL_SPI_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 */
lldbg("...[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_SAMDL_SPI_REGDEBUG
if (spi_checkreg(priv, false, (uint32_t)regval, regaddr))
{
lldbg("%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_SAMDL_SPI_REGDEBUG
if (spi_checkreg(priv, true, (uint32_t)regval, regaddr))
{
lldbg("%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_SAMDL_SPI_REGDEBUG
if (spi_checkreg(priv, false, (uint32_t)regval, regaddr))
{
lldbg("%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_SAMDL_SPI_REGDEBUG
if (spi_checkreg(priv, true, (uint32_t)regval, regaddr))
{
lldbg("%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_SAMDL_SPI_REGDEBUG
if (spi_checkreg(priv, false, regval, regaddr))
{
lldbg("%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_SAMDL_SPI_REGDEBUG
if (spi_checkreg(priv, true, regval, regaddr))
{
lldbg("%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
*
****************************************************************************/
#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(" 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));
spivdbg(" 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));
spivdbg(" 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(struct sam_spidev_s *dev)
{
struct sam_dev_s *priv = (struct sam_dev_s*)dev->priv;;
uint8_t pending;
uint8_t intflag;
uint8_t inten;
/* 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: spiN_interrupt
*
* Description:
* Handle each SERCOM SPI interrupt by calling the common interrupt
* handling logic with the SPI-specific state.
*
****************************************************************************/
#if 0 /* Not used */
#ifdef SAMDL_HAVE_SPI0
static int spi0_interrupt(int irq, void *context)
{
return spi_interrupt(&g_spi0dev);
}
#endif
#ifdef SAMDL_HAVE_SPI1
static int spi1_interrupt(int irq, void *context)
{
return spi_interrupt(&g_spi1dev);
}
#endif
#ifdef SAMDL_HAVE_SPI2
static int spi2_interrupt(int irq, void *context)
{
return spi_interrupt(&g_spi2dev);
}
#endif
#ifdef SAMDL_HAVE_SPI3
static int spi3_interrupt(int irq, void *context)
{
return spi_interrupt(&g_spi3dev);
}
#endif
#ifdef SAMDL_HAVE_SPI4
static int spi4_interrupt(int irq, void *context)
{
return spi_interrupt(&g_spi4dev);
}
#endif
#ifdef SAMDL_HAVE_SPI5
static int spi5_interrupt(int irq, void *context)
{
return spi_interrupt(&g_spi5dev);
}
#endif
#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 buss 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
*
****************************************************************************/
#ifndef CONFIG_SPI_OWNBUS
static int spi_lock(struct spi_dev_s *dev, bool lock)
{
struct sam_spidev_s *priv = (struct sam_spidev_s *)dev;
spivdbg("lock=%d\n", lock);
if (lock)
{
/* Take the semaphore (perhaps waiting) */
while (sem_wait(&priv->spilock) != 0)
{
/* The only case that an error should occur here is if the wait was awakened
* by a signal.
*/
ASSERT(errno == EINTR);
}
}
else
{
(void)sem_post(&priv->spilock);
}
return OK;
}
#endif
/****************************************************************************
* 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;
spivdbg("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 */
spidbg("ERROR: Cannot realize frequency: %ld\n", (long)frequency);
frequency = maxfreq;
}
/* Check if the requested frequency is the same as the frequency selection */
#ifndef CONFIG_SPI_OWNBUS
if (priv->frequency == frequency)
{
/* We are already at this frequency. Return the actual. */
return priv->actual;
}
#endif
/* 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)
{
spidbg("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 */
#ifndef CONFIG_SPI_OWNBUS
priv->frequency = frequency;
priv->actual = actual;
#endif
spivdbg("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;
spivdbg("sercom=%d mode=%d\n", priv->sercom, mode);
/* Has the mode changed? */
#ifndef CONFIG_SPI_OWNBUS
if (mode != priv->mode)
#endif
{
/* Yes... Set the mode appropriately */
regval = spi_getreg32(priv, SAM_SPI_CTRLA_OFFSET);
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, SAM_SPI_CTRLA_OFFSET);
/* Save the mode so that subsequent re-configurations will be faster */
#ifndef CONFIG_SPI_OWNBUS
priv->mode = mode;
#endif
}
}
/****************************************************************************
* 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;
spivdbg("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 uint16_t spi_send(struct spi_dev_s *dev, uint16_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);
spivdbg("Sent %02x received %02x\n", txbyte, rxbyte);
return (uint16_t)rxbyte;
}
/****************************************************************************
* 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 recieve 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;
const uint16_t *ptx16;
const uint8_t *ptx8;
uint16_t *prx16;
uint8_t *prx8;
uint16_t data;
spivdbg("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)
{
spidbg("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;
}
}
}
/***************************************************************************
* 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 recieve 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_STATUS_OFFSET) & SPI_STATUS_SYNCBUSY) != 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_configport(priv->pad0);
}
if (priv->pad1 != 0)
{
sam_configport(priv->pad1);
}
if (priv->pad2 != 0)
{
sam_configport(priv->pad2);
}
if (priv->pad3 != 0)
{
sam_configport(priv->pad3);
}
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: up_spiinitialize
*
* Description:
* Initialize the selected SPI port
*
* Input Parameter:
* 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 *up_spiinitialize(int port)
{
struct sam_spidev_s *priv;
irqstate_t flags;
uint32_t regval;
#if 0 /* Not used */
int ret;
#endif
/* Get the port state structure */
spivdbg("port: %d \n", port);
#ifdef SAMDL_HAVE_SPI0
if (port == 0)
{
priv = &g_spi0dev;
}
else
#endif
#ifdef SAMDL_HAVE_SPI1
if (port == 1)
{
priv = &g_spi1dev;
}
else
#endif
#ifdef SAMDL_HAVE_SPI2
if (port == 2)
{
priv = &g_spi2dev;
}
else
#endif
#ifdef SAMDL_HAVE_SPI3
if (port == 3)
{
priv = &g_spi3dev;
}
else
#endif
#ifdef SAMDL_HAVE_SPI4
if (port == 4)
{
priv = &g_spi4dev;
}
else
#endif
#ifdef SAMDL_HAVE_SPI5
if (port == 5)
{
priv = &g_spi5dev;
}
else
#endif
{
spidbg("ERROR: Unsupported port: %d\n", port);
return NULL;
}
/* Enable clocking to the SERCOM module in PM */
flags = irqsave();
sercom_enable(priv->sercom);
/* Configure the GCLKs for the SERCOM module */
sercom_coreclk_configure(priv->sercom, priv->gclkgen, 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;
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.
*/
(void)spi_setfrequency((struct spi_dev_s *)priv, 400000);
/* Set MSB first data order and the configured pad mux setting,
* Note that SPI mode 0 is assumed initially (CPOL=0 and CPHA=0).
*/
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, priv->handler);
if (ret < 0)
{
spidbg("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");
irqrestore(flags);
return (struct spi_dev_s *)priv;
}
#endif /* SAMDL_HAVE_SPI */