cpu/cc2538: add periph/spi driver

pr/gpio
Ian Martin 8 years ago
parent 34d2940afb
commit a2ac92b2bd

@ -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

@ -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
* @{

@ -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

@ -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
* @{

@ -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

@ -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
* @{

@ -1,3 +1,6 @@
export CPU_ARCH := cortex-m3
# include common SPI functions
USEMODULE += periph_common
include $(RIOTCPU)/Makefile.include.cortexm_common

@ -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 */
/** @} */

@ -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

@ -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 */
Loading…
Cancel
Save