1942 lines
72 KiB
C
1942 lines
72 KiB
C
/*
|
|
* SEMIDRIVE Copyright Statement
|
|
* Copyright (c) SEMIDRIVE. All rights reserved
|
|
*
|
|
* This software and all rights therein are owned by SEMIDRIVE, and are
|
|
* protected by copyright law and other relevant laws, regulations and
|
|
* protection. Without SEMIDRIVE's prior written consent and/or related rights,
|
|
* please do not use this software or any potion thereof in any form or by any
|
|
* means. You may not reproduce, modify or distribute this software except in
|
|
* compliance with the License. Unless required by applicable law or agreed to
|
|
* in writing, software distributed under the License is distributed on
|
|
* an "AS IS" basis, WITHOUT WARRANTIES OF ANY KIND, either express or implied.
|
|
*
|
|
* You should have received a copy of the License along with this program.
|
|
* If not, see <http://www.semidrive.com/licenses/>.
|
|
*/
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#include <armv7-r/cache.h>
|
|
#include <clock_ip.h>
|
|
#include <debug.h>
|
|
#include <udelay/udelay.h>
|
|
|
|
#include "sdrv_spi.h"
|
|
|
|
#include "common.h"
|
|
#include "core_id.h"
|
|
#include "irq.h"
|
|
#include "irq_num.h"
|
|
#include "param.h"
|
|
#include "regs_base.h"
|
|
#include "sdrv_ckgen.h"
|
|
#include "sdrv_dma.h"
|
|
#include "sdrv_gpio.h"
|
|
|
|
#define SPI_IS_ALIGNED(tx_ptr, rx_ptr) \
|
|
(IS_ALIGNED((tx_ptr), CONFIG_ARCH_CACHE_LINE) && \
|
|
IS_ALIGNED((rx_ptr), CONFIG_ARCH_CACHE_LINE))
|
|
|
|
#ifndef ENABLE_SLAVE_MCS_TRIG
|
|
#define ENABLE_SLAVE_MCS_TRIG 0
|
|
#endif
|
|
static int spi_handler(uint32_t irq, void *bus);
|
|
|
|
static uint32_t errta_data __attribute__((__aligned__(32)));
|
|
#if (ENABLE_SLAVE_MCS_TRIG == 0)
|
|
static inline bool support_large_frame_size(void)
|
|
{
|
|
#if (CONFIG_E3 || CONFIG_D3)
|
|
return IS_P1;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
#endif
|
|
static bool spi_fdelay_is_en(struct sdrv_spi *bus)
|
|
{
|
|
if ((bus->dev_config->fream_delay == 0) ||
|
|
(bus->state & BUS_BUSY_DMA_MASK) || (!bus->com_config->is_master) ||
|
|
(bus->dev_config->is_soft_cs)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
static void spi_repeat_cmd(struct sdrv_spi *bus, bool is_last)
|
|
{
|
|
uint32_t cmd = bus->cur_cmd;
|
|
|
|
cmd &= (uint32_t)~BM_SPI_CMD_CTRL_LAST;
|
|
if (is_last) {
|
|
cmd |= BM_SPI_CMD_CTRL_LAST;
|
|
}
|
|
writel(cmd, bus->base + SPI_TX_FIFO_CMD_OFF);
|
|
}
|
|
static inline void spi_write(struct sdrv_spi *bus, uint32_t data)
|
|
{
|
|
writel(data, bus->base + SPI_TX_FIFO_DATA_OFF);
|
|
}
|
|
|
|
static inline uint32_t spi_read(struct sdrv_spi *bus)
|
|
{
|
|
return readl(bus->base + SPI_RX_FIFO_DATA_OFF);
|
|
}
|
|
|
|
static inline bool spi_can_write(struct sdrv_spi *bus)
|
|
{
|
|
/*
|
|
* Master mode rx fifo full stop to send, but the slave does not have this
|
|
* mechanism. Slave should stop writing immediately when it finds that
|
|
* rxfifo is full in the process of writing data, and then read rx fifo to
|
|
* avoid rx fifo overflow.
|
|
*/
|
|
bool ret = false;
|
|
uint32_t reg_val = readl(bus->base + SPI_FIFO_STAT_OFF);
|
|
if (spi_fdelay_is_en(bus)) {
|
|
/* Make sure there are two free itme for writing */
|
|
ret = (SPI_FIFO_LEN - 2) > GFV_SPI_FIFO_STAT_TX_FIFO_DPTR(reg_val);
|
|
} else {
|
|
ret =
|
|
!(reg_val & (BM_SPI_FIFO_STAT_TX_FULL | BM_SPI_FIFO_STAT_RX_FULL));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static inline bool spi_can_read(struct sdrv_spi *bus)
|
|
{
|
|
return !(readl(bus->base + SPI_FIFO_STAT_OFF) & BM_SPI_FIFO_STAT_RX_EMPTY);
|
|
}
|
|
|
|
static bool spi_tx_finished(struct sdrv_spi *bus)
|
|
{
|
|
return !!(readl(bus->base + SPI_IRQ_STAT_OFF) &
|
|
BM_SPI_IRQ_STAT_MST_FRM_END);
|
|
}
|
|
|
|
static int32_t spi_write_remain(struct sdrv_spi *bus)
|
|
{
|
|
uint32_t remain;
|
|
uint32_t len = 0;
|
|
/* spi_data_width */
|
|
uint8_t width_type;
|
|
|
|
if (!bus)
|
|
return -1;
|
|
|
|
if (!bus->transmit_sche.ptxdata.val)
|
|
return 0;
|
|
|
|
remain = bus->transmit_sche.expect_len - bus->transmit_sche.tx_cur;
|
|
|
|
if (remain <= 0)
|
|
return 0;
|
|
|
|
width_type = bus->transmit_sche.width_type;
|
|
|
|
while (remain && spi_can_write(bus)) {
|
|
if (bus->state & SPI_STATE_IS_RO_END) {
|
|
spi_write(bus, errta_data);
|
|
} else {
|
|
if (width_type == SPI_DATA_WIDTH_BYTE) {
|
|
spi_write(bus, *(bus->transmit_sche.ptxdata.u8_ptr +
|
|
bus->transmit_sche.tx_cur));
|
|
} else if (width_type == SPI_DATA_WIDTH_HALF_WORD) {
|
|
spi_write(bus, *(bus->transmit_sche.ptxdata.u16_ptr +
|
|
bus->transmit_sche.tx_cur));
|
|
} else {
|
|
spi_write(bus, *(bus->transmit_sche.ptxdata.u32_ptr +
|
|
bus->transmit_sche.tx_cur));
|
|
}
|
|
}
|
|
|
|
bus->transmit_sche.tx_cur++;
|
|
len++;
|
|
remain--;
|
|
if (spi_fdelay_is_en(bus)) {
|
|
if (remain) {
|
|
spi_repeat_cmd(bus, remain == 1 && !bus->transmit_sche.next &&
|
|
bus->transmit_sche.expect_len ==
|
|
bus->transmit_sche.len);
|
|
}
|
|
}
|
|
}
|
|
|
|
return len;
|
|
}
|
|
static void spi_slave_tx_curr_quirks(struct sdrv_spi *bus)
|
|
{
|
|
uint8_t pipe_line_remain =
|
|
GFV_SPI_FIFO_STAT_TX_FIFO_DPTR(readl(bus->base + SPI_FIFO_STAT_OFF));
|
|
|
|
if (!bus->transmit_sche.ptxdata.val)
|
|
return;
|
|
if (!bus->transmit_sche.prxdata.val)
|
|
if (bus->transmit_sche.width_type == SPI_DATA_WIDTH_BYTE) {
|
|
pipe_line_remain += SPI_PIPE_LINE_SIZE_8B;
|
|
} else {
|
|
pipe_line_remain += SPI_PIPE_LINE_SIZE_OB;
|
|
}
|
|
|
|
bus->transmit_sche.tx_cur -= pipe_line_remain;
|
|
}
|
|
|
|
static int32_t spi_read_remain(struct sdrv_spi *bus)
|
|
{
|
|
uint32_t remain;
|
|
uint32_t len = 0;
|
|
/* spi_data_width */
|
|
uint8_t width_type;
|
|
|
|
if (!bus)
|
|
return -1;
|
|
|
|
if (!bus->transmit_sche.prxdata.val)
|
|
return 0;
|
|
|
|
remain = bus->transmit_sche.expect_len - bus->transmit_sche.rx_cur;
|
|
|
|
if (remain <= 0)
|
|
return 0;
|
|
|
|
width_type = bus->transmit_sche.width_type;
|
|
|
|
while (remain && spi_can_read(bus)) {
|
|
if (width_type == SPI_DATA_WIDTH_BYTE) {
|
|
*(bus->transmit_sche.prxdata.u8_ptr + bus->transmit_sche.rx_cur) =
|
|
spi_read(bus);
|
|
} else if (width_type == SPI_DATA_WIDTH_HALF_WORD) {
|
|
*(bus->transmit_sche.prxdata.u16_ptr + bus->transmit_sche.rx_cur) =
|
|
spi_read(bus);
|
|
} else {
|
|
*(bus->transmit_sche.prxdata.u32_ptr + bus->transmit_sche.rx_cur) =
|
|
spi_read(bus);
|
|
}
|
|
|
|
bus->transmit_sche.rx_cur++;
|
|
len++;
|
|
remain--;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static inline void next_transmit(struct spi_transmit_cb *src,
|
|
struct spi_transmit_cb *dst)
|
|
{
|
|
dst->len = src->len;
|
|
dst->next = src->next;
|
|
dst->prxdata.val = src->prxdata.val;
|
|
dst->ptxdata.val = src->ptxdata.val;
|
|
dst->cur_remian = dst->len;
|
|
dst->rx_cur = 0;
|
|
dst->tx_cur = 0;
|
|
}
|
|
static void sspi_recover(struct sdrv_spi *bus);
|
|
|
|
|
|
static inline void spi_bus_state_clr(struct sdrv_spi *bus,uint8_t need_swrst)
|
|
{
|
|
const struct spi_device_config *dev_cfg = bus->dev_config;
|
|
|
|
if (bus->state & SPI_STATE_CS_ACTIVEED) {
|
|
if (dev_cfg->cs_pol == CS_ACTIVE_HIGH) {
|
|
sdrv_gpio_set_pin_output_level(dev_cfg->cs_sel, 0);
|
|
} else {
|
|
sdrv_gpio_set_pin_output_level(dev_cfg->cs_sel, 1);
|
|
}
|
|
}
|
|
bus->state &= ~(BUS_BUSY_STATUS_MASK);
|
|
bus->state &= ~SPI_SATE_EXT_FLAGS;
|
|
#if CONFIG_SPI_ENABLE_DMA
|
|
bus->dma_err = 0;
|
|
bus->tx_context.is_need_handle = 0;
|
|
bus->rx_context.is_need_handle = 0;
|
|
#endif
|
|
if(need_swrst)
|
|
sspi_recover(bus);
|
|
}
|
|
|
|
static int32_t params_check_comm(struct spi_common_config *cfg)
|
|
{
|
|
|
|
if (cfg->base == 0U) {
|
|
return -1;
|
|
}
|
|
if (cfg->clk == NULL) {
|
|
return -3;
|
|
}
|
|
#if CONFIG_SPI_ENABLE_DMA
|
|
if (cfg->dma_ins == NULL) {
|
|
return -4;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static void spi_comm_init(struct sdrv_spi *bus)
|
|
{
|
|
uint32_t reg_val;
|
|
const struct spi_common_config *cfg = bus->com_config;
|
|
// init reg val
|
|
reg_val = readl(bus->base + SPI_CTRL_OFF);
|
|
// soft_rst
|
|
writel(BM_SPI_CTRL_SW_RST | reg_val, bus->base + SPI_CTRL_OFF);
|
|
/* wait hw clr this bit */
|
|
while (readl(bus->base + SPI_CTRL_OFF) & BM_SPI_CTRL_SW_RST)
|
|
;
|
|
|
|
if (cfg->is_master == false) {
|
|
bus->state |= SPI_STATE_IS_SLAVE;
|
|
reg_val |= BM_SPI_CTRL_SLV_MODE;
|
|
// reg_val |= BM_SPI_CTRL_SLV_UNS_SIZE_EN;
|
|
} else {
|
|
reg_val &= ~BM_SPI_CTRL_SLV_MODE;
|
|
}
|
|
reg_val &= ~FM_SPI_CTRL_NSS_POL;
|
|
reg_val &= ~BM_SPI_CTRL_SLV_UNS_SIZE_EN;
|
|
/* default is disable DMA req */
|
|
reg_val &= ~BM_SPI_CTRL_RX_DMA_EN;
|
|
reg_val &= ~BM_SPI_CTRL_TX_DMA_EN;
|
|
if (cfg->is_half_mode) {
|
|
reg_val |= BM_SPI_CTRL_HALF_MODE;
|
|
} else {
|
|
reg_val &= ~BM_SPI_CTRL_HALF_MODE;
|
|
}
|
|
if (cfg->is_spi_mode == false) {
|
|
reg_val |= BM_SPI_CTRL_MODE;
|
|
/* ssp mode cs Polarity is specified as high effective */
|
|
reg_val &= ~FM_SPI_CTRL_NSS_POL;
|
|
reg_val |= FV_SPI_CTRL_NSS_POL(0xF);
|
|
/* ti mode is not allowed to enable uns_size_mode */
|
|
reg_val &= ~BM_SPI_CTRL_SLV_UNS_SIZE_EN;
|
|
} else {
|
|
reg_val &= ~BM_SPI_CTRL_MODE;
|
|
}
|
|
/* BM_SPI_CTRL_SSP_CLK_MODE Prohibited to set !!!! */
|
|
reg_val &= ~BM_SPI_CTRL_SSP_CLK_MODE;
|
|
reg_val &= ~BM_SPI_CTRL_SAMPLE_POINT;
|
|
/* idle */
|
|
reg_val &= ~FM_SPI_CTRL_IDLE;
|
|
reg_val |= FV_SPI_CTRL_IDLE(4);
|
|
/* timeout */
|
|
reg_val &= ~FM_SPI_CTRL_TIMEOUT;
|
|
reg_val |= FV_SPI_CTRL_TIMEOUT(4);
|
|
writel(reg_val, bus->base + SPI_CTRL_OFF);
|
|
/* deafult mask all irq */
|
|
writel(0xFFFFFFFF, bus->base + SPI_IRQ_MASK_OFF);
|
|
/* default disable bus */
|
|
writel(0, bus->base + SPI_EN_OFF);
|
|
/* clear */
|
|
writel(0xFFFF, bus->base + SPI_IRQ_STAT_OFF);
|
|
}
|
|
|
|
static void spi_adapted_to_cs_pol(uint32_t base,
|
|
const struct spi_device_config *cfg)
|
|
{
|
|
uint32_t reg_val, pol;
|
|
// init reg val
|
|
reg_val = readl(base + SPI_CTRL_OFF);
|
|
pol = GFV_SPI_CTRL_NSS_POL(reg_val);
|
|
if (cfg->cs_pol == CS_ACTIVE_LOW) {
|
|
pol &= ~(0x1 << cfg->cs_sel);
|
|
} else {
|
|
pol |= (0x1 << cfg->cs_sel);
|
|
}
|
|
reg_val &= ~FM_SPI_CTRL_NSS_POL;
|
|
reg_val |= FV_SPI_CTRL_NSS_POL(pol);
|
|
|
|
writel(reg_val, base + SPI_CTRL_OFF);
|
|
}
|
|
|
|
int32_t spi_comm_deinit(struct sdrv_spi *bus)
|
|
{
|
|
if (NULL == bus)
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
writel(0xFFFFFFFF, bus->base + SPI_IRQ_MASK_OFF);
|
|
writel(~BM_SPI_EN_ENABLE, bus->base + SPI_EN_OFF);
|
|
// softrst
|
|
writel(BM_SPI_CTRL_SW_RST, bus->base + SPI_CTRL_OFF);
|
|
// wait softrst hwclr
|
|
while (BM_SPI_CTRL_SW_RST & readl(bus->base + SPI_CTRL_OFF))
|
|
;
|
|
writel(0xFFFF, bus->base + SPI_IRQ_STAT_OFF);
|
|
return SDRV_STATUS_OK;
|
|
}
|
|
|
|
static void sspi_recover(struct sdrv_spi *bus)
|
|
{
|
|
uint32_t reg_val;
|
|
/* default disable spi avoid slave mode lost bit */
|
|
writel(~BM_SPI_EN_ENABLE, bus->base + SPI_EN_OFF);
|
|
// init reg val
|
|
reg_val = readl(bus->base + SPI_CTRL_OFF);
|
|
reg_val &= ~BM_SPI_CTRL_SLV_UNS_SIZE_EN;
|
|
/* only slave mode do this sclk @133M *6cycle = 50ns softrst */
|
|
writel(reg_val | BM_SPI_CTRL_SW_RST, bus->base + SPI_CTRL_OFF);
|
|
/* wait hw clr this bit */
|
|
while (readl(bus->base + SPI_CTRL_OFF) & BM_SPI_CTRL_SW_RST)
|
|
;
|
|
writel(reg_val, bus->base + SPI_CTRL_OFF);
|
|
writel(0xFFFFFFFF, bus->base + SPI_IRQ_MASK_OFF);
|
|
writel(0xFFFF, bus->base + SPI_IRQ_STAT_OFF);
|
|
}
|
|
|
|
static inline void spi_irq_update_mask(struct sdrv_spi *bus, uint32_t set_mask,
|
|
uint32_t clr_mask)
|
|
{
|
|
uint32_t reg_val = readl(bus->base + SPI_IRQ_MASK_OFF);
|
|
reg_val &= ~(clr_mask);
|
|
reg_val |= set_mask;
|
|
/* update pre irqs */
|
|
writel(reg_val, bus->base + SPI_IRQ_MASK_OFF);
|
|
}
|
|
|
|
/**
|
|
* @brief Initialize the SPI module.
|
|
*
|
|
* @param[in] spi SPI bus
|
|
* @param[in] cfg SPI device configuration.
|
|
*
|
|
* @note SPI device configuration memory must not be recycled during transfer.
|
|
* @return 0 is ok, otherwise bus error occurred.
|
|
*/
|
|
int sdrv_spi_init(struct sdrv_spi *bus, struct spi_common_config *cfg)
|
|
{
|
|
|
|
if (bus == NULL || cfg == NULL)
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
if (0 == params_check_comm(cfg)) {
|
|
errta_data = 0xFFFFFFFF;
|
|
bus->base = cfg->base;
|
|
bus->irq = cfg->irq;
|
|
bus->com_config = cfg;
|
|
bus->dev_config = NULL;
|
|
bus->callback = NULL;
|
|
bus->state = SPI_STATE_INITED;
|
|
spi_comm_init(bus);
|
|
bus->max_baudrate =
|
|
sdrv_ckgen_get_rate((sdrv_ckgen_node_t *)&(cfg->clk->clk_node)) >>
|
|
SPI_INTERNAL_DIV_SHIFT;
|
|
bus->min_baudrate =
|
|
sdrv_ckgen_get_rate((sdrv_ckgen_node_t *)&(cfg->clk->clk_node)) >>
|
|
(SPI_CLK_PRESSCALE_MAX_SHIFT + SPI_INTERNAL_DIV_SHIFT);
|
|
if (cfg->is_polling_mode == false) {
|
|
if (irq_attach(bus->irq, spi_handler, bus)) {
|
|
ssdk_printf(SSDK_ERR, "irq_attach error\n");
|
|
return SDRV_SPI_STATUS_FAIL_IRQ_SETUP;
|
|
}
|
|
#if CONFIG_SPI_LOOPBACK_ON_SAME_CORE
|
|
if (!cfg->is_master) {
|
|
/*
|
|
* If both the master and the slave are processed on the same
|
|
* core, the interrupt priority of the slave needs to be higher
|
|
* than that of the master to ensure the data processing of the
|
|
* slave.
|
|
*/
|
|
irq_set_priority(bus->irq, irq_get_priority(bus->irq) +
|
|
SPI_SLAVE_IRQ_NICE_ADJUST);
|
|
}
|
|
#endif
|
|
irq_enable(bus->irq);
|
|
} else {
|
|
irq_disable(bus->irq);
|
|
}
|
|
} else {
|
|
return SDRV_SPI_STATUS_PARAM_ILLEGAL;
|
|
}
|
|
return SDRV_STATUS_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief Deinitialize the SPI device.
|
|
*
|
|
* @param[in] bus SPI bus
|
|
*
|
|
* @return 0 is ok, otherwise bus error occurred.
|
|
*/
|
|
int sdrv_spi_deinit(struct sdrv_spi *bus)
|
|
{
|
|
if (bus == NULL) {
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
} else {
|
|
spi_comm_deinit(bus);
|
|
bus->base = 0;
|
|
bus->irq = 0;
|
|
bus->com_config = NULL;
|
|
bus->dev_config = NULL;
|
|
bus->callback = NULL;
|
|
bus->state = 0;
|
|
}
|
|
return SDRV_STATUS_OK;
|
|
}
|
|
|
|
static int32_t params_check_device(struct sdrv_spi *bus,
|
|
const struct spi_device_config *dev_cfg)
|
|
{
|
|
const struct spi_common_config *com_cfg = bus->com_config;
|
|
|
|
if (com_cfg->is_master == false && bus->dev_config)
|
|
return -1;
|
|
if (dev_cfg->sclk_freq > bus->max_baudrate ||
|
|
bus->min_baudrate > dev_cfg->sclk_freq)
|
|
return -2;
|
|
if (dev_cfg->width < SPI_DATA_WIDTH_MIN ||
|
|
dev_cfg->width > SPI_DATA_WIDTH_MAX)
|
|
return -3;
|
|
if (dev_cfg->fream_delay &&
|
|
(!bus->com_config->is_master || dev_cfg->is_soft_cs))
|
|
return -4;
|
|
return SDRV_STATUS_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief SPI device configuration.
|
|
*
|
|
* This function select and activate an external device. Data transfers are
|
|
* performed according to this device configuration.
|
|
*
|
|
* @param[in] spi SPI bus
|
|
* @param[in] cfg SPI device configuration.
|
|
*
|
|
* @note SPI device configuration memory must not be recycled during
|
|
* transfer.
|
|
* @return 0 is ok, and other values mean bus is busy.
|
|
*/
|
|
int sdrv_spi_config_device(struct sdrv_spi *bus,
|
|
const struct spi_device_config *cfg)
|
|
{
|
|
irq_state_t irq_stat;
|
|
int ret = SDRV_STATUS_OK;
|
|
|
|
if (NULL == bus || NULL == cfg) {
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
}
|
|
|
|
irq_stat = arch_irq_save();
|
|
|
|
if (bus->state & BUS_BUSY_MASK) {
|
|
/* Bus busy */
|
|
ret = SDRV_STATUS_BUSY;
|
|
}
|
|
if (ret == SDRV_STATUS_OK) {
|
|
ret = params_check_device(bus, cfg);
|
|
}
|
|
if (ret == SDRV_STATUS_OK) {
|
|
bus->dev_config = cfg;
|
|
spi_adapted_to_cs_pol(bus->base, cfg);
|
|
}
|
|
arch_irq_restore(irq_stat);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Setup the async callback handler.
|
|
*
|
|
* @param[in] spi SPI bus
|
|
* @param[in] cb The async callback handler.
|
|
*/
|
|
void sdrv_spi_attach_async_callback(struct sdrv_spi *bus, spi_callback_t cb)
|
|
{
|
|
if (NULL != bus) {
|
|
bus->callback = cb;
|
|
}
|
|
}
|
|
|
|
static void setup_spi_irq(struct sdrv_spi *bus, enum spi_ops_type type)
|
|
{
|
|
uint32_t reg_val;
|
|
uint32_t rx_thrd = 0, tx_thrd = 0;
|
|
/* transmit manager unit */
|
|
struct spi_transmit_cb *transmit = &bus->transmit_sche;
|
|
|
|
reg_val = readl(bus->base + SPI_IRQ_STAT_OFF);
|
|
/* clr irqs */
|
|
writel(reg_val & 0xBFFF, bus->base + SPI_IRQ_STAT_OFF);
|
|
reg_val = readl(bus->base + SPI_IRQ_MASK_OFF);
|
|
|
|
if (transmit->ptxdata.val && type == OP_MODE_IRQ) {
|
|
/* if size < spi_FIFO_SIZE only frame done will into irq */
|
|
tx_thrd = SPI_FIFO_LEN / 2;
|
|
reg_val &= ~BM_SPI_IRQ_MASK_TX_FIFO_PRE_EMPTY;
|
|
}
|
|
|
|
if (transmit->prxdata.val && type == OP_MODE_IRQ) {
|
|
rx_thrd = transmit->expect_len >= SPI_FIFO_LEN
|
|
? SPI_FIFO_LEN / 2
|
|
: transmit->expect_len - 1;
|
|
/* fifo remain data this case used frame done to read */
|
|
reg_val &= ~BM_SPI_IRQ_MASK_RX_FIFO_PRE_FULL;
|
|
}
|
|
if ((transmit->prxdata.val || transmit->ptxdata.val) &&
|
|
type != OP_MODE_DMA) {
|
|
reg_val &= ~BM_SPI_IRQ_MASK_FRM_DONE;
|
|
}
|
|
if (type == OP_MODE_IRQ) {
|
|
/*
|
|
* Config thrd. DMA also depends on this reg, so this reg only can
|
|
* touch once.
|
|
*/
|
|
writel(FV_SPI_TX_FIFO_CTRL_THRD(tx_thrd),
|
|
bus->base + SPI_TX_FIFO_CTRL_OFF);
|
|
writel(FV_SPI_RX_FIFO_CTRL_THRD(rx_thrd),
|
|
bus->base + SPI_RX_FIFO_CTRL_OFF);
|
|
}
|
|
if (bus->state & SPI_STATE_IS_SLAVE) {
|
|
reg_val &= ~(BM_SPI_IRQ_MASK_RX_FIFO_OVR | BM_SPI_IRQ_MASK_TX_FIFO_UDR);
|
|
}
|
|
/*
|
|
undef size mode workaround step"
|
|
1.enable nss valid irq use to enable cs unvalid irq.
|
|
2.enable nss unvalid irq in cs selected irq handler service.
|
|
3.handling workaround in cs inactiveed irq handler service.
|
|
worksround step1:
|
|
enable cs inactive irq disable cs active irq
|
|
*/
|
|
if (bus->state & SPI_STATE_IS_SLAVE) {
|
|
/* undef size mode enables cs active interrupt to enable nss invalid
|
|
* interrupt */
|
|
if (bus->state & SPI_STATE_IS_UNS_EN) {
|
|
reg_val &= ~BM_SPI_IRQ_MASK_SLV_NSS_VLD;
|
|
}
|
|
}
|
|
ssdk_printf(SSDK_DEBUG, "%s t_thrd:%d r_thrd:%d reg:%08x\n", __FUNCTION__,
|
|
tx_thrd, rx_thrd, reg_val);
|
|
/* enable pre irqs*/
|
|
writel(reg_val, bus->base + SPI_IRQ_MASK_OFF);
|
|
}
|
|
|
|
static int32_t update_cmd(struct sdrv_spi *bus)
|
|
{
|
|
uint32_t fifo_e_timeout;
|
|
const struct spi_device_config *config = bus->dev_config;
|
|
uint32_t cmd = readl(bus->base + SPI_CMD_CTRL_OFF);
|
|
cmd &= ~FM_SPI_CMD_CTRL_FRAM_SIZE;
|
|
if (bus->transmit_sche.cur_remian > SPI_FRAME_SIZE_MAX) {
|
|
cmd |= FV_SPI_CMD_CTRL_FRAM_SIZE(SPI_FRAME_SIZE_MAX - 1);
|
|
bus->transmit_sche.expect_len += SPI_FRAME_SIZE_MAX;
|
|
} else {
|
|
/* last write */
|
|
cmd |= FV_SPI_CMD_CTRL_FRAM_SIZE(bus->transmit_sche.cur_remian - 1);
|
|
bus->transmit_sche.expect_len += bus->transmit_sche.cur_remian;
|
|
/* last default set will limit 1024 once tansmit */
|
|
if (!bus->transmit_sche.next)
|
|
cmd |= BM_SPI_CMD_CTRL_LAST;
|
|
}
|
|
/* The FIFO can write to CMD when it has a free position */
|
|
fifo_e_timeout =
|
|
BUS_FIFO_EMPTYE_TIMEOUT(config->fream_delay, config->clk2cs_delay,
|
|
config->clk2cs_end_delay, config->sclk_freq);
|
|
;
|
|
while (!spi_can_write(bus) && --fifo_e_timeout) {
|
|
udelay(1);
|
|
}
|
|
if (fifo_e_timeout == 0) {
|
|
/* exception event ,soft can't handle this situation */
|
|
sspi_recover(bus);
|
|
return SDRV_SPI_STATUS_UCMD_TIMEOUT_WRITE;
|
|
}
|
|
if (spi_fdelay_is_en(bus)) {
|
|
spi_repeat_cmd(bus, 1 == bus->transmit_sche.cur_remian);
|
|
} else {
|
|
/* updata cmd */
|
|
writel(cmd, bus->base + SPI_TX_FIFO_CMD_OFF);
|
|
bus->cur_cmd = cmd;
|
|
}
|
|
/* In the worst case, the FIFO is full, and you need to wait until all the
|
|
* data in the FIFO is sent */
|
|
fifo_e_timeout =
|
|
SPI_READ_FIFO_TIMEOUT_MAX(config->fream_delay, config->clk2cs_delay,
|
|
config->clk2cs_end_delay, config->sclk_freq);
|
|
if (!(bus->state & SPI_STATE_IS_SLAVE)) {
|
|
while (!spi_tx_finished(bus) && --fifo_e_timeout) {
|
|
udelay(1);
|
|
}
|
|
}
|
|
if (fifo_e_timeout == 0) {
|
|
/* exception event ,soft can't handle this situation */
|
|
sspi_recover(bus);
|
|
return SDRV_SPI_STATUS_UCMD_TIMEOUT_FINISH;
|
|
}
|
|
/* continue whether set or not */
|
|
writel(BM_SPI_IRQ_STAT_MST_FRM_END, bus->base + SPI_IRQ_STAT_OFF);
|
|
ssdk_printf(SSDK_DEBUG, "updata cmd %08x \n", cmd);
|
|
return 0;
|
|
}
|
|
|
|
static int32_t generate_cmd(struct sdrv_spi *bus)
|
|
{
|
|
uint32_t cmd = 0, en, fifo_e_timeout;
|
|
const struct spi_device_config *config = bus->dev_config;
|
|
uint8_t setup = config->clk2cs_delay;
|
|
uint8_t hold = config->clk2cs_end_delay;
|
|
/* The internal state machine consumes 2 cycles */
|
|
uint8_t proi = config->fream_delay > 2 ? config->fream_delay - 2 : 0;
|
|
if (config->is_lsb_mode)
|
|
cmd |= BM_SPI_CMD_CTRL_LSB;
|
|
if (!bus->transmit_sche.ptxdata.val)
|
|
cmd |= BM_SPI_CMD_CTRL_TX_MASK;
|
|
if (!bus->transmit_sche.prxdata.val)
|
|
cmd |= BM_SPI_CMD_CTRL_RX_MASK;
|
|
// mode
|
|
if (config->cpha == DATA_CPT_ON_SECOND_SCK_EDGE)
|
|
cmd |= BM_SPI_CMD_CTRL_SPI_CPHA;
|
|
if (config->cpol == SCK_IDLE_HIGH)
|
|
cmd |= BM_SPI_CMD_CTRL_SPI_CPOL;
|
|
// frame size
|
|
if (bus->transmit_sche.len > SPI_FRAME_SIZE_MAX) {
|
|
cmd |= FV_SPI_CMD_CTRL_FRAM_SIZE(SPI_FRAME_SIZE_MAX - 1);
|
|
bus->transmit_sche.expect_len = SPI_FRAME_SIZE_MAX;
|
|
} else {
|
|
cmd |= FV_SPI_CMD_CTRL_FRAM_SIZE(bus->transmit_sche.len - 1);
|
|
bus->transmit_sche.expect_len = bus->transmit_sche.len;
|
|
/* last default set will limit 1024 once tansmit */
|
|
if (!bus->transmit_sche.next)
|
|
cmd |= BM_SPI_CMD_CTRL_LAST;
|
|
}
|
|
cmd |= FV_SPI_CMD_CTRL_WORD_SIZE(config->width - 1);
|
|
if (bus->com_config->is_master)
|
|
cmd |= FV_SPI_CMD_CTRL_PRESSCALE(
|
|
(bus->max_baudrate / config->sclk_freq) - 1);
|
|
if (config->is_soft_cs) {
|
|
cmd |= FV_SPI_CMD_CTRL_NSS(0);
|
|
} else {
|
|
cmd |= FV_SPI_CMD_CTRL_NSS(config->cs_sel);
|
|
}
|
|
/*
|
|
When the sclk is disturbed, the slave may enter the REAPT state.
|
|
At this time, the new transmission CMD write cannot take effect, so the
|
|
slave state cannot return to the normal state; therefore, in order to
|
|
ensure the robustness of the slave, the reset guarantee is performed
|
|
before each transmission starts. Can recover from errors, the cost is
|
|
us-level time consumption before starting the transmission.
|
|
*/
|
|
if (bus->state & SPI_STATE_IS_SLAVE) {
|
|
sspi_recover(bus);
|
|
} else if (spi_fdelay_is_en(bus)) {
|
|
cmd &= (uint32_t)~FM_SPI_CMD_CTRL_FRAM_SIZE;
|
|
cmd &= (uint32_t)~BM_SPI_CMD_CTRL_LAST;
|
|
} else {
|
|
proi = 0;
|
|
}
|
|
/*
|
|
The workaround will be enabled when both conditions are met at the same
|
|
time:
|
|
1. chip version >1.0.
|
|
2. Transmission length greater than SPI_FRAME_SIZE_MAX.
|
|
*/
|
|
if ((bus->transmit_sche.len > SPI_FRAME_SIZE_MAX) &&
|
|
(bus->state & SPI_STATE_IS_SLAVE)) {
|
|
|
|
#if ENABLE_SLAVE_MCS_TRIG
|
|
/* Multiple CS is not supported for undefined lengths */
|
|
return SDRV_SPI_STATUS_MODE_ERROR;
|
|
#else
|
|
if (support_large_frame_size()) {
|
|
/* only for 1.1*/
|
|
uint32_t val = readl(bus->base + SPI_CTRL_OFF);
|
|
writel(val | BM_SPI_CTRL_SLV_UNS_SIZE_EN, bus->base + SPI_CTRL_OFF);
|
|
bus->state |= SPI_STATE_IS_UNS_EN;
|
|
bus->transmit_sche.expect_len = bus->transmit_sche.len;
|
|
|
|
/* slave mast only one vector item mean that next == NULL always */
|
|
if (!bus->transmit_sche.next)
|
|
cmd |= BM_SPI_CMD_CTRL_LAST;
|
|
} else {
|
|
/*
|
|
chip version 1.0 does not support the case where the length of a
|
|
single transfer exceeds SPI_FRAME_SIZE_MAX
|
|
*/
|
|
return SDRV_SPI_STATUS_GCMD_NO_SUPPORT;
|
|
}
|
|
#endif
|
|
}
|
|
/* RO Mode errata quirks */
|
|
if (bus->state & SPI_STATE_IS_SLAVE) {
|
|
setup = 0;
|
|
hold = 0;
|
|
proi = 0;
|
|
} else {
|
|
/* workaround the rx only errata */
|
|
if (!bus->transmit_sche.ptxdata.val) {
|
|
cmd &= ~BM_SPI_CMD_CTRL_TX_MASK;
|
|
bus->transmit_sche.ptxdata.val = (uint32_t)&errta_data;
|
|
bus->state |= SPI_STATE_BUSY_TX | SPI_STATE_IS_RO_END;
|
|
}
|
|
}
|
|
#if (ENABLE_SLAVE_MCS_TRIG == 0)
|
|
if (bus->state & SPI_STATE_IS_SLAVE) {
|
|
/* software undef size mode */
|
|
bus->state |= SPI_STATE_IS_UNS_EN;
|
|
}
|
|
#endif
|
|
/* The FIFO can write to CMD when it has a free position */
|
|
fifo_e_timeout =
|
|
BUS_FIFO_EMPTYE_TIMEOUT(config->fream_delay, config->clk2cs_delay,
|
|
config->clk2cs_end_delay, config->sclk_freq);
|
|
while (!spi_can_write(bus) && --fifo_e_timeout) {
|
|
udelay(1);
|
|
}
|
|
if (fifo_e_timeout == 0) {
|
|
/* exception event ,soft can't handle this situation */
|
|
sspi_recover(bus);
|
|
return SDRV_SPI_STATUS_GCMD_TIMEOUT_WRITE;
|
|
}
|
|
/* config cmd */
|
|
writel(cmd, bus->base + SPI_TX_FIFO_CMD_OFF);
|
|
bus->cur_cmd = cmd;
|
|
en = readl(bus->base + SPI_EN_OFF);
|
|
/* In the worst case, the FIFO is full, and you need to wait until all the
|
|
* data in the FIFO is sent */
|
|
fifo_e_timeout =
|
|
SPI_READ_FIFO_TIMEOUT_MAX(config->fream_delay, config->clk2cs_delay,
|
|
config->clk2cs_end_delay, config->sclk_freq);
|
|
if (en & BM_SPI_EN_ENABLE) {
|
|
if (!(bus->state & SPI_STATE_IS_SLAVE)) {
|
|
/* wait last transation finish */
|
|
while (!spi_tx_finished(bus) && --fifo_e_timeout) {
|
|
udelay(1);
|
|
}
|
|
}
|
|
}
|
|
if (fifo_e_timeout == 0) {
|
|
/* exception event ,soft can't handle this situation */
|
|
sspi_recover(bus);
|
|
return SDRV_SPI_STATUS_GCMD_TIMEOUT_FINISH;
|
|
}
|
|
/* clr master frame end */
|
|
writel(BM_SPI_IRQ_STAT_MST_FRM_END, bus->base + SPI_IRQ_STAT_OFF);
|
|
/* config Timing */
|
|
writel(SPI_TIMMING_CFG(proi, setup, hold), bus->base + SPI_TIM_CTRL_OFF);
|
|
/* enable bus */
|
|
writel(BM_SPI_EN_ENABLE, bus->base + SPI_EN_OFF);
|
|
|
|
ssdk_printf(SSDK_DEBUG, "first cmd %08x\n", cmd);
|
|
return 0;
|
|
}
|
|
|
|
#if CONFIG_SPI_ENABLE_DMA
|
|
static void spi_schduler(struct sdrv_spi *bus);
|
|
|
|
static inline void disable_dma_req(struct sdrv_spi *bus, uint32_t flags)
|
|
{
|
|
uint32_t reg_val;
|
|
reg_val = readl(bus->base + SPI_CTRL_OFF);
|
|
if (flags & SPI_RX_DIR) {
|
|
reg_val &= ~BM_SPI_CTRL_RX_DMA_EN;
|
|
}
|
|
if (flags & SPI_TX_DIR) {
|
|
reg_val &= ~BM_SPI_CTRL_TX_DMA_EN;
|
|
}
|
|
writel(reg_val, bus->base + SPI_CTRL_OFF);
|
|
ssdk_printf(SSDK_DEBUG, "spi_dma disable %08x\n", reg_val);
|
|
}
|
|
|
|
static void spi_dma_handle(uint32_t status, uint32_t ch_stat, void *context)
|
|
{
|
|
uint32_t xfer_bytes;
|
|
irq_state_t irq_stat;
|
|
struct spi_dma_context *spi_context = (struct spi_dma_context *)context;
|
|
struct sdrv_spi *bus = (struct sdrv_spi *)spi_context->bus;
|
|
uint8_t width_shift = SPI_GET_WIDTH_SHIFT(bus->transmit_sche.width_type);
|
|
/* fixed-length mode need this part handles */
|
|
irq_stat = arch_irq_save();
|
|
if (spi_context->is_need_handle) {
|
|
if (status == SDRV_DMA_COMPLETED) {
|
|
if (spi_context->dir == SPI_RX_DIR) {
|
|
disable_dma_req(bus, SPI_RX_DIR);
|
|
xfer_bytes = sdrv_dma_get_channel_xfer_bytes(&bus->rx_dma_chan);
|
|
arch_invalidate_cache_range(
|
|
(uint32_t)bus->transmit_sche.prxdata.val +
|
|
(bus->transmit_sche.rx_cur << width_shift),
|
|
ROUNDUP(xfer_bytes, CONFIG_ARCH_CACHE_LINE));
|
|
xfer_bytes = xfer_bytes >> width_shift;
|
|
if (xfer_bytes < (bus->transmit_sche.expect_len -
|
|
bus->transmit_sche.rx_cur)) {
|
|
bus->dma_err |= SPI_RX_DMA_ERR;
|
|
}
|
|
bus->transmit_sche.rx_cur += xfer_bytes;
|
|
bus->transmit_sche.cur_remian =
|
|
bus->transmit_sche.len - bus->transmit_sche.rx_cur;
|
|
sdrv_dma_stop_channel_xfer(&bus->rx_dma_chan);
|
|
} else if (spi_context->dir == SPI_TX_DIR) {
|
|
disable_dma_req(bus, SPI_TX_DIR);
|
|
xfer_bytes = sdrv_dma_get_channel_xfer_bytes(&bus->tx_dma_chan);
|
|
xfer_bytes = xfer_bytes >> width_shift;
|
|
if (xfer_bytes < (bus->transmit_sche.expect_len -
|
|
bus->transmit_sche.tx_cur)) {
|
|
bus->dma_err |= SPI_TX_DMA_ERR;
|
|
}
|
|
bus->transmit_sche.tx_cur += xfer_bytes;
|
|
if (!(bus->state & SPI_STATE_DMA_RX)) {
|
|
bus->transmit_sche.cur_remian =
|
|
bus->transmit_sche.len - bus->transmit_sche.tx_cur;
|
|
}
|
|
sdrv_dma_stop_channel_xfer(&bus->tx_dma_chan);
|
|
}
|
|
} else {
|
|
disable_dma_req(bus, SPI_TX_DIR | SPI_RX_DIR);
|
|
/* DMA_PENDING DMA_PAUSED */
|
|
if (bus->rx_context.is_need_handle) {
|
|
sdrv_dma_stop_channel_xfer(&bus->rx_dma_chan);
|
|
bus->rx_context.is_need_handle = false;
|
|
bus->dma_err |= SPI_RX_DMA_ERR;
|
|
}
|
|
if (bus->tx_context.is_need_handle) {
|
|
sdrv_dma_stop_channel_xfer(&bus->tx_dma_chan);
|
|
bus->tx_context.is_need_handle = false;
|
|
bus->dma_err |= SPI_TX_DMA_ERR;
|
|
}
|
|
}
|
|
spi_context->is_need_handle = false;
|
|
}
|
|
arch_irq_restore(irq_stat);
|
|
spi_schduler(bus);
|
|
}
|
|
static void fast_dma_config_tx(struct sdrv_spi *bus, uint32_t ptxdata, uint16_t size)
|
|
{
|
|
sdrv_dma_channel_t *channel_tx = &bus->tx_dma_chan;
|
|
|
|
sdrv_dma_set_channel_source_address(channel_tx,ptxdata);
|
|
|
|
if ((bus->state & (SPI_STATE_IS_SLAVE | SPI_STATE_IS_UNS_EN)) ==
|
|
(SPI_STATE_IS_SLAVE | SPI_STATE_IS_UNS_EN)) {
|
|
size += SPI_FIFO_LEN >> 1;
|
|
}
|
|
sdrv_dma_set_channel_buffer_size(channel_tx,SDRV_DMA_LOOP_MODE_2,
|
|
size,SPI_FIFO_LEN >> 1);
|
|
sdrv_dma_start_channel_xfer(channel_tx);
|
|
bus->tx_context.is_need_handle = true;
|
|
}
|
|
|
|
static void fast_dma_config_rx(struct sdrv_spi *bus,uint32_t prxdata, uint16_t size)
|
|
{
|
|
sdrv_dma_channel_t *channel_rx = &bus->rx_dma_chan;
|
|
sdrv_dma_set_channel_destination_address(channel_rx,prxdata);
|
|
sdrv_dma_set_channel_buffer_size(channel_rx,SDRV_DMA_LOOP_MODE_2,
|
|
size,SPI_FIFO_LEN >> 1);
|
|
sdrv_dma_start_channel_xfer(channel_rx);
|
|
bus->rx_context.is_need_handle = true;
|
|
}
|
|
static int32_t setup_spi_dma(struct sdrv_spi *bus, uint32_t ptxdata,
|
|
uint32_t prxdata, uint16_t size)
|
|
{
|
|
|
|
int ret = 0;
|
|
sdrv_dma_channel_config_t xfer_config;
|
|
sdrv_dma_t *dma = bus->com_config->dma_ins;
|
|
sdrv_dma_channel_t *chan_tx = 0, *chan_rx = 0;
|
|
sdrv_dma_bus_width_e dma_width;
|
|
uint8_t width_type = bus->transmit_sche.width_type;
|
|
uint8_t data_width_shift = SPI_GET_WIDTH_SHIFT(width_type);
|
|
|
|
if (!ptxdata && !prxdata) {
|
|
ret = -1;
|
|
}
|
|
if (width_type == SPI_DATA_WIDTH_BYTE) {
|
|
dma_width = SDRV_DMA_BUSWIDTH_1_BYTE;
|
|
} else if (width_type == SPI_DATA_WIDTH_HALF_WORD) {
|
|
dma_width = SDRV_DMA_BUSWIDTH_2_BYTES;
|
|
} else {
|
|
dma_width = SDRV_DMA_BUSWIDTH_4_BYTES;
|
|
}
|
|
size <<= data_width_shift;
|
|
if (prxdata)
|
|
prxdata = prxdata + (bus->transmit_sche.rx_cur << data_width_shift);
|
|
if (ptxdata)
|
|
ptxdata = ptxdata + (bus->transmit_sche.tx_cur << data_width_shift);
|
|
|
|
if (!ret && prxdata && (!(bus->state & SPI_STATE_DMA_RX_RD))) {
|
|
/* config fifo thrd */
|
|
writel(FV_SPI_RX_FIFO_CTRL_THRD((SPI_FIFO_LEN >> 1) - 1),
|
|
bus->base + SPI_RX_FIFO_CTRL_OFF);
|
|
sdrv_dma_init_channel_config(&xfer_config, dma);
|
|
xfer_config.channel_id = bus->com_config->rx_ch_id; /* select channel */
|
|
xfer_config.src_port_sel = SDRV_DMA_PORT_AHB32;
|
|
xfer_config.trig_mode = SDRV_DMA_TRIGGER_BY_HARDWARE;
|
|
xfer_config.xfer_type =
|
|
SDRV_DMA_DIR_DEV2MEM; /* mem2mem or mem2dev or dev2mem */
|
|
xfer_config.xfer_mode =
|
|
SDRV_DMA_TRANSFER_MODE_SINGLE; /* single or continuous or linklist
|
|
*/
|
|
xfer_config.src_inc =
|
|
SDRV_DMA_ADDR_NO_INC; /* source address increase or not */
|
|
xfer_config.dst_inc =
|
|
SDRV_DMA_ADDR_INC; /* destination address increase or not */
|
|
xfer_config.loop_mode =
|
|
SDRV_DMA_LOOP_MODE_2; /* mem2mem(MODE_0) mem2dev/dev2mem(MODE_1) */
|
|
xfer_config.dst_cache =
|
|
0; /* mem2mem need to set 1, mem2dev/dev2mem set 0 */
|
|
xfer_config.interrupt_type = SDRV_DMA_LAST_MAD_DONE; /* set interrupt type */
|
|
xfer_config.src_addr =
|
|
(paddr_t)bus->base + SPI_RX_FIFO_DATA_OFF; /* set source address */
|
|
xfer_config.dst_addr = (paddr_t)prxdata; /* set destination address */
|
|
xfer_config.xfer_bytes = size; /* set transfer bytes */
|
|
xfer_config.src_width = dma_width;
|
|
xfer_config.dst_width = dma_width;
|
|
xfer_config.src_burst_len = SPI_FIFO_LEN >> 1;
|
|
xfer_config.dst_burst_len = SPI_FIFO_LEN >> 1;
|
|
chan_rx = &bus->rx_dma_chan;
|
|
|
|
if (SDRV_STATUS_OK != sdrv_dma_init_channel(chan_rx, &xfer_config)) {
|
|
ret = -2;
|
|
} else {
|
|
bus->rx_context.dir = SPI_RX_DIR;
|
|
bus->rx_context.bus = (void *)bus;
|
|
bus->rx_context.is_need_handle = true;
|
|
chan_rx->irq_callback = spi_dma_handle;
|
|
chan_rx->irq_context = (void *)&bus->rx_context;
|
|
}
|
|
}else if(bus->state & SPI_STATE_DMA_RX_RD){
|
|
chan_rx = &bus->rx_dma_chan;
|
|
}
|
|
|
|
if (!ret && ptxdata && (!(bus->state & SPI_STATE_DMA_TX_RD))) {
|
|
/* config fifo thrd */
|
|
writel(FV_SPI_TX_FIFO_CTRL_THRD(SPI_FIFO_LEN >> 1),
|
|
bus->base + SPI_TX_FIFO_CTRL_OFF);
|
|
sdrv_dma_init_channel_config(&xfer_config, dma);
|
|
xfer_config.channel_id = bus->com_config->tx_ch_id; /* select channel */
|
|
xfer_config.dst_port_sel = SDRV_DMA_PORT_AHB32;
|
|
xfer_config.trig_mode = SDRV_DMA_TRIGGER_BY_HARDWARE;
|
|
xfer_config.xfer_type =
|
|
SDRV_DMA_DIR_MEM2DEV; /* mem2mem or mem2dev or dev2mem */
|
|
xfer_config.xfer_mode =
|
|
SDRV_DMA_TRANSFER_MODE_SINGLE; /* single or continuous or linklist
|
|
*/
|
|
if (bus->state & SPI_STATE_IS_RO_END) {
|
|
xfer_config.src_inc =
|
|
SDRV_DMA_ADDR_NO_INC; /* source address increase or not */
|
|
} else {
|
|
xfer_config.src_inc =
|
|
SDRV_DMA_ADDR_INC; /* source address increase or not */
|
|
}
|
|
xfer_config.dst_inc =
|
|
SDRV_DMA_ADDR_NO_INC; /* destination address increase or not */
|
|
xfer_config.loop_mode =
|
|
SDRV_DMA_LOOP_MODE_2; /* mem2mem(MODE_0) mem2dev/dev2mem(MODE_1) */
|
|
xfer_config.dst_cache =
|
|
0; /* mem2mem need to set 1, mem2dev/dev2mem set 0 */
|
|
xfer_config.interrupt_type = SDRV_DMA_LAST_MAD_DONE; /* set interrupt type */
|
|
xfer_config.src_addr = (paddr_t)ptxdata; /* set source address */
|
|
xfer_config.dst_addr =
|
|
(paddr_t)bus->base +
|
|
SPI_TX_FIFO_DATA_OFF; /* set destination address */
|
|
xfer_config.xfer_bytes = size; /* set transfer bytes */
|
|
xfer_config.src_width = dma_width;
|
|
xfer_config.dst_width = dma_width;
|
|
xfer_config.src_burst_len = SPI_FIFO_LEN >> 1;
|
|
xfer_config.dst_burst_len = SPI_FIFO_LEN >> 1;
|
|
chan_tx = &bus->tx_dma_chan;
|
|
/*
|
|
Undef size mode need to give more data to tx fifo to avoid udr
|
|
of ip bug. always give tx FIFO more 8bytes data, even in the case of
|
|
32bit data bit width, it can ensure that there are two redundant data
|
|
in txfifo to avoid tx fifo underflow.
|
|
*/
|
|
if ((bus->state & (SPI_STATE_IS_SLAVE | SPI_STATE_IS_UNS_EN)) ==
|
|
(SPI_STATE_IS_SLAVE | SPI_STATE_IS_UNS_EN)) {
|
|
xfer_config.xfer_bytes += SPI_FIFO_LEN >> 1;
|
|
}
|
|
if (SDRV_STATUS_OK != sdrv_dma_init_channel(chan_tx, &xfer_config)) {
|
|
ret = -3;
|
|
} else {
|
|
bus->tx_context.dir = SPI_TX_DIR;
|
|
bus->tx_context.bus = (void *)bus;
|
|
bus->tx_context.is_need_handle = true;
|
|
chan_tx->irq_callback = spi_dma_handle;
|
|
chan_tx->irq_context = (void *)&bus->tx_context;
|
|
}
|
|
}else if(bus->state & SPI_STATE_DMA_TX_RD){
|
|
chan_tx = &bus->tx_dma_chan;
|
|
}
|
|
|
|
if (!ret) {
|
|
if (prxdata && chan_rx && chan_rx->channel) {
|
|
arch_clean_invalidate_cache_range((uint32_t)prxdata, ROUNDUP(size, 32));
|
|
sdrv_dma_clear_channel_xfer_bytes(chan_rx);
|
|
if(bus->state & SPI_STATE_DMA_RX_RD){
|
|
fast_dma_config_rx(bus,prxdata,size);
|
|
}else{
|
|
sdrv_dma_start_channel_xfer(chan_rx);
|
|
bus->state |= SPI_STATE_DMA_RX_RD;
|
|
}
|
|
}
|
|
if (ptxdata && chan_tx && chan_tx->channel) {
|
|
arch_clean_cache_range((uint32_t)ptxdata, ROUNDUP(size, 32));
|
|
sdrv_dma_clear_channel_xfer_bytes(chan_tx);
|
|
if(bus->state & SPI_STATE_DMA_TX_RD){
|
|
fast_dma_config_tx(bus,ptxdata,size);
|
|
}else{
|
|
sdrv_dma_start_channel_xfer(chan_tx);
|
|
bus->state |= SPI_STATE_DMA_TX_RD;
|
|
}
|
|
}
|
|
bus->dma_err = 0;
|
|
} else {
|
|
if (ptxdata && chan_tx && chan_tx->channel)
|
|
sdrv_dma_stop_channel_xfer(chan_tx);
|
|
if (prxdata && chan_rx && chan_rx->channel)
|
|
sdrv_dma_stop_channel_xfer(chan_rx);
|
|
bus->rx_context.is_need_handle = false;
|
|
bus->tx_context.is_need_handle = false;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline void enable_dma_req(struct sdrv_spi *bus)
|
|
{
|
|
uint32_t reg_val = readl(bus->base + SPI_CTRL_OFF);
|
|
|
|
if (bus->transmit_sche.prxdata.val) {
|
|
reg_val |= BM_SPI_CTRL_RX_DMA_EN;
|
|
} else {
|
|
reg_val &= ~BM_SPI_CTRL_RX_DMA_EN;
|
|
}
|
|
if (bus->transmit_sche.ptxdata.val) {
|
|
reg_val |= BM_SPI_CTRL_TX_DMA_EN;
|
|
} else {
|
|
reg_val &= ~BM_SPI_CTRL_TX_DMA_EN;
|
|
}
|
|
|
|
writel(reg_val, bus->base + SPI_CTRL_OFF);
|
|
}
|
|
#endif
|
|
|
|
static int transmission_check(struct sdrv_spi *bus, void *tx_buf, void *rx_buf,
|
|
uint32_t bytes, bool dma)
|
|
{
|
|
int ret = 0;
|
|
/* default Config or slave mode cfg */
|
|
const struct spi_device_config *dev_config;
|
|
|
|
if (!bus) {
|
|
ret = -1;
|
|
} else {
|
|
dev_config = bus->dev_config;
|
|
/* if bus*/
|
|
if (!bus->com_config) {
|
|
ret = -2;
|
|
} else if (!dev_config) {
|
|
ret = -3;
|
|
} else if (!(bus->state & SPI_STATE_INITED)) {
|
|
ret = -4;
|
|
} else if (!tx_buf && !rx_buf) {
|
|
ret = -5;
|
|
} else if (!SPI_IS_VALID_LEN(bytes, dev_config->width)) {
|
|
ret = -6;
|
|
} else if (dma && !SPI_IS_ALIGNED(tx_buf, rx_buf)) {
|
|
ret = -7;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
static int32_t spi_vector_transmission_ops(struct sdrv_spi *bus,
|
|
enum spi_ops_type type,
|
|
uint32_t timeout)
|
|
{
|
|
uint8_t fsm;
|
|
uint16_t cnt = 0;
|
|
int32_t ret = 0;
|
|
irq_state_t irq_stat;
|
|
uint32_t timeout_cnt, delay = 0, read_timeout,read_timeout_cnt;
|
|
/* default Config or slave mode cfg */
|
|
const struct spi_device_config *const dev_config = bus->dev_config;
|
|
/* transmit manager unit */
|
|
struct spi_transmit_cb *transmit = &bus->transmit_sche;
|
|
/* In the worst case, the FIFO is full, and you need to wait until all the
|
|
* data in the FIFO is sent */
|
|
read_timeout = SPI_READ_FIFO_TIMEOUT_MAX(
|
|
dev_config->fream_delay, dev_config->clk2cs_delay,
|
|
dev_config->clk2cs_end_delay, dev_config->sclk_freq);
|
|
if (!transmit->expect_len) {
|
|
/* config cmd */
|
|
ret = generate_cmd(bus);
|
|
if (ret)
|
|
return ret;
|
|
/* soft cs */
|
|
if (!(bus->state & SPI_STATE_IS_SLAVE)) {
|
|
if (!(bus->state & SPI_STATE_CS_ACTIVEED)) {
|
|
if (dev_config->is_soft_cs) {
|
|
timeout_cnt = 1000;
|
|
/* Software CS needs to wait for the SPI sync pad status to
|
|
* be ready */
|
|
fsm = GFV_SPI_IRQ_STAT_SPI_FSM_ST(
|
|
readl(bus->base + SPI_IRQ_STAT_OFF));
|
|
while ((fsm != SPI_FSM_WAIT_DATA_STA) &&
|
|
(fsm != SPI_FSM_M_STR_STA) &&
|
|
(fsm != SPI_FSM_IDLE_STA) && --timeout_cnt) {
|
|
fsm = GFV_SPI_IRQ_STAT_SPI_FSM_ST(
|
|
readl(bus->base + SPI_IRQ_STAT_OFF));
|
|
}
|
|
if (timeout_cnt == 0) {
|
|
return SDRV_SPI_STATUS_SOFT_CS_TIMEOUT;
|
|
}
|
|
|
|
/* Soft Cs mast active cs after spi mode ready */
|
|
if (dev_config->cs_pol == CS_ACTIVE_HIGH) {
|
|
sdrv_gpio_set_pin_output_level(dev_config->cs_sel, 1);
|
|
} else {
|
|
sdrv_gpio_set_pin_output_level(dev_config->cs_sel, 0);
|
|
}
|
|
|
|
bus->state |= SPI_STATE_CS_ACTIVEED;
|
|
}
|
|
}
|
|
/* sample point delay half sclk cycle */
|
|
uint32_t reg_val = readl(bus->base + SPI_CTRL_OFF);
|
|
|
|
if (dev_config->is_tx_delay) {
|
|
reg_val |= BM_SPI_CTRL_SAMPLE_POINT;
|
|
} else {
|
|
reg_val &= ~BM_SPI_CTRL_SAMPLE_POINT;
|
|
}
|
|
|
|
writel(reg_val, bus->base + SPI_CTRL_OFF);
|
|
}
|
|
} else {
|
|
ret = update_cmd(bus);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
if (bus->state & SPI_STATE_IS_RO_END) {
|
|
if (type == OP_MODE_IRQ) {
|
|
bus->state |= SPI_STATE_IRQ_TX;
|
|
}
|
|
#if CONFIG_SPI_ENABLE_DMA
|
|
else {
|
|
bus->state |= SPI_STATE_DMA_TX;
|
|
}
|
|
#endif
|
|
}
|
|
if (type == OP_MODE_SYNC) {
|
|
/*
|
|
(1/baudrate) * width = write once delay
|
|
16 ===> 8 makes the bus more efficient in case of CPU overload.
|
|
*/
|
|
delay = ((dev_config->width + dev_config->fream_delay +
|
|
dev_config->clk2cs_delay + dev_config->clk2cs_end_delay) *
|
|
8000000 / dev_config->sclk_freq);
|
|
while (1) {
|
|
timeout_cnt = timeout;
|
|
read_timeout_cnt = read_timeout;
|
|
/* device mode aways block */
|
|
ssdk_printf(SSDK_DEBUG, "Normal mode \n");
|
|
while ((transmit->ptxdata.val &&
|
|
transmit->tx_cur < transmit->expect_len) ||
|
|
(transmit->prxdata.val &&
|
|
transmit->rx_cur < transmit->expect_len)) {
|
|
spi_write_remain(bus);
|
|
spi_read_remain(bus);
|
|
/* Improve the efficiency of synchronous transmission of small
|
|
data volumes When Tx is finished, do not need delay to wait
|
|
for the fifo to be vacant that to avoid the delay of the new
|
|
cmd (100us+)
|
|
*/
|
|
if (transmit->ptxdata.val &&
|
|
transmit->tx_cur < transmit->expect_len) {
|
|
if (!spi_can_write(bus))
|
|
udelay(delay);
|
|
} else if (transmit->prxdata.val) {
|
|
while (transmit->rx_cur < transmit->expect_len &&
|
|
read_timeout_cnt) {
|
|
/* Read RxFIFO data as fast as possible */
|
|
if (0 == spi_read_remain(bus)) {
|
|
read_timeout_cnt--;
|
|
udelay(1);
|
|
}
|
|
}
|
|
}
|
|
if (timeout_cnt) {
|
|
timeout_cnt--;
|
|
}
|
|
if (timeout_cnt == 0 || read_timeout_cnt == 0) {
|
|
ssdk_printf(SSDK_DEBUG, "timeout\n");
|
|
return SDRV_SPI_STATUS_SYNC_TIMEOUT;
|
|
}
|
|
}
|
|
|
|
if (transmit->expect_len < transmit->len) {
|
|
/* update remain and except */
|
|
if (transmit->prxdata.val) {
|
|
transmit->cur_remian = transmit->len - transmit->rx_cur;
|
|
} else {
|
|
transmit->cur_remian = transmit->len - transmit->tx_cur;
|
|
}
|
|
ret = update_cmd(bus);
|
|
if (ret)
|
|
return ret;
|
|
|
|
} else {
|
|
/* next vector */
|
|
if (!transmit->next)
|
|
break;
|
|
next_transmit(transmit->next, &bus->transmit_sche);
|
|
ret = generate_cmd(bus);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
}
|
|
/* In the worst case, the FIFO is full, and you need to wait until all
|
|
* the data in the FIFO is sent */
|
|
uint32_t fifo_e_timeout = SPI_READ_FIFO_TIMEOUT_MAX(
|
|
dev_config->fream_delay, dev_config->clk2cs_delay,
|
|
dev_config->clk2cs_end_delay, dev_config->sclk_freq);
|
|
if ((!(bus->state & SPI_STATE_IS_SLAVE)) && dev_config->is_soft_cs) {
|
|
while (!spi_tx_finished(bus) && --fifo_e_timeout) {
|
|
udelay(1);
|
|
}
|
|
}
|
|
if (fifo_e_timeout == 0) {
|
|
/*
|
|
Did not wait until the sending was completed , exception
|
|
event soft can't handle this situation.
|
|
*/
|
|
sspi_recover(bus);
|
|
return SDRV_SPI_STATUS_SOFT_CS_WAIT_TIMEOUT;
|
|
}
|
|
spi_bus_state_clr(bus,false);
|
|
ssdk_printf(SSDK_DEBUG, "write %d", bus->transmit_sche.tx_cur);
|
|
ssdk_printf(SSDK_DEBUG, "read %d\n", bus->transmit_sche.rx_cur);
|
|
} else {
|
|
ssdk_printf(SSDK_DEBUG, "Irq mode\n");
|
|
/* slave need tx fifo not empty to avoid too early udr */
|
|
if (type != OP_MODE_DMA){
|
|
spi_write_remain(bus);
|
|
setup_spi_irq(bus, type);
|
|
}else{
|
|
#if CONFIG_SPI_ENABLE_DMA
|
|
/* dma mode */
|
|
ssdk_printf(SSDK_DEBUG, "DMA mode \n");
|
|
if (bus->transmit_sche.prxdata.val) {
|
|
cnt = bus->transmit_sche.expect_len - bus->transmit_sche.rx_cur;
|
|
} else {
|
|
cnt = bus->transmit_sche.expect_len - bus->transmit_sche.tx_cur;
|
|
}
|
|
ret = setup_spi_dma(bus, bus->transmit_sche.ptxdata.val,
|
|
bus->transmit_sche.prxdata.val, cnt);
|
|
if (!ret) {
|
|
/* Slave mode if a dma completion interrupt occurs before the spi interrupt is configured,
|
|
the dma interrupt is processed and returned to continue enabling the spi interrupt,
|
|
it will result in a situation where no transmission is made but the interrupt is enabled */
|
|
irq_stat = arch_irq_save();
|
|
enable_dma_req(bus);
|
|
/* for slave */
|
|
setup_spi_irq(bus, type);
|
|
arch_irq_restore(irq_stat);
|
|
} else {
|
|
/*
|
|
setup dma fail,need mask all irq avoid sync Mode enable
|
|
irqs.
|
|
*/
|
|
return SDRV_SPI_STATUS_FAIL_DMA_SETUP;
|
|
}
|
|
#else
|
|
return SDRV_SPI_STATUS_MODE_ERROR;
|
|
#endif
|
|
}
|
|
}
|
|
return SDRV_STATUS_OK;
|
|
}
|
|
static void hw_spi_clr_irq_state(struct sdrv_spi *bus, uint32_t clr)
|
|
{
|
|
writel(clr, bus->base + SPI_IRQ_STAT_OFF);
|
|
}
|
|
|
|
static int32_t hw_spi_irq_state(struct sdrv_spi *bus, uint32_t *reg_vel)
|
|
{
|
|
int32_t ret = 0;
|
|
if (!bus)
|
|
return -1;
|
|
uint8_t max_read_cnt = SPI_FIFO_LEN;
|
|
#if CONFIG_SPI_ENABLE_DMA
|
|
irq_state_t irq_stat;
|
|
uint32_t xfer = 0;
|
|
uint8_t data_width_shift =
|
|
SPI_GET_WIDTH_SHIFT(bus->transmit_sche.width_type);
|
|
#endif
|
|
uint32_t irq_s = readl(bus->base + SPI_IRQ_STAT_OFF);
|
|
/* Mst fream end clr only on write cmd */
|
|
*reg_vel = irq_s;
|
|
*reg_vel &= ~BM_SPI_IRQ_STAT_MST_FRM_END;
|
|
ssdk_printf(SSDK_DEBUG, "%p sta:%08x \n", bus, irq_s);
|
|
|
|
if (bus->state & SPI_STATE_IS_SLAVE) {
|
|
ret = SPI_TX_FIFO_WRITE | SPI_RX_FIFO_READ;
|
|
} else {
|
|
if (irq_s &
|
|
(BM_SPI_IRQ_STAT_RX_FIFO_PRE_FULL | BM_SPI_IRQ_STAT_FRM_DONE)) {
|
|
ret |= SPI_RX_FIFO_READ;
|
|
ssdk_printf(SSDK_DEBUG, "rx_pre_full\n");
|
|
}
|
|
if (irq_s & BM_SPI_IRQ_STAT_TX_FIFO_PRE_EMPTY) {
|
|
ret |= SPI_TX_FIFO_WRITE;
|
|
ssdk_printf(SSDK_DEBUG, "tx_pre_empty\n");
|
|
}
|
|
}
|
|
if (irq_s & BM_SPI_IRQ_STAT_TX_FIFO_UDR) {
|
|
/* workaround spi slave mode will have a udr interrupt after the
|
|
* transfer is over */
|
|
#if CONFIG_SPI_ENABLE_DMA
|
|
irq_stat = arch_irq_save();
|
|
if (bus->state & SPI_STATE_DMA_TX) {
|
|
xfer = sdrv_dma_get_channel_xfer_bytes(&bus->tx_dma_chan);
|
|
bus->transmit_sche.tx_cur = xfer >> data_width_shift;
|
|
}
|
|
arch_irq_restore(irq_stat);
|
|
#endif
|
|
if (bus->transmit_sche.tx_cur < bus->transmit_sche.expect_len) {
|
|
ret |= SPI_TX_FIFO_UDR;
|
|
ssdk_printf(SSDK_DEBUG, "Bus:%p UDF\n", bus);
|
|
}
|
|
}
|
|
|
|
if (irq_s & BM_SPI_IRQ_STAT_RX_FIFO_OVR) {
|
|
ret |= SPI_RX_FIFO_OVR;
|
|
ssdk_printf(SSDK_DEBUG, "Bus:%p OVR\n", bus);
|
|
}
|
|
uint32_t reg_val = readl(bus->base + SPI_IRQ_MASK_OFF);
|
|
|
|
if ((bus->state & (SPI_STATE_IS_UNS_EN | SPI_STATE_IS_SLAVE)) ==
|
|
(SPI_STATE_IS_UNS_EN | SPI_STATE_IS_SLAVE)) {
|
|
if ((irq_s & BM_SPI_IRQ_STAT_SLV_NSS_VLD) &&
|
|
(~reg_val & BM_SPI_IRQ_MASK_SLV_NSS_VLD)) {
|
|
/*
|
|
worksround step2:
|
|
enable cs inactive irq disable cs active irq
|
|
*/
|
|
spi_irq_update_mask(bus, BM_SPI_IRQ_MASK_SLV_NSS_VLD,
|
|
BM_SPI_IRQ_MASK_SLV_NSS_INVLD);
|
|
}
|
|
|
|
if (irq_s & BM_SPI_IRQ_STAT_SLV_NSS_INVLD &&
|
|
(~reg_val & BM_SPI_IRQ_MASK_SLV_NSS_INVLD)) {
|
|
|
|
/*
|
|
worksround step3:
|
|
disable cs inactive irq.
|
|
handle spi reset and dma resource release related processing
|
|
*/
|
|
spi_irq_update_mask(bus, BM_SPI_IRQ_MASK_SLV_NSS_INVLD, 0);
|
|
#if CONFIG_SPI_ENABLE_DMA
|
|
irq_stat = arch_irq_save();
|
|
if (bus->rx_context.is_need_handle) {
|
|
disable_dma_req(bus, SPI_RX_DIR);
|
|
sdrv_dma_stop_channel_xfer(&bus->rx_dma_chan);
|
|
xfer = sdrv_dma_get_channel_xfer_bytes(&bus->rx_dma_chan);
|
|
arch_invalidate_cache_range(
|
|
(uint32_t)bus->transmit_sche.prxdata.val +
|
|
(bus->transmit_sche.rx_cur << data_width_shift),
|
|
ROUNDUP(xfer, CONFIG_ARCH_CACHE_LINE));
|
|
xfer >>= data_width_shift;
|
|
bus->transmit_sche.rx_cur = xfer;
|
|
bus->rx_context.is_need_handle = false;
|
|
sdrv_dma_clear_channel_xfer_bytes(&bus->rx_dma_chan);
|
|
}
|
|
|
|
if (bus->tx_context.is_need_handle) {
|
|
disable_dma_req(bus, SPI_TX_DIR);
|
|
sdrv_dma_stop_channel_xfer(&bus->tx_dma_chan);
|
|
xfer = sdrv_dma_get_channel_xfer_bytes(&bus->tx_dma_chan);
|
|
xfer >>= data_width_shift;
|
|
bus->transmit_sche.tx_cur = xfer;
|
|
bus->tx_context.is_need_handle = false;
|
|
sdrv_dma_clear_channel_xfer_bytes(&bus->tx_dma_chan);
|
|
}
|
|
arch_irq_restore(irq_stat);
|
|
#endif
|
|
while (spi_can_read(bus) && max_read_cnt) {
|
|
spi_read_remain(bus);
|
|
max_read_cnt--;
|
|
}
|
|
|
|
spi_slave_tx_curr_quirks(bus);
|
|
/* updata expect_len and len make the transfer end */
|
|
if (bus->state & SPI_STATE_BUSY_RX) {
|
|
bus->transmit_sche.cur_remian = 0;
|
|
bus->transmit_sche.expect_len = bus->transmit_sche.rx_cur;
|
|
bus->transmit_sche.len = bus->transmit_sche.expect_len;
|
|
}
|
|
|
|
if ((bus->state & (SPI_STATE_BUSY_RX | SPI_STATE_BUSY_TX)) ==
|
|
SPI_STATE_BUSY_TX) {
|
|
bus->transmit_sche.cur_remian = 0;
|
|
bus->transmit_sche.expect_len = bus->transmit_sche.tx_cur;
|
|
}
|
|
bus->transmit_sche.len = bus->transmit_sche.expect_len;
|
|
bus->state &= ~SPI_STATE_IS_UNS_EN;
|
|
/* slave mode undef size cs inactive mean that the transmition is
|
|
* over --- for irq */
|
|
ret |= SPI_RX_FIFO_READ | SPI_TX_FIFO_WRITE; /* */
|
|
}
|
|
}
|
|
#if CONFIG_SPI_ENABLE_DMA
|
|
/* dma error */
|
|
ret |= bus->dma_err;
|
|
#endif
|
|
if(irq_s & BM_SPI_IRQ_STAT_MST_FRM_END)
|
|
ret |= SPI_TX_ONLY_DONE;
|
|
ssdk_printf(SSDK_DEBUG, "%s ret:%08x\n", __FUNCTION__, ret);
|
|
return ret;
|
|
}
|
|
|
|
static void hw_spi_setup_irq_mask(struct sdrv_spi *bus, uint32_t irq_mask)
|
|
{
|
|
uint32_t reg_val = readl(bus->base + SPI_IRQ_MASK_OFF);
|
|
if (irq_mask & SPI_RX_READ_REQ)
|
|
reg_val |= BM_SPI_IRQ_MASK_RX_FIFO_PRE_FULL;
|
|
if (irq_mask & SPI_TX_WRITE_REQ)
|
|
reg_val |= BM_SPI_IRQ_MASK_TX_FIFO_PRE_EMPTY;
|
|
/*
|
|
In indefinite length mode, the cs invalid interrupt enable
|
|
can only be turned off after the cs invalid interrupt occurs so
|
|
0xFFFF ---> 0xDFFF.
|
|
*/
|
|
if (irq_mask & SPI_TRASPORT_FINISH)
|
|
reg_val |= ~BM_SPI_IRQ_MASK_SLV_NSS_INVLD;
|
|
|
|
if (irq_mask & SPI_CS_INVLD_REQ)
|
|
reg_val |= BM_SPI_IRQ_MASK_SLV_NSS_INVLD;
|
|
|
|
ssdk_printf(SSDK_DEBUG, "%s reg:%08x\n", __FUNCTION__, reg_val);
|
|
/* disable pre irqs*/
|
|
writel(reg_val, bus->base + SPI_IRQ_MASK_OFF);
|
|
}
|
|
|
|
static void spi_bus_abort_processing(struct sdrv_spi *bus)
|
|
{
|
|
#if CONFIG_SPI_ENABLE_DMA
|
|
irq_state_t irq_stat = arch_irq_save();
|
|
if (bus->rx_context.is_need_handle) {
|
|
disable_dma_req(bus, SPI_RX_DIR);
|
|
sdrv_dma_stop_channel_xfer(&bus->rx_dma_chan);
|
|
bus->rx_context.is_need_handle = false;
|
|
}
|
|
|
|
if (bus->tx_context.is_need_handle) {
|
|
disable_dma_req(bus, SPI_TX_DIR);
|
|
sdrv_dma_stop_channel_xfer(&bus->tx_dma_chan);
|
|
bus->tx_context.is_need_handle = false;
|
|
}
|
|
arch_irq_restore(irq_stat);
|
|
#endif
|
|
spi_bus_state_clr(bus,true);
|
|
if (bus->callback)
|
|
bus->callback(bus, SPI_TRANS_FAIL);
|
|
}
|
|
|
|
static void spi_schduler(struct sdrv_spi *bus)
|
|
{
|
|
int32_t ret = -1;
|
|
int32_t ops_ret;
|
|
uint32_t irq_state;
|
|
struct spi_transmit_cb *inherit;
|
|
const struct spi_device_config *dev_config = bus->dev_config;
|
|
|
|
if (!(bus->state & SPI_STATE_INITED)) {
|
|
ssdk_printf(SSDK_ERR, "%s error not inited\n", __FUNCTION__);
|
|
return;
|
|
}
|
|
ret = hw_spi_irq_state(bus, &irq_state);
|
|
/* Slave Mode OverFlow or UnderFlow error */
|
|
if (ret & BUS_FIFO_STATE_ERR_MASK) {
|
|
spi_bus_abort_processing(bus);
|
|
return;
|
|
} else {
|
|
if (bus->state & BUS_BUSY_DMA_MASK) {
|
|
ret = SPI_TX_FIFO_WRITE | SPI_RX_FIFO_READ;
|
|
}
|
|
if (bus->state & SPI_STATE_BUSY_TX && (ret & SPI_TX_FIFO_WRITE)) {
|
|
|
|
if ((bus->transmit_sche.tx_cur < bus->transmit_sche.expect_len) &&
|
|
!(bus->state & SPI_STATE_DMA_TX)) {
|
|
/* feed data to hw ,dma mode soft do not need do this */
|
|
spi_write_remain(bus);
|
|
}
|
|
|
|
if (bus->transmit_sche.tx_cur >= bus->transmit_sche.expect_len) {
|
|
/* tx except satisfy ,If you need to receive,
|
|
just turn off the send trigger, otherwise all turn off*/
|
|
if (bus->state & SPI_STATE_BUSY_RX)
|
|
hw_spi_setup_irq_mask(bus, SPI_TX_WRITE_REQ);
|
|
else
|
|
hw_spi_setup_irq_mask(bus, SPI_TX_WRITE_REQ |
|
|
SPI_TRASPORT_FINISH);
|
|
|
|
if (bus->transmit_sche.expect_len == bus->transmit_sche.len) {
|
|
/* vector item end check next need to transmit ? */
|
|
if (!bus->transmit_sche.next) {
|
|
/*
|
|
vector end and not have others need to process
|
|
,Tx Finished
|
|
*/
|
|
if ((bus->state &
|
|
(SPI_STATE_CS_ACTIVEED | SPI_STATE_BUSY_RX)) ==
|
|
SPI_STATE_CS_ACTIVEED) {
|
|
/* SPI_STATE_CS_ACTIVEED only for master do not need
|
|
* check spi mode*/
|
|
if(ret & SPI_TX_ONLY_DONE){
|
|
if (dev_config->cs_pol != CS_ACTIVE_HIGH) {
|
|
sdrv_gpio_set_pin_output_level(
|
|
dev_config->cs_sel, 1);
|
|
} else {
|
|
sdrv_gpio_set_pin_output_level(
|
|
dev_config->cs_sel, 0);
|
|
}
|
|
bus->state &= ~SPI_STATE_CS_ACTIVEED;
|
|
}else{
|
|
/* enable master fream done irq to deassert soft cs */
|
|
spi_irq_update_mask(bus, 0,BM_SPI_IRQ_MASK_MST_FRM_END);
|
|
return ;
|
|
}
|
|
}
|
|
if(0u == (bus->state & SPI_STATE_IS_UNS_EN)){
|
|
bus->state &= ~BUS_STATE_TX_MASK;
|
|
|
|
/* rx is on going or undefine size not finish ,do not finished the transmitssion */
|
|
if (bus->callback &&
|
|
(!(bus->state & SPI_STATE_BUSY_RX))) {
|
|
bus->callback(bus, SPI_TRANS_DONE);
|
|
}
|
|
}
|
|
ssdk_printf(SSDK_DEBUG, "%s bus%p tx pass\n",
|
|
__FUNCTION__, bus);
|
|
} else {
|
|
/* vector next */
|
|
if (bus->state & SPI_STATE_BUSY_RX) {
|
|
/* do not start a new transmit, Should be started
|
|
when the reception is complete */
|
|
} else {
|
|
/* inherit from last item dev */
|
|
inherit = bus->transmit_sche.next;
|
|
inherit->expect_len = 0;
|
|
if (bus->state & BUS_BUSY_DMA_MASK) {
|
|
ops_ret = spi_vector_transmission_ops(
|
|
bus, OP_MODE_DMA, 0);
|
|
ssdk_printf(SSDK_DEBUG, "ops_ret %d\n",
|
|
ops_ret);
|
|
if (ops_ret != SDRV_STATUS_OK) {
|
|
spi_bus_abort_processing(bus);
|
|
return;
|
|
}
|
|
} else {
|
|
ops_ret = spi_vector_transmission_ops(
|
|
bus, OP_MODE_IRQ, 0);
|
|
if (ops_ret != SDRV_STATUS_OK) {
|
|
spi_bus_abort_processing(bus);
|
|
return;
|
|
}
|
|
ssdk_printf(SSDK_DEBUG, "ops_ret %d\n",
|
|
ops_ret);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
/* vector item continue */
|
|
if (bus->state & SPI_STATE_BUSY_RX) {
|
|
/* do not start a new transmit, Should be started
|
|
when the reception is complete */
|
|
} else {
|
|
bus->transmit_sche.cur_remian =
|
|
bus->transmit_sche.len - bus->transmit_sche.tx_cur;
|
|
if (bus->state & BUS_BUSY_DMA_MASK) {
|
|
ops_ret = spi_vector_transmission_ops(
|
|
bus, OP_MODE_DMA, 0);
|
|
if (ops_ret != SDRV_STATUS_OK) {
|
|
spi_bus_abort_processing(bus);
|
|
return;
|
|
}
|
|
ssdk_printf(SSDK_DEBUG, "ops_ret %d\n", ops_ret);
|
|
} else {
|
|
ops_ret = spi_vector_transmission_ops(
|
|
bus, OP_MODE_IRQ, 0);
|
|
if (ops_ret != SDRV_STATUS_OK) {
|
|
spi_bus_abort_processing(bus);
|
|
return;
|
|
}
|
|
ssdk_printf(SSDK_DEBUG, "ops_ret %d\n", ops_ret);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (bus->state & SPI_STATE_BUSY_RX && (ret & SPI_RX_FIFO_READ)) {
|
|
/* check next need to transmit ? */
|
|
if ((bus->transmit_sche.rx_cur < bus->transmit_sche.expect_len) &&
|
|
!(bus->state & SPI_STATE_DMA_RX)) {
|
|
/* read data to hw ,dma mode do not need to do anything */
|
|
spi_read_remain(bus);
|
|
}
|
|
/* !!! Cannot be implemented using if-else must independent to check
|
|
*/
|
|
if (bus->transmit_sche.rx_cur == bus->transmit_sche.expect_len) {
|
|
/* rx except satisfy , all turn off*/
|
|
hw_spi_setup_irq_mask(bus, SPI_TX_WRITE_REQ | SPI_RX_READ_REQ |
|
|
SPI_TRASPORT_FINISH);
|
|
if (bus->transmit_sche.expect_len == bus->transmit_sche.len) {
|
|
/* vector item end check next need to transmit ? */
|
|
if (!bus->transmit_sche.next) {
|
|
/*
|
|
vector end and not have others need to process
|
|
,Rx Finished
|
|
*/
|
|
if (bus->state & SPI_STATE_CS_ACTIVEED) {
|
|
if (dev_config->cs_pol != CS_ACTIVE_HIGH) {
|
|
sdrv_gpio_set_pin_output_level(
|
|
dev_config->cs_sel, 1);
|
|
} else {
|
|
sdrv_gpio_set_pin_output_level(
|
|
dev_config->cs_sel, 0);
|
|
}
|
|
}
|
|
bus->state &= ~SPI_STATE_CS_ACTIVEED;
|
|
bus->state &= ~SPI_STATE_IS_RO_END;
|
|
|
|
if(0u == (bus->state & SPI_STATE_IS_UNS_EN)){
|
|
bus->state &= ~BUS_STATE_RX_MASK;
|
|
if (bus->callback)
|
|
bus->callback(bus, SPI_TRANS_DONE);
|
|
}
|
|
ssdk_printf(SSDK_DEBUG, "%s bus%p rx pass\n",
|
|
__FUNCTION__, bus);
|
|
} else {
|
|
inherit = bus->transmit_sche.next;
|
|
inherit->expect_len = 0;
|
|
/* vector next */
|
|
if (bus->state & BUS_BUSY_DMA_MASK) {
|
|
ops_ret = spi_vector_transmission_ops(
|
|
bus, OP_MODE_DMA, 0);
|
|
if (ops_ret != SDRV_STATUS_OK) {
|
|
spi_bus_abort_processing(bus);
|
|
return;
|
|
}
|
|
ssdk_printf(SSDK_DEBUG, "ops_ret %d\n", ops_ret);
|
|
} else {
|
|
ops_ret = spi_vector_transmission_ops(
|
|
bus, OP_MODE_IRQ, 0);
|
|
if (ops_ret != SDRV_STATUS_OK) {
|
|
spi_bus_abort_processing(bus);
|
|
return;
|
|
}
|
|
ssdk_printf(SSDK_DEBUG, "ops_ret %d\n", ops_ret);
|
|
}
|
|
}
|
|
} else {
|
|
bus->transmit_sche.cur_remian =
|
|
bus->transmit_sche.len - bus->transmit_sche.rx_cur;
|
|
/* vector item continue */
|
|
if (bus->state & BUS_BUSY_DMA_MASK) {
|
|
ops_ret =
|
|
spi_vector_transmission_ops(bus, OP_MODE_DMA, 0);
|
|
if (ops_ret != SDRV_STATUS_OK) {
|
|
spi_bus_abort_processing(bus);
|
|
return;
|
|
}
|
|
ssdk_printf(SSDK_DEBUG, "ops_ret %d\n", ops_ret);
|
|
} else {
|
|
ops_ret =
|
|
spi_vector_transmission_ops(bus, OP_MODE_IRQ, 0);
|
|
if (ops_ret != SDRV_STATUS_OK) {
|
|
spi_bus_abort_processing(bus);
|
|
return;
|
|
}
|
|
ssdk_printf(SSDK_DEBUG, "ops_ret %d\n", ops_ret);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ssdk_printf(SSDK_DEBUG, "irq_sta reg%08x\n", irq_state);
|
|
hw_spi_clr_irq_state(bus, irq_state);
|
|
}
|
|
}
|
|
|
|
static int spi_handler(uint32_t irq, void *bus)
|
|
{
|
|
(void)irq;
|
|
if (((struct sdrv_spi *)bus)->com_config->is_polling_mode == false) {
|
|
spi_schduler((struct sdrv_spi *)bus);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void sdrv_spi_polling(struct sdrv_spi *bus)
|
|
{
|
|
if (bus && bus->com_config->is_polling_mode == true) {
|
|
spi_schduler(bus);
|
|
}
|
|
}
|
|
|
|
static inline void setup_transmit_sche(struct sdrv_spi *bus, void *ptxdata,
|
|
void *prxdata, uint32_t size)
|
|
{
|
|
bus->transmit_sche.prxdata.val = (uint32_t)prxdata;
|
|
bus->transmit_sche.ptxdata.val = (uint32_t)ptxdata;
|
|
bus->transmit_sche.rx_cur = 0;
|
|
bus->transmit_sche.tx_cur = 0;
|
|
bus->transmit_sche.next = NULL;
|
|
bus->transmit_sche.width_type = SPI_GET_WIDTH_TYPE(bus->dev_config->width);
|
|
size >>= SPI_GET_WIDTH_SHIFT(bus->transmit_sche.width_type);
|
|
bus->transmit_sche.cur_remian = size;
|
|
bus->transmit_sche.len = size;
|
|
bus->transmit_sche.expect_len = 0;
|
|
}
|
|
|
|
/**
|
|
* @brief SPI sync transmit.
|
|
*
|
|
* @param[in] spi SPI bus.
|
|
* @param[in] tx_buf Pointer to data to transmit, which must be aligned with the
|
|
* corresponding data width, otherwise unaligned access will occur and cause an
|
|
* exception.
|
|
* @param[out] rx_buf Pointer to receive data storage, which must be aligned
|
|
* with the corresponding data width, otherwise unaligned access will occur and
|
|
* cause an exception.
|
|
* @param[in] bytes The amount of data in bytes.
|
|
* @param[in] timeout Timeout time >= total transmitted bytes / data-width*2.
|
|
* @return 0 is ok, and other values for error.
|
|
*/
|
|
int sdrv_spi_sync_transmit(struct sdrv_spi *bus, void *tx_buf, void *rx_buf,
|
|
uint32_t bytes, uint32_t timeout)
|
|
{
|
|
int ret = SDRV_STATUS_OK;
|
|
irq_state_t irq_stat;
|
|
|
|
ret = transmission_check(bus, tx_buf, rx_buf, bytes, false);
|
|
|
|
if (SDRV_STATUS_OK == ret) {
|
|
|
|
irq_stat = arch_irq_save();
|
|
|
|
if (bus->state & BUS_BUSY_MASK) {
|
|
/* Bus busy */
|
|
ret = SDRV_STATUS_BUSY;
|
|
arch_irq_restore(irq_stat);
|
|
} else {
|
|
|
|
setup_transmit_sche(bus, tx_buf, rx_buf, bytes);
|
|
|
|
if (bus->transmit_sche.ptxdata.val)
|
|
bus->state |= SPI_STATE_BUSY_TX;
|
|
if (bus->transmit_sche.prxdata.val)
|
|
bus->state |= SPI_STATE_BUSY_RX;
|
|
arch_irq_restore(irq_stat);
|
|
ret = spi_vector_transmission_ops(bus, OP_MODE_SYNC, timeout);
|
|
if (ret != SDRV_STATUS_OK) {
|
|
spi_bus_state_clr(bus,true);
|
|
ssdk_printf(SSDK_ERR, "%s error: %d\n", __FUNCTION__, ret);
|
|
}
|
|
}
|
|
} else {
|
|
ret = SDRV_SPI_STATUS_PARAM_ILLEGAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief SPI async transmit in IRQ or DMA mode.
|
|
*
|
|
* @param[in] spi SPI bus
|
|
* @param[in] tx_buf Pointer to data to transmit, which must be aligned with the
|
|
* corresponding data width, otherwise unaligned access will occur and cause an
|
|
* exception.
|
|
* @param[out] rx_buf Pointer to receive data storage, which must be aligned
|
|
* with the corresponding data width, otherwise unaligned access will occur and
|
|
* cause an exception.
|
|
* @param[in] bytes The amount of data in bytes.
|
|
* @param[in] type OP_MODE_IRQ for IRQ Mode. OP_MODE_DMA for DMA Mode.
|
|
* @return 0 is ok, and other values for error.
|
|
*/
|
|
int sdrv_spi_async_transmit(struct sdrv_spi *bus, void *tx_buf, void *rx_buf,
|
|
uint32_t bytes, enum spi_ops_type type)
|
|
{
|
|
int32_t ret = SDRV_STATUS_OK;
|
|
irq_state_t irq_stat;
|
|
|
|
ret = transmission_check(bus, tx_buf, rx_buf, bytes, type == OP_MODE_DMA);
|
|
|
|
if (SDRV_STATUS_OK == ret) {
|
|
irq_stat = arch_irq_save();
|
|
if (bus->state & BUS_BUSY_MASK) {
|
|
/* Bus busy */
|
|
ret = SDRV_STATUS_BUSY;
|
|
arch_irq_restore(irq_stat);
|
|
} else {
|
|
setup_transmit_sche(bus, tx_buf, rx_buf, bytes);
|
|
|
|
if (bus->transmit_sche.ptxdata.val) {
|
|
bus->state |= SPI_STATE_BUSY_TX;
|
|
if (type == OP_MODE_DMA) {
|
|
bus->state |= SPI_STATE_DMA_TX;
|
|
} else {
|
|
bus->state |= SPI_STATE_IRQ_TX;
|
|
}
|
|
}
|
|
if (bus->transmit_sche.prxdata.val) {
|
|
bus->state |= SPI_STATE_BUSY_RX;
|
|
if (type == OP_MODE_DMA) {
|
|
bus->state |= SPI_STATE_DMA_RX;
|
|
} else {
|
|
bus->state |= SPI_STATE_IRQ_RX;
|
|
}
|
|
}
|
|
arch_irq_restore(irq_stat);
|
|
ret = spi_vector_transmission_ops(bus, type, 0);
|
|
if (ret != SDRV_STATUS_OK) {
|
|
spi_bus_state_clr(bus,true);
|
|
ssdk_printf(SSDK_ERR, "%s error: %d\n", __FUNCTION__, ret);
|
|
}
|
|
}
|
|
} else {
|
|
ret = SDRV_SPI_STATUS_PARAM_ILLEGAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Get SPI bus state
|
|
*
|
|
* @param[in] spi SPI bus
|
|
* @return SPI_BUSY: spi bus is busy. SPI_IDLE: spi bus is idle.
|
|
*/
|
|
enum spi_bus_state sdrv_spi_get_status(struct sdrv_spi *bus)
|
|
{
|
|
if (NULL == bus) {
|
|
return SPI_BUSY;
|
|
}
|
|
if (bus->state & BUS_BUSY_STATUS_MASK) {
|
|
return SPI_BUSY;
|
|
} else {
|
|
return SPI_IDLE;
|
|
}
|
|
}
|
|
/**
|
|
* @brief Get slave transmit cnt
|
|
*
|
|
* @param[in] spi SPI bus
|
|
* @return uint32_t: transmited bytes.
|
|
*/
|
|
uint32_t sdrv_spi_slave_get_transmit_len(struct sdrv_spi *bus)
|
|
{
|
|
uint32_t len = 0;
|
|
|
|
if (bus && bus->dev_config) {
|
|
if (bus->transmit_sche.prxdata.val != NULL) {
|
|
len = bus->transmit_sche.rx_cur;
|
|
} else {
|
|
len = bus->transmit_sche.tx_cur;
|
|
}
|
|
|
|
len <<= SPI_GET_WIDTH_SHIFT(SPI_GET_WIDTH_TYPE(bus->dev_config->width));
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|