diff --git a/boards/cc2538dk/Makefile.features b/boards/cc2538dk/Makefile.features index 328367ec499111f0f1122e70fdf4ba452ca2d86d..c2f4c92a7961e634345da8bc59907546e2f1df06 100644 --- a/boards/cc2538dk/Makefile.features +++ b/boards/cc2538dk/Makefile.features @@ -3,6 +3,7 @@ FEATURES_PROVIDED += periph_cpuid FEATURES_PROVIDED += periph_gpio FEATURES_PROVIDED += periph_hwrng FEATURES_PROVIDED += periph_i2c +FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart diff --git a/boards/cc2538dk/include/periph_conf.h b/boards/cc2538dk/include/periph_conf.h index 16a706cfd98e6aae0f8dc321a90e5e807d28f232..3cc9ad366fe215b84cbc0666fd13c39272c7ef26 100644 --- a/boards/cc2538dk/include/periph_conf.h +++ b/boards/cc2538dk/include/periph_conf.h @@ -129,6 +129,27 @@ static const i2c_conf_t i2c_config[I2C_NUMOF] = { }; /** @} */ +/** + * @name SPI configuration + * @{ + */ +#define SPI_NUMOF 1 +#define SPI_0_EN 1 + +#ifdef HAVE_PERIPH_SPI_CONF_T +static const periph_spi_conf_t spi_config[SPI_NUMOF] = { + { + .dev = SSI0, + .mosi_pin = GPIO_PA4, + .miso_pin = GPIO_PA5, + .sck_pin = GPIO_PA2, + .cs_pin = GPIO_PD0, + }, +}; +#endif + +/** @} */ + /** * @name GPIO configuration * @{ diff --git a/boards/openmote-cc2538/Makefile.features b/boards/openmote-cc2538/Makefile.features index b6dc26a6f027ba7f620aec73d9fa8a0dba032bf3..819901ddc85175a1a5edc80a62d2cd05fcebb2ec 100644 --- a/boards/openmote-cc2538/Makefile.features +++ b/boards/openmote-cc2538/Makefile.features @@ -3,6 +3,7 @@ FEATURES_PROVIDED += periph_cpuid FEATURES_PROVIDED += periph_gpio FEATURES_PROVIDED += periph_hwrng FEATURES_PROVIDED += periph_i2c +FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart diff --git a/boards/openmote-cc2538/include/periph_conf.h b/boards/openmote-cc2538/include/periph_conf.h index f7c7afa0c708560cecb9f8d2fe01894ddfefd432..a2d490908f7f82d1ce4a2c30f180ec8530d6b313 100644 --- a/boards/openmote-cc2538/include/periph_conf.h +++ b/boards/openmote-cc2538/include/periph_conf.h @@ -121,6 +121,25 @@ static const i2c_conf_t i2c_config[I2C_NUMOF] = { }; /** @} */ +/** + * @name SPI configuration + * @{ + */ +#define SPI_NUMOF 1 +#define SPI_0_EN 1 + +static const periph_spi_conf_t spi_config[SPI_NUMOF] = { + { + .dev = SSI0, + .mosi_pin = GPIO_PA5, + .miso_pin = GPIO_PA4, + .sck_pin = GPIO_PA2, + .cs_pin = GPIO_PA3, + }, +}; + +/** @} */ + /** * @name GPIO configuration * @{ diff --git a/boards/remote/Makefile.features b/boards/remote/Makefile.features index b6dc26a6f027ba7f620aec73d9fa8a0dba032bf3..819901ddc85175a1a5edc80a62d2cd05fcebb2ec 100644 --- a/boards/remote/Makefile.features +++ b/boards/remote/Makefile.features @@ -3,6 +3,7 @@ FEATURES_PROVIDED += periph_cpuid FEATURES_PROVIDED += periph_gpio FEATURES_PROVIDED += periph_hwrng FEATURES_PROVIDED += periph_i2c +FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart diff --git a/boards/remote/include/periph_conf.h b/boards/remote/include/periph_conf.h index ece54877c19708ce048bf2bafb25487a816c33af..7323b0ec384c1aa4373d81dc842e25671cbe4f38 100644 --- a/boards/remote/include/periph_conf.h +++ b/boards/remote/include/periph_conf.h @@ -124,6 +124,34 @@ static const i2c_conf_t i2c_config[I2C_NUMOF] = { }; /** @} */ +/** + * @name SPI configuration + * @{ + */ +#define SPI_NUMOF 2 +#define SPI_0_EN 1 +#define SPI_1_EN 1 + +#ifdef HAVE_PERIPH_SPI_CONF_T +static const periph_spi_conf_t spi_config[SPI_NUMOF] = { + { + .dev = SSI0, + .mosi_pin = GPIO_PD0, + .miso_pin = GPIO_PC4, + .sck_pin = GPIO_PD1, + .cs_pin = GPIO_PD3, + }, + { + .dev = SSI1, + .mosi_pin = GPIO_PC7, + .miso_pin = GPIO_PA4, + .sck_pin = GPIO_PB5, + }, +}; +#endif + +/** @} */ + /** * @name GPIO configuration * @{ diff --git a/cpu/cc2538/Makefile.include b/cpu/cc2538/Makefile.include index 507f47d21b0db8d52986d51ce0588500b83a8966..3c829bccf014286dc40a209526ea9e9c8058474d 100644 --- a/cpu/cc2538/Makefile.include +++ b/cpu/cc2538/Makefile.include @@ -1,3 +1,6 @@ export CPU_ARCH := cortex-m3 +# include common SPI functions +USEMODULE += periph_common + include $(RIOTCPU)/Makefile.include.cortexm_common diff --git a/cpu/cc2538/include/cc2538_ssi.h b/cpu/cc2538/include/cc2538_ssi.h new file mode 100644 index 0000000000000000000000000000000000000000..2dc08cff02e43c385797cdfa5c182c624ee2621a --- /dev/null +++ b/cpu/cc2538/include/cc2538_ssi.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2014 Loci Controls Inc. + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @addtogroup cpu_cc2538 + * @{ + * + * @file + * @brief CC2538 SSI interface + * + * @author Ian Martin <ian@locicontrols.com> + */ + +#ifndef CC2538_SSI_H +#define CC2538_SSI_H + +#include "cc2538.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief SSI component registers + */ +typedef struct { + union { + cc2538_reg_t CR0; /**< SSI Control Register 0 */ + struct { + cc2538_reg_t DSS : 4; /**< SSI data size select */ + cc2538_reg_t FRF : 2; /**< SSI frame format select */ + cc2538_reg_t SPO : 1; /**< SSI serial clock polarity */ + cc2538_reg_t SPH : 1; /**< SSI serial clock phase */ + cc2538_reg_t SCR : 8; /**< SSI serial clock rate */ + cc2538_reg_t RESERVED : 16; /**< Reserved bits */ + } CR0bits; + }; + + union { + cc2538_reg_t CR1; /**< SSI Control Register 1 */ + struct { + cc2538_reg_t LBM : 1; /**< SSI loop-back mode */ + cc2538_reg_t SSE : 1; /**< SSI synchronous serial port enable */ + cc2538_reg_t MS : 1; /**< SSI master and slave select */ + cc2538_reg_t SOD : 1; /**< SSI slave mode output disable */ + cc2538_reg_t RESERVED : 28; /**< Reserved bits */ + } CR1bits; + }; + + cc2538_reg_t DR; /**< SSI Data register */ + + union { + cc2538_reg_t SR; /**< SSI FIFO/busy Status Register */ + struct { + cc2538_reg_t TFE : 1; /**< SSI transmit FIFO empty */ + cc2538_reg_t TNF : 1; /**< SSI transmit FIFO not full */ + cc2538_reg_t RNE : 1; /**< SSI receive FIFO not empty */ + cc2538_reg_t RFF : 1; /**< SSI receive FIFO full */ + cc2538_reg_t BSY : 1; /**< SSI busy bit */ + cc2538_reg_t RESERVED : 27; /**< Reserved bits */ + } SRbits; + }; + cc2538_reg_t CPSR; /**< SSI Clock Register */ + cc2538_reg_t IM; /**< SSI Interrupt Mask register */ + cc2538_reg_t RIS; /**< SSI Raw Interrupt Status register */ + cc2538_reg_t MIS; /**< SSI Masked Interrupt Status register */ + cc2538_reg_t ICR; /**< SSI Interrupt Clear Register */ + cc2538_reg_t DMACTL; /**< SSI uDMA Control Register. */ + cc2538_reg_t CC; /**< SSI clock configuration */ +} cc2538_ssi_t; + +#define SSI0 ( (cc2538_ssi_t*)0x40008000 ) /**< SSI0 Instance */ +#define SSI1 ( (cc2538_ssi_t*)0x40009000 ) /**< SSI1 Instance */ + +#ifdef __cplusplus +} /* end extern "C" */ +#endif + +#endif /* CC2538_SSI_H */ +/** @} */ diff --git a/cpu/cc2538/include/periph_cpu.h b/cpu/cc2538/include/periph_cpu.h index 6a108feab0819622e1ec7b9d32b321ca47d81095..6532429b307780341764ee5c2955266d5771e1a2 100644 --- a/cpu/cc2538/include/periph_cpu.h +++ b/cpu/cc2538/include/periph_cpu.h @@ -21,6 +21,8 @@ #include <stdint.h> +#include "cc2538_ssi.h" + #ifdef __cplusplus extern "C" { #endif @@ -43,11 +45,31 @@ typedef uint32_t gpio_t; */ #define GPIO_UNDEF (0xffffffff) +/** + * @brief declare needed generic SPI functions + * @{ + */ +#define PERIPH_SPI_NEEDS_TRANSFER_REG +#define PERIPH_SPI_NEEDS_TRANSFER_REGS +/** @} */ + typedef struct { gpio_t scl_pin; /**< pin used for SCL */ gpio_t sda_pin; /**< pin used for SDA */ } i2c_conf_t; +/** + * @brief SPI configuration data structure + */ +#define HAVE_PERIPH_SPI_CONF_T +typedef struct { + cc2538_ssi_t *dev; /**< SSI device */ + gpio_t mosi_pin; /**< pin used for MOSI */ + gpio_t miso_pin; /**< pin used for MISO */ + gpio_t sck_pin; /**< pin used for SCK */ + gpio_t cs_pin; /**< pin used for CS */ +} periph_spi_conf_t; + #ifdef __cplusplus } #endif diff --git a/cpu/cc2538/periph/spi.c b/cpu/cc2538/periph/spi.c new file mode 100644 index 0000000000000000000000000000000000000000..3656df7793e9cb838bf7081c92767a0bc12afa2f --- /dev/null +++ b/cpu/cc2538/periph/spi.c @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2015 Loci Controls Inc. + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @addtogroup driver_periph + * @{ + * + * @file + * @brief Low-level SPI driver implementation + * + * @author Ian Martin <ian@locicontrols.com> + * + * @} + */ + +#include <assert.h> +#include <stdio.h> + +#include "cc2538_ssi.h" + +#include "cpu.h" +#include "mutex.h" +#include "periph/spi.h" +#include "periph_conf.h" +#include "thread.h" +#include "sched.h" + +/* guard file in case no SPI device is defined */ +#if SPI_NUMOF + +/* clock sources for the SSI_CC register */ +#define CS_SYS_DIV 0 +#define CS_IO_DIV 1 + +#define SSI0_MASK (1 << 0) +#define SSI1_MASK (1 << 1) + +#ifndef SPI_DATA_BITS_NUMOF +#define SPI_DATA_BITS_NUMOF 8 +#endif + +#define spin_until(condition) while (!(condition)) thread_yield() + +/** + * @brief Array holding one pre-initialized mutex for each SPI device + */ +static mutex_t locks[SPI_NUMOF] = {MUTEX_INIT}; + +int spi_init_master(spi_t dev, spi_conf_t conf, spi_speed_t speed) +{ + cc2538_ssi_t* ssi = spi_config[dev].dev; + + if (dev >= SPI_NUMOF) { + return -1; + } + + /* power on the SPI device */ + spi_poweron(dev); + + /* configure SCK, MISO and MOSI pin */ + spi_conf_pins(dev); + + /* Disable the SSI and configure it for SPI master mode */ + ssi->CR1 = 0; + + /* 3. Configure the SSI clock source */ + ssi->CC = CS_SYS_DIV; + + /* 4. Configure the clock prescale divisor by writing the SSI_CPSR register. + * frequency of the SSIClk is defined by: SSIClk = SysClk / (CPSDVSR x (1 + SCR)) + */ + + const int32_t speed_lut[] = { + [SPI_SPEED_100KHZ] = 100000 /* Hz */, + [SPI_SPEED_400KHZ] = 400000 /* Hz */, + [SPI_SPEED_1MHZ ] = 1000000 /* Hz */, + [SPI_SPEED_5MHZ ] = 5000000 /* Hz */, + [SPI_SPEED_10MHZ ] = 10000000 /* Hz */, + }; + + int32_t SysClk = sys_clock_freq(); + int32_t f_desired = speed_lut[speed]; + int32_t f_actual; + int32_t err; + int32_t best_err = INT32_MAX; + int32_t div1; + int32_t div2; + int32_t best_div1 = 2; + int32_t best_div2 = 1; + + /* System clock is first divided by CPSDVSR, then by SCR */ + for (div1 = 2; div1 <= 254; div1++) { + div2 = SysClk; + int32_t denom = div1 * f_desired; + div2 += denom / 2; + div2 /= denom; + + if (div2 < 1) { + div2 = 1; + } + else if (div2 > 256) { + div2 = 256; + } + + f_actual = SysClk / (div1 * div2); + err = f_actual - f_desired; + if (err < 0) { + err = -err; + } + if (err <= best_err) { + best_div1 = div1; + best_div2 = div2; + best_err = err; + } + } + + ssi->CPSR = best_div1; /* CPSDVSR */ + ssi->CR0bits.SCR = best_div2 - 1; /* Serial clock rate (SCR) */ + + switch (conf) { + case SPI_CONF_FIRST_RISING: + ssi->CR0bits.SPO = 0; + ssi->CR0bits.SPH = 0; + break; + + case SPI_CONF_SECOND_RISING: + ssi->CR0bits.SPO = 0; + ssi->CR0bits.SPH = 1; + break; + + case SPI_CONF_FIRST_FALLING: + ssi->CR0bits.SPO = 1; + ssi->CR0bits.SPH = 0; + break; + + case SPI_CONF_SECOND_FALLING: + ssi->CR0bits.SPO = 1; + ssi->CR0bits.SPH = 1; + break; + } + + ssi->CR0bits.FRF = 0; /* SPI protocol mode */ + ssi->CR0bits.DSS = SPI_DATA_BITS_NUMOF - 1; /* The data size */ + + ssi->CR1bits.SSE = 1; + + return 0; +} + +int spi_init_slave(spi_t dev, spi_conf_t conf, char(*cb)(char data)) +{ + /* slave mode is not (yet) supported */ + return -1; +} + +int spi_conf_pins(spi_t dev) +{ + if (dev >= SPI_NUMOF) { + return -1; + } + + switch ((uintptr_t)spi_config[dev].dev) { + case (uintptr_t)SSI0: + IOC_PXX_SEL[spi_config[dev].mosi_pin] = SSI0_TXD; + IOC_PXX_SEL[spi_config[dev].sck_pin ] = SSI0_CLKOUT; + IOC_PXX_SEL[spi_config[dev].cs_pin ] = SSI0_FSSOUT; + + IOC_SSIRXD_SSI0 = spi_config[dev].miso_pin; + break; + + case (uintptr_t)SSI1: + IOC_PXX_SEL[spi_config[dev].mosi_pin] = SSI1_TXD; + IOC_PXX_SEL[spi_config[dev].sck_pin ] = SSI1_CLKOUT; + IOC_PXX_SEL[spi_config[dev].cs_pin ] = SSI1_FSSOUT; + + IOC_SSIRXD_SSI1 = spi_config[dev].miso_pin; + break; + } + + IOC_PXX_OVER[spi_config[dev].mosi_pin] = IOC_OVERRIDE_OE; + IOC_PXX_OVER[spi_config[dev].sck_pin ] = IOC_OVERRIDE_OE; + IOC_PXX_OVER[spi_config[dev].cs_pin ] = IOC_OVERRIDE_OE; + IOC_PXX_OVER[spi_config[dev].miso_pin] = IOC_OVERRIDE_DIS; + + gpio_hardware_control(spi_config[dev].mosi_pin); + gpio_hardware_control(spi_config[dev].miso_pin); + gpio_hardware_control(spi_config[dev].sck_pin); + gpio_hardware_control(spi_config[dev].cs_pin); + + return 0; +} + +int spi_acquire(spi_t dev) +{ + if (dev >= SPI_NUMOF) { + return -1; + } + mutex_lock(&locks[dev]); + return 0; +} + +int spi_release(spi_t dev) +{ + if (dev >= SPI_NUMOF) { + return -1; + } + mutex_unlock(&locks[dev]); + return 0; +} + +static char ssi_flush_input(cc2538_ssi_t *ssi) +{ + char tmp; + + while (ssi->SRbits.RNE) { + tmp = ssi->DR; + } + + return tmp; +} + +int spi_transfer_byte(spi_t dev, char out, char *in) +{ + cc2538_ssi_t* ssi = spi_config[dev].dev; + char tmp; + + ssi_flush_input(ssi); + + /* transmit byte */ + spin_until(ssi->SRbits.TNF); + ssi->DR = out; + + /* receive byte */ + spin_until(ssi->SRbits.RNE); + tmp = ssi->DR; + + if (in) { + *in = tmp; + } + + return 1; +} + +int spi_transfer_bytes(spi_t dev, char *out, char *in, unsigned int length) +{ + cc2538_ssi_t* ssi = spi_config[dev].dev; + typeof(length) tx_n = 0, rx_n = 0; + + if (dev >= SPI_NUMOF) { + return -1; + } + + ssi_flush_input(ssi); + + /* transmit and receive bytes */ + while (tx_n < length) { + spin_until(ssi->SRbits.TNF || ssi->SRbits.RNE); + + if (ssi->SRbits.TNF) { + ssi->DR = out[tx_n]; + tx_n++; + } + else if (ssi->SRbits.RNE) { + assert(rx_n < length); + in[rx_n] = ssi->DR; + rx_n++; + } + } + + /* receive remaining bytes */ + while (rx_n < length) { + spin_until(ssi->SRbits.RNE); + assert(rx_n < length); + in[rx_n] = ssi->DR; + rx_n++; + } + + return rx_n; +} + +void spi_transmission_begin(spi_t dev, char reset_val) +{ + /* slave mode is not (yet) supported */ +} + +void spi_poweron(spi_t dev) +{ + switch ((uintptr_t)spi_config[dev].dev) { + case (uintptr_t)SSI0: + /* enable SSI0 in all three power modes */ + SYS_CTRL_RCGCSSI |= SSI0_MASK; + SYS_CTRL_SCGCSSI |= SSI0_MASK; + SYS_CTRL_DCGCSSI |= SSI0_MASK; + break; + + case (uintptr_t)SSI1: + /* enable SSI1 in all three power modes */ + SYS_CTRL_RCGCSSI |= SSI1_MASK; + SYS_CTRL_SCGCSSI |= SSI1_MASK; + SYS_CTRL_DCGCSSI |= SSI1_MASK; + break; + } +} + +void spi_poweroff(spi_t dev) +{ + switch ((uintptr_t)spi_config[dev].dev) { + case (uintptr_t)SSI0: + /* disable SSI0 in all three power modes */ + SYS_CTRL_RCGCSSI &= ~SSI0_MASK; + SYS_CTRL_SCGCSSI &= ~SSI0_MASK; + SYS_CTRL_DCGCSSI &= ~SSI0_MASK; + break; + + case (uintptr_t)SSI1: + /* disable SSI1 in all three power modes */ + SYS_CTRL_RCGCSSI &= ~SSI1_MASK; + SYS_CTRL_SCGCSSI &= ~SSI1_MASK; + SYS_CTRL_DCGCSSI &= ~SSI1_MASK; + break; + } +} + +#endif /* SPI_NUMOF */