mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
cpu/rpx0xx: add spi functionality
This commit is contained in:
parent
7213c0ad3e
commit
6353181c74
@ -731,6 +731,39 @@ void rosc_stop(void);
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @brief Override SPI clock speed values
|
||||
* @{
|
||||
*/
|
||||
#define HAVE_SPI_CLK_T
|
||||
enum {
|
||||
SPI_CLK_100KHZ = KHZ(100), /**< drive the SPI bus with 100KHz */
|
||||
SPI_CLK_400KHZ = KHZ(400), /**< drive the SPI bus with 400KHz */
|
||||
SPI_CLK_1MHZ = MHZ(1), /**< drive the SPI bus with 1MHz */
|
||||
SPI_CLK_5MHZ = MHZ(5), /**< drive the SPI bus with 5MHz */
|
||||
SPI_CLK_10MHZ = MHZ(10), /**< drive the SPI bus with 10MHz */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief SPI clock type
|
||||
*/
|
||||
typedef uint32_t spi_clk_t;
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @brief Configuration details for an SPI interface needed by the RPX0XX peripheral
|
||||
*/
|
||||
typedef struct {
|
||||
SPI0_Type *dev; /**< Base address of the I/O registers of the device */
|
||||
gpio_t miso_pin; /**< GPIO pin to use for MISO */
|
||||
gpio_t mosi_pin; /**< GPIO pin to use for MOSI */
|
||||
gpio_t clk_pin; /**< GPIO pin to use for CLK */
|
||||
} spi_conf_t;
|
||||
|
||||
#define PERIPH_SPI_NEEDS_TRANSFER_REG
|
||||
#define PERIPH_SPI_NEEDS_TRANSFER_REGS
|
||||
#define PERIPH_SPI_NEEDS_TRANSFER_BYTE
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
404
cpu/rpx0xx/periph/spi.c
Normal file
404
cpu/rpx0xx/periph/spi.c
Normal file
@ -0,0 +1,404 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Frank Engelhardt
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup cpu_rpx0xx
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Implementation of SPI.
|
||||
*
|
||||
* @author Frank Engelhardt <frank@f9e.de>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include "assert.h"
|
||||
#include "bitarithm.h"
|
||||
#include "cpu.h"
|
||||
#include "mutex.h"
|
||||
#include "periph/gpio.h"
|
||||
#include "periph/spi.h"
|
||||
|
||||
#define ENABLE_DEBUG 0
|
||||
#include "debug.h"
|
||||
|
||||
/**
|
||||
* @brief Allocate one lock per SPI device.
|
||||
*/
|
||||
static mutex_t locks[SPI_NUMOF];
|
||||
|
||||
/**
|
||||
* @brief Save the clock prescaler values for faster bus acquisition.
|
||||
*/
|
||||
typedef struct {
|
||||
spi_clk_t clk;
|
||||
uint8_t cpsdvsr;
|
||||
uint8_t scr;
|
||||
} _pl022_clk_t;
|
||||
static _pl022_clk_t pl022_clk[SPI_NUMOF];
|
||||
|
||||
gpio_t spi_pin_clk(spi_t spi)
|
||||
{
|
||||
assert((unsigned)spi < SPI_NUMOF);
|
||||
return spi_config[spi].clk_pin;
|
||||
}
|
||||
|
||||
gpio_t spi_pin_miso(spi_t spi)
|
||||
{
|
||||
assert((unsigned)spi < SPI_NUMOF);
|
||||
return spi_config[spi].miso_pin;
|
||||
}
|
||||
|
||||
gpio_t spi_pin_mosi(spi_t spi)
|
||||
{
|
||||
assert((unsigned)spi < SPI_NUMOF);
|
||||
return spi_config[spi].mosi_pin;
|
||||
}
|
||||
|
||||
static void _poweron(spi_t spi)
|
||||
{
|
||||
uint32_t reset_bit_mask = (spi_config[spi].dev == SPI0)
|
||||
? RESETS_RESET_spi0_Msk
|
||||
: RESETS_RESET_spi1_Msk;
|
||||
|
||||
periph_reset(reset_bit_mask);
|
||||
periph_reset_done(reset_bit_mask);
|
||||
}
|
||||
|
||||
static void _poweroff(spi_t spi)
|
||||
{
|
||||
uint32_t reset_bit_mask = (spi_config[spi].dev == SPI0)
|
||||
? RESETS_RESET_spi0_Msk
|
||||
: RESETS_RESET_spi1_Msk;
|
||||
|
||||
periph_reset(reset_bit_mask);
|
||||
}
|
||||
|
||||
void spi_init(spi_t spi)
|
||||
{
|
||||
DEBUG("[rpx0xx] Call spi_init(spi=%" PRIdFAST8 ")", spi);
|
||||
|
||||
assert((unsigned)spi < SPI_NUMOF);
|
||||
|
||||
/* initialize device lock */
|
||||
mutex_init(&locks[spi]);
|
||||
/* trigger pin initialization */
|
||||
spi_init_pins(spi);
|
||||
/* clock prescaler values must be calculated */
|
||||
pl022_clk[spi].clk = 0xff;
|
||||
}
|
||||
|
||||
void spi_init_pins(spi_t spi)
|
||||
{
|
||||
DEBUG("[rpx0xx] Call spi_init_pins(spi=%" PRIdFAST8 ")", spi);
|
||||
|
||||
assert((unsigned)spi < SPI_NUMOF);
|
||||
|
||||
const gpio_pad_ctrl_t mosi_pad_config = {
|
||||
/* SPI should typically draw less than 2 mA */
|
||||
.drive_strength = DRIVE_STRENGTH_2MA,
|
||||
};
|
||||
const gpio_io_ctrl_t mosi_io_config = {
|
||||
.function_select = FUNCTION_SELECT_SPI,
|
||||
};
|
||||
const gpio_pad_ctrl_t clk_pad_config = {
|
||||
/* SPI should typically draw less than 2 mA */
|
||||
.drive_strength = DRIVE_STRENGTH_2MA,
|
||||
};
|
||||
const gpio_io_ctrl_t clk_io_config = {
|
||||
.function_select = FUNCTION_SELECT_SPI,
|
||||
};
|
||||
const gpio_pad_ctrl_t miso_pad_config = {
|
||||
.input_enable = 1,
|
||||
.schmitt_trig_enable = 1,
|
||||
};
|
||||
const gpio_io_ctrl_t miso_io_config = {
|
||||
.function_select = FUNCTION_SELECT_SPI,
|
||||
};
|
||||
|
||||
if (gpio_is_valid(spi_config[spi].mosi_pin)) {
|
||||
gpio_set_pad_config(spi_config[spi].mosi_pin, mosi_pad_config);
|
||||
gpio_set_io_config(spi_config[spi].mosi_pin, mosi_io_config);
|
||||
}
|
||||
if (gpio_is_valid(spi_config[spi].clk_pin)) {
|
||||
gpio_set_pad_config(spi_config[spi].clk_pin, clk_pad_config);
|
||||
gpio_set_io_config(spi_config[spi].clk_pin, clk_io_config);
|
||||
}
|
||||
if (gpio_is_valid(spi_config[spi].miso_pin)) {
|
||||
gpio_set_pad_config(spi_config[spi].miso_pin, miso_pad_config);
|
||||
gpio_set_io_config(spi_config[spi].miso_pin, miso_io_config);
|
||||
}
|
||||
|
||||
/* allow access to the bus */
|
||||
mutex_unlock(&locks[spi]);
|
||||
}
|
||||
|
||||
void spi_deinit_pins(spi_t spi)
|
||||
{
|
||||
DEBUG("[rpx0xx] Call spi_deinit_pins(spi=%" PRIdFAST8 ")\n", spi);
|
||||
|
||||
/* lock mutex to block usage of the SPI bus */
|
||||
mutex_lock(&locks[spi]);
|
||||
|
||||
assert((unsigned)spi < SPI_NUMOF);
|
||||
|
||||
if (gpio_is_valid(spi_config[spi].mosi_pin)) {
|
||||
gpio_reset_all_config(spi_config[spi].mosi_pin);
|
||||
}
|
||||
|
||||
if (gpio_is_valid(spi_config[spi].miso_pin)) {
|
||||
gpio_reset_all_config(spi_config[spi].miso_pin);
|
||||
}
|
||||
|
||||
if (gpio_is_valid(spi_config[spi].clk_pin)) {
|
||||
gpio_reset_all_config(spi_config[spi].clk_pin);
|
||||
}
|
||||
}
|
||||
|
||||
int spi_init_cs(spi_t spi, spi_cs_t cs)
|
||||
{
|
||||
DEBUG("[rpx0xx] Call spi_init_cs(spi=%" PRIdFAST8 ", cs=%" PRIu32 ")\n",
|
||||
spi, cs);
|
||||
|
||||
if (spi >= SPI_NUMOF) {
|
||||
return SPI_NODEV;
|
||||
}
|
||||
if (!gpio_is_valid(cs)) {
|
||||
return SPI_NOCS;
|
||||
}
|
||||
|
||||
gpio_init((gpio_t)cs, GPIO_OUT);
|
||||
gpio_set((gpio_t)cs);
|
||||
|
||||
return SPI_OK;
|
||||
}
|
||||
|
||||
int spi_deinit_cs(spi_t spi, spi_cs_t cs)
|
||||
{
|
||||
DEBUG("[rpx0xx] Call spi_deinit_cs(spi=%" PRIdFAST8 ", cs=%" PRIu32 ")\n",
|
||||
spi, cs);
|
||||
|
||||
if (spi >= SPI_NUMOF) {
|
||||
return SPI_NODEV;
|
||||
}
|
||||
if (!gpio_is_valid(cs)) {
|
||||
return SPI_NOCS;
|
||||
}
|
||||
|
||||
gpio_reset_all_config((gpio_t)cs);
|
||||
|
||||
return SPI_OK;
|
||||
}
|
||||
|
||||
static inline void _check_best_clk_result(uint16_t target_dvsr,
|
||||
uint16_t cur_dvsr,
|
||||
uint16_t scr,
|
||||
uint8_t cpsdvsr,
|
||||
uint16_t *best_diff,
|
||||
uint16_t *best_scr,
|
||||
uint8_t *best_cpsdvsr)
|
||||
{
|
||||
uint16_t diff = (target_dvsr > cur_dvsr)
|
||||
? target_dvsr - cur_dvsr
|
||||
: cur_dvsr - target_dvsr;
|
||||
|
||||
if (diff < *best_diff) {
|
||||
*best_diff = diff;
|
||||
*best_cpsdvsr = cpsdvsr;
|
||||
*best_scr = scr;
|
||||
}
|
||||
}
|
||||
|
||||
static void _calc_pl022_clk(_pl022_clk_t *pl022_clk, spi_clk_t clk)
|
||||
{
|
||||
uint16_t dvsr = CLOCK_PERIPH / clk;
|
||||
/* The divisor must be split into two 8-bit divisors, cpsdvsr and scr,
|
||||
* dvsr = cpsdvsr*scr. cpsdvsr must be an even number greater than 0. */
|
||||
uint8_t cpsdvsr = 2, best_cpsdvsr = 2;
|
||||
uint16_t scr = 256, best_scr = 256;
|
||||
uint16_t best_diff = dvsr;
|
||||
uint16_t cur_dvsr = scr * cpsdvsr;
|
||||
|
||||
/* Come from above with cpsdvsr and from below with scr.
|
||||
* Check all intermediate combinations and use the best. */
|
||||
_check_best_clk_result(dvsr, cur_dvsr, scr, cpsdvsr, &best_diff,
|
||||
&best_scr, &best_cpsdvsr);
|
||||
while (scr > 0 && cpsdvsr < 254 && best_diff != 0) {
|
||||
while (dvsr > cur_dvsr && cpsdvsr < 254) {
|
||||
cpsdvsr += 2;
|
||||
cur_dvsr += 2 * scr;
|
||||
_check_best_clk_result(dvsr, cur_dvsr, scr, cpsdvsr, &best_diff,
|
||||
&best_scr, &best_cpsdvsr);
|
||||
}
|
||||
while (dvsr < cur_dvsr && scr > 0) {
|
||||
scr -= 1;
|
||||
cur_dvsr -= cpsdvsr;
|
||||
_check_best_clk_result(dvsr, cur_dvsr, scr, cpsdvsr, &best_diff,
|
||||
&best_scr, &best_cpsdvsr);
|
||||
}
|
||||
}
|
||||
uint32_t resulting_clk_hz = CLOCK_PERIPH / (best_cpsdvsr * best_scr);
|
||||
|
||||
pl022_clk->clk = clk;
|
||||
pl022_clk->cpsdvsr = best_cpsdvsr;
|
||||
/* For scr, +1 is added internally. */
|
||||
pl022_clk->scr = (best_scr - 1);
|
||||
DEBUG("[rpx0xx] Values for spi clock divider registers CPSDVSR=%" PRIu16
|
||||
", SCR=%" PRIu16 ". Resulting clock is %" PRIu32 " Hz.\n",
|
||||
best_cpsdvsr, best_scr - 1, resulting_clk_hz);
|
||||
}
|
||||
|
||||
void spi_acquire(spi_t spi, spi_cs_t cs, spi_mode_t mode, spi_clk_t clk)
|
||||
{
|
||||
DEBUG("[rpx0xx] Call spi_acquire(spi=%" PRIuFAST8 ", cs=%" PRIu32
|
||||
", mode=%" PRIu16 ", clk=%" PRIu32 ")\n", spi, cs, mode, clk);
|
||||
|
||||
assert((unsigned)spi < SPI_NUMOF);
|
||||
assert(clk == SPI_CLK_100KHZ || clk == SPI_CLK_10MHZ || clk == SPI_CLK_1MHZ
|
||||
|| clk == SPI_CLK_400KHZ || clk == SPI_CLK_5MHZ);
|
||||
(void)cs;
|
||||
|
||||
/* lock bus */
|
||||
mutex_lock(&locks[spi]);
|
||||
|
||||
_poweron(spi);
|
||||
|
||||
SPI0_Type *dev = spi_config[spi].dev;
|
||||
|
||||
io_reg_write_dont_corrupt(&dev->SSPCR0,
|
||||
0x7 << SPI0_SSPCR0_DSS_Pos,
|
||||
SPI0_SSPCR0_DSS_Msk);
|
||||
/* uncomment the following line for debug loopback mode */
|
||||
/* io_reg_atomic_set(&dev->SSPCR1, SPI0_SSPCR1_LBM_Msk); */
|
||||
|
||||
/* set SPI mode */
|
||||
switch (mode) {
|
||||
case SPI_MODE_0:
|
||||
io_reg_atomic_clear(&dev->SSPCR0, SPI0_SSPCR0_SPO_Msk);
|
||||
io_reg_atomic_clear(&dev->SSPCR0, SPI0_SSPCR0_SPH_Msk);
|
||||
break;
|
||||
case SPI_MODE_1:
|
||||
io_reg_atomic_clear(&dev->SSPCR0, SPI0_SSPCR0_SPO_Msk);
|
||||
io_reg_atomic_set(&dev->SSPCR0, SPI0_SSPCR0_SPH_Msk);
|
||||
break;
|
||||
case SPI_MODE_2:
|
||||
io_reg_atomic_set(&dev->SSPCR0, SPI0_SSPCR0_SPO_Msk);
|
||||
io_reg_atomic_clear(&dev->SSPCR0, SPI0_SSPCR0_SPH_Msk);
|
||||
break;
|
||||
case SPI_MODE_3:
|
||||
io_reg_atomic_set(&dev->SSPCR0, SPI0_SSPCR0_SPO_Msk);
|
||||
io_reg_atomic_set(&dev->SSPCR0, SPI0_SSPCR0_SPH_Msk);
|
||||
break;
|
||||
}
|
||||
|
||||
/* set clock speed */
|
||||
if (clk != pl022_clk[spi].clk) {
|
||||
_calc_pl022_clk(&pl022_clk[spi], clk);
|
||||
}
|
||||
io_reg_write_dont_corrupt(&dev->SSPCPSR,
|
||||
pl022_clk[spi].cpsdvsr << SPI0_SSPCPSR_CPSDVSR_Pos,
|
||||
SPI0_SSPCPSR_CPSDVSR_Msk);
|
||||
io_reg_write_dont_corrupt(&dev->SSPCR0,
|
||||
pl022_clk[spi].scr << SPI0_SSPCR0_SCR_Pos,
|
||||
SPI0_SSPCR0_SCR_Msk);
|
||||
|
||||
/* enable SPI */
|
||||
io_reg_atomic_set(&dev->SSPCR1, SPI0_SSPCR1_SSE_Msk);
|
||||
}
|
||||
|
||||
void spi_release(spi_t spi)
|
||||
{
|
||||
DEBUG("[rpx0xx] Call spi_release(spi=%" PRIdFAST8 ")\n", spi);
|
||||
|
||||
assert((unsigned)spi < SPI_NUMOF);
|
||||
|
||||
SPI0_Type *dev = spi_config[spi].dev;
|
||||
|
||||
/* disable SPI */
|
||||
io_reg_atomic_clear(&dev->SSPCR1, SPI0_SSPCR1_SSE_Msk);
|
||||
|
||||
_poweroff(spi);
|
||||
|
||||
mutex_unlock(&locks[spi]);
|
||||
}
|
||||
|
||||
static inline void _wait_for_end(spi_t spi)
|
||||
{
|
||||
DEBUG("[rpx0xx] Call _wait_for_end(spi=%" PRIdFAST8 ")\n", spi);
|
||||
SPI0_Type *dev = spi_config[spi].dev;
|
||||
|
||||
while (!(dev->SSPSR & SPI0_SSPSR_TFE_Msk)) {}
|
||||
while (dev->SSPSR & SPI0_SSPSR_BSY_Msk) {}
|
||||
}
|
||||
|
||||
static void _transfer_no_dma(spi_t spi, const void *out, void *in, size_t len)
|
||||
{
|
||||
DEBUG("[rpx0xx] Call _transfer_no_dma(spi=%" PRIdFAST8
|
||||
", out=%p, in=%p, len=%" PRIu16 ")\n",
|
||||
spi, out, in, len);
|
||||
|
||||
const uint8_t *outbuf = out;
|
||||
uint8_t *inbuf = in;
|
||||
SPI0_Type *dev = spi_config[spi].dev;
|
||||
|
||||
/* transfer data, use shortpath if only sending data */
|
||||
if (!inbuf) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
while (!(dev->SSPSR & SPI0_SSPSR_TFE_Msk)) {}
|
||||
dev->SSPDR = outbuf[i];
|
||||
}
|
||||
/* wait until everything is finished and empty the receive buffer */
|
||||
while (!(dev->SSPSR & SPI0_SSPSR_TFE_Msk)) {}
|
||||
while (dev->SSPSR & SPI0_SSPSR_BSY_Msk) {}
|
||||
while (dev->SSPSR & SPI0_SSPSR_RNE_Msk) {
|
||||
dev->SSPDR;
|
||||
}
|
||||
}
|
||||
else if (!outbuf) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
while (!(dev->SSPSR & SPI0_SSPSR_TFE_Msk)) {}
|
||||
dev->SSPDR = 0;
|
||||
while (!(dev->SSPSR & SPI0_SSPSR_RNE_Msk)) {}
|
||||
inbuf[i] = dev->SSPDR & 0xffUL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
while (!(dev->SSPSR & SPI0_SSPSR_TFE_Msk)) {}
|
||||
dev->SSPDR = outbuf[i];
|
||||
while (!(dev->SSPSR & SPI0_SSPSR_RNE_Msk)) {}
|
||||
inbuf[i] = dev->SSPDR & 0xffUL;
|
||||
}
|
||||
}
|
||||
|
||||
_wait_for_end(spi);
|
||||
}
|
||||
|
||||
void spi_transfer_bytes(spi_t spi, spi_cs_t cs, bool cont,
|
||||
const void *out, void *in, size_t len)
|
||||
{
|
||||
DEBUG("[rpx0xx] Call spi_transfer_bytes(spi=%" PRIdFAST8
|
||||
", out=%p, in=%p, len=%" PRIu16 ")\n",
|
||||
spi, out, in, len);
|
||||
|
||||
/* make sure at least one input or one output buffer is given */
|
||||
assert(out || in);
|
||||
|
||||
/* activate the given chip select line */
|
||||
if (gpio_is_valid(cs)) {
|
||||
gpio_clear((gpio_t)cs);
|
||||
}
|
||||
|
||||
_transfer_no_dma(spi, out, in, len);
|
||||
|
||||
/* release the chip select if not specified differently */
|
||||
if ((!cont) && gpio_is_valid(cs)) {
|
||||
gpio_set((gpio_t)cs);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user