From 5e4c63ed5828b3640500c057721597a1959e7fa1 Mon Sep 17 00:00:00 2001 From: Gregory Nutt Date: Sun, 29 Jul 2018 07:43:21 -0600 Subject: [PATCH] arch/arm/src/samd5e5: Bring in untested SPI and I2C drivers from SAMD2L2. --- arch/arm/src/samd5e5/Kconfig | 42 +- arch/arm/src/samd5e5/Make.defs | 8 + arch/arm/src/samd5e5/sam_config.h | 44 +- arch/arm/src/samd5e5/sam_i2c_master.c | 1514 +++++++++++++++++++++ arch/arm/src/samd5e5/sam_spi.c | 1743 +++++++++++++++++++++++++ arch/arm/src/samd5e5/sam_spi.h | 310 +++++ 6 files changed, 3619 insertions(+), 42 deletions(-) create mode 100644 arch/arm/src/samd5e5/sam_i2c_master.c create mode 100644 arch/arm/src/samd5e5/sam_spi.c create mode 100644 arch/arm/src/samd5e5/sam_spi.h diff --git a/arch/arm/src/samd5e5/Kconfig b/arch/arm/src/samd5e5/Kconfig index 339f49549c..2410ff1d75 100644 --- a/arch/arm/src/samd5e5/Kconfig +++ b/arch/arm/src/samd5e5/Kconfig @@ -480,9 +480,9 @@ choice depends on SAMD5E5_SERCOM0 config SAMD5E5_SERCOM0_ISI2C - bool "I2C" + bool "I2C Master" select I2C - select SAMD5E5_HAVE_I2C + select SAMD5E5_HAVE_I2C_MASTER config SAMD5E5_SERCOM0_ISSPI bool "SPI" @@ -500,9 +500,9 @@ choice depends on SAMD5E5_SERCOM1 config SAMD5E5_SERCOM1_ISI2C - bool "I2C" + bool "I2C Master" select I2C - select SAMD5E5_HAVE_I2C + select SAMD5E5_HAVE_I2C_MASTER config SAMD5E5_SERCOM1_ISSPI bool "SPI" @@ -520,9 +520,9 @@ choice depends on SAMD5E5_SERCOM2 config SAMD5E5_SERCOM2_ISI2C - bool "I2C" + bool "I2C Master" select I2C - select SAMD5E5_HAVE_I2C + select SAMD5E5_HAVE_I2C_MASTER config SAMD5E5_SERCOM2_ISSPI bool "SPI" @@ -540,9 +540,9 @@ choice depends on SAMD5E5_SERCOM3 config SAMD5E5_SERCOM3_ISI2C - bool "I2C" + bool "I2C Master" select I2C - select SAMD5E5_HAVE_I2C + select SAMD5E5_HAVE_I2C_MASTER config SAMD5E5_SERCOM3_ISSPI bool "SPI" @@ -560,9 +560,9 @@ choice depends on SAMD5E5_SERCOM4 config SAMD5E5_SERCOM4_ISI2C - bool "I2C" + bool "I2C Master" select I2C - select SAMD5E5_HAVE_I2C + select SAMD5E5_HAVE_I2C_MASTER config SAMD5E5_SERCOM4_ISSPI bool "SPI" @@ -580,9 +580,9 @@ choice depends on SAMD5E5_SERCOM5 config SAMD5E5_SERCOM5_ISI2C - bool "I2C" + bool "I2C Master" select I2C - select SAMD5E5_HAVE_I2C + select SAMD5E5_HAVE_I2C_MASTER config SAMD5E5_SERCOM5_ISSPI bool "SPI" @@ -600,9 +600,9 @@ choice depends on SAMD5E5_SERCOM6 config SAMD5E5_SERCOM6_ISI2C - bool "I2C" + bool "I2C Master" select I2C - select SAMD5E5_HAVE_I2C + select SAMD5E5_HAVE_I2C_MASTER config SAMD5E5_SERCOM6_ISSPI bool "SPI" @@ -620,9 +620,9 @@ choice depends on SAMD5E5_SERCOM7 config SAMD5E5_SERCOM7_ISI2C - bool "I2C" + bool "I2C Master" select I2C - select SAMD5E5_HAVE_I2C + select SAMD5E5_HAVE_I2C_MASTER config SAMD5E5_SERCOM7_ISSPI bool "SPI" @@ -636,10 +636,11 @@ endchoice config SAMD5E5_HAVE_SPI bool + default n select SPI menu "SPI options" - depends on SAMD5E5_HAVE_SPI + depends on SAMD5E5_HAVE_I2C_MASTER config SAMD5E5_SPI_DMA bool "SPI DMA" @@ -657,12 +658,13 @@ config SAMD5E5_SPI_REGDEBUG endmenu # SPI options -config SAMD5E5_HAVE_I2C +config SAMD5E5_HAVE_I2C_MASTER bool + default n select I2C -menu "I2C options" - depends on SAMD5E5_HAVE_I2C +menu "I2C master options" + depends on SAMD5E5_HAVE_I2C_MASTER config SAMD5E5_I2C_REGDEBUG bool "I2C register-Level Debug" diff --git a/arch/arm/src/samd5e5/Make.defs b/arch/arm/src/samd5e5/Make.defs index 5653b9e4df..6cfb21e63e 100644 --- a/arch/arm/src/samd5e5/Make.defs +++ b/arch/arm/src/samd5e5/Make.defs @@ -128,6 +128,14 @@ ifneq ($(CONFIG_ARCH_IDLE_CUSTOM),y) CHIP_CSRCS += sam_idle.c endif +ifeq ($(CONFIG_SAMD5E5_HAVE_SPI),y) +CHIP_CSRCS += sam_spi.c +endif + +ifeq ($(CONFIG_SAMD5E5_HAVE_I2C_MASTER),y) +CHIP_CSRCS += sam_i2c_master.c +endif + ifeq ($(CONFIG_SAMD5E5_WDT),y) CHIP_CSRCS += sam_wdt.c endif diff --git a/arch/arm/src/samd5e5/sam_config.h b/arch/arm/src/samd5e5/sam_config.h index 53de35fe5a..bfb8da95cb 100644 --- a/arch/arm/src/samd5e5/sam_config.h +++ b/arch/arm/src/samd5e5/sam_config.h @@ -298,63 +298,63 @@ /* Are any SERCOM peripherals are configured as I2C peripherals? */ -#define SAMD5E5_HAVE_I2C0 1 -#define SAMD5E5_HAVE_I2C1 1 -#define SAMD5E5_HAVE_I2C2 1 -#define SAMD5E5_HAVE_I2C3 1 -#define SAMD5E5_HAVE_I2C4 1 -#define SAMD5E5_HAVE_I2C5 1 -#define SAMD5E5_HAVE_I2C6 1 -#define SAMD5E5_HAVE_I2C7 1 +#define SAMD5E5_HAVE_I2C0_MASTER 1 +#define SAMD5E5_HAVE_I2C1_MASTER 1 +#define SAMD5E5_HAVE_I2C2_MASTER 1 +#define SAMD5E5_HAVE_I2C3_MASTER 1 +#define SAMD5E5_HAVE_I2C4_MASTER 1 +#define SAMD5E5_HAVE_I2C5_MASTER 1 +#define SAMD5E5_HAVE_I2C6_MASTER 1 +#define SAMD5E5_HAVE_I2C7_MASTER 1 #if !defined(CONFIG_SAMD5E5_SERCOM0) || !defined(CONFIG_SAMD5E5_SERCOM0_ISI2C) -# undef SAMD5E5_HAVE_I2C0 +# undef SAMD5E5_HAVE_I2C0_MASTER # undef CONFIG_SAMD5E5_SERCOM0_ISI2C #endif #if !defined(CONFIG_SAMD5E5_SERCOM1) || !defined(CONFIG_SAMD5E5_SERCOM1_ISI2C) -# undef SAMD5E5_HAVE_I2C1 +# undef SAMD5E5_HAVE_I2C1_MASTER # undef CONFIG_SAMD5E5_SERCOM1_ISI2C #endif #if !defined(CONFIG_SAMD5E5_SERCOM2) || !defined(CONFIG_SAMD5E5_SERCOM2_ISI2C) -# undef SAMD5E5_HAVE_I2C2 +# undef SAMD5E5_HAVE_I2C2_MASTER # undef CONFIG_SAMD5E5_SERCOM2_ISI2C #endif #if !defined(CONFIG_SAMD5E5_SERCOM3) || !defined(CONFIG_SAMD5E5_SERCOM3_ISI2C) -# undef SAMD5E5_HAVE_I2C3 +# undef SAMD5E5_HAVE_I2C3_MASTER # undef CONFIG_SAMD5E5_SERCOM3_ISI2C #endif #if !defined(CONFIG_SAMD5E5_SERCOM4) || !defined(CONFIG_SAMD5E5_SERCOM4_ISI2C) -# undef SAMD5E5_HAVE_I2C4 +# undef SAMD5E5_HAVE_I2C4_MASTER # undef CONFIG_SAMD5E5_SERCOM4_ISI2C #endif #if !defined(CONFIG_SAMD5E5_SERCOM5) || !defined(CONFIG_SAMD5E5_SERCOM5_ISI2C) -# undef SAMD5E5_HAVE_I2C5 +# undef SAMD5E5_HAVE_I2C5_MASTER # undef CONFIG_SAMD5E5_SERCOM5_ISI2C #endif #if !defined(CONFIG_SAMD5E5_SERCOM6) || !defined(CONFIG_SAMD5E5_SERCOM6_ISI2C) -# undef SAMD5E5_HAVE_I2C6 +# undef SAMD5E5_HAVE_I2C6_MASTER # undef CONFIG_SAMD5E5_SERCOM6_ISI2C #endif #if !defined(CONFIG_SAMD5E5_SERCOM7) || !defined(CONFIG_SAMD5E5_SERCOM7_ISI2C) -# undef SAMD5E5_HAVE_I2C7 +# undef SAMD5E5_HAVE_I2C7_MASTER # undef CONFIG_SAMD5E5_SERCOM7_ISI2C #endif /* Are any SERCOMs configured for I2C? */ -#undef SAMD5E5_HAVE_I2C -#if defined(SAMD5E5_HAVE_I2C0) || defined(SAMD5E5_HAVE_I2C1) || \ - defined(SAMD5E5_HAVE_I2C2) || defined(SAMD5E5_HAVE_I2C3) || \ - defined(SAMD5E5_HAVE_I2C4) || defined(SAMD5E5_HAVE_I2C5) || \ - defined(SAMD5E5_HAVE_I2C6) || defined(SAMD5E5_HAVE_I2C7) -# define SAMD5E5_HAVE_I2C 1 +#undef SAMD5E5_HAVE_I2C_MASTER +#if defined(SAMD5E5_HAVE_I2C0_MASTER) || defined(SAMD5E5_HAVE_I2C1_MASTER) || \ + defined(SAMD5E5_HAVE_I2C2_MASTER) || defined(SAMD5E5_HAVE_I2C3_MASTER) || \ + defined(SAMD5E5_HAVE_I2C4_MASTER) || defined(SAMD5E5_HAVE_I2C5_MASTER) || \ + defined(SAMD5E5_HAVE_I2C6_MASTER) || defined(SAMD5E5_HAVE_I2C7_MASTER) +# define SAMD5E5_HAVE_I2C_MASTER 1 #endif /************************************************************************************ diff --git a/arch/arm/src/samd5e5/sam_i2c_master.c b/arch/arm/src/samd5e5/sam_i2c_master.c new file mode 100644 index 0000000000..7b973cae8f --- /dev/null +++ b/arch/arm/src/samd5e5/sam_i2c_master.c @@ -0,0 +1,1514 @@ +/******************************************************************************* + * arch/arm/src/samd5e5/sam_i2c_master.c + * + * Copyright (C) 2013-2014, 2018 Gregory Nutt. All rights reserved. + * Copyright (C) 2015 Filament - www.filament.com + * Author: Matt Thompson + * Author: Alan Carvalho de Assis + * Author: Gregory Nutt + * + * References: + * SAMD/SAML Series Data Sheet + * Atmel NoOS sample code. + * + * The Atmel sample code has a BSD compatible license that requires this + * copyright notice: + * + * Copyright (c) 2011, Atmel Corporation + * + * 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, Atmel, nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *******************************************************************************/ + +/******************************************************************************* + * Included Files + *******************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "up_internal.h" +#include "up_arch.h" + +#include "chip/sam_i2c_master.h" +#include "chip/sam_pinmap.h" +#include "sam_gclk.h" +#include "sam_port.h" +#include "sam_sercom.h" + +#if defined(SAMD5E5_HAVE_I2C_MASTER) + +/******************************************************************************* + * Pre-processor Definitions + *******************************************************************************/ + +/* Configuration ***************************************************************/ + +#ifndef CONFIG_SAM_I2C0_FREQUENCY +# define CONFIG_SAM_I2C0_FREQUENCY 100000 +#endif + +#ifndef CONFIG_SAM_I2C1_FREQUENCY +# define CONFIG_SAM_I2C1_FREQUENCY 100000 +#endif + +#ifndef CONFIG_SAM_I2C2_FREQUENCY +# define CONFIG_SAM_I2C2_FREQUENCY 100000 +#endif + +#ifndef CONFIG_SAM_I2C3_FREQUENCY +# define CONFIG_SAM_I2C3_FREQUENCY 100000 +#endif + +#ifndef CONFIG_SAM_I2C4_FREQUENCY +# define CONFIG_SAM_I2C4_FREQUENCY 100000 +#endif + +#ifndef CONFIG_SAM_I2C5_FREQUENCY +# define CONFIG_SAM_I2C5_FREQUENCY 100000 +#endif + +/* Driver internal definitions *************************************************/ + +/* If verbose I2C debug output is enable, then allow more time before we declare + * a timeout. The debug output from i2c_interrupt will really slow things down! + * + * With a very slow clock (say 100,000 Hz), less than 100 usec would be required + * to transfer on byte. So these define a "long" timeout. + */ + +#ifdef CONFIG_DEBUG_I2C_INFO +# define I2C_TIMEOUT_MSPB (65000) /* 50 msec/byte */ +#else +# define I2C_TIMEOUT_MSPB (5000) /* 5 msec/byte */ +#endif + +/* Clocking to the I2C module(s) is provided by the main clock, divided down + * as necessary. + */ + +#define I2C_MAX_FREQUENCY 66000000 /* Maximum I2C frequency */ + +/******************************************************************************* + * Private Types + *******************************************************************************/ + +/* Invariant attributes of a I2C bus */ + +struct i2c_attr_s +{ + uint8_t i2c; /* I2C device number (for debug output) */ + uint8_t sercom; /* Identifies the SERCOM peripheral */ + uint8_t irq; /* SERCOM IRQ number */ + uint8_t coregen; /* 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 */ + uint32_t muxconfig; /* Pad multiplexing configuration */ + uint32_t srcfreq; /* Source clock frequency */ + uintptr_t base; /* Base address of I2C registers */ + bool runinstdby; /* Run in Stand-by ? */ + uint32_t sdaholdtime; /* Hold time after start bit */ + uint32_t speed; /* I2C Speed: Standard; Fast; High */ + bool scllowtout; /* SCL low timeout */ + uint32_t inactout; /* Inactive Bus Timeout */ + bool sclstretch; /* SCL stretch only after ACK */ + bool sclslvextout; /* SCL Slave extend timeout */ + bool sclmstextout; /* SCL Master extend timeout */ +}; + +/* State of a I2C bus */ + +struct sam_i2c_dev_s +{ + struct i2c_master_s dev; /* I2C master device */ + const struct i2c_attr_s *attr; /* Invariant attributes of I2C device */ + struct i2c_msg_s *msg; /* Current message being processed */ + uint32_t frequency; /* I2C transfer clock frequency */ + uint16_t flags; /* Transfer flags */ + + sem_t exclsem; /* Only one thread can access at a time */ + sem_t waitsem; /* Wait for I2C transfer completion */ + volatile int result; /* The result of the transfer */ + volatile int xfrd; /* Number of bytes transfers */ + + /* Debug stuff */ + +#ifdef CONFIG_SAM_I2C_REGDEBUG + bool wrlast; /* Last was a write */ + uint32_t addrlast; /* Last address */ + uint32_t vallast; /* Last value */ + int ntimes; /* Number of times */ +#endif +}; + +/******************************************************************************* + * Private Function Prototypes + *******************************************************************************/ + +/* Low-level helper functions */ + +static uint8_t i2c_getreg8(struct sam_i2c_dev_s *priv, unsigned int offset); +static void i2c_putreg8(struct sam_i2c_dev_s *priv, uint8_t regval, + unsigned int offset); +static uint16_t i2c_getreg16(struct sam_i2c_dev_s *priv, unsigned int offset); +static void i2c_putreg16(struct sam_i2c_dev_s *priv, uint16_t regval, + unsigned int offset); +static uint32_t i2c_getreg32(struct sam_i2c_dev_s *priv, unsigned int offset); +static void i2c_putreg32(struct sam_i2c_dev_s *priv, uint32_t regval, + unsigned int offset); + +static void i2c_takesem(sem_t * sem); +#define i2c_givesem(sem) (nxsem_post(sem)) + +#ifdef CONFIG_SAM_I2C_REGDEBUG +static bool i2c_checkreg(struct sam_i2c_dev_s *priv, bool wr, + uint32_t value, uintptr_t address); +static uint32_t i2c_getabs(struct sam_i2c_dev_s *priv, uintptr_t address); +static void i2c_putabs(struct sam_i2c_dev_s *priv, uintptr_t address, + uint32_t value); +#else +# define i2c_checkreg(priv,wr,value,address) (false) +# define i2c_putabs(p,a,v) putreg32(v,a) +# define i2c_getabs(p,a) getreg32(a) +#endif + +static inline uint32_t i2c_getrel(struct sam_i2c_dev_s *priv, + unsigned int offset); +static inline void i2c_putrel(struct sam_i2c_dev_s *priv, unsigned int offset, + uint32_t value); + +/* I2C transfer helper functions */ + +static int i2c_wait_for_bus(struct sam_i2c_dev_s *priv, unsigned int size); + +static void i2c_wakeup(struct sam_i2c_dev_s *priv, int result); +static int i2c_interrupt(int irq, FAR void *context, void *arg); + +static void i2c_startread(struct sam_i2c_dev_s *priv, struct i2c_msg_s *msg); +static void i2c_startwrite(struct sam_i2c_dev_s *priv, struct i2c_msg_s *msg); +static void i2c_startmessage(struct sam_i2c_dev_s *priv, struct i2c_msg_s *msg); + +static int sam_i2c_transfer(FAR struct i2c_master_s *dev, + FAR struct i2c_msg_s *msgs, int count); + +/* Initialization */ + +static uint32_t sam_i2c_setfrequency(struct sam_i2c_dev_s *priv, + uint32_t frequency); +static void i2c_hw_initialize(struct sam_i2c_dev_s *priv, uint32_t frequency); +static void i2c_wait_synchronization(struct sam_i2c_dev_s *priv); +static void i2c_pad_configure(struct sam_i2c_dev_s *priv); + +/******************************************************************************* + * Private Data + *******************************************************************************/ + +#ifdef SAMD5E5_HAVE_I2C0_MASTER +static const struct i2c_attr_s g_i2c0attr = +{ + .i2c = 0, + .sercom = 0, + .irq = SAM_IRQ_SERCOM0, + .coregen = BOARD_SERCOM0_GCLKGEN, + .slowgen = BOARD_SERCOM0_SLOW_GCLKGEN, + .pad0 = BOARD_SERCOM0_PINMAP_PAD0, + .pad1 = BOARD_SERCOM0_PINMAP_PAD1, + .muxconfig = BOARD_SERCOM0_MUXCONFIG, + .srcfreq = BOARD_SERCOM0_FREQUENCY, + .base = SAM_SERCOM0_BASE, +}; + +static struct sam_i2c_dev_s g_i2c0; +#endif + +#ifdef SAMD5E5_HAVE_I2C1_MASTER +static const struct i2c_attr_s g_i2c1attr = +{ + .i2c = 1, + .sercom = 1, + .irq = SAM_IRQ_SERCOM1, + .coregen = BOARD_SERCOM1_GCLKGEN, + .slowgen = BOARD_SERCOM1_SLOW_GCLKGEN, + .pad0 = BOARD_SERCOM1_PINMAP_PAD0, + .pad1 = BOARD_SERCOM1_PINMAP_PAD1, + .muxconfig = BOARD_SERCOM1_MUXCONFIG, + .srcfreq = BOARD_SERCOM1_FREQUENCY, + .base = SAM_SERCOM1_BASE, +}; + +static struct sam_i2c_dev_s g_i2c1; +#endif + +#ifdef SAMD5E5_HAVE_I2C2_MASTER +static const struct i2c_attr_s g_i2c2attr = +{ + .i2c = 2, + .sercom = 2, + .irq = SAM_IRQ_SERCOM2, + .coregen = BOARD_SERCOM2_GCLKGEN, + .slowgen = BOARD_SERCOM2_SLOW_GCLKGEN, + .pad0 = BOARD_SERCOM2_PINMAP_PAD0, + .pad1 = BOARD_SERCOM2_PINMAP_PAD1, + .muxconfig = BOARD_SERCOM2_MUXCONFIG, + .srcfreq = BOARD_SERCOM2_FREQUENCY, + .base = SAM_SERCOM2_BASE, +}; + +static struct sam_i2c_dev_s g_i2c2; +#endif + +#ifdef SAMD5E5_HAVE_I2C3_MASTER +static const struct i2c_attr_s g_i2c3attr = +{ + .i2c = 3, + .sercom = 3, + .irq = SAM_IRQ_SERCOM3, + .coregen = BOARD_SERCOM3_GCLKGEN, + .slowgen = BOARD_SERCOM3_SLOW_GCLKGEN, + .pad0 = BOARD_SERCOM3_PINMAP_PAD0, + .pad1 = BOARD_SERCOM3_PINMAP_PAD1, + .muxconfig = BOARD_SERCOM3_MUXCONFIG, + .srcfreq = BOARD_SERCOM3_FREQUENCY, + .base = SAM_SERCOM3_BASE, +}; + +static struct sam_i2c_dev_s g_i2c3; +#endif + +#ifdef SAMD5E5_HAVE_I2C4_MASTER +static const struct i2c_attr_s g_i2c4attr = +{ + .i2c = 4, + .sercom = 4, + .irq = SAM_IRQ_SERCOM4, + .coregen = BOARD_SERCOM4_GCLKGEN, + .slowgen = BOARD_SERCOM4_SLOW_GCLKGEN, + .pad0 = BOARD_SERCOM4_PINMAP_PAD0, + .pad1 = BOARD_SERCOM4_PINMAP_PAD1, + .muxconfig = BOARD_SERCOM4_MUXCONFIG, + .srcfreq = BOARD_SERCOM4_FREQUENCY, + .base = SAM_SERCOM4_BASE, +}; + +static struct sam_i2c_dev_s g_i2c4; +#endif + +#ifdef SAMD5E5_HAVE_I2C5_MASTER +static const struct i2c_attr_s g_i2c5attr = +{ + .i2c = 5, + .sercom = 5, + .irq = SAM_IRQ_SERCOM5, + .coregen = BOARD_SERCOM5_GCLKGEN, + .slowgen = BOARD_SERCOM5_SLOW_GCLKGEN, + .pad0 = BOARD_SERCOM5_PINMAP_PAD0, + .pad1 = BOARD_SERCOM5_PINMAP_PAD1, + .muxconfig = BOARD_SERCOM5_MUXCONFIG, + .srcfreq = BOARD_SERCOM5_FREQUENCY, + .base = SAM_SERCOM5_BASE, +}; + +static struct sam_i2c_dev_s g_i2c5; +#endif + +struct i2c_ops_s g_i2cops = +{ + .transfer = sam_i2c_transfer, +#ifdef CONFIG_I2C_RESET + .reset = sam_i2c_reset, +#endif +}; + +#ifdef SAMD5E5_HAVE_I2C6_MASTER +static const struct i2c_attr_s g_i2c6attr = +{ + .i2c = 6, + .sercom = 6, + .irq = SAM_IRQ_SERCOM6, + .coregen = BOARD_SERCOM6_GCLKGEN, + .slowgen = BOARD_SERCOM6_SLOW_GCLKGEN, + .pad0 = BOARD_SERCOM6_PINMAP_PAD0, + .pad1 = BOARD_SERCOM6_PINMAP_PAD1, + .muxconfig = BOARD_SERCOM6_MUXCONFIG, + .srcfreq = BOARD_SERCOM6_FREQUENCY, + .base = SAM_SERCOM6_BASE, +}; + +static struct sam_i2c_dev_s g_i2c6; +#endif + +#ifdef SAMD5E5_HAVE_I2C7_MASTER +static const struct i2c_attr_s g_i2c7attr = +{ + .i2c = 7, + .sercom = 7, + .irq = SAM_IRQ_SERCOM7, + .coregen = BOARD_SERCOM7_GCLKGEN, + .slowgen = BOARD_SERCOM7_SLOW_GCLKGEN, + .pad0 = BOARD_SERCOM7_PINMAP_PAD0, + .pad1 = BOARD_SERCOM7_PINMAP_PAD1, + .muxconfig = BOARD_SERCOM7_MUXCONFIG, + .srcfreq = BOARD_SERCOM7_FREQUENCY, + .base = SAM_SERCOM7_BASE, +}; + +static struct sam_i2c_dev_s g_i2c7; +#endif + +struct i2c_ops_s g_i2cops = +{ + .transfer = sam_i2c_transfer, +#ifdef CONFIG_I2C_RESET + .reset = sam_i2c_reset, +#endif +}; + +/******************************************************************************* + * Low-level Helpers + *******************************************************************************/ + +/******************************************************************************* + * Name: i2c_getreg8 + * + * Description: + * Get a 8-bit register value by offset + * + *******************************************************************************/ + +static uint8_t i2c_getreg8(struct sam_i2c_dev_s *priv, unsigned int offset) +{ + return getreg8(priv->attr->base + offset); +} + +/******************************************************************************* + * Name: i2c_putreg8 + * + * Description: + * Put a 8-bit register value by offset + * + *******************************************************************************/ + +static void i2c_putreg8(struct sam_i2c_dev_s *priv, uint8_t regval, + unsigned int offset) +{ + putreg8(regval, priv->attr->base + offset); +} + +/******************************************************************************* + * Name: i2c_getreg16 + * + * Description: + * Get a 16-bit register value by offset + * + *******************************************************************************/ + +static uint16_t i2c_getreg16(struct sam_i2c_dev_s *priv, unsigned int offset) +{ + return getreg16(priv->attr->base + offset); +} + +/******************************************************************************* + * Name: i2c_putreg16 + * + * Description: + * Put a 16-bit register value by offset + * + *******************************************************************************/ + +static void i2c_putreg16(struct sam_i2c_dev_s *priv, uint16_t regval, + unsigned int offset) +{ + putreg16(regval, priv->attr->base + offset); +} + +/******************************************************************************* + * Name: i2c_getreg32 + * + * Description: + * Get a 32-bit register value by offset + * + *******************************************************************************/ + +static uint32_t i2c_getreg32(struct sam_i2c_dev_s *priv, unsigned int offset) +{ + return getreg32(priv->attr->base + offset); +} + +/******************************************************************************* + * Name: i2c_putreg32 + * + * Description: + * Put a 32-bit register value by offset + * + *******************************************************************************/ + +static void i2c_putreg32(struct sam_i2c_dev_s *priv, uint32_t regval, + unsigned int offset) +{ + putreg32(regval, priv->attr->base + offset); +} + +/******************************************************************************* + * Name: i2c_takesem + * + * Description: + * Take the wait semaphore (handling false alarm wake-ups due to the receipt + * of signals). + * + * Input Parameters: + * dev - Instance of the SDIO device driver state structure. + * + * Returned Value: + * None + * + *******************************************************************************/ + +static void i2c_takesem(sem_t *sem) +{ + int ret; + + do + { + /* Take the semaphore (perhaps waiting) */ + + ret = nxsem_wait(sem); + + /* The only case that an error should occur here is if the wait was + * awakened by a signal. + */ + + DEBUGASSERT(ret == OK || ret == -EINTR); + } + while (ret == -EINTR); +} + +/******************************************************************************* + * Name: i2c_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. + * false: This is the same as the preceding register access. + * + *******************************************************************************/ + +#ifdef CONFIG_SAM_I2C_REGDEBUG +static bool i2c_checkreg(struct sam_i2c_dev_s *priv, bool wr, uint32_t value, + uint32_t address) +{ + if (wr == priv->wrlast && /* Same kind of access? */ + value == priv->vallast && /* Same value? */ + address == priv->addrlast) /* 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 */ + + i2cinfo("...[Repeats %d times]...\n", priv->ntimes); + } + + /* Save information about the new access */ + + priv->wrlast = wr; + priv->vallast = value; + priv->addrlast = address; + priv->ntimes = 0; + } + + /* Return true if this is the first time that we have done this operation */ + + return true; +} +#endif + +/******************************************************************************* + * Name: i2c_getabs + * + * Description: + * Read any 32-bit register using an absolute + * + *******************************************************************************/ + +#ifdef CONFIG_SAM_I2C_REGDEBUG +static uint32_t i2c_getabs(struct sam_i2c_dev_s *priv, uintptr_t address) +{ + uint32_t value = getreg32(address); + + if (i2c_checkreg(priv, false, value, address)) + { + i2cinfo("%08x->%08x\n", address, value); + } + + return value; +} +#endif + +/******************************************************************************* + * Name: i2c_putabs + * + * Description: + * Write to any 32-bit register using an absolute address + * + *******************************************************************************/ + +#ifdef CONFIG_SAM_I2C_REGDEBUG +static void i2c_putabs(struct sam_i2c_dev_s *priv, uintptr_t address, + uint32_t value) +{ + if (i2c_checkreg(priv, true, value, address)) + { + i2cinfo("%08x<-%08x\n", address, value); + } + + putreg32(value, address); +} +#endif + +/******************************************************************************* + * Name: i2c_getrel + * + * Description: + * Read a I2C register using an offset relative to the I2C base address + * + *******************************************************************************/ + +static inline uint32_t i2c_getrel(struct sam_i2c_dev_s *priv, + unsigned int offset) +{ + return i2c_getabs(priv, priv->attr->base + offset); +} + +/******************************************************************************* + * Name: i2c_putrel + * + * Description: + * Write a value to a I2C register using an offset relative to the I2C base + * address. + * + *******************************************************************************/ + +static inline void i2c_putrel(struct sam_i2c_dev_s *priv, unsigned int offset, + uint32_t value) +{ + i2c_putabs(priv, priv->attr->base + offset, value); +} + +/******************************************************************************* + * I2C transfer helper functions + *******************************************************************************/ + +/******************************************************************************* + * Name: i2c_wait_for_bus + * + * Description: + * Wait for the ISR to post the semaphore, indicating the transaction is + * complete + * + * Assumptions: + * Interrupts are disabled + * + *******************************************************************************/ + +static int i2c_wait_for_bus(struct sam_i2c_dev_s *priv, unsigned int size) +{ + struct timespec ts; + int ret; + + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_nsec += 200e3; + + ret = nxsem_timedwait(&priv->waitsem, (const struct timespec *)&ts); + if (ret < 0) + { + i2cinfo("timedwait error %d\n", ret); + return ret; + } + + return priv->result; +} + +/******************************************************************************* + * Name: i2c_wakeup + * + * Description: + * A terminal event has occurred. Wake-up the waiting thread + * + *******************************************************************************/ + +static void i2c_wakeup(struct sam_i2c_dev_s *priv, int result) +{ + /* Disable any further I2C interrupts */ + + i2c_putreg8(priv, I2C_INT_MB | I2C_INT_SB, SAM_I2C_INTENCLR_OFFSET); + + /* Wake up the waiting thread with the result of the transfer */ + + priv->result = result; + nxsem_post(&priv->waitsem); +} + +/******************************************************************************* + * Name: i2c_interrupt + * + * Description: + * The I2C Interrupt Handler + * + *******************************************************************************/ + +static int i2c_interrupt(int irq, FAR void *context, FAR void *arg) +{ + struct sam_i2c_dev_s *priv = (struct sam_i2c_dev_s *)arg; + struct i2c_msg_s *msg; + uint32_t regval; + + msg = priv->msg; + + /* Check arbitration */ + + if (i2c_getreg16(priv, SAM_I2C_STATUS_OFFSET) & I2C_STATUS_ARBLOST) + { + i2cinfo("I2C Bus Collision!\n"); + + /* Clear error INTFLAG */ + + i2c_putreg16(priv, I2C_INT_ERROR, SAM_I2C_INTFLAG_OFFSET); + + /* Cancel timeout */ + + i2c_wakeup(priv, -EBUSY); + + return -EBUSY; + } + + if ((i2c_getreg8(priv, SAM_I2C_INTFLAG_OFFSET) & I2C_INT_SB) == I2C_INT_SB) + { + /* Send NACK and STOP after transmission of last byte */ + + if (priv->xfrd == (msg->length - 1)) + { + /* NACK */ + + regval = i2c_getreg32(priv, SAM_I2C_CTRLB_OFFSET); + regval |= I2C_CTRLB_ACKACT; + i2c_putreg32(priv, regval, SAM_I2C_CTRLB_OFFSET); + i2c_wait_synchronization(priv); + + /* STOP */ + + regval = i2c_getreg32(priv, SAM_I2C_CTRLB_OFFSET); + regval |= I2C_CTRLB_CMD_ACKSTOP; + i2c_putreg32(priv, regval, SAM_I2C_CTRLB_OFFSET); + i2c_wait_synchronization(priv); + + msg->buffer[priv->xfrd++] = i2c_getreg8(priv, SAM_I2C_DATA_OFFSET); + i2c_wait_synchronization(priv); + } + else + { + regval = i2c_getreg32(priv, SAM_I2C_CTRLB_OFFSET); + regval &= ~I2C_CTRLB_ACKACT; + i2c_putreg32(priv, regval, SAM_I2C_CTRLB_OFFSET); + i2c_wait_synchronization(priv); + + msg->buffer[priv->xfrd++] = i2c_getreg8(priv, SAM_I2C_DATA_OFFSET); + i2c_wait_synchronization(priv); + + regval = i2c_getreg32(priv, SAM_I2C_CTRLB_OFFSET); + regval |= I2C_CTRLB_CMD_ACKREAD; + i2c_putreg32(priv, regval, SAM_I2C_CTRLB_OFFSET); + i2c_wait_synchronization(priv); + } + + /* Disable Interrupt after last byte */ + + if (priv->xfrd == msg->length) + { + /* Cancel timeout */ + + i2c_wakeup(priv, OK); + // i2cinfo("Got data = 0x%02X\n", msg->buffer[0]); + } + + i2c_putreg8(priv, I2C_INT_SB, SAM_I2C_INTFLAG_OFFSET); + } + + if ((i2c_getreg8(priv, SAM_I2C_INTFLAG_OFFSET) & I2C_INT_MB) == I2C_INT_MB) + { + /* If no device responded to the address packet, STATUS.RXNACK will be + * set + */ + + if ((i2c_getreg16(priv, SAM_I2C_STATUS_OFFSET) & I2C_STATUS_RXNACK) == + I2C_STATUS_RXNACK) + { + i2c_wakeup(priv, -ENODEV); + return OK; + } + + if (priv->xfrd == msg->length) + { + /* Send STOP condition */ + + regval = i2c_getreg32(priv, SAM_I2C_CTRLB_OFFSET); + regval |= I2C_CTRLB_CMD_ACKSTOP; + i2c_putreg32(priv, regval, SAM_I2C_CTRLB_OFFSET); + i2c_wait_synchronization(priv); + + i2c_wakeup(priv, OK); + } + else + { + i2c_putreg8(priv, msg->buffer[priv->xfrd++], SAM_I2C_DATA_OFFSET); + } + + i2c_putreg8(priv, I2C_INT_MB, SAM_I2C_INTFLAG_OFFSET); + } + + return OK; +} + +/******************************************************************************* + * Name: i2c_startread + * + * Description: + * Start the next read message + * + *******************************************************************************/ + +static void i2c_startread(struct sam_i2c_dev_s *priv, struct i2c_msg_s *msg) +{ + uint32_t regval; + + /* Setup for the transfer */ + + priv->result = -EBUSY; + priv->xfrd = 0; + + /* Set action to ACK */ + + regval = i2c_getreg32(priv, SAM_I2C_CTRLB_OFFSET); + regval &= ~I2C_CTRLB_ACKACT; + i2c_putreg32(priv, regval, SAM_I2C_CTRLB_OFFSET); + + /* Enable Interrupts */ + + regval = I2C_INT_MB | I2C_INT_SB; + i2c_putreg8(priv, regval, SAM_I2C_INTENSET_OFFSET); + + /* Create the ADDR register */ + + regval = (msg->addr) << 1; + + /* 7 or 10 bits ? */ + + if (msg->flags & I2C_M_TEN) + { + regval |= I2C_ADDR_TENBITEN; + } + + /* Is it a read or write? */ + + regval |= (msg->flags & I2C_M_READ); + + /* Set the ADDR register */ + + i2c_putreg32(priv, regval, SAM_I2C_ADDR_OFFSET); + i2c_wait_synchronization(priv); +} + +/******************************************************************************* + * Name: i2c_startwrite + * + * Description: + * Start the next write message + * + *******************************************************************************/ + +static void i2c_startwrite(struct sam_i2c_dev_s *priv, struct i2c_msg_s *msg) +{ + uint32_t regval; + + /* Setup for the transfer */ + + priv->result = -EBUSY; + priv->xfrd = 0; + + /* Wait bus sync */ + + i2c_wait_synchronization(priv); + + /* Set action to ACK */ + + regval = i2c_getreg32(priv, SAM_I2C_CTRLB_OFFSET); + regval &= ~I2C_CTRLB_ACKACT; + i2c_putreg32(priv, regval, SAM_I2C_CTRLB_OFFSET); + + /* Enable Interrupts */ + + regval = I2C_INT_MB | I2C_INT_SB; + i2c_putreg8(priv, regval, SAM_I2C_INTENSET_OFFSET); + + /* Create the ADDR register */ + + regval = (msg->addr) << 1; + + /* 7 or 10 bits ? */ + + if (priv->flags & I2C_M_TEN) + { + regval |= I2C_ADDR_TENBITEN; + } + + /* Is it a read or write? */ + + regval |= (priv->flags & I2C_M_READ); + + /* Set the ADDR register */ + + i2c_putreg32(priv, regval, SAM_I2C_ADDR_OFFSET); + i2c_wait_synchronization(priv); +} + +/******************************************************************************* + * Name: i2c_startmessage + * + * Description: + * Start the next write message + * + *******************************************************************************/ + +static void i2c_startmessage(struct sam_i2c_dev_s *priv, struct i2c_msg_s *msg) +{ + if ((msg->flags & I2C_M_READ) != 0) + { + i2c_startread(priv, msg); + } + else + { + i2c_startwrite(priv, msg); + } +} + +/******************************************************************************* + * I2C device operations + *******************************************************************************/ + +/******************************************************************************* + * Name: sam_i2c_transfer + * + * Description: + * Receive a block of data on I2C using the previously selected I2C + * frequency and slave address. + * + * Returned Value: + * Returns zero on success; a negated errno value on failure. + * + *******************************************************************************/ + +static int sam_i2c_transfer(FAR struct i2c_master_s *dev, + FAR struct i2c_msg_s *msgs, int count) +{ + struct sam_i2c_dev_s *priv = (struct sam_i2c_dev_s *)dev; + irqstate_t flags; + unsigned int size; + int i; + int ret = -EBUSY; + + DEBUGASSERT(dev != NULL && msgs != NULL && count > 0); + + /* Set the frequency from the first message in the msgs vector */ + + if (count) + { + if (priv->frequency != msgs->frequency) + { + sam_i2c_setfrequency(priv, msgs->frequency); + priv->frequency = msgs->frequency; + } + } + + /* Calculate the total transfer size so that we can calculate a reasonable + * timeout value. + */ + + size = 0; + for (i = 0; i < count; i++) + { + size += msgs[i].length; + } + + DEBUGASSERT(size > 0); + + /* Get exclusive access to the device */ + + i2c_takesem(&priv->exclsem); + + /* Initiate the message transfer */ + + /* Initiate the transfer. The rest will be handled from interrupt logic. + * Interrupts must be disabled to prevent re-entrance from the interrupt + * level. + */ + + while (count--) + { + priv->msg = msgs; + flags = enter_critical_section(); + i2c_startmessage(priv, msgs); + + /* And wait for the transfers to complete. Interrupts will be re-enabled + * while we are waiting. + */ + + ret = i2c_wait_for_bus(priv, msgs->length); + if (ret < 0) + { +#if 0 + i2cerr("ERROR: Transfer failed: %d\n", ret); + i2cinfo("STATUS: 0x%08x\n", + i2c_getreg16(priv, SAM_I2C_STATUS_OFFSET)); + i2cinfo("INTFLAG: 0x%02x\n", + i2c_getreg8(priv, SAM_I2C_INTFLAG_OFFSET)); +#endif + leave_critical_section(flags); + i2c_givesem(&priv->exclsem); + return ret; + } + + leave_critical_section(flags); + + /* Move to the next message */ + + msgs++; + } + + i2c_givesem(&priv->exclsem); + return ret; +} + +/******************************************************************************* + * Initialization + *******************************************************************************/ + +/******************************************************************************* + * Name: sam_i2c_setfrequency + * + * Description: + * Set the frequency for the next transfer + * + *******************************************************************************/ + +static uint32_t sam_i2c_setfrequency(struct sam_i2c_dev_s *priv, + uint32_t frequency) +{ + uint32_t maxfreq; + uint32_t baud = 0; + uint32_t baud_hs = 0; + uint32_t ctrla; + + // i2cinfo("sercom=%d frequency=%d\n", priv->attr->sercom, frequency); + + /* Check if the configured BAUD is within the valid range */ + + maxfreq = (priv->attr->srcfreq >> 1); + if (frequency > maxfreq) + { + /* Set the frequency to the maximum */ + + i2cerr("ERROR: Cannot realize frequency: %ld\n", (long)frequency); + frequency = maxfreq; + } + + /* Check if the requested frequency is the same as the frequency selection */ + + if (priv->frequency == frequency) + { + /* We are already at this frequency. Return the actual. */ + + return priv->frequency; + } + + /* Calculate and setup baud rate */ + + baud = ((priv->attr->srcfreq * 10) / (24 * frequency)) - 4; + + /* Verify that the resulting if BAUD divisor is within range */ + + if (baud > 255) + { + i2cerr("ERROR: BAUD is out of range: %d\n", baud); + baud = 255; + } + else + { + /* Find baudrate for high speed */ + + baud_hs = ((priv->attr->srcfreq * 10) / (478 * frequency)) - 1; + + if (baud_hs > 255) + { + i2cerr("ERROR: BAUD is out of range: %d\n", baud); + baud_hs = 255; + } + } + + /* Momentarily disable I2C while we apply the new BAUD setting (if it was + * previously enabled). + */ + + ctrla = i2c_getreg32(priv, SAM_I2C_CTRLA_OFFSET); + if ((ctrla & I2C_CTRLA_ENABLE) != 0) + { + /* Disable I2C.. waiting for synchronization */ + + i2c_putreg32(priv, ctrla & ~I2C_CTRLA_ENABLE, SAM_I2C_CTRLA_OFFSET); + i2c_wait_synchronization(priv); + + /* Set the new BAUD value */ + + i2c_putreg32(priv, (uint32_t) ((baud_hs << 16) | baud), + SAM_I2C_BAUD_OFFSET); + + /* Re-enable I2C.. waiting for synchronization */ + + i2c_putreg32(priv, ctrla, SAM_I2C_CTRLA_OFFSET); + i2c_wait_synchronization(priv); + + i2c_putreg16(priv, I2C_STATUS_BUSSTATE_IDLE, SAM_I2C_STATUS_OFFSET); + i2c_wait_synchronization(priv); + } + else + { + /* Set the new BAUD when the I2C is already disabled */ + + i2c_putreg32(priv, (uint32_t) ((baud_hs << 16) | baud), + SAM_I2C_BAUD_OFFSET); + } + + priv->frequency = frequency; + return priv->frequency; +} + +/******************************************************************************* + * Name: i2c_hw_initialize + * + * Description: + * Initialize/Re-initialize the I2C peripheral. This logic performs only + * repeatable initialization after either (1) the one-time initialization, or + * (2) after each bus reset. + * + *******************************************************************************/ + +static void i2c_hw_initialize(struct sam_i2c_dev_s *priv, uint32_t frequency) +{ + irqstate_t flags; + uint32_t regval; + uint32_t ctrla = 0; + + i2cinfo("I2C%d Initializing\n", priv->attr->i2c); + + /* Enable clocking to the SERCOM module in PM */ + + flags = enter_critical_section(); + sercom_enable(priv->attr->sercom); + + /* Configure the GCLKs for the SERCOM module */ + + sercom_coreclk_configure(priv->attr->sercom, priv->attr->coregen, false); + sercom_slowclk_configure(priv->attr->sercom, priv->attr->slowgen); + + /* Check if module is enabled */ + + regval = i2c_getreg32(priv, SAM_I2C_CTRLA_OFFSET); + if (regval & I2C_CTRLA_ENABLE) + { + i2cerr + ("ERROR: Cannot initialize I2C because it is already initialized!\n"); + return; + } + + /* Check if reset is in progress */ + + regval = i2c_getreg32(priv, SAM_I2C_CTRLA_OFFSET); + if (regval & I2C_CTRLA_SWRST) + { + i2cerr("ERROR: Module is in RESET process!\n"); + return; + } + + /* Configure pads */ + + i2c_pad_configure(priv); + + ctrla = + I2C_CTRLA_MODE_MASTER | I2C_CTRLA_RUNSTDBY | I2C_CTRLA_SPEED_FAST | + I2C_CTRLA_SDAHOLD_450NS | priv->attr->muxconfig; + i2c_putreg32(priv, ctrla, SAM_I2C_CTRLA_OFFSET); + i2c_wait_synchronization(priv); + + /* Enable Smart Mode */ + + i2c_putreg32(priv, I2C_CTRLB_SMEN, SAM_I2C_CTRLB_OFFSET); + + /* Set an initial baud value. */ + + sam_i2c_setfrequency(priv, 100000); + + /* Enable I2C */ + + regval = i2c_getreg32(priv, SAM_I2C_CTRLA_OFFSET); + regval |= I2C_CTRLA_ENABLE; + i2c_putreg32(priv, regval, SAM_I2C_CTRLA_OFFSET); + i2c_wait_synchronization(priv); + + /* Force IDLE bus state */ + + i2c_putreg16(priv, I2C_STATUS_BUSSTATE_IDLE, SAM_I2C_STATUS_OFFSET); + i2c_wait_synchronization(priv); + + i2c_putreg8(priv, I2C_INT_ALL, SAM_I2C_INTENCLR_OFFSET); + i2c_putreg8(priv, I2C_INT_ALL, SAM_I2C_INTFLAG_OFFSET); + + /* Enable SERCOM interrupts at the NVIC */ + + up_enable_irq(priv->attr->irq); + leave_critical_section(flags); +} + +/******************************************************************************* + * Name: i2c_wait_synchronization + * + * Description: + * Wait until the SERCOM I2C reports that it is synchronized. + * + *******************************************************************************/ + +static void i2c_wait_synchronization(struct sam_i2c_dev_s *priv) +{ + while ((i2c_getreg16(priv, SAM_I2C_SYNCBUSY_OFFSET) & 0x7) != 0); +} + +/******************************************************************************* + * Name: i2c_pad_configure + * + * Description: + * Configure the SERCOM I2C pads. + * + *******************************************************************************/ + +static void i2c_pad_configure(struct sam_i2c_dev_s *priv) +{ + /* Configure SERCOM pads */ + + if (priv->attr->pad0 != 0) + { + sam_configport(priv->attr->pad0); + } + + if (priv->attr->pad1 != 0) + { + sam_configport(priv->attr->pad1); + } +} + +/******************************************************************************* + * Public Functions + *******************************************************************************/ + +/******************************************************************************* + * Name: sam_i2c_master_initialize + * + * Description: + * Initialize a I2C device for I2C operation + * + *******************************************************************************/ + +struct i2c_master_s *sam_i2c_master_initialize(int bus) +{ + struct sam_i2c_dev_s *priv; + uint32_t frequency; + irqstate_t flags; + int ret = 0; + +#ifdef SAMD5E5_HAVE_I2C0_MASTER + if (bus == 0) + { + /* Select up I2C0 and setup invariant attributes */ + + priv = &g_i2c0; + priv->attr = &g_i2c0attr; + + /* Select the (initial) I2C frequency */ + + frequency = CONFIG_SAM_I2C0_FREQUENCY; + } + else +#endif +#ifdef SAMD5E5_HAVE_I2C1_MASTER + if (bus == 1) + { + /* Select up I2C1 and setup invariant attributes */ + + priv = &g_i2c1; + priv->attr = &g_i2c1attr; + + /* Select the (initial) I2C frequency */ + + frequency = CONFIG_SAM_I2C1_FREQUENCY; + } + else +#endif +#ifdef SAMD5E5_HAVE_I2C2_MASTER + if (bus == 2) + { + /* Select up I2C2 and setup invariant attributes */ + + priv = &g_i2c2; + priv->attr = &g_i2c2attr; + + /* Select the (initial) I2C frequency */ + + frequency = CONFIG_SAM_I2C2_FREQUENCY; + } + else +#endif +#ifdef SAMD5E5_HAVE_I2C3_MASTER + if (bus == 3) + { + /* Select up I2C3 and setup invariant attributes */ + + priv = &g_i2c3; + priv->attr = &g_i2c3attr; + + /* Select the (initial) I2C frequency */ + + frequency = CONFIG_SAM_I2C3_FREQUENCY; + } + else +#endif +#ifdef SAMD5E5_HAVE_I2C4_MASTER + if (bus == 4) + { + /* Select up I2C4 and setup invariant attributes */ + + priv = &g_i2c4; + priv->attr = &g_i2c4attr; + + /* Select the (initial) I2C frequency */ + + frequency = CONFIG_SAM_I2C4_FREQUENCY; + } + else +#endif +#ifdef SAMD5E5_HAVE_I2C5_MASTER + if (bus == 5) + { + /* Select up I2C5 and setup invariant attributes */ + + priv = &g_i2c5; + priv->attr = &g_i2c5attr; + + /* Select the (initial) I2C frequency */ + + frequency = CONFIG_SAM_I2C5_FREQUENCY; + } + else +#endif +#ifdef SAMD5E5_HAVE_I2C6_MASTER + if (bus == 6) + { + /* Select up I2C6 and setup invariant attributes */ + + priv = &g_i2c6; + priv->attr = &g_i2c6attr; + + /* Select the (initial) I2C frequency */ + + frequency = CONFIG_SAM_I2C6_FREQUENCY; + } + else +#endif +#ifdef SAMD5E5_HAVE_I2C7_MASTER + if (bus == 7) + { + /* Select up I2C7 and setup invariant attributes */ + + priv = &g_i2c7; + priv->attr = &g_i2c7attr; + + /* Select the (initial) I2C frequency */ + + frequency = CONFIG_SAM_I2C7_FREQUENCY; + } + else +#endif + { + i2cerr("ERROR: Unsupported bus: I2C%d\n", bus); + return NULL; + } + + /* Perform one-time I2C initialization */ + + flags = enter_critical_section(); + + /* Attach Interrupt Handler */ + + ret = irq_attach(priv->attr->irq, i2c_interrupt, priv); + if (ret < 0) + { + i2cerr("ERROR: Failed to attach irq %d\n", priv->attr->irq); + leave_critical_section(flags); + return NULL; + } + + /* Initialize the I2C driver structure */ + + priv->dev.ops = &g_i2cops; + priv->flags = 0; + + (void)nxsem_init(&priv->exclsem, 0, 1); + (void)nxsem_init(&priv->waitsem, 0, 0); + + /* Perform repeatable I2C hardware initialization */ + + i2c_hw_initialize(priv, frequency); + leave_critical_section(flags); + return &priv->dev; +} + +/******************************************************************************* + * Name: sam_i2c_uninitalize + * + * Description: + * Uninitialize an I2C device + * + *******************************************************************************/ + +int sam_i2c_uninitialize(FAR struct i2c_master_s *dev) +{ + struct sam_i2c_dev_s *priv = (struct sam_i2c_dev_s *)dev; + + i2cinfo("I2C%d Un-initializing\n", priv->attr->i2c); + + /* Disable I2C interrupts */ + + up_disable_irq(priv->attr->irq); + + /* Reset data structures */ + + nxsem_destroy(&priv->exclsem); + nxsem_destroy(&priv->waitsem); + + /* Detach Interrupt Handler */ + + (void)irq_detach(priv->attr->irq); + return OK; +} + +/******************************************************************************* + * Name: sam_i2c_reset + * + * Description: + * Reset an I2C bus + * + *******************************************************************************/ + +#ifdef CONFIG_I2C_RESET +int sam_i2c_reset(FAR struct i2c_master_s *dev) +{ + struct sam_i2c_dev_s *priv = (struct sam_i2c_dev_s *)dev; + int ret; + + ASSERT(priv); + + /* Get exclusive access to the I2C device */ + + i2c_takesem(&priv->exclsem); + + /* Disable I2C interrupts */ + + up_disable_irq(priv->attr->irq); + + /* Disable I2C */ + + i2c_putreg32(priv, ctrla & ~I2C_CTRLA_ENABLE, SAM_I2C_CTRLA_OFFSET); + + /* Wait it get sync */ + + i2c_wait_synchronization(priv); + + /* Reset I2C */ + + i2c_putreg32(priv, I2C_CTRLA_SWRST, SAM_I2C_CTRLA_OFFSET); + + /* Wait sync again before re-initialize */ + + i2c_wait_synchronization(priv); + + /* Re-initialize the port hardware */ + + i2c_hw_initialize(priv, priv->frequency); + ret = OK; + + /* Release our lock on the bus */ + + i2c_givesem(&priv->exclsem); + return ret; +} +#endif /* CONFIG_I2C_RESET */ +#endif /* CONFIG_SAM_I2C_MASTER */ diff --git a/arch/arm/src/samd5e5/sam_spi.c b/arch/arm/src/samd5e5/sam_spi.c new file mode 100644 index 0000000000..cbd33ca729 --- /dev/null +++ b/arch/arm/src/samd5e5/sam_spi.c @@ -0,0 +1,1743 @@ +/**************************************************************************** + * arch/arm/src/samd5e5/sam_spi.c + * + * Copyright (C) 2018 Gregory Nutt. All rights reserved. + * Authors: Gregory Nutt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "up_internal.h" +#include "up_arch.h" + +#include "chip/sam_pinmap.h" +#include "sam_gclk.h" +#include "sam_port.h" +#include "sam_sercom.h" +#include "sam_spi.h" + +#ifdef CONFIG_SAMD5E5SPI_DMA +# include "sam_dmac.h" +#endif + +#include + +#ifdef SAMD5E5_HAVE_SPI + +/**************************************************************************** + * Pre-process Definitions + ****************************************************************************/ + +#ifndef CONFIG_DEBUG_SPI_INFO +# undef CONFIG_SAMD5E5SPI_REGDEBUG +#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 coregen; /* 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 */ + + 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 */ + uint8_t nbits; /* Width of word in bits (8 to 16) */ + +#ifdef CONFIG_SAMD5E5SPI_DMA + /* DMA */ + + uint8_t dma_tx_trig; /* DMA TX trigger source to use */ + uint8_t dma_rx_trig; /* DMA RX trigger source to use */ + DMA_HANDLE dma_tx; /* DMA TX channel handle */ + DMA_HANDLE dma_rx; /* DMA RX channel handle */ + sem_t dmasem; /* Transfer wait semaphore */ +#endif + + /* Debug stuff */ + +#ifdef CONFIG_SAMD5E5SPI_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_SAMD5E5SPI_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); + +#ifdef CONFIG_SAMD5E5SPI_DMA +static void spi_dma_setup(struct sam_spidev_s *priv); +#endif + +#ifdef CONFIG_DEBUG_SPI_INFO +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(int irq, void *context, FAR void *arg); +#endif + +/* SPI methods */ + +static int spi_lock(struct spi_dev_s *dev, bool lock); +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 SAMD5E5_HAVE_SPI0 +/* SPI0 driver operations */ + +static const struct spi_ops_s g_spi0ops = +{ + .lock = spi_lock, + .select = sam_spi0select, + .setfrequency = spi_setfrequency, + .setmode = spi_setmode, + .setbits = spi_setbits, +#ifdef CONFIG_SPI_HWFEATURES + .hwfeatures = 0, /* Not supported */ +#endif + .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 + .coregen = 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, + .spilock = SEM_INITIALIZER(1), +#ifdef CONFIG_SAMD5E5SPI_DMA + .dma_tx_trig = DMAC_TRIGSRC_SERCOM0_TX, + .dma_rx_trig = DMAC_TRIGSRC_SERCOM0_RX, +#endif +}; +#endif + +#ifdef SAMD5E5_HAVE_SPI1 +/* SPI1 driver operations */ + +static const struct spi_ops_s g_spi1ops = +{ + .lock = spi_lock, + .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 + .coregen = 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, + .spilock = SEM_INITIALIZER(1), +#ifdef CONFIG_SAMD5E5SPI_DMA + .dma_tx_trig = DMAC_TRIGSRC_SERCOM1_TX, + .dma_rx_trig = DMAC_TRIGSRC_SERCOM1_RX, +#endif +}; +#endif + +#ifdef SAMD5E5_HAVE_SPI2 +/* SPI2 driver operations */ + +static const struct spi_ops_s g_spi2ops = +{ + .lock = spi_lock, + .select = sam_spi2select, + .setfrequency = spi_setfrequency, + .setmode = spi_setmode, + .setbits = spi_setbits, + .status = sam_spi2status, +#ifdef CONFIG_SPI_CMDDATA + .cmddata = sam_spi2cmddata, +#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_spi2ops, + .sercom = 2, +#if 0 /* Not used */ + .irq = SAM_IRQ_SERCOM2, +#endif + .coregen = 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, + .spilock = SEM_INITIALIZER(1), +#ifdef CONFIG_SAMD5E5SPI_DMA + .dma_tx_trig = DMAC_TRIGSRC_SERCOM2_TX, + .dma_rx_trig = DMAC_TRIGSRC_SERCOM2_RX, +#endif +}; +#endif + +#ifdef SAMD5E5_HAVE_SPI3 +/* SPI3 driver operations */ + +static const struct spi_ops_s g_spi3ops = +{ + .lock = spi_lock, + .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 + .coregen = 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, + .spilock = SEM_INITIALIZER(1), +#ifdef CONFIG_SAMD5E5SPI_DMA + .dma_tx_trig = DMAC_TRIGSRC_SERCOM3_TX, + .dma_rx_trig = DMAC_TRIGSRC_SERCOM3_RX, +#endif +}; +#endif + +#ifdef SAMD5E5_HAVE_SPI4 +/* SPI4 driver operations */ + +static const struct spi_ops_s g_spi4ops = +{ + .lock = spi_lock, + .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 + .coregen = 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, + .spilock = SEM_INITIALIZER(1), +#ifdef CONFIG_SAMD5E5SPI_DMA + .dma_tx_trig = DMAC_TRIGSRC_SERCOM4_TX, + .dma_rx_trig = DMAC_TRIGSRC_SERCOM4_RX, +#endif +}; +#endif + +#ifdef SAMD5E5_HAVE_SPI5 +/* SPI5 driver operations */ + +static const struct spi_ops_s g_spi5ops = +{ + .lock = spi_lock, + .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 + .coregen = 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, + .spilock = SEM_INITIALIZER(1), +#ifdef CONFIG_SAMD5E5SPI_DMA + .dma_tx_trig = DMAC_TRIGSRC_SERCOM5_TX, + .dma_rx_trig = DMAC_TRIGSRC_SERCOM5_RX, +#endif +}; +#endif + +#ifdef SAMD5E5_HAVE_SPI6 +/* SPI6 driver operations */ + +static const struct spi_ops_s g_spi6ops = +{ + .lock = spi_lock, + .select = sam_spi6select, + .setfrequency = spi_setfrequency, + .setmode = spi_setmode, + .setbits = spi_setbits, + .status = sam_spi6status, +#ifdef CONFIG_SPI_CMDDATA + .cmddata = sam_spi6cmddata, +#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 SPI6 controller */ + +static struct sam_spidev_s g_spi6dev = +{ + .ops = &g_spi6ops, + .sercom = 6, +#if 0 /* Not used */ + .irq = SAM_IRQ_SERCOM6, +#endif + .coregen = BOARD_SERCOM6_GCLKGEN, + .slowgen = BOARD_SERCOM6_SLOW_GCLKGEN, + .pad0 = BOARD_SERCOM6_PINMAP_PAD0, + .pad1 = BOARD_SERCOM6_PINMAP_PAD1, + .pad2 = BOARD_SERCOM6_PINMAP_PAD2, + .pad3 = BOARD_SERCOM6_PINMAP_PAD3, + .muxconfig = BOARD_SERCOM6_MUXCONFIG, + .srcfreq = BOARD_SERCOM6_FREQUENCY, + .base = SAM_SERCOM6_BASE, + .spilock = SEM_INITIALIZER(1), +#ifdef CONFIG_SAMD5E5SPI_DMA + .dma_tx_trig = DMAC_TRIGSRC_SERCOM6_TX, + .dma_rx_trig = DMAC_TRIGSRC_SERCOM6_RX, +#endif +}; +#endif + +#ifdef SAMD5E5_HAVE_SPI7 +/* SPI7 driver operations */ + +static const struct spi_ops_s g_spi7ops = +{ + .lock = spi_lock, + .select = sam_spi7select, + .setfrequency = spi_setfrequency, + .setmode = spi_setmode, + .setbits = spi_setbits, + .status = sam_spi7status, +#ifdef CONFIG_SPI_CMDDATA + .cmddata = sam_spi7cmddata, +#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 SPI7 controller */ + +static struct sam_spidev_s g_spi7dev = +{ + .ops = &g_spi7ops, + .sercom = 7, +#if 0 /* Not used */ + .irq = SAM_IRQ_SERCOM7, +#endif + .coregen = BOARD_SERCOM7_GCLKGEN, + .slowgen = BOARD_SERCOM7_SLOW_GCLKGEN, + .pad0 = BOARD_SERCOM7_PINMAP_PAD0, + .pad1 = BOARD_SERCOM7_PINMAP_PAD1, + .pad2 = BOARD_SERCOM7_PINMAP_PAD2, + .pad3 = BOARD_SERCOM7_PINMAP_PAD3, + .muxconfig = BOARD_SERCOM7_MUXCONFIG, + .srcfreq = BOARD_SERCOM7_FREQUENCY, + .base = SAM_SERCOM7_BASE, + .spilock = SEM_INITIALIZER(1), +#ifdef CONFIG_SAMD5E5SPI_DMA + .dma_tx_trig = DMAC_TRIGSRC_SERCOM7_TX, + .dma_rx_trig = DMAC_TRIGSRC_SERCOM7_RX, +#endif +}; +#endif + +/**************************************************************************** + * 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_SAMD5E5SPI_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 */ + + spiinfo("...[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_SAMD5E5SPI_REGDEBUG + if (spi_checkreg(priv, false, (uint32_t)regval, regaddr)) + { + spiinfo("%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_SAMD5E5SPI_REGDEBUG + if (spi_checkreg(priv, true, (uint32_t)regval, regaddr)) + { + spiinfo("%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_SAMD5E5SPI_REGDEBUG + if (spi_checkreg(priv, false, (uint32_t)regval, regaddr)) + { + spiinfo("%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_SAMD5E5SPI_REGDEBUG + if (spi_checkreg(priv, true, (uint32_t)regval, regaddr)) + { + spiinfo("%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_SAMD5E5SPI_REGDEBUG + if (spi_checkreg(priv, false, regval, regaddr)) + { + spiinfo("%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_SAMD5E5SPI_REGDEBUG + if (spi_checkreg(priv, true, regval, regaddr)) + { + spiinfo("%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 + * + ****************************************************************************/ + +#ifdef CONFIG_DEBUG_SPI_INFO +static void spi_dumpregs(struct sam_spidev_s *priv, const char *msg) +{ + spiinfo("%s:\n", msg); + spiinfo(" 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)); + spiinfo(" 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)); + spiinfo(" 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(int irq, void *context, FAR void *arg) +{ + struct sam_dev_s *priv = (struct sam_dev_s *)arg + uint8_t pending; + uint8_t intflag; + uint8_t inten; + + DEBUGASSERT(priv != NULL); + + /* 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: 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 + * + ****************************************************************************/ + +static int spi_lock(struct spi_dev_s *dev, bool lock) +{ + struct sam_spidev_s *priv = (struct sam_spidev_s *)dev; + int ret; + + spiinfo("lock=%d\n", lock); + if (lock) + { + /* Take the semaphore (perhaps waiting) */ + + do + { + ret = nxsem_wait(&priv->spilock); + + /* The only case that an error should occur here is if the wait + * was awakened by a signal. + */ + + DEBUGASSERT(ret == OK || ret == -EINTR); + } + while (ret == -EINTR); + } + else + { + (void)nxsem_post(&priv->spilock); + ret = OK; + } + + return ret; +} + +/**************************************************************************** + * 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; + + spiinfo("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 */ + + spierr("ERROR: Cannot realize frequency: %ld\n", (long)frequency); + frequency = maxfreq; + } + + /* Check if the requested frequency is the same as the frequency selection */ + + if (priv->frequency == frequency) + { + /* We are already at this frequency. Return the actual. */ + + return priv->actual; + } + + /* 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) + { + spierr("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 */ + + priv->frequency = frequency; + priv->actual = actual; + + spiinfo("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; + + spiinfo("sercom=%d mode=%d\n", priv->sercom, mode); + + /* Has the mode changed? */ + + if (mode != priv->mode) + { + /* Yes... Set the mode appropriately */ + + /* First we need to disable SPI while we change the mode */ + + regval = spi_getreg32(priv, SAM_SPI_CTRLA_OFFSET); + spi_putreg32(priv, regval & ~SPI_CTRLA_ENABLE, SAM_SPI_CTRLA_OFFSET); + spi_wait_synchronization(priv); + + 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 | SPI_CTRLA_ENABLE, SAM_SPI_CTRLA_OFFSET); + + /* 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: + * 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; + + spiinfo("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); + + spiinfo("Sent %02x received %02x\n", txbyte, rxbyte); + return (uint16_t)rxbyte; +} + +/**************************************************************************** + * Name: spi_dma_callback + * + * Description: + * DMA completion callback + * + * Input Parameters: + * dma - Allocate DMA handle + * arg - User argument provided with callback + * result - The result of the DMA operation + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_SAMD5E5SPI_DMA +static void spi_dma_callback(DMA_HANDLE dma, void *arg, int result) +{ + struct sam_spidev_s *priv = (struct sam_spidev_s *)arg; + + if (dma == priv->dma_rx) + { + /* Notify the blocked spi_exchange() call that the transaction + * has completed by posting to the semaphore + */ + + nxsem_post(&priv->dmasem); + } + else if (dma == priv->dma_tx) + { + if (result != OK) + { + spierr("ERROR: DMA transmission failed: %d\n", result); + } + } +} +#endif + +/**************************************************************************** + * 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; + +#ifdef CONFIG_SAMD5E5SPI_DMA + uint32_t regval; + int ret; + + spiinfo("txbuffer=%p rxbuffer=%p nwords=%d\n", txbuffer, rxbuffer, nwords); + + /* Disable SPI while we configure new DMA descriptors */ + + regval = spi_getreg32(priv, SAM_SPI_CTRLA_OFFSET); + regval &= ~SPI_CTRLA_ENABLE; + spi_putreg32(priv, regval, SAM_SPI_CTRLA_OFFSET); + spi_wait_synchronization(priv); + + /* Setup RX and TX DMA channels */ + + sam_dmatxsetup(priv->dma_tx, priv->base + SAM_SPI_DATA_OFFSET, + (uint32_t)txbuffer, nwords); + sam_dmarxsetup(priv->dma_rx, priv->base + SAM_SPI_DATA_OFFSET, + (uint32_t)rxbuffer, nwords); + + /* Start RX and TX DMA channels */ + + sam_dmastart(priv->dma_tx, spi_dma_callback, (void *)priv); + sam_dmastart(priv->dma_rx, spi_dma_callback, (void *)priv); + + /* Enable SPI to trigger the TX DMA channel */ + + regval = spi_getreg32(priv, SAM_SPI_CTRLA_OFFSET); + regval |= SPI_CTRLA_ENABLE; + spi_putreg32(priv, regval, SAM_SPI_CTRLA_OFFSET); + spi_wait_synchronization(priv); + + do + { + /* Wait for the DMA callback to notify us that the transfer is complete */ + + ret = nxsem_wait(&priv->dmasem); + } + while (ret < 0 && ret == -EINTR); +#else + const uint16_t *ptx16; + const uint8_t *ptx8; + uint16_t *prx16; + uint8_t *prx8; + uint16_t data; + + spiinfo("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) + { + spierr("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; + } + } +#endif +} + +/**************************************************************************** + * 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_SYNCBUSY_OFFSET) & SPI_SYNCBUSY_ALL) != 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); + } +} + +/**************************************************************************** + * Name: spi_dma_setup + * + * Description: + * Configure the SPI DMA operation. + * + ****************************************************************************/ + +#ifdef CONFIG_SAMD5E5SPI_DMA +static void spi_dma_setup(struct sam_spidev_s *priv) +{ + /* Allocate a pair of DMA channels */ + + priv->dma_rx = sam_dmachannel(DMACH_FLAG_BEATSIZE_BYTE | + DMACH_FLAG_MEM_INCREMENT | + DMACH_FLAG_PERIPH_RXTRIG(priv->dma_rx_trig)); + + priv->dma_tx = sam_dmachannel(DMACH_FLAG_BEATSIZE_BYTE | + DMACH_FLAG_MEM_INCREMENT | + DMACH_FLAG_PERIPH_TXTRIG(priv->dma_tx_trig)); + + /* Initialize the semaphore used to notify when DMA is complete */ + + nxsem_init(&priv->dmasem, 0, 0); + nxsem_setprotocol(&priv->dmasem, SEM_PRIO_NONE); +} +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: sam_spibus_initialize + * + * Description: + * Initialize the selected SPI port + * + * Input Parameters: + * 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 *sam_spibus_initialize(int port) +{ + struct sam_spidev_s *priv; + irqstate_t flags; + uint32_t regval; +#ifdef CONFIG_ARCH_FAMILY_SAML21 + int channel; +#endif +#if 0 /* Not used */ + int ret; +#endif + + /* Get the port state structure */ + + spiinfo("port: %d \n", port); + +#ifdef SAMD5E5_HAVE_SPI0 + if (port == 0) + { + priv = &g_spi0dev; + } + else +#endif + +#ifdef SAMD5E5_HAVE_SPI1 + if (port == 1) + { + priv = &g_spi1dev; + } + else +#endif + +#ifdef SAMD5E5_HAVE_SPI2 + if (port == 2) + { + priv = &g_spi2dev; + } + else +#endif + +#ifdef SAMD5E5_HAVE_SPI3 + if (port == 3) + { + priv = &g_spi3dev; + } + else +#endif + +#ifdef SAMD5E5_HAVE_SPI4 + if (port == 4) + { + priv = &g_spi4dev; + } + else +#endif + +#ifdef SAMD5E5_HAVE_SPI5 + if (port == 5) + { + priv = &g_spi5dev; + } + else +#endif +#ifdef SAMD5E5_HAVE_SPI6 + if (port == 6) + { + priv = &g_spi6dev; + } + else +#endif +#ifdef SAMD5E5_HAVE_SPI7 + if (port == 7) + { + priv = &g_spi7dev; + } + else +#endif + { + spierr("ERROR: Unsupported port: %d\n", port); + return NULL; + } + +#ifdef CONFIG_SAMD5E5SPI_DMA + spi_dma_setup(priv); +#endif + + /* Enable clocking to the SERCOM module in PM */ + + flags = enter_critical_section(); + sercom_enable(priv->sercom); + + /* Configure the GCLKs for the SERCOM module */ + + sercom_coreclk_configure(priv->sercom, priv->coregen, false); + + channel = priv->sercom + GCLK_CHAN_SERCOM0_CORE; + sam_gclk_chan_enable(channel, config->coregen); + + 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 | SPI_CTRLA_FORM_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. + * SPI mode 0 is assumed initially (CPOL=0 and CPHA=0). + */ + + regval &= ~(SPI_CTRLA_DOPO_MASK | SPI_CTRLA_DIPO_MASK); + regval &= ~(SPI_CTRLA_CPHA | SPI_CTRLA_CPOL); + 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, spi_interrupt, priv); + if (ret < 0) + { + spierr("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"); + leave_critical_section(flags); + return (struct spi_dev_s *)priv; +} + +#endif /* SAMD5E5_HAVE_SPI */ diff --git a/arch/arm/src/samd5e5/sam_spi.h b/arch/arm/src/samd5e5/sam_spi.h new file mode 100644 index 0000000000..b1b1838205 --- /dev/null +++ b/arch/arm/src/samd5e5/sam_spi.h @@ -0,0 +1,310 @@ +/**************************************************************************** + * arch/arm/src/samd5e5/sam_spi.h + * + * Copyright (C) 2018Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * 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. + * + ****************************************************************************/ + +#ifndef __ARCH_ARM_SRC_SAMD5E5_SAM_SPI_H +#define __ARCH_ARM_SRC_SAMD5E5_SAM_SPI_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include "chip/sam_spi.h" + +#ifdef SAMD5E5_HAVE_SPI + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/**************************************************************************** + * Inline Functions + ****************************************************************************/ + +#ifndef __ASSEMBLY__ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +struct spi_dev_s; /* Forward reference */ + +/**************************************************************************** + * Name: sam_spibus_initialize + * + * Description: + * Initialize the selected SPI port + * + * Input Parameters: + * 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 *sam_spibus_initialize(int port); + +/**************************************************************************** + * Name: sam_spi[n]select, sam_spi[n]status, and sam_spi[n]cmddata + * + * Description: + * These external functions must be provided by board-specific logic. They + * include: + * + * o sam_spi[n]select is a functions to manage the board-specific chip + * selects + * o sam_spi[n]status and sam_spi[n]cmddata: Implementations of the status + * and cmddata methods of the SPI interface defined by struct spi_ops_ + * (see include/nuttx/spi/spi.h). All other methods including + * sam_spibus_initialize()) are provided by common SAMD/L logic. + * + * Where [n] is the SERCOM number for the SPI module. + * + * To use this common SPI logic on your board: + * + * 1. Provide logic in sam_boardinitialize() to configure SPI chip select + * pins. + * 2. Provide sam_spi[n]select() and sam_spi[n]status() functions in your + * board-specific logic. These functions will perform chip selection + * and status operations using GPIOs in the way your board is configured. + * 2. If CONFIG_SPI_CMDDATA is defined in the NuttX configuration, provide + * sam_spi[n]cmddata() functions in your board-specific logic. This + * function will perform cmd/data selection operations using GPIOs in + * the way your board is configured. + * 3. Add a call to sam_spibus_initialize() in your low level application + * initialization logic + * 4. The handle returned by sam_spibus_initialize() may then be used to bind + * the SPI driver to higher level logic (e.g., calling + * mmcsd_spislotinitialize(), for example, will bind the SPI driver to + * the SPI MMC/SD driver). + * + ****************************************************************************/ + +/**************************************************************************** + * Name: sam_spi[n]select + * + * Description: + * PIO chip select pins may be programmed by the board specific logic in + * one of two different ways. First, the pins may be programmed as SPI + * peripherals. In that case, the pins are completely controlled by the + * SPI driver. This method still needs to be provided, but it may be only + * a stub. + * + * An alternative way to program the PIO chip select pins is as a normal + * GPIO output. In that case, the automatic control of the CS pins is + * bypassed and this function must provide control of the chip select. + * NOTE: In this case, the GPIO output pin does *not* have to be the + * same as the NPCS pin normal associated with the chip select number. + * + * Input Parameters: + * dev - SPI device info + * devid - Identifies the (logical) device + * selected - TRUE:Select the device, FALSE:De-select the device + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef SAMD5E5_HAVE_SPI0 +void sam_spi0select(FAR struct spi_dev_s *dev, uint32_t devid, + bool selected); +#endif + +#ifdef SAMD5E5_HAVE_SPI1 +void sam_spi1select(FAR struct spi_dev_s *dev, uint32_t devid, + bool selected); +#endif + +#ifdef SAMD5E5_HAVE_SPI2 +void sam_spi2select(FAR struct spi_dev_s *dev, uint32_t devid, + bool selected); +#endif + +#ifdef SAMD5E5_HAVE_SPI3 +void sam_spi3select(FAR struct spi_dev_s *dev, uint32_t devid, + bool selected); +#endif + +#ifdef SAMD5E5_HAVE_SPI4 +void sam_spi4select(FAR struct spi_dev_s *dev, uint32_t devid, + bool selected); +#endif + +#ifdef SAMD5E5_HAVE_SPI5 +void sam_spi5select(FAR struct spi_dev_s *dev, uint32_t devid, + bool selected); +#endif + +#ifdef SAMD5E5_HAVE_SPI6 +void sam_spi6select(FAR struct spi_dev_s *dev, uint32_t devid, + bool selected); +#endif + +#ifdef SAMD5E5_HAVE_SPI7 +void sam_spi7select(FAR struct spi_dev_s *dev, uint32_t devid, + bool selected); +#endif + +/**************************************************************************** + * Name: sam_spi[n]status + * + * Description: + * Return status information associated with the SPI device. + * + * Input Parameters: + * dev - SPI device info + * devid - Identifies the (logical) device + * + * Returned Value: + * Bit-encoded SPI status (see include/nuttx/spi/spi.h. + * + ****************************************************************************/ + +#ifdef SAMD5E5_HAVE_SPI0 +uint8_t sam_spi0status(FAR struct spi_dev_s *dev, uint32_t devid); +#endif + +#ifdef SAMD5E5_HAVE_SPI1 +uint8_t sam_spi1status(FAR struct spi_dev_s *dev, uint32_t devid); +#endif + +#ifdef SAMD5E5_HAVE_SPI2 +uint8_t sam_spi2status(FAR struct spi_dev_s *dev, uint32_t devid); +#endif + +#ifdef SAMD5E5_HAVE_SPI3 +uint8_t sam_spi3status(FAR struct spi_dev_s *dev, uint32_t devid); +#endif + +#ifdef SAMD5E5_HAVE_SPI4 +uint8_t sam_spi4status(FAR struct spi_dev_s *dev, uint32_t devid); +#endif + +#ifdef SAMD5E5_HAVE_SPI5 +uint8_t sam_spi5status(FAR struct spi_dev_s *dev, uint32_t devid); +#endif + +#ifdef SAMD5E5_HAVE_SPI6 +uint8_t sam_spi6status(FAR struct spi_dev_s *dev, uint32_t devid); +#endif + +#ifdef SAMD5E5_HAVE_SPI7 +uint8_t sam_spi7status(FAR struct spi_dev_s *dev, uint32_t devid); +#endif + +/**************************************************************************** + * Name: sam_spi[n]cmddata + * + * Description: + * Some SPI devices require an additional control to determine if the SPI + * data being sent is a command or is data. If CONFIG_SPI_CMDDATA then + * this function will be called to different be command and data transfers. + * + * This is often needed, for example, by LCD drivers. Some LCD hardware + * may be configured to use 9-bit data transfers with the 9th bit + * indicating command or data. That same hardware may be configurable, + * instead, to use 8-bit data but to require an additional, board- + * specific GPIO control to distinguish command and data. This function + * would be needed in that latter case. + * + * Input Parameters: + * dev - SPI device info + * devid - Identifies the (logical) device + * + * Returned Value: + * Zero on success; a negated errno on failure. + * + ****************************************************************************/ + +#ifdef CONFIG_SPI_CMDDATA +#ifdef SAMD5E5_HAVE_SPI0 +int sam_spi0cmddata(FAR struct spi_dev_s *dev, uint32_t devid, bool cmd); +#endif + +#ifdef SAMD5E5_HAVE_SPI1 +int sam_spi1cmddata(FAR struct spi_dev_s *dev, uint32_t devid, bool cmd); +#endif + +#ifdef SAMD5E5_HAVE_SPI2 +int sam_spi2cmddata(FAR struct spi_dev_s *dev, uint32_t devid, bool cmd); +#endif + +#ifdef SAMD5E5_HAVE_SPI3 +int sam_spi3cmddata(FAR struct spi_dev_s *dev, uint32_t devid, bool cmd); +#endif + +#ifdef SAMD5E5_HAVE_SPI4 +int sam_spi4cmddata(FAR struct spi_dev_s *dev, uint32_t devid, bool cmd); +#endif + +#ifdef SAMD5E5_HAVE_SPI5 +int sam_spi5cmddata(FAR struct spi_dev_s *dev, uint32_t devid, bool cmd); +#endif + +#ifdef SAMD5E5_HAVE_SPI6 +int sam_spi6cmddata(FAR struct spi_dev_s *dev, uint32_t devid, bool cmd); +#endif + +#ifdef SAMD5E5_HAVE_SPI7 +int sam_spi7cmddata(FAR struct spi_dev_s *dev, uint32_t devid, bool cmd); +#endif +#endif + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __ASSEMBLY__ */ +#endif /* SAMD5E5_HAVE_SPI */ +#endif /* __ARCH_ARM_SRC_SAMD5E5_SAM_SPI_H */