mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
Merge pull request #15689 from iosabi/qn908x_spi
cpu/qn908x: Implement blocking SPI support
This commit is contained in:
commit
e87874ae54
@ -19,6 +19,7 @@ config BOARD_QN9080DK
|
||||
select BOARD_HAS_XTAL_32M
|
||||
select HAS_PERIPH_ADC
|
||||
select HAS_PERIPH_I2C
|
||||
select HAS_PERIPH_SPI
|
||||
select HAS_PERIPH_TIMER
|
||||
select HAS_PERIPH_UART
|
||||
select HAS_PERIPH_UART_MODECFG
|
||||
|
@ -2,3 +2,8 @@ ifneq (,$(filter saul_default,$(USEMODULE)))
|
||||
USEMODULE += saul_gpio
|
||||
USEMODULE += mma8x5x
|
||||
endif
|
||||
|
||||
# For MX25R2035F on SPI_DEV(0).
|
||||
ifneq (,$(filter mtd,$(USEMODULE)))
|
||||
USEMODULE += mtd_spi_nor
|
||||
endif
|
||||
|
@ -5,6 +5,7 @@ CPU_MODEL = qn9080xhn
|
||||
FEATURES_PROVIDED += periph_adc
|
||||
FEATURES_PROVIDED += periph_gpio periph_gpio_irq
|
||||
FEATURES_PROVIDED += periph_i2c
|
||||
FEATURES_PROVIDED += periph_spi
|
||||
FEATURES_PROVIDED += periph_timer
|
||||
FEATURES_PROVIDED += periph_uart periph_uart_modecfg
|
||||
|
||||
|
@ -21,8 +21,44 @@
|
||||
#include "cpu.h"
|
||||
#include "board.h"
|
||||
|
||||
#include "mtd.h"
|
||||
#include "mtd_spi_nor.h"
|
||||
#include "timex.h"
|
||||
|
||||
#include "periph/gpio.h"
|
||||
|
||||
#ifdef MODULE_MTD
|
||||
/* MX25R2035F */
|
||||
static const mtd_spi_nor_params_t _mtd_nor_params = {
|
||||
.opcode = &mtd_spi_nor_opcode_default,
|
||||
.wait_chip_erase = 15000LU * US_PER_MS,
|
||||
.wait_64k_erase = 3500LU * US_PER_MS,
|
||||
.wait_32k_erase = 1750LU * US_PER_MS,
|
||||
.wait_sector_erase = 240LU * US_PER_MS,
|
||||
.wait_chip_wake_up = 1LU * US_PER_MS,
|
||||
.clk = CLOCK_CORECLOCK, /* Max fR and fC is 33 MHz, max core is 32 MHz. */
|
||||
.flag = SPI_NOR_F_SECT_4K | SPI_NOR_F_SECT_32K | SPI_NOR_F_SECT_64K,
|
||||
.spi = SPI_DEV(0),
|
||||
.mode = SPI_MODE_0,
|
||||
.cs = SPI_HWCS(0), /* GPIO(PORT_A, 3) is used for HWCS(0) on FC2 */
|
||||
.wp = GPIO_UNDEF,
|
||||
.hold = GPIO_UNDEF,
|
||||
.addr_width = 3, /* 24-bit addresses */
|
||||
};
|
||||
|
||||
static mtd_spi_nor_t mtd_nor_dev = {
|
||||
.base = {
|
||||
.driver = &mtd_spi_nor_driver,
|
||||
.page_size = 256,
|
||||
.pages_per_sector = 16, /* 4 KiB sectors */
|
||||
.sector_count = 64,
|
||||
},
|
||||
.params = &_mtd_nor_params,
|
||||
};
|
||||
|
||||
mtd_dev_t *mtd0 = (mtd_dev_t *)&mtd_nor_dev;
|
||||
#endif /* MODULE_MTD */
|
||||
|
||||
void board_init(void)
|
||||
{
|
||||
/* Initialize LEDs and Buttons. */
|
||||
|
@ -20,6 +20,7 @@
|
||||
#define BOARD_H
|
||||
|
||||
#include "cpu.h"
|
||||
#include "mtd.h"
|
||||
#include "periph_conf.h"
|
||||
#include "periph_cpu.h"
|
||||
|
||||
@ -68,6 +69,14 @@ extern "C" {
|
||||
#define MMA8X5X_PARAM_TYPE (MMA8X5X_TYPE_MMA8652)
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name MTD configuration
|
||||
* @{
|
||||
*/
|
||||
extern mtd_dev_t *mtd0;
|
||||
#define MTD_0 mtd0
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @brief Initialize board specific hardware
|
||||
*/
|
||||
|
@ -66,6 +66,28 @@ static const i2c_conf_t i2c_config[] = {
|
||||
#define I2C_NUMOF ARRAY_SIZE(i2c_config)
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name SPI configuration
|
||||
* @{
|
||||
*/
|
||||
static const spi_conf_t spi_config[] = {
|
||||
{
|
||||
.dev = SPI0, /* Flexcomm 2 */
|
||||
.cipo_pin = GPIO_PIN(PORT_A, 5),
|
||||
.copi_pin = GPIO_PIN(PORT_A, 4),
|
||||
.clk_pin = GPIO_PIN(PORT_A, 30),
|
||||
.cs_pin = {
|
||||
GPIO_PIN(PORT_A, 3), /* MX25R2035F CS# connected here. */
|
||||
GPIO_UNDEF,
|
||||
GPIO_UNDEF,
|
||||
GPIO_UNDEF
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
#define SPI_NUMOF ARRAY_SIZE(spi_config)
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name UART configuration
|
||||
* @{
|
||||
|
@ -15,6 +15,7 @@ config CPU_FAM_QN908X
|
||||
select HAS_PERIPH_GPIO_IRQ
|
||||
select HAS_PERIPH_I2C_RECONFIGURE
|
||||
select HAS_PERIPH_RTC
|
||||
select HAS_PERIPH_SPI_RECONFIGURE
|
||||
select HAS_PERIPH_WDT
|
||||
select HAS_PERIPH_WDT_CB
|
||||
|
||||
|
@ -6,6 +6,7 @@ FEATURES_PROVIDED += periph_cpuid
|
||||
FEATURES_PROVIDED += periph_gpio periph_gpio_irq
|
||||
FEATURES_PROVIDED += periph_i2c_reconfigure
|
||||
FEATURES_PROVIDED += periph_rtc
|
||||
FEATURES_PROVIDED += periph_spi_reconfigure
|
||||
FEATURES_PROVIDED += periph_wdt periph_wdt_cb
|
||||
|
||||
include $(RIOTCPU)/cortexm_common/Makefile.features
|
||||
|
@ -137,6 +137,55 @@ same time since they are both the same FLEXCOMM1 interface.
|
||||
#define I2C_NUMOF ARRAY_SIZE(i2c_config)
|
||||
|
||||
|
||||
@defgroup cpu_qn908x_spi NXP QN908x Serial Peripheral Interface (SPI)
|
||||
@ingroup cpu_qn908x
|
||||
@brief NXP QN908x timer driver
|
||||
|
||||
Two of the FLEXCOMM interfaces in this chip can be used as SPI interfaces named
|
||||
SPI0 and SPI1, which correspond to FLEXCOMM2 and FLEXCOMM3. Note that FLEXCOMM2
|
||||
(SPI0) is also shared with the I2C peripheral I2C1 and both can't be used at
|
||||
the same time.
|
||||
|
||||
The SPI flexcomm clock is directly driven from the AHB bus, so its clock is
|
||||
limited by the core CPU clock and the AHB divisor on the higher side with an
|
||||
optional frequency divider of up to 65536 to generate lower clock frequencies.
|
||||
|
||||
Multiple peripherals can be connected to the same SPI bus, using different CS
|
||||
pins, with a maximum of 4 hardware CS peripherals per bus and any number of
|
||||
software CS peripherals.
|
||||
|
||||
This driver uses the [OSHA SPI Signal Names](
|
||||
https://www.oshwa.org/a-resolution-to-redefine-spi-signal-names/) and while it
|
||||
only implements the Controller mode, the hardware is capable of operating in
|
||||
Peripheral mode as well so we use the COPI/CIPO names.
|
||||
|
||||
### SPI configuration example (for periph_conf.h) ###
|
||||
|
||||
The following example uses only one hardware CS (number 0) and leaves the rest
|
||||
unused. Check the user manual for the full list of CS pins available.
|
||||
|
||||
When configuring the CS line on a driver, you should pass a @ref SPI_HWCS to use
|
||||
the hardware CS mode defined in this configuration. To use any other GPIO as a
|
||||
CS line selected by software it is also possible to pass a @ref GPIO_PIN pin.
|
||||
|
||||
@code
|
||||
static const spi_conf_t spi_config[] = {
|
||||
{
|
||||
.dev = SPI0,
|
||||
.cipo_pin = GPIO_PIN(PORT_A, 5),
|
||||
.copi_pin = GPIO_PIN(PORT_A, 4),
|
||||
.clk_pin = GPIO_PIN(PORT_A, 30),
|
||||
.cs_pin = {
|
||||
GPIO_PIN(PORT_A, 3), /* Use as SPI_HWCS(0) */
|
||||
GPIO_UNDEF,
|
||||
GPIO_UNDEF,
|
||||
GPIO_UNDEF
|
||||
},
|
||||
},
|
||||
};
|
||||
@endcode
|
||||
|
||||
|
||||
@defgroup cpu_qn908x_timer NXP QN908x Standard counter/timers (CTIMER)
|
||||
@ingroup cpu_qn908x
|
||||
@brief NXP QN908x timer driver
|
||||
|
@ -56,6 +56,16 @@ extern "C" {
|
||||
#error "GPIO_T_ADDR(GPIO_PIN(1, x)) must be the GPIOB address"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Return whether the given pin is a CSHW pin.
|
||||
*/
|
||||
#define GPIO_T_IS_HWCS(pin) (((pin) & 0xff00u) == 0x8000)
|
||||
|
||||
/**
|
||||
* @brief Return the given CSHW number from the gpio_t pin.
|
||||
*/
|
||||
#define GPIO_T_HWCS(pin) ((pin) & 0x0003u)
|
||||
|
||||
/**
|
||||
* @brief Configure the pin mux to the given function.
|
||||
*
|
||||
|
@ -384,6 +384,92 @@ typedef struct {
|
||||
#define i2c_pin_scl(dev) i2c_config[dev].pin_scl
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @brief Use some common SPI functions
|
||||
* @{
|
||||
*/
|
||||
#define PERIPH_SPI_NEEDS_TRANSFER_BYTE
|
||||
#define PERIPH_SPI_NEEDS_TRANSFER_REG
|
||||
#define PERIPH_SPI_NEEDS_TRANSFER_REGS
|
||||
/** @} */
|
||||
|
||||
#ifndef DOXYGEN
|
||||
/**
|
||||
* @brief Define a CPU specific SPI hardware chip select line macro
|
||||
*
|
||||
* GPIO numbers use the lower 5 bits and the bit 12. We define the CS numbers
|
||||
* to have the bit 15 set.
|
||||
*/
|
||||
#define SPI_HWCS(x) (1u << 15u | (x))
|
||||
|
||||
/**
|
||||
* @brief Number of HW CS pins supported
|
||||
*/
|
||||
#define SPI_HWCS_NUMOF 4
|
||||
|
||||
/**
|
||||
* @brief SPI mode select helper macro
|
||||
*
|
||||
* The polarity is determined by the bits CPOL and CPHA in the SPI CFG register.
|
||||
*/
|
||||
#define SPI_MODE_SEL(pol, pha) (SPI_CFG_CPOL(pol) | SPI_CFG_CPHA(pha))
|
||||
|
||||
/**
|
||||
* @name Override the SPI mode bitmask
|
||||
*
|
||||
* Override the SPI mode value so we can use it directly as a bitmask to CFG.
|
||||
* @{
|
||||
*/
|
||||
#define HAVE_SPI_MODE_T
|
||||
typedef enum {
|
||||
SPI_MODE_0 = SPI_MODE_SEL(0, 0), /**< mode 0 */
|
||||
SPI_MODE_1 = SPI_MODE_SEL(0, 1), /**< mode 1 */
|
||||
SPI_MODE_2 = SPI_MODE_SEL(1, 0), /**< mode 2 */
|
||||
SPI_MODE_3 = SPI_MODE_SEL(1, 1) /**< mode 3 */
|
||||
} spi_mode_t;
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Override SPI speed values
|
||||
*
|
||||
* The speed is configured at run time based on the AHB clock speed using an
|
||||
* arbitrary divider between /1 and /65536. The standard macro values just map
|
||||
* to the frequency in Hz. The maximum possible speed is 32 MHz assuming a
|
||||
* core clock and AHB bus clock of 32 MHz.
|
||||
* @{
|
||||
*/
|
||||
#define HAVE_SPI_CLK_T
|
||||
typedef enum {
|
||||
SPI_CLK_100KHZ = 100000u, /**< drive the SPI bus with 100KHz */
|
||||
SPI_CLK_400KHZ = 400000u, /**< drive the SPI bus with 400KHz */
|
||||
SPI_CLK_1MHZ = 1000000u, /**< drive the SPI bus with 1MHz */
|
||||
SPI_CLK_5MHZ = 5000000u, /**< drive the SPI bus with 5MHz */
|
||||
SPI_CLK_10MHZ = 10000000u /**< drive the SPI bus with 10MHz */
|
||||
} spi_clk_t;
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @brief SPI pin getters
|
||||
* @{
|
||||
*/
|
||||
#define spi_pin_mosi(bus) spi_config[bus].copi_pin
|
||||
#define spi_pin_miso(bus) spi_config[bus].cipo_pin
|
||||
#define spi_pin_clk(bus) spi_config[bus].clk_pin
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @brief SPI module configuration options
|
||||
*/
|
||||
typedef struct {
|
||||
SPI_Type *dev; /**< SPI device to use */
|
||||
gpio_t cipo_pin; /**< Controller Input Peripheral Output */
|
||||
gpio_t copi_pin; /**< Controller Output Peripheral Input */
|
||||
gpio_t clk_pin; /**< CLK pin */
|
||||
gpio_t cs_pin[SPI_HWCS_NUMOF]; /**< pins used for HW cs lines */
|
||||
} spi_conf_t;
|
||||
|
||||
#endif /* ifndef DOXYGEN */
|
||||
|
||||
/**
|
||||
* @brief UART module configuration options
|
||||
*
|
||||
|
341
cpu/qn908x/periph/spi.c
Normal file
341
cpu/qn908x/periph/spi.c
Normal file
@ -0,0 +1,341 @@
|
||||
/*
|
||||
* Copyright (C) 2020 iosabi
|
||||
*
|
||||
* 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_qn908x
|
||||
* @ingroup drivers_periph_spi
|
||||
*
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Low-level SPI driver implementation
|
||||
*
|
||||
* @author iosabi <iosabi@protonmail.com>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include "assert.h"
|
||||
#include "bitarithm.h"
|
||||
#include "mutex.h"
|
||||
|
||||
#include "cpu.h"
|
||||
#include "periph_conf.h"
|
||||
#include "periph/spi.h"
|
||||
|
||||
#include "vendor/drivers/fsl_clock.h"
|
||||
#include "flexcomm.h"
|
||||
#include "gpio_mux.h"
|
||||
|
||||
#define ENABLE_DEBUG 0
|
||||
#include "debug.h"
|
||||
|
||||
typedef struct {
|
||||
uint8_t *in; /**< The RX buffer pointer or NULL if unused. */
|
||||
uint32_t in_len; /**< The remaining bytes to receive or 0 if unused. */
|
||||
|
||||
const uint8_t *out; /**< The TX buffer pointer or NULL if unused. */
|
||||
|
||||
/**
|
||||
* @brief The remaining transfer length.
|
||||
*
|
||||
* This value is set even if we are not transferring any data, in which case
|
||||
* it indicates the remaining 8-bit clock pulses needed to be sent to the
|
||||
* FIFO to finish the transfer.
|
||||
*/
|
||||
uint32_t tr_len;
|
||||
|
||||
uint32_t tx_mask; /** FIFOWR mask used when transmitting. */
|
||||
} spi_pending_transfer_t;
|
||||
|
||||
/**
|
||||
* @brief Mutex for accessing each SPI bus.
|
||||
*/
|
||||
static mutex_t locks[SPI_NUMOF];
|
||||
|
||||
/**
|
||||
* @brief Bitmask of Port A pins that use Function 4 for the FLEXCOMM2.
|
||||
*
|
||||
* SPI pins are either function 4 or 5 depending on the pin and flexcomm.
|
||||
* All FLEXCOMM3 possible pins are mapped to function 5, while in the
|
||||
* case of FLEXCOMM2 some are in function 4. Some pins can act as a function
|
||||
* in FLEXCOMM2 (function 4) while act as another function in FLEXCOM3 (function
|
||||
* 5)
|
||||
*/
|
||||
static const uint32_t _spi_func5_mask_fc2 =
|
||||
(1u << 0) | /* FC2_SSEL3 */
|
||||
(1u << 1) | /* FC2_SSEL2 */
|
||||
(1u << 2) | /* FC2_SSEL1 */
|
||||
(1u << 3) | /* FC2_SSEL0 */
|
||||
(1u << 4) | /* FC2_COPI */
|
||||
(1u << 5); /* FC2_CIPO */
|
||||
|
||||
/**
|
||||
* @brief Set the clock divided for the target frequency.
|
||||
*/
|
||||
static void _spi_controller_set_speed(SPI_Type *spi_bus, uint32_t speed_hz)
|
||||
{
|
||||
/* The SPI clock source is based on the FLEXCOMM clock with a simple
|
||||
* frequency divider between /1 and /65536. */
|
||||
const uint32_t bus_freq = CLOCK_GetFreq(kCLOCK_BusClk);
|
||||
uint32_t divider = (bus_freq + speed_hz / 2) / speed_hz;
|
||||
|
||||
if (divider == 0) {
|
||||
divider = 1;
|
||||
}
|
||||
else if (divider > (1u << 16)) {
|
||||
divider = 1u << 16;
|
||||
}
|
||||
DEBUG("[spi] clock requested: %" PRIu32 " Hz, actual: %" PRIu32
|
||||
" Hz, divider: /%" PRIu32 "\n", speed_hz, bus_freq / divider,
|
||||
divider);
|
||||
/* The value stored in DIV is always (divider - 1), meaning that a value of
|
||||
* 0 divides by 1. */
|
||||
spi_bus->DIV = divider - 1;
|
||||
}
|
||||
|
||||
void spi_init(spi_t bus)
|
||||
{
|
||||
assert(bus < SPI_NUMOF);
|
||||
const spi_conf_t *const conf = &spi_config[bus];
|
||||
SPI_Type *const spi_bus = conf->dev;
|
||||
|
||||
int flexcomm_num = flexcomm_init((FLEXCOMM_Type *)spi_bus, FLEXCOMM_ID_SPI);
|
||||
DEBUG("[spi] init: bus=%u, flexcomm=%d\n", (unsigned)bus, flexcomm_num);
|
||||
assert(flexcomm_num >= 0);
|
||||
|
||||
/* Set controller mode, but don't enable it. All CS are active low. MSB
|
||||
* first bit order (standard). */
|
||||
spi_bus->CFG = SPI_CFG_MASTER_MASK;
|
||||
/* Configure to use the RX and TX FIFO. */
|
||||
spi_bus->FIFOCFG = SPI_FIFOCFG_ENABLETX_MASK | SPI_FIFOCFG_ENABLERX_MASK;
|
||||
locks[bus] = (mutex_t)MUTEX_INIT_LOCKED;
|
||||
spi_init_pins(bus);
|
||||
}
|
||||
|
||||
void spi_init_pins(spi_t bus)
|
||||
{
|
||||
assert(bus < SPI_NUMOF);
|
||||
const spi_conf_t *const conf = &spi_config[bus];
|
||||
|
||||
const uint32_t mask = conf->dev == (SPI_Type *)FLEXCOMM2_BASE
|
||||
? _spi_func5_mask_fc2
|
||||
: 0xffffffff;
|
||||
gpio_init_mux(conf->copi_pin,
|
||||
((1u << GPIO_T_PIN(conf->copi_pin)) & mask) ? 5 : 4);
|
||||
gpio_init_mux(conf->cipo_pin,
|
||||
((1u << GPIO_T_PIN(conf->cipo_pin)) & mask) ? 5 : 4);
|
||||
gpio_init_mux(conf->clk_pin,
|
||||
((1u << GPIO_T_PIN(conf->clk_pin)) & mask) ? 5 : 4);
|
||||
/* Enables the SPI block and sets it to idle. */
|
||||
conf->dev->CFG |= SPI_CFG_ENABLE_MASK;
|
||||
mutex_unlock(&locks[bus]);
|
||||
}
|
||||
|
||||
int spi_init_cs(spi_t bus, spi_cs_t cs)
|
||||
{
|
||||
/* Initializing the CS pin doesn't require to acquire the mutex since each
|
||||
* peripheral has its own independent CS pin. */
|
||||
if (bus >= SPI_NUMOF) {
|
||||
return SPI_NODEV;
|
||||
}
|
||||
const spi_conf_t *const conf = &spi_config[bus];
|
||||
gpio_t pin = cs;
|
||||
|
||||
if (GPIO_T_IS_HWCS(cs)) {
|
||||
/* The gpio_t value comes from the board config rather than the cs
|
||||
* variable itself when a HWCS number is passed. */
|
||||
pin = conf->cs_pin[GPIO_T_HWCS(cs)];
|
||||
}
|
||||
if (!gpio_is_valid(pin)) {
|
||||
return SPI_NOCS;
|
||||
}
|
||||
DEBUG("[spi] init_cs: cs=0x%.4" PRIx16 " pin=0x%.4" PRIx16 "\n", cs, pin);
|
||||
|
||||
if (GPIO_T_IS_HWCS(cs)) {
|
||||
const uint32_t mask = conf->dev == (SPI_Type *)FLEXCOMM2_BASE
|
||||
? _spi_func5_mask_fc2
|
||||
: 0xffffffff;
|
||||
gpio_init_mux(pin, ((1u << GPIO_T_PIN(pin)) & mask) ? 5 : 4);
|
||||
}
|
||||
else {
|
||||
gpio_init(pin, GPIO_OUT);
|
||||
gpio_set(pin);
|
||||
}
|
||||
|
||||
return SPI_OK;
|
||||
}
|
||||
|
||||
#ifdef MODULE_PERIPH_SPI_RECONFIGURE
|
||||
void spi_deinit_pins(spi_t bus)
|
||||
{
|
||||
assert(bus < SPI_NUMOF);
|
||||
mutex_lock(&locks[bus]);
|
||||
const spi_conf_t *const conf = &spi_config[bus];
|
||||
|
||||
/* Disables the SPI block. It must be already idle. */
|
||||
conf->dev->CFG &= ~SPI_CFG_ENABLE_MASK;
|
||||
|
||||
gpio_init(conf->copi_pin, GPIO_IN);
|
||||
gpio_init(conf->cipo_pin, GPIO_IN);
|
||||
gpio_init(conf->clk_pin, GPIO_IN);
|
||||
}
|
||||
#endif /* MODULE_PERIPH_SPI_RECONFIGURE */
|
||||
|
||||
int spi_acquire(spi_t bus, spi_cs_t cs, spi_mode_t mode, spi_clk_t clk)
|
||||
{
|
||||
const spi_conf_t *const conf = &spi_config[bus];
|
||||
|
||||
mutex_lock(&locks[bus]);
|
||||
|
||||
/* Set SPI clock speed. This silently chooses the closest frequency, no
|
||||
* matter how far it is from the requested one. */
|
||||
_spi_controller_set_speed(conf->dev, clk);
|
||||
|
||||
if ((mode & ~(SPI_CFG_CPHA_MASK | SPI_CFG_CPOL_MASK)) != 0) {
|
||||
return SPI_NOMODE;
|
||||
}
|
||||
|
||||
DEBUG("[spi] acquire: mode CPHA=%d CPOL=%d, cs=0x%" PRIx32 "\n",
|
||||
!!(mode & SPI_CFG_CPHA_MASK), !!(mode & SPI_CFG_CPOL_MASK),
|
||||
(uint32_t)cs);
|
||||
|
||||
conf->dev->CFG =
|
||||
(conf->dev->CFG & ~(SPI_CFG_CPHA_MASK | SPI_CFG_CPOL_MASK)) | mode;
|
||||
|
||||
return SPI_OK;
|
||||
}
|
||||
|
||||
void spi_release(spi_t bus)
|
||||
{
|
||||
assert(bus < SPI_NUMOF);
|
||||
DEBUG("[spi] release\n");
|
||||
mutex_unlock(&locks[bus]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief: Wait for the FIFO to be empty.
|
||||
*/
|
||||
static void _spi_wait_txempty(SPI_Type *spi_bus)
|
||||
{
|
||||
while (!(spi_bus->FIFOSTAT & SPI_FIFOSTAT_TXEMPTY_MASK)) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Bitmask for the FIFOWR register with all the HWCS deasserted.
|
||||
*/
|
||||
#define SPI_HWCS_DEASSERT_ALL \
|
||||
(((1u << SPI_HWCS_NUMOF) - 1) << SPI_FIFOWR_TXSSEL0_N_SHIFT)
|
||||
|
||||
/**
|
||||
* @brief Initialize a SPI transfer given the transfer parameters.
|
||||
*/
|
||||
static void _spi_config_transfer(spi_pending_transfer_t *tr, spi_cs_t cs,
|
||||
bool cont, const void *out, void *in,
|
||||
size_t len)
|
||||
{
|
||||
tr->in = in;
|
||||
tr->in_len = in ? len : 0;
|
||||
tr->out = out;
|
||||
tr->tr_len = len;
|
||||
tr->tx_mask = SPI_HWCS_DEASSERT_ALL;
|
||||
if (GPIO_T_IS_HWCS(cs)) {
|
||||
/* Flag that the TX should assert this HWCS by clearing the bit. */
|
||||
tr->tx_mask &= ~(1u << (SPI_FIFOWR_TXSSEL0_N_SHIFT + GPIO_T_HWCS(cs)));
|
||||
if (!cont) {
|
||||
/* Flag the End of Transfer (EOT) in the mask. This will only be
|
||||
* used in the last byte. */
|
||||
tr->tx_mask |= SPI_FIFOWR_EOT_MASK;
|
||||
}
|
||||
}
|
||||
if (!in) {
|
||||
/* Ignores the RX side when the @p in is NULL so we don't need to read
|
||||
* the FIFO at all. */
|
||||
tr->tx_mask |= SPI_FIFOWR_RXIGNORE_MASK;
|
||||
}
|
||||
tr->tx_mask |= SPI_FIFOWR_LEN(7); /* Data transfers of 8 bits. */
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Perform a blocking SPI transfer.
|
||||
*/
|
||||
static void _spi_transfer_blocking(spi_t bus, spi_pending_transfer_t *tr)
|
||||
{
|
||||
SPI_Type *const spi_bus = spi_config[bus].dev;
|
||||
|
||||
/* Configure to use the RX and TX fifo, and empty them. */
|
||||
spi_bus->FIFOCFG = SPI_FIFOCFG_ENABLETX_MASK
|
||||
| SPI_FIFOCFG_ENABLERX_MASK
|
||||
| SPI_FIFOCFG_EMPTYTX_MASK | SPI_FIFOCFG_EMPTYRX_MASK;
|
||||
spi_bus->FIFOSTAT = SPI_FIFOSTAT_TXERR_MASK | SPI_FIFOSTAT_RXERR_MASK;
|
||||
|
||||
while (tr->in_len || tr->tr_len) {
|
||||
/* Read from RX FIFO if possible. */
|
||||
if (spi_bus->FIFOSTAT & SPI_FIFOSTAT_RXNOTEMPTY_MASK) {
|
||||
uint32_t rd = spi_bus->FIFORD;
|
||||
if (tr->in_len) {
|
||||
*(tr->in++) = (uint8_t)rd;
|
||||
tr->in_len--;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write when able to write and we have data to send or bogus (0) bytes
|
||||
* to send when in receive-only mode. */
|
||||
if ((spi_bus->FIFOSTAT & SPI_FIFOSTAT_TXNOTFULL_MASK) && tr->tr_len) {
|
||||
uint32_t wr = tr->tx_mask;
|
||||
if (tr->out) {
|
||||
wr |= *(tr->out++);
|
||||
}
|
||||
|
||||
/* If this is *not* the last byte, remove the EOT flag if any. */
|
||||
tr->tr_len--;
|
||||
if (tr->tr_len) {
|
||||
wr &= ~SPI_FIFOWR_EOT_MASK;
|
||||
}
|
||||
/* Push the data to the FIFO. */
|
||||
spi_bus->FIFOWR = wr;
|
||||
}
|
||||
}
|
||||
_spi_wait_txempty(spi_bus);
|
||||
}
|
||||
|
||||
void spi_transfer_bytes(spi_t bus, spi_cs_t cs, bool cont,
|
||||
const void *out, void *in, size_t len)
|
||||
{
|
||||
spi_pending_transfer_t tr;
|
||||
|
||||
_spi_config_transfer(&tr, cs, cont, out, in, len);
|
||||
|
||||
/* At least one of input or one output buffer is given */
|
||||
assert(bus < SPI_NUMOF);
|
||||
|
||||
if (!GPIO_T_IS_HWCS(cs)) {
|
||||
/* Assert CS using a gpio. */
|
||||
gpio_clear((gpio_t)cs);
|
||||
}
|
||||
|
||||
DEBUG("[spi] transfer: cs=0x%.4" PRIx16 " cont=%d len=%" PRIu32 "\n",
|
||||
cs, cont, (uint32_t)len);
|
||||
_spi_transfer_blocking(bus, &tr);
|
||||
|
||||
/* Deassert the CS only in gpio mode. HWCS deassert are handled by the
|
||||
* hardware when EOT is set in the mask. */
|
||||
if (!cont && !GPIO_T_IS_HWCS(cs)) {
|
||||
gpio_set((gpio_t)cs);
|
||||
}
|
||||
}
|
||||
|
||||
/* ISR routine called for FLEXCOMM devices configured as SPI. */
|
||||
void isr_flexcomm_spi(USART_Type *dev, uint32_t flexcomm_num)
|
||||
{
|
||||
// TODO: Set up async mode with interrupts.
|
||||
(void)dev;
|
||||
(void)flexcomm_num;
|
||||
|
||||
cortexm_isr_end();
|
||||
}
|
@ -30,11 +30,11 @@
|
||||
|
||||
/* SPI bus speed and mode */
|
||||
#define BUS_CLK SPI_CLK_5MHZ
|
||||
#define MODE SPI_MODE_0
|
||||
#define BUS_MODE SPI_MODE_0
|
||||
#define BUS_OK SPI_OK
|
||||
/* shortcuts for SPI bus parameters */
|
||||
#define BUS (dev->p->spi)
|
||||
#define CS (dev->p->cs)
|
||||
#define BUS_CS (dev->p->cs)
|
||||
/* flag to set when reading from the device */
|
||||
#define FLAG_READ (0x80)
|
||||
/* flag to enable address auto incrementation on read or write */
|
||||
@ -43,7 +43,7 @@
|
||||
static int _init_bus(const lis2dh12_t *dev)
|
||||
{
|
||||
/* for SPI, we only need to initialize the chip select pin */
|
||||
if (spi_init_cs(BUS, CS) != SPI_OK) {
|
||||
if (spi_init_cs(BUS, BUS_CS) != SPI_OK) {
|
||||
return LIS2DH12_NOBUS;
|
||||
}
|
||||
return LIS2DH12_OK;
|
||||
@ -51,7 +51,7 @@ static int _init_bus(const lis2dh12_t *dev)
|
||||
|
||||
static int _acquire(const lis2dh12_t *dev)
|
||||
{
|
||||
return spi_acquire(BUS, CS, MODE, BUS_CLK);
|
||||
return spi_acquire(BUS, BUS_CS, BUS_MODE, BUS_CLK);
|
||||
}
|
||||
|
||||
static void _release(const lis2dh12_t *dev)
|
||||
@ -61,19 +61,19 @@ static void _release(const lis2dh12_t *dev)
|
||||
|
||||
static uint8_t _read(const lis2dh12_t *dev, uint8_t reg)
|
||||
{
|
||||
return spi_transfer_reg(BUS, CS, (FLAG_READ | reg), 0);
|
||||
return spi_transfer_reg(BUS, BUS_CS, (FLAG_READ | reg), 0);
|
||||
}
|
||||
|
||||
static void _read_burst(const lis2dh12_t *dev, uint8_t reg,
|
||||
void *data, size_t len)
|
||||
{
|
||||
spi_transfer_regs(BUS, CS, (FLAG_READ | FLAG_AINC | reg), NULL, data, len);
|
||||
static void _read_burst(const lis2dh12_t *dev, uint8_t reg, void *data,
|
||||
size_t len) {
|
||||
spi_transfer_regs(BUS, BUS_CS, (FLAG_READ | FLAG_AINC | reg), NULL, data,
|
||||
len);
|
||||
}
|
||||
|
||||
static void _write(const lis2dh12_t *dev, uint8_t reg, uint8_t data)
|
||||
{
|
||||
DEBUG("[lis2dh12] write: reg 0x%02x, val 0x%02x\n", (int)reg, (int)data);
|
||||
spi_transfer_reg(BUS, CS, reg, data);
|
||||
spi_transfer_reg(BUS, BUS_CS, reg, data);
|
||||
}
|
||||
|
||||
/* and now the I2C specific part of the driver */
|
||||
|
Loading…
Reference in New Issue
Block a user