drivers: spi: introduce new Telink B91 SPI driver
SPI driver basic support for Telink B91 SoC. Signed-off-by: Yuriy Vynnychek <yura.vynnychek@telink-semi.com>
This commit is contained in:
parent
5d9d35dbb2
commit
87018c50cc
6 changed files with 499 additions and 0 deletions
|
@ -327,6 +327,7 @@
|
|||
/drivers/net/ @rlubos @tbursztyka
|
||||
/drivers/ptp_clock/ @tbursztyka
|
||||
/drivers/spi/ @tbursztyka
|
||||
/drivers/spi/*b91* @yurvyn
|
||||
/drivers/spi/spi_rv32m1_lpspi* @karstenkoenig
|
||||
/drivers/timer/apic_timer.c @dcpleung @nashif
|
||||
/drivers/timer/apic_tsc.c @andyross
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
zephyr_library()
|
||||
|
||||
zephyr_library_sources_ifdef(CONFIG_SPI_TELINK_B91 spi_b91.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_SPI_CC13XX_CC26XX spi_cc13xx_cc26xx.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_SPI_DW spi_dw.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_SPI_EMUL spi_emul.c)
|
||||
|
|
|
@ -41,6 +41,8 @@ module = SPI
|
|||
module-str = spi
|
||||
source "subsys/logging/Kconfig.template.log_config"
|
||||
|
||||
source "drivers/spi/Kconfig.b91"
|
||||
|
||||
source "drivers/spi/Kconfig.stm32"
|
||||
|
||||
source "drivers/spi/Kconfig.dw"
|
||||
|
|
8
drivers/spi/Kconfig.b91
Normal file
8
drivers/spi/Kconfig.b91
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Copyright (c) 2021 Telink Semiconductor
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config SPI_TELINK_B91
|
||||
bool "Telink Semiconductor B91 SPI driver"
|
||||
depends on SOC_RISCV_TELINK_B91
|
||||
help
|
||||
Enables Telink B91 SPI driver.
|
480
drivers/spi/spi_b91.c
Normal file
480
drivers/spi/spi_b91.c
Normal file
|
@ -0,0 +1,480 @@
|
|||
/*
|
||||
* Copyright (c) 2021 Telink Semiconductor
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#define DT_DRV_COMPAT telink_b91_spi
|
||||
|
||||
/* Redefine 'spi_read' and 'spi_write' functions names from HAL */
|
||||
#define spi_read hal_spi_read
|
||||
#define spi_write hal_spi_write
|
||||
#include "spi.c"
|
||||
#undef spi_read
|
||||
#undef spi_write
|
||||
|
||||
#include "clock.h"
|
||||
|
||||
#include <logging/log.h>
|
||||
LOG_MODULE_REGISTER(spi_telink);
|
||||
|
||||
#include <drivers/spi.h>
|
||||
#include "spi_context.h"
|
||||
#include <drivers/pinmux.h>
|
||||
#include <dt-bindings/pinctrl/b91-pinctrl.h>
|
||||
|
||||
|
||||
#define CHIP_SELECT_COUNT 3u
|
||||
#define SPI_WORD_SIZE 8u
|
||||
#define SPI_WR_RD_CHUNK_SIZE_MAX 16u
|
||||
|
||||
|
||||
/* SPI configuration structure */
|
||||
struct spi_b91_cfg {
|
||||
uint8_t peripheral_id;
|
||||
gpio_pin_e cs_pin[CHIP_SELECT_COUNT];
|
||||
const uint32_t *pinctrl_list;
|
||||
size_t pinctrl_list_size;
|
||||
};
|
||||
#define SPI_CFG(dev) ((struct spi_b91_cfg *) ((dev)->config))
|
||||
|
||||
/* SPI data structure */
|
||||
struct spi_b91_data {
|
||||
struct spi_context ctx;
|
||||
};
|
||||
#define SPI_DATA(dev) ((struct spi_b91_data *) ((dev)->data))
|
||||
|
||||
|
||||
/* disable hardware cs flow control */
|
||||
static void spi_b91_hw_cs_disable(const struct spi_b91_cfg *config)
|
||||
{
|
||||
gpio_pin_e pin;
|
||||
|
||||
/* loop through all cs pins (cs0..cs2) */
|
||||
for (int i = 0; i < CHIP_SELECT_COUNT; i++) {
|
||||
/* get CS pin defined in device tree */
|
||||
pin = config->cs_pin[i];
|
||||
|
||||
/* if CS pin is defined in device tree */
|
||||
if (pin != 0) {
|
||||
if (config->peripheral_id == PSPI_MODULE) {
|
||||
/* disable CS pin for PSPI */
|
||||
pspi_cs_pin_dis(pin);
|
||||
} else {
|
||||
/* disable CS pin for MSPI */
|
||||
hspi_cs_pin_dis(pin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* config cs flow control: hardware or software */
|
||||
static bool spi_b91_config_cs(const struct device *dev,
|
||||
const struct spi_config *config)
|
||||
{
|
||||
pspi_csn_pin_def_e cs_pin = 0;
|
||||
const struct spi_b91_cfg *b91_config = SPI_CFG(dev);
|
||||
|
||||
/* software flow control */
|
||||
if (config->cs) {
|
||||
/* disable all hardware CS pins */
|
||||
spi_b91_hw_cs_disable(b91_config);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* hardware flow control */
|
||||
|
||||
/* check for correct slave id */
|
||||
if (config->slave >= CHIP_SELECT_COUNT) {
|
||||
LOG_ERR("Slave %d not supported (max. %d)", config->slave, CHIP_SELECT_COUNT - 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* loop through all cs pins: cs0, cs1 and cs2 */
|
||||
for (int cs_id = 0; cs_id < CHIP_SELECT_COUNT; cs_id++) {
|
||||
/* get cs pin defined in device tree */
|
||||
cs_pin = b91_config->cs_pin[cs_id];
|
||||
|
||||
/* if cs pin is not defined for the selected slave, return error */
|
||||
if ((cs_pin == 0) && (cs_id == config->slave)) {
|
||||
LOG_ERR("cs%d-pin is not defined in device tree", config->slave);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* disable cs pin if it is defined and is not requested */
|
||||
if ((cs_pin != 0) && (cs_id != config->slave)) {
|
||||
if (b91_config->peripheral_id == PSPI_MODULE) {
|
||||
pspi_cs_pin_dis(cs_pin);
|
||||
} else {
|
||||
hspi_cs_pin_dis(cs_pin);
|
||||
}
|
||||
}
|
||||
|
||||
/* enable cs pin if it is defined and is requested */
|
||||
if ((cs_pin != 0) && (cs_id == config->slave)) {
|
||||
if (b91_config->peripheral_id == PSPI_MODULE) {
|
||||
pspi_set_pin_mux(cs_pin);
|
||||
pspi_cs_pin_en(cs_pin);
|
||||
} else {
|
||||
hspi_set_pin_mux(cs_pin);
|
||||
hspi_cs_pin_en(cs_pin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* get spi transaction length */
|
||||
static uint32_t spi_b91_get_txrx_len(const struct spi_buf_set *tx_bufs,
|
||||
const struct spi_buf_set *rx_bufs)
|
||||
{
|
||||
uint32_t len_tx = 0;
|
||||
uint32_t len_rx = 0;
|
||||
const struct spi_buf *tx_buf = tx_bufs->buffers;
|
||||
const struct spi_buf *rx_buf = rx_bufs->buffers;
|
||||
|
||||
/* calculate tx len */
|
||||
for (int i = 0; i < tx_bufs->count; i++) {
|
||||
len_tx += tx_buf->len;
|
||||
tx_buf++;
|
||||
}
|
||||
|
||||
/* calculate rx len */
|
||||
for (int i = 0; i < rx_bufs->count; i++) {
|
||||
len_rx += rx_buf->len;
|
||||
rx_buf++;
|
||||
}
|
||||
|
||||
return MAX(len_tx, len_rx);
|
||||
}
|
||||
|
||||
/* process tx data */
|
||||
_attribute_ram_code_sec_
|
||||
static void spi_b91_tx(uint8_t peripheral_id, struct spi_context *ctx, uint8_t len)
|
||||
{
|
||||
uint8_t tx;
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (spi_context_tx_buf_on(ctx)) {
|
||||
tx = *(uint8_t *)(ctx->tx_buf);
|
||||
} else {
|
||||
tx = 0;
|
||||
}
|
||||
spi_context_update_tx(ctx, 1, 1);
|
||||
while (reg_spi_fifo_state(peripheral_id) & FLD_SPI_TXF_FULL) {
|
||||
};
|
||||
reg_spi_wr_rd_data(peripheral_id, i % 4) = tx;
|
||||
}
|
||||
}
|
||||
|
||||
/* process rx data */
|
||||
_attribute_ram_code_sec_
|
||||
static void spi_b91_rx(uint8_t peripheral_id, struct spi_context *ctx, uint8_t len)
|
||||
{
|
||||
uint8_t rx = 0;
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
while (reg_spi_fifo_state(peripheral_id) & FLD_SPI_RXF_EMPTY) {
|
||||
};
|
||||
rx = reg_spi_wr_rd_data(peripheral_id, i % 4);
|
||||
|
||||
if (spi_context_rx_buf_on(ctx)) {
|
||||
*ctx->rx_buf = rx;
|
||||
}
|
||||
spi_context_update_rx(ctx, 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* SPI transceive internal */
|
||||
_attribute_ram_code_sec_
|
||||
static void spi_b91_txrx(const struct device *dev, uint32_t len)
|
||||
{
|
||||
unsigned int chunk_size = SPI_WR_RD_CHUNK_SIZE_MAX;
|
||||
struct spi_b91_cfg *cfg = SPI_CFG(dev);
|
||||
struct spi_context *ctx = &SPI_DATA(dev)->ctx;
|
||||
|
||||
/* prepare SPI module */
|
||||
spi_set_transmode(cfg->peripheral_id, SPI_MODE_WRITE_AND_READ);
|
||||
spi_set_cmd(cfg->peripheral_id, 0);
|
||||
spi_tx_cnt(cfg->peripheral_id, len);
|
||||
spi_rx_cnt(cfg->peripheral_id, len);
|
||||
|
||||
/* write and read bytes in chunks */
|
||||
for (int i = 0; i < len; i = i + chunk_size) {
|
||||
/* check for tail */
|
||||
if (chunk_size > (len - i)) {
|
||||
chunk_size = len - i;
|
||||
}
|
||||
|
||||
/* write bytes */
|
||||
spi_b91_tx(cfg->peripheral_id, ctx, chunk_size);
|
||||
|
||||
/* read bytes */
|
||||
if (len <= SPI_WR_RD_CHUNK_SIZE_MAX) {
|
||||
/* read all bytes if len is less than chunk size */
|
||||
spi_b91_rx(cfg->peripheral_id, ctx, chunk_size);
|
||||
} else if (i == 0) {
|
||||
/* head, read 1 byte less than is sent */
|
||||
spi_b91_rx(cfg->peripheral_id, ctx, chunk_size - 1);
|
||||
} else if ((len - i) > SPI_WR_RD_CHUNK_SIZE_MAX) {
|
||||
/* body, read so many bytes as is sent*/
|
||||
spi_b91_rx(cfg->peripheral_id, ctx, chunk_size);
|
||||
} else {
|
||||
/* tail, read the rest bytes */
|
||||
spi_b91_rx(cfg->peripheral_id, ctx, chunk_size + 1);
|
||||
}
|
||||
|
||||
/* clear TX and RX fifo */
|
||||
BM_SET(reg_spi_fifo_state(cfg->peripheral_id), FLD_SPI_TXF_CLR);
|
||||
BM_SET(reg_spi_fifo_state(cfg->peripheral_id), FLD_SPI_RXF_CLR);
|
||||
}
|
||||
|
||||
/* wait fot SPI is ready */
|
||||
while (spi_is_busy(cfg->peripheral_id)) {
|
||||
};
|
||||
|
||||
/* context complate */
|
||||
spi_context_complete(ctx, 0);
|
||||
}
|
||||
|
||||
/* Check for supported configuration */
|
||||
static bool spi_b91_is_config_supported(const struct spi_config *config,
|
||||
struct spi_b91_cfg *b91_config)
|
||||
{
|
||||
/* check for loop back */
|
||||
if (config->operation & SPI_MODE_LOOP) {
|
||||
LOG_ERR("Loop back mode not supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* check for transfer LSB first */
|
||||
if (config->operation & SPI_TRANSFER_LSB) {
|
||||
LOG_ERR("LSB first not supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* check word size */
|
||||
if (SPI_WORD_SIZE_GET(config->operation) != SPI_WORD_SIZE) {
|
||||
LOG_ERR("Word size must be %d", SPI_WORD_SIZE);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* check for CS active hich */
|
||||
if (config->operation & SPI_CS_ACTIVE_HIGH) {
|
||||
LOG_ERR("CS active high not supported for HW flow control");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* check for lines configuration */
|
||||
if ((config->operation & SPI_LINES_MASK) == SPI_LINES_OCTAL) {
|
||||
LOG_ERR("SPI lines Octal configuration is not supported");
|
||||
return false;
|
||||
} else if (((config->operation & SPI_LINES_MASK) == SPI_LINES_QUAD) &&
|
||||
(b91_config->peripheral_id == PSPI_MODULE)) {
|
||||
LOG_ERR("SPI lines Quad configuration is not supported by PSPI");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* check for slave configuration */
|
||||
if (SPI_OP_MODE_GET(config->operation) == SPI_OP_MODE_SLAVE) {
|
||||
LOG_ERR("SPI Slave is not implemented");
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* SPI configuration */
|
||||
static int spi_b91_config(const struct device *dev,
|
||||
const struct spi_config *config)
|
||||
{
|
||||
const struct device *pinmux;
|
||||
spi_mode_type_e mode = SPI_MODE0;
|
||||
struct spi_b91_cfg *b91_config = SPI_CFG(dev);
|
||||
struct spi_b91_data *b91_data = SPI_DATA(dev);
|
||||
uint8_t clk_src = b91_config->peripheral_id == PSPI_MODULE ? sys_clk.pclk : sys_clk.hclk;
|
||||
|
||||
/* check for unsupported configuration */
|
||||
if (!spi_b91_is_config_supported(config, b91_config)) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
/* config slave selection (CS): hw or sw */
|
||||
if (!spi_b91_config_cs(dev, config)) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
/* get SPI mode */
|
||||
if (((config->operation & SPI_MODE_CPHA) == 0) &&
|
||||
((config->operation & SPI_MODE_CPOL) == 0)) {
|
||||
mode = SPI_MODE0;
|
||||
} else if (((config->operation & SPI_MODE_CPHA) == 0) &&
|
||||
((config->operation & SPI_MODE_CPOL) == SPI_MODE_CPOL)) {
|
||||
mode = SPI_MODE1;
|
||||
} else if (((config->operation & SPI_MODE_CPHA) == SPI_MODE_CPHA) &&
|
||||
((config->operation & SPI_MODE_CPOL) == 0)) {
|
||||
mode = SPI_MODE2;
|
||||
} else if (((config->operation & SPI_MODE_CPHA) == SPI_MODE_CPHA) &&
|
||||
((config->operation & SPI_MODE_CPOL) == SPI_MODE_CPOL)) {
|
||||
mode = SPI_MODE3;
|
||||
}
|
||||
|
||||
/* init SPI master */
|
||||
spi_master_init(b91_config->peripheral_id,
|
||||
clk_src * 1000000 / (2 * config->frequency) - 1, mode);
|
||||
spi_master_config(b91_config->peripheral_id, SPI_NOMAL);
|
||||
|
||||
/* set lines configuration */
|
||||
if ((config->operation & SPI_LINES_MASK) == SPI_LINES_SINGLE) {
|
||||
spi_set_io_mode(b91_config->peripheral_id, SPI_SINGLE_MODE);
|
||||
} else if ((config->operation & SPI_LINES_MASK) == SPI_LINES_DUAL) {
|
||||
spi_set_io_mode(b91_config->peripheral_id, SPI_DUAL_MODE);
|
||||
} else if ((config->operation & SPI_LINES_MASK) == SPI_LINES_QUAD) {
|
||||
spi_set_io_mode(b91_config->peripheral_id, HSPI_QUAD_MODE);
|
||||
}
|
||||
|
||||
/* get pinmux driver */
|
||||
pinmux = DEVICE_DT_GET(DT_NODELABEL(pinmux));
|
||||
if (!device_is_ready(pinmux)) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* config pins */
|
||||
for (int i = 0; i < b91_config->pinctrl_list_size; i++) {
|
||||
pinmux_pin_set(pinmux, B91_PINMUX_GET_PIN(b91_config->pinctrl_list[i]),
|
||||
B91_PINMUX_GET_FUNC(b91_config->pinctrl_list[i]));
|
||||
}
|
||||
|
||||
/* save context config */
|
||||
b91_data->ctx.config = config;
|
||||
|
||||
/* config software CS control if enabled */
|
||||
if (config->cs != NULL) {
|
||||
spi_context_cs_configure(&b91_data->ctx);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* API implementation: init */
|
||||
static int spi_b91_init(const struct device *dev)
|
||||
{
|
||||
struct spi_b91_data *data = SPI_DATA(dev);
|
||||
|
||||
spi_context_unlock_unconditionally(&data->ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* API implementation: transceive */
|
||||
static int spi_b91_transceive(const struct device *dev,
|
||||
const struct spi_config *config,
|
||||
const struct spi_buf_set *tx_bufs,
|
||||
const struct spi_buf_set *rx_bufs)
|
||||
{
|
||||
int status = 0;
|
||||
struct spi_b91_data *data = SPI_DATA(dev);
|
||||
uint32_t txrx_len = spi_b91_get_txrx_len(tx_bufs, rx_bufs);
|
||||
|
||||
/* set configuration */
|
||||
status = spi_b91_config(dev, config);
|
||||
if (status) {
|
||||
return status;
|
||||
}
|
||||
|
||||
/* context setup */
|
||||
spi_context_lock(&data->ctx, false, NULL, config);
|
||||
spi_context_buffers_setup(&data->ctx, tx_bufs, rx_bufs, 1);
|
||||
|
||||
/* if cs is defined: software cs control, set active true */
|
||||
if (config->cs) {
|
||||
spi_context_cs_control(&data->ctx, true);
|
||||
}
|
||||
|
||||
/* transceive data */
|
||||
spi_b91_txrx(dev, txrx_len);
|
||||
|
||||
/* if cs is defined: software cs control, set active false */
|
||||
if (config->cs) {
|
||||
spi_context_cs_control(&data->ctx, false);
|
||||
}
|
||||
|
||||
/* release context */
|
||||
status = spi_context_wait_for_completion(&data->ctx);
|
||||
spi_context_release(&data->ctx, status);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SPI_ASYNC
|
||||
/* API implementation: transceive_async */
|
||||
static int spi_b91_transceive_async(const struct device *dev,
|
||||
const struct spi_config *config,
|
||||
const struct spi_buf_set *tx_bufs,
|
||||
const struct spi_buf_set *rx_bufs,
|
||||
struct k_poll_signal *async)
|
||||
{
|
||||
ARG_UNUSED(dev);
|
||||
ARG_UNUSED(config);
|
||||
ARG_UNUSED(tx_bufs);
|
||||
ARG_UNUSED(rx_bufs);
|
||||
ARG_UNUSED(async);
|
||||
|
||||
return -ENOTSUP;
|
||||
}
|
||||
#endif /* CONFIG_SPI_ASYNC */
|
||||
|
||||
/* API implementation: release */
|
||||
static int spi_b91_release(const struct device *dev,
|
||||
const struct spi_config *config)
|
||||
{
|
||||
struct spi_b91_data *data = SPI_DATA(dev);
|
||||
|
||||
if (!spi_context_configured(&data->ctx, config)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
spi_context_unlock_unconditionally(&data->ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* SPI driver APIs structure */
|
||||
static struct spi_driver_api spi_b91_api = {
|
||||
.transceive = spi_b91_transceive,
|
||||
.release = spi_b91_release,
|
||||
#ifdef CONFIG_SPI_ASYNC
|
||||
.transceive_async = spi_b91_transceive_async,
|
||||
#endif /* CONFIG_SPI_ASYNC */
|
||||
};
|
||||
|
||||
/* SPI driver registration */
|
||||
#define SPI_B91_INIT(inst) \
|
||||
\
|
||||
static const uint32_t spi_pins_##inst[] = \
|
||||
B91_PINMUX_DT_INST_GET_ARRAY(inst, 0); \
|
||||
\
|
||||
static struct spi_b91_data spi_b91_data_##inst = { \
|
||||
SPI_CONTEXT_INIT_LOCK(spi_b91_data_##inst, ctx), \
|
||||
SPI_CONTEXT_INIT_SYNC(spi_b91_data_##inst, ctx), \
|
||||
}; \
|
||||
\
|
||||
static struct spi_b91_cfg spi_b91_cfg_##inst = { \
|
||||
.peripheral_id = DT_ENUM_IDX(DT_DRV_INST(inst), peripheral_id), \
|
||||
.cs_pin[0] = DT_STRING_TOKEN(DT_DRV_INST(inst), cs0_pin), \
|
||||
.cs_pin[1] = DT_STRING_TOKEN(DT_DRV_INST(inst), cs1_pin), \
|
||||
.cs_pin[2] = DT_STRING_TOKEN(DT_DRV_INST(inst), cs2_pin), \
|
||||
.pinctrl_list_size = ARRAY_SIZE(spi_pins_##inst), \
|
||||
.pinctrl_list = spi_pins_##inst \
|
||||
}; \
|
||||
\
|
||||
DEVICE_DT_INST_DEFINE(inst, spi_b91_init, \
|
||||
NULL, \
|
||||
&spi_b91_data_##inst, \
|
||||
&spi_b91_cfg_##inst, \
|
||||
POST_KERNEL, \
|
||||
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
|
||||
&spi_b91_api);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(SPI_B91_INIT)
|
|
@ -0,0 +1,7 @@
|
|||
#
|
||||
# Copyright (c) 2021 Telink Semiconductor
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
CONFIG_SPI_LOOPBACK_DRV_NAME="PSPI"
|
Loading…
Add table
Add a link
Reference in a new issue