1608 lines
43 KiB
C
1608 lines
43 KiB
C
/****************************************************************************
|
||
* 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", 42129J–SAM–12/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 */
|