rt-thread/bsp/nuvoton/libraries/m480/rtt_port/drv_uspi.c

682 lines
19 KiB
C

/**************************************************************************//**
*
* @copyright (C) 2020 Nuvoton Technology Corp. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2020-3-31 Philo First version
*
******************************************************************************/
#include <rtconfig.h>
#if defined(BSP_USING_USPI)
#define LOG_TAG "drv.uspi"
#define DBG_ENABLE
#define DBG_SECTION_NAME LOG_TAG
#define DBG_LEVEL DBG_INFO
#define DBG_COLOR
#include <rtdbg.h>
#include <rthw.h>
#include <rtdevice.h>
#include <rtdef.h>
#include "NuMicro.h"
#include <nu_bitutil.h>
#if defined(BSP_USING_USPI_PDMA)
#include <drv_pdma.h>
#endif
/* Private define ---------------------------------------------------------------*/
#ifndef NU_SPI_USE_PDMA_MIN_THRESHOLD
#define NU_SPI_USE_PDMA_MIN_THRESHOLD (128)
#endif
enum
{
USPI_START = -1,
#if defined(BSP_USING_USPI0)
USPI0_IDX,
#endif
#if defined(BSP_USING_USPI1)
USPI1_IDX,
#endif
USPI_CNT
};
/* Private typedef --------------------------------------------------------------*/
struct nu_uspi
{
struct rt_spi_bus dev;
char *name;
USPI_T *uspi_base;
struct rt_spi_configuration configuration;
uint32_t dummy;
#if defined(BSP_USING_USPI_PDMA)
int16_t pdma_perp_tx;
int8_t pdma_chanid_tx;
int16_t pdma_perp_rx;
int8_t pdma_chanid_rx;
rt_sem_t m_psSemBus;
#endif
};
typedef struct nu_uspi *uspi_t;
/* Private functions ------------------------------------------------------------*/
static rt_err_t nu_uspi_bus_configure(struct rt_spi_device *device, struct rt_spi_configuration *configuration);
static rt_uint32_t nu_uspi_bus_xfer(struct rt_spi_device *device, struct rt_spi_message *message);
static void nu_uspi_transmission_with_poll(struct nu_uspi *uspi_bus,
uint8_t *send_addr, uint8_t *recv_addr, int length, uint8_t bytes_per_word);
static int nu_uspi_register_bus(struct nu_uspi *uspi_bus, const char *name);
static void nu_uspi_drain_rxfifo(USPI_T *uspi_base);
#if defined(BSP_USING_USPI_PDMA)
static void nu_pdma_uspi_rx_cb(void *pvUserData, uint32_t u32EventFilter);
static rt_err_t nu_pdma_uspi_rx_config(struct nu_uspi *uspi_bus, uint8_t *pu8Buf, int32_t i32RcvLen, uint8_t bytes_per_word);
static rt_err_t nu_pdma_uspi_tx_config(struct nu_uspi *uspi_bus, const uint8_t *pu8Buf, int32_t i32SndLen, uint8_t bytes_per_word);
static rt_ssize_t nu_uspi_pdma_transmit(struct nu_uspi *uspi_bus, const uint8_t *send_addr, uint8_t *recv_addr, int length, uint8_t bytes_per_word);
static rt_err_t nu_hw_uspi_pdma_allocate(struct nu_uspi *uspi_bus);
#endif
/* Public functions -------------------------------------------------------------*/
/* Private variables ------------------------------------------------------------*/
static struct rt_spi_ops nu_uspi_poll_ops =
{
.configure = nu_uspi_bus_configure,
.xfer = nu_uspi_bus_xfer,
};
static struct nu_uspi nu_uspi_arr [] =
{
#if defined(BSP_USING_USPI0)
{
.name = "uspi0",
.uspi_base = USPI0,
#if defined(BSP_USING_USPI_PDMA)
#if defined(BSP_USING_USPI0_PDMA)
.pdma_perp_tx = PDMA_USCI0_TX,
.pdma_perp_rx = PDMA_USCI0_RX,
#else
.pdma_perp_tx = NU_PDMA_UNUSED,
.pdma_perp_rx = NU_PDMA_UNUSED,
#endif //BSP_USING_USPI0_PDMA
#endif //BSP_USING_USPI_PDMA
},
#endif
#if defined(BSP_USING_USPI1)
{
.name = "uspi1",
.uspi_base = USPI1,
#if defined(BSP_USING_USPI_PDMA)
#if defined(BSP_USING_USPI1_PDMA)
.pdma_perp_tx = PDMA_USCI1_TX,
.pdma_perp_rx = PDMA_USCI1_RX,
#else
.pdma_perp_tx = NU_PDMA_UNUSED,
.pdma_perp_rx = NU_PDMA_UNUSED,
#endif //BSP_USING_USPI1_PDMA
#endif //BSP_USING_USPI_PDMA
},
#endif
{0}
}; /* uspi nu_uspi */
static rt_err_t nu_uspi_bus_configure(struct rt_spi_device *device,
struct rt_spi_configuration *configuration)
{
struct nu_uspi *uspi_bus;
uint32_t u32SPIMode;
uint32_t u32BusClock;
rt_err_t ret = RT_EOK;
void *pvUserData;
RT_ASSERT(device != RT_NULL);
RT_ASSERT(configuration != RT_NULL);
uspi_bus = (struct nu_uspi *) device->bus;
pvUserData = device->parent.user_data;
/* Check mode */
switch (configuration->mode & RT_SPI_MODE_3)
{
case RT_SPI_MODE_0:
u32SPIMode = USPI_MODE_0;
break;
case RT_SPI_MODE_1:
u32SPIMode = USPI_MODE_1;
break;
case RT_SPI_MODE_2:
u32SPIMode = USPI_MODE_2;
break;
case RT_SPI_MODE_3:
u32SPIMode = USPI_MODE_3;
break;
default:
ret = -RT_EIO;
goto exit_nu_uspi_bus_configure;
}
/* Check data width */
if (!(configuration->data_width == 8 ||
configuration->data_width == 16))
{
ret = -RT_EINVAL;
goto exit_nu_uspi_bus_configure;
}
/* Try to set clock and get actual uspi bus clock */
u32BusClock = USPI_SetBusClock(uspi_bus->uspi_base, configuration->max_hz);
if (configuration->max_hz > u32BusClock)
{
LOG_W("%s clock max frequency is %dHz (!= %dHz)\n", uspi_bus->name, u32BusClock, configuration->max_hz);
configuration->max_hz = u32BusClock;
}
/* Need to initialize new configuration? */
if (rt_memcmp(configuration, &uspi_bus->configuration, sizeof(*configuration)) != 0)
{
rt_memcpy(&uspi_bus->configuration, configuration, sizeof(*configuration));
USPI_Open(uspi_bus->uspi_base, USPI_MASTER, u32SPIMode, configuration->data_width, u32BusClock);
if (configuration->mode & RT_SPI_CS_HIGH)
{
/* Set CS pin to LOW */
if (pvUserData != RT_NULL)
{
// set to LOW */
rt_pin_write(*((rt_base_t *)pvUserData), PIN_LOW);
}
else
{
USPI_SET_SS_LOW(uspi_bus->uspi_base);
}
}
else
{
/* Set CS pin to HIGH */
if (pvUserData != RT_NULL)
{
// set to HIGH */
rt_pin_write(*((rt_base_t *)pvUserData), PIN_HIGH);
}
else
{
/* Set CS pin to HIGH */
USPI_SET_SS_HIGH(uspi_bus->uspi_base);
}
}
if (configuration->mode & RT_SPI_MSB)
{
/* Set sequence to MSB first */
USPI_SET_MSB_FIRST(uspi_bus->uspi_base);
}
else
{
/* Set sequence to LSB first */
USPI_SET_LSB_FIRST(uspi_bus->uspi_base);
}
}
/* Clear USPI RX FIFO */
nu_uspi_drain_rxfifo(uspi_bus->uspi_base);
exit_nu_uspi_bus_configure:
return -(ret);
}
#if defined(BSP_USING_USPI_PDMA)
static void nu_pdma_uspi_rx_cb(void *pvUserData, uint32_t u32EventFilter)
{
rt_err_t result;
struct nu_uspi *uspi_bus = (struct nu_uspi *)pvUserData;
RT_ASSERT(uspi_bus != RT_NULL);
result = rt_sem_release(uspi_bus->m_psSemBus);
RT_ASSERT(result == RT_EOK);
}
static rt_err_t nu_pdma_uspi_rx_config(struct nu_uspi *uspi_bus, uint8_t *pu8Buf, int32_t i32RcvLen, uint8_t bytes_per_word)
{
rt_err_t result;
rt_uint8_t *dst_addr = NULL;
nu_pdma_memctrl_t memctrl = eMemCtl_Undefined;
/* Get base address of uspi register */
USPI_T *uspi_base = uspi_bus->uspi_base;
rt_uint8_t uspi_pdma_rx_chid = uspi_bus->pdma_chanid_rx;
result = nu_pdma_callback_register(uspi_pdma_rx_chid,
nu_pdma_uspi_rx_cb,
(void *)uspi_bus,
NU_PDMA_EVENT_TRANSFER_DONE);
if (result != RT_EOK)
{
goto exit_nu_pdma_uspi_rx_config;
}
if (pu8Buf == RT_NULL)
{
memctrl = eMemCtl_SrcFix_DstFix;
dst_addr = (rt_uint8_t *) &uspi_bus->dummy;
}
else
{
memctrl = eMemCtl_SrcFix_DstInc;
dst_addr = pu8Buf;
}
result = nu_pdma_channel_memctrl_set(uspi_pdma_rx_chid, memctrl);
if (result != RT_EOK)
{
goto exit_nu_pdma_uspi_rx_config;
}
result = nu_pdma_transfer(uspi_pdma_rx_chid,
bytes_per_word * 8,
(uint32_t)&uspi_base->RXDAT,
(uint32_t)dst_addr,
i32RcvLen / bytes_per_word,
0);
exit_nu_pdma_uspi_rx_config:
return result;
}
static rt_err_t nu_pdma_uspi_tx_config(struct nu_uspi *uspi_bus, const uint8_t *pu8Buf, int32_t i32SndLen, uint8_t bytes_per_word)
{
rt_err_t result;
rt_uint8_t *src_addr = NULL;
nu_pdma_memctrl_t memctrl = eMemCtl_Undefined;
/* Get base address of uspi register */
USPI_T *uspi_base = uspi_bus->uspi_base;
rt_uint8_t uspi_pdma_tx_chid = uspi_bus->pdma_chanid_tx;
if (pu8Buf == RT_NULL)
{
uspi_bus->dummy = 0;
memctrl = eMemCtl_SrcFix_DstFix;
src_addr = (rt_uint8_t *)&uspi_bus->dummy;
}
else
{
memctrl = eMemCtl_SrcInc_DstFix;
src_addr = (rt_uint8_t *)pu8Buf;
}
result = nu_pdma_channel_memctrl_set(uspi_pdma_tx_chid, memctrl);
if (result != RT_EOK)
{
goto exit_nu_pdma_uspi_tx_config;
}
result = nu_pdma_transfer(uspi_pdma_tx_chid,
bytes_per_word * 8,
(uint32_t)src_addr,
(uint32_t)&uspi_base->TXDAT,
i32SndLen / bytes_per_word,
0);
exit_nu_pdma_uspi_tx_config:
return result;
}
/**
* USPI PDMA transfer
**/
static rt_ssize_t nu_uspi_pdma_transmit(struct nu_uspi *uspi_bus, const uint8_t *send_addr, uint8_t *recv_addr, int length, uint8_t bytes_per_word)
{
rt_err_t result = RT_EOK;
rt_uint32_t u32Offset = 0;
rt_uint32_t u32TransferCnt = length / bytes_per_word;
rt_uint32_t u32TxCnt = 0;
/* Get base address of uspi register */
USPI_T *uspi_base = uspi_bus->uspi_base;
do
{
u32TxCnt = (u32TransferCnt > NU_PDMA_MAX_TXCNT) ? NU_PDMA_MAX_TXCNT : u32TransferCnt;
result = nu_pdma_uspi_rx_config(uspi_bus, (recv_addr == RT_NULL) ? recv_addr : &recv_addr[u32Offset], (u32TxCnt * bytes_per_word), bytes_per_word);
RT_ASSERT(result == RT_EOK);
result = nu_pdma_uspi_tx_config(uspi_bus, (send_addr == RT_NULL) ? send_addr : &send_addr[u32Offset], (u32TxCnt * bytes_per_word), bytes_per_word);
RT_ASSERT(result == RT_EOK);
/* Trigger TX/RX PDMA transfer. */
USPI_TRIGGER_TX_RX_PDMA(uspi_base);
/* Wait RX-PDMA transfer done */
result = rt_sem_take(uspi_bus->m_psSemBus, RT_WAITING_FOREVER);
RT_ASSERT(result == RT_EOK);
/* Stop TX/RX DMA transfer. */
USPI_DISABLE_TX_RX_PDMA(uspi_base);
u32TransferCnt -= u32TxCnt;
u32Offset += u32TxCnt;
}
while (u32TransferCnt > 0);
return length;
}
static rt_err_t nu_hw_uspi_pdma_allocate(struct nu_uspi *uspi_bus)
{
/* Allocate USPI_TX nu_dma channel */
if ((uspi_bus->pdma_chanid_tx = nu_pdma_channel_allocate(uspi_bus->pdma_perp_tx)) < 0)
{
goto exit_nu_hw_uspi_pdma_allocate;
}
/* Allocate USPI_RX nu_dma channel */
else if ((uspi_bus->pdma_chanid_rx = nu_pdma_channel_allocate(uspi_bus->pdma_perp_rx)) < 0)
{
nu_pdma_channel_free(uspi_bus->pdma_chanid_tx);
goto exit_nu_hw_uspi_pdma_allocate;
}
uspi_bus->m_psSemBus = rt_sem_create("uspibus_sem", 0, RT_IPC_FLAG_FIFO);
RT_ASSERT(uspi_bus->m_psSemBus != RT_NULL);
return RT_EOK;
exit_nu_hw_uspi_pdma_allocate:
return -(RT_ERROR);
}
#endif
static void nu_uspi_drain_rxfifo(USPI_T *uspi_base)
{
while (USPI_IS_BUSY(uspi_base));
// Drain USPI RX FIFO, make sure RX FIFO is empty
while (!USPI_GET_RX_EMPTY_FLAG(uspi_base))
{
USPI_ClearRxBuf(uspi_base);
}
}
static int nu_uspi_read(USPI_T *uspi_base, uint8_t *recv_addr, uint8_t bytes_per_word)
{
int size = 0;
// Read RX data
if (!USPI_GET_RX_EMPTY_FLAG(uspi_base))
{
uint32_t val;
// Read data from USPI RX FIFO
switch (bytes_per_word)
{
case 2:
val = USPI_READ_RX(uspi_base);
nu_set16_le(recv_addr, val);
break;
case 1:
*recv_addr = USPI_READ_RX(uspi_base);
break;
default:
LOG_E("Data length is not supported.\n");
break;
}
size = bytes_per_word;
}
return size;
}
static int nu_uspi_write(USPI_T *uspi_base, const uint8_t *send_addr, uint8_t bytes_per_word)
{
// Wait USPI TX send data
while (USPI_GET_TX_FULL_FLAG(uspi_base));
// Input data to USPI TX
switch (bytes_per_word)
{
case 2:
USPI_WRITE_TX(uspi_base, nu_get16_le(send_addr));
break;
case 1:
USPI_WRITE_TX(uspi_base, *((uint8_t *)send_addr));
break;
default:
LOG_E("Data length is not supported.\n");
break;
}
return bytes_per_word;
}
/**
* @brief USPI bus polling
* @param dev : The pointer of the specified USPI module.
* @param send_addr : Source address
* @param recv_addr : Destination address
* @param length : Data length
*/
static void nu_uspi_transmission_with_poll(struct nu_uspi *uspi_bus,
uint8_t *send_addr, uint8_t *recv_addr, int length, uint8_t bytes_per_word)
{
USPI_T *uspi_base = uspi_bus->uspi_base;
// Write-only
if ((send_addr != RT_NULL) && (recv_addr == RT_NULL))
{
while (length > 0)
{
send_addr += nu_uspi_write(uspi_base, send_addr, bytes_per_word);
length -= bytes_per_word;
}
} // if (send_addr != RT_NULL && recv_addr == RT_NULL)
// Read-only
else if ((send_addr == RT_NULL) && (recv_addr != RT_NULL))
{
uspi_bus->dummy = 0;
while (length > 0)
{
/* Input data to USPI TX FIFO */
length -= nu_uspi_write(uspi_base, (const uint8_t *)&uspi_bus->dummy, bytes_per_word);
/* Read data from USPI RX FIFO */
while (USPI_GET_RX_EMPTY_FLAG(uspi_base));
recv_addr += nu_uspi_read(uspi_base, recv_addr, bytes_per_word);
}
} // else if (send_addr == RT_NULL && recv_addr != RT_NULL)
// Read&Write
else
{
while (length > 0)
{
/* Input data to USPI TX FIFO */
send_addr += nu_uspi_write(uspi_base, send_addr, bytes_per_word);
length -= bytes_per_word;
/* Read data from USPI RX FIFO */
while (USPI_GET_RX_EMPTY_FLAG(uspi_base));
recv_addr += nu_uspi_read(uspi_base, recv_addr, bytes_per_word);
}
} // else
/* Wait USPI RX or drain USPI RX-FIFO */
if (recv_addr)
{
// Wait USPI transmission done
while (USPI_IS_BUSY(uspi_base))
{
while (!USPI_GET_RX_EMPTY_FLAG(uspi_base))
{
recv_addr += nu_uspi_read(uspi_base, recv_addr, bytes_per_word);
}
}
while (!USPI_GET_RX_EMPTY_FLAG(uspi_base))
{
recv_addr += nu_uspi_read(uspi_base, recv_addr, bytes_per_word);
}
}
else
{
/* Clear USPI RX FIFO */
nu_uspi_drain_rxfifo(uspi_base);
}
}
static void nu_uspi_transfer(struct nu_uspi *uspi_bus, uint8_t *tx, uint8_t *rx, int length, uint8_t bytes_per_word)
{
RT_ASSERT(uspi_bus != RT_NULL);
#if defined(BSP_USING_USPI_PDMA)
/* PDMA transfer constrains */
if ((uspi_bus->pdma_chanid_rx >= 0) &&
!((uint32_t)tx % bytes_per_word) &&
!((uint32_t)rx % bytes_per_word) &&
(length >= NU_SPI_USE_PDMA_MIN_THRESHOLD))
nu_uspi_pdma_transmit(uspi_bus, tx, rx, length, bytes_per_word);
else
nu_uspi_transmission_with_poll(uspi_bus, tx, rx, length, bytes_per_word);
#else
nu_uspi_transmission_with_poll(uspi_bus, tx, rx, length, bytes_per_word);
#endif
}
static rt_uint32_t nu_uspi_bus_xfer(struct rt_spi_device *device, struct rt_spi_message *message)
{
struct nu_uspi *uspi_bus;
struct rt_spi_configuration *configuration;
uint8_t bytes_per_word;
void *pvUserData;
RT_ASSERT(device != RT_NULL);
RT_ASSERT(device->bus != RT_NULL);
RT_ASSERT(message != RT_NULL);
uspi_bus = (struct nu_uspi *) device->bus;
configuration = &uspi_bus->configuration;
bytes_per_word = configuration->data_width / 8;
pvUserData = device->parent.user_data;
if ((message->length % bytes_per_word) != 0)
{
/* Say bye. */
LOG_E("%s: error payload length(%d%%%d != 0).\n", uspi_bus->name, message->length, bytes_per_word);
return 0;
}
if (message->length > 0)
{
if (message->cs_take && !(configuration->mode & RT_SPI_NO_CS))
{
if (pvUserData != RT_NULL)
{
if (configuration->mode & RT_SPI_CS_HIGH)
{
// set to HIGH */
rt_pin_write(*((rt_base_t *)pvUserData), PIN_HIGH);
}
else
{
// set to LOW */
rt_pin_write(*((rt_base_t *)pvUserData), PIN_LOW);
}
}
else
{
if (configuration->mode & RT_SPI_CS_HIGH)
{
USPI_SET_SS_HIGH(uspi_bus->uspi_base);
}
else
{
USPI_SET_SS_LOW(uspi_bus->uspi_base);
}
}
}
nu_uspi_transfer(uspi_bus, (uint8_t *)message->send_buf, (uint8_t *)message->recv_buf, message->length, bytes_per_word);
if (message->cs_release && !(configuration->mode & RT_SPI_NO_CS))
{
if (pvUserData != RT_NULL)
{
if (configuration->mode & RT_SPI_CS_HIGH)
{
// set to LOW */
rt_pin_write(*((rt_base_t *)pvUserData), PIN_LOW);
}
else
{
// set to HIGH */
rt_pin_write(*((rt_base_t *)pvUserData), PIN_HIGH);
}
}
else
{
if (configuration->mode & RT_SPI_CS_HIGH)
{
USPI_SET_SS_LOW(uspi_bus->uspi_base);
}
else
{
USPI_SET_SS_HIGH(uspi_bus->uspi_base);
}
}
}
}
return message->length;
}
static int nu_uspi_register_bus(struct nu_uspi *uspi_bus, const char *name)
{
return rt_spi_bus_register(&uspi_bus->dev, name, &nu_uspi_poll_ops);
}
/**
* Hardware USPI Initial
*/
static int rt_hw_uspi_init(void)
{
int i;
for (i = (USPI_START + 1); i < USPI_CNT; i++)
{
nu_uspi_register_bus(&nu_uspi_arr[i], nu_uspi_arr[i].name);
#if defined(BSP_USING_USPI_PDMA)
nu_uspi_arr[i].pdma_chanid_tx = -1;
nu_uspi_arr[i].pdma_chanid_rx = -1;
if ((nu_uspi_arr[i].pdma_perp_tx != NU_PDMA_UNUSED) && (nu_uspi_arr[i].pdma_perp_rx != NU_PDMA_UNUSED))
{
if (nu_hw_uspi_pdma_allocate(&nu_uspi_arr[i]) != RT_EOK)
{
LOG_E("Failed to allocate DMA channels for %s. We will use poll-mode for this bus.\n", nu_uspi_arr[i].name);
}
}
#endif
}
return 0;
}
INIT_DEVICE_EXPORT(rt_hw_uspi_init);
#endif //#if defined(BSP_USING_USPI)