mirror of
https://github.com/RT-Thread/rt-thread.git
synced 2025-11-16 04:24:33 +00:00
390 lines
9.9 KiB
C
390 lines
9.9 KiB
C
/*
|
|
* Copyright (c) 2006-2025 RT-Thread Development Team
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* spi driver for synopsys dw apb spi
|
|
*
|
|
* Change Logs:
|
|
* Date Author Notes
|
|
* 2025-01-03 ZhangJing first version
|
|
*/
|
|
#include <rtconfig.h>
|
|
|
|
#ifdef BSP_USING_DW_SPI
|
|
#include <rtthread.h>
|
|
#include <rtdevice.h>
|
|
#include "drv_dw_spi.h"
|
|
#include <rtdbg.h>
|
|
#include <io.h>
|
|
#ifdef RT_USING_SMART
|
|
#include <ioremap.h>
|
|
#endif
|
|
#define SPI0_BUS_NAME "spi0"
|
|
#define SPI0_BUS_DEVICE0_NAME "spi00"
|
|
|
|
static struct dw_spi dw_spi_device =
|
|
{
|
|
.device_name = "spi0",
|
|
.irq = DW_SPI_IRQ_BASE,
|
|
.max_freq = DW_SPI_MAX_FREQ,
|
|
.bits_per_word = 8,
|
|
.cs = 0,
|
|
.fifo_len = 0,
|
|
.type = DW_SPI_CTRLR0_FRF_MOT,
|
|
};
|
|
|
|
static rt_uint32_t update_control_reg_16(struct dw_spi *dw_spi_dev)
|
|
{
|
|
return ((dw_spi_dev->bits_per_word - 1) << CTRLR0_DFS_SHIFT) |
|
|
(dw_spi_dev->type << DW_SPI_CTRLR0_FRF_SHIFT) |
|
|
(dw_spi_dev->mode << DW_SPI_CTRLR0_MODE_SHIFT) |
|
|
(dw_spi_dev->tmode << DW_SPI_CTRLR0_TMOD_SHIFT);
|
|
}
|
|
|
|
static rt_uint32_t update_control_reg_32(struct dw_spi *dw_spi_dev)
|
|
{
|
|
return ((dw_spi_dev->bits_per_word - 1) << CTRLR0_DFS_32_SHIFT) |
|
|
(dw_spi_dev->type << DW_SPI_CTRLR0_FRF_SHIFT) |
|
|
(dw_spi_dev->mode << DW_SPI_CTRLR0_MODE_SHIFT) |
|
|
(dw_spi_dev->tmode << DW_SPI_CTRLR0_TMOD_SHIFT);
|
|
}
|
|
|
|
static rt_err_t _dw_drv_spi_configure(struct rt_spi_device *device,
|
|
struct rt_spi_configuration *config)
|
|
{
|
|
struct dw_spi *dw_spi_dev = (struct dw_spi *)(device->bus->parent.user_data);
|
|
if (!dw_spi_dev)
|
|
{
|
|
LOG_E("SPI device is null");
|
|
return -RT_ERROR;
|
|
}
|
|
|
|
struct dw_spi_regs *regs = dw_spi_dev->reg;
|
|
writel(0, ®s->ssienr);
|
|
writel(0, ®s->ser);
|
|
writel(0, ®s->imr);
|
|
writel(0, ®s->dmacr);
|
|
|
|
rt_uint32_t ctrl = 0;
|
|
switch (config->mode & (RT_SPI_CPHA | RT_SPI_CPOL))
|
|
{
|
|
case RT_SPI_MODE_0:
|
|
ctrl |= 0;
|
|
break;
|
|
case RT_SPI_MODE_1:
|
|
ctrl |= DW_SPI_CTRLR0_SCPH;
|
|
break;
|
|
case RT_SPI_MODE_2:
|
|
ctrl |= DW_SPI_CTRLR0_SCPOL;
|
|
break;
|
|
case RT_SPI_MODE_3:
|
|
ctrl |= DW_SPI_CTRLR0_SCPH | DW_SPI_CTRLR0_SCPOL;
|
|
break;
|
|
}
|
|
|
|
dw_spi_dev->mode = ctrl;
|
|
|
|
|
|
/* div = dw_spi_dev->max_freq / configuration->max_hz;*/
|
|
rt_uint32_t div = 0;
|
|
div = dw_spi_dev->max_freq / 15625000;
|
|
if (div % 2)
|
|
div++;
|
|
/* TODO: */
|
|
/* write clock divider register */
|
|
writel(div, ®s->baudr);
|
|
|
|
/* write TX threshold register */
|
|
writel(0, ®s->txflr);
|
|
/* write RX threshold register */
|
|
writel(0, ®s->rxflr);
|
|
/* enable spi */
|
|
writel(1, ®s->ssienr);
|
|
|
|
return RT_EOK;
|
|
}
|
|
|
|
static rt_uint32_t min3(rt_uint32_t a, rt_uint32_t b, rt_uint32_t c)
|
|
{
|
|
return (a < b) ? ((a < c) ? a : c) : ((b < c) ? b : c);
|
|
}
|
|
|
|
|
|
static inline rt_uint32_t tx_max(struct dw_spi *dw_spi_dev)
|
|
{
|
|
rt_uint32_t tx_left, tx_room, rxtx_gap;
|
|
struct dw_spi_regs *regs = dw_spi_dev->reg;
|
|
|
|
tx_left = dw_spi_dev->tx_len;
|
|
tx_room = dw_spi_dev->fifo_len - (rt_uint32_t)(readl(®s->txflr));
|
|
|
|
/*
|
|
* Another concern is about the tx/rx mismatch, we
|
|
* thought about using (priv->fifo_len - rxflr - txflr) as
|
|
* one maximum value for tx, but it doesn't cover the
|
|
* data which is out of tx/rx fifo and inside the
|
|
* shift registers. So a control from sw point of
|
|
* view is taken.
|
|
*/
|
|
if (dw_spi_dev->rx != NULL && dw_spi_dev->tx != NULL)
|
|
{
|
|
rxtx_gap = dw_spi_dev->fifo_len - (dw_spi_dev->rx_len - dw_spi_dev->tx_len);
|
|
return min3(tx_left, tx_room, (rt_uint32_t)(rxtx_gap));
|
|
|
|
}
|
|
else
|
|
{
|
|
return tx_left < tx_room ? tx_left : tx_room;
|
|
}
|
|
}
|
|
|
|
static inline rt_uint32_t rx_max(struct dw_spi *dw_spi_dev)
|
|
{
|
|
rt_uint32_t rx_left = dw_spi_dev->rx_len;
|
|
struct dw_spi_regs *regs = dw_spi_dev->reg;
|
|
|
|
rt_uint32_t val = readl(®s->rxflr);
|
|
return (rx_left < val) ? rx_left : val;
|
|
}
|
|
|
|
/* write data to spi */
|
|
static void dw_spi_writer(struct dw_spi *dw_spi_dev)
|
|
{
|
|
rt_uint32_t max = tx_max(dw_spi_dev);
|
|
rt_uint32_t txw = 0xFF;
|
|
struct dw_spi_regs *regs = dw_spi_dev->reg;
|
|
|
|
while (max--)
|
|
{
|
|
/* Set the tx word if the transfer's original "tx" is not null */
|
|
if (dw_spi_dev->tx)
|
|
{
|
|
if (dw_spi_dev->bits_per_word == 8)
|
|
txw = *(rt_uint8_t *)(dw_spi_dev->tx);
|
|
else
|
|
txw = *(rt_uint16_t *)(dw_spi_dev->tx);
|
|
|
|
dw_spi_dev->tx += dw_spi_dev->bits_per_word >> 3;
|
|
}
|
|
writel(txw, ®s->dr);
|
|
dw_spi_dev->tx_len--;
|
|
}
|
|
}
|
|
|
|
/* read data from spi */
|
|
static void dw_spi_reader(struct dw_spi *dw_spi_dev)
|
|
{
|
|
struct dw_spi_regs *regs = dw_spi_dev->reg;
|
|
rt_uint32_t max = rx_max(dw_spi_dev);
|
|
rt_uint16_t rxw;
|
|
|
|
while (max--)
|
|
{
|
|
rxw = readl(®s->dr);
|
|
if (dw_spi_dev->rx)
|
|
{
|
|
if (dw_spi_dev->bits_per_word == 8)
|
|
*(rt_uint8_t *)(dw_spi_dev->rx) = rxw;
|
|
else
|
|
*(rt_uint16_t *)(dw_spi_dev->rx) = rxw;
|
|
|
|
dw_spi_dev->rx += dw_spi_dev->bits_per_word >> 3;
|
|
|
|
}
|
|
dw_spi_dev->rx_len--;
|
|
}
|
|
}
|
|
|
|
static void wait_for_idle(struct dw_spi *dw_spi_dev)
|
|
{
|
|
rt_uint32_t status;
|
|
struct dw_spi_regs *regs = dw_spi_dev->reg;
|
|
|
|
while (1)
|
|
{
|
|
status = readl(®s->sr);
|
|
if (!(status & DW_SPI_SR_BUSY))
|
|
{
|
|
if (status & DW_SPI_SR_TX_EMPTY)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
rt_thread_yield();
|
|
}
|
|
}
|
|
|
|
static int dw_spi_poll_transfer(struct dw_spi *dw_spi_dev)
|
|
{
|
|
do
|
|
{
|
|
dw_spi_writer(dw_spi_dev);
|
|
wait_for_idle(dw_spi_dev);
|
|
dw_spi_reader(dw_spi_dev);
|
|
}
|
|
while (dw_spi_dev->rx_len && dw_spi_dev->tx_len);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static rt_ssize_t _dw_spixfer(struct rt_spi_device *device, struct rt_spi_message *message)
|
|
{
|
|
struct dw_spi *dw_spi_dev = (struct dw_spi *)(device->bus->parent.user_data);
|
|
if (!dw_spi_dev)
|
|
{
|
|
LOG_E("SPI device is null");
|
|
return -RT_ERROR;
|
|
}
|
|
|
|
dw_spi_dev->tx = (void *)message->send_buf;
|
|
dw_spi_dev->tx_end = dw_spi_dev->tx + dw_spi_dev->tx_len;
|
|
|
|
dw_spi_dev->rx = (void *)message->recv_buf;
|
|
dw_spi_dev->rx_end = dw_spi_dev->rx + dw_spi_dev->rx_len;
|
|
|
|
dw_spi_dev->tx_len = message->length;
|
|
dw_spi_dev->rx_len = message->length;
|
|
|
|
if (dw_spi_dev->tx && dw_spi_dev->rx)
|
|
{
|
|
/* set mode to Tx & Rx */
|
|
dw_spi_dev->tmode = DW_SPI_CTRLR0_TMOD_TX_RX;
|
|
}
|
|
else if (dw_spi_dev->rx)
|
|
{
|
|
/* set mode to Rx only */
|
|
dw_spi_dev->tmode = DW_SPI_CTRLR0_TMOD_RX;
|
|
}
|
|
else
|
|
{
|
|
/* set mode to Tx only */
|
|
dw_spi_dev->tmode = DW_SPI_CTRLR0_TMOD_TX;
|
|
}
|
|
|
|
struct dw_spi_regs *regs = dw_spi_dev->reg;
|
|
/* disable spi */
|
|
writel(0, ®s->ssienr);
|
|
|
|
LOG_D("dw_spixfer:tx 0x%x rx 0x%x len %d\n", dw_spi_dev->tx, dw_spi_dev->rx, message->length);
|
|
|
|
rt_uint32_t cr0 = dw_spi_dev->update_cr0(dw_spi_dev);
|
|
/* write control register */
|
|
if (cr0 != readl(®s->ctrl0))
|
|
{
|
|
writel(cr0, ®s->ctrl0);
|
|
}
|
|
/* set slave select */
|
|
writel(1 << (dw_spi_dev->cs), ®s->ser);
|
|
|
|
/* enable spi */
|
|
writel(1, ®s->ssienr);
|
|
/* transfer data */
|
|
dw_spi_poll_transfer(dw_spi_dev);
|
|
|
|
/* wait for transfer complete */
|
|
wait_for_idle(dw_spi_dev);
|
|
|
|
return message->length;
|
|
}
|
|
|
|
const static struct rt_spi_ops dw_drv_spi_ops =
|
|
{
|
|
_dw_drv_spi_configure,
|
|
_dw_spixfer,
|
|
};
|
|
|
|
|
|
static int dw_spi_hw_init(struct dw_spi *dw_spi_dev)
|
|
{
|
|
struct dw_spi_regs *regs = dw_spi_dev->reg;
|
|
|
|
/* disable spi */
|
|
writel(0, ®s->ssienr);
|
|
/* disbale all interrupts */
|
|
writel(0, ®s->imr);
|
|
/* read and clear interrupt status */
|
|
readl(®s->icr);
|
|
/* set slave select */
|
|
writel(0, ®s->ser);
|
|
/* enable spi */
|
|
writel(1, ®s->ssienr);
|
|
|
|
dw_spi_dev->version = readl(®s->version);
|
|
rt_kprintf("ssi_version_id=%c.%c%c%c\n",
|
|
dw_spi_dev->version >> 24, dw_spi_dev->version >> 16,
|
|
dw_spi_dev->version >> 8, dw_spi_dev->version);
|
|
|
|
/* detect the FIFO depth if not set by interface driver */
|
|
if (!dw_spi_dev->fifo_len)
|
|
{
|
|
uint32_t fifo;
|
|
for (fifo = 1; fifo < 256; fifo++)
|
|
{
|
|
writel(fifo, ®s->txftlr);
|
|
if (fifo != readl(®s->txftlr))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
dw_spi_dev->fifo_len = (fifo == 1) ? 0 : fifo;
|
|
writel(0, ®s->txftlr);
|
|
rt_kprintf("fifo length is %d\n", dw_spi_dev->fifo_len);
|
|
}
|
|
|
|
rt_uint32_t cr0, tmp = readl(®s->ctrl0);
|
|
writel(0, ®s->ssienr);
|
|
|
|
writel(0xffffffff, ®s->ctrl0);
|
|
cr0 = readl(®s->ctrl0);
|
|
writel(tmp, ®s->ctrl0);
|
|
|
|
writel(1, ®s->ssienr);
|
|
|
|
if (cr0 & DW_SPI_CTRLR0_DFS_MASK)
|
|
{
|
|
dw_spi_device.update_cr0 = update_control_reg_16;
|
|
}
|
|
else
|
|
{
|
|
dw_spi_device.update_cr0 = update_control_reg_32;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int rt_hw_dw_spi_init(void)
|
|
{
|
|
rt_err_t ret = RT_EOK;
|
|
struct dw_spi_regs *reg = NULL;
|
|
|
|
/* set reg base */
|
|
#ifdef RT_USING_SMART
|
|
reg = (struct dw_spi_regs *)rt_ioremap((void *)DW_SPI_BASE_ADDR, sizeof(struct dw_spi_regs));
|
|
#else
|
|
reg = (struct dw_spi_regs *)DW_SPI_BASE_ADDR;
|
|
#endif
|
|
LOG_D("%s: reg base %p\n", __func__, reg);
|
|
if (!reg)
|
|
return -RT_ERROR;
|
|
|
|
dw_spi_device.reg = reg;
|
|
dw_spi_device.spi_bus.parent.user_data = &dw_spi_device;
|
|
|
|
dw_spi_device.tmode = 0; /* Tx & Rx */
|
|
dw_spi_device.bits_per_word = 8;
|
|
|
|
/* init spi */
|
|
dw_spi_hw_init(&dw_spi_device);
|
|
|
|
/* Register SPI bus*/
|
|
ret = rt_spi_bus_register(&dw_spi_device.spi_bus, dw_spi_device.device_name, &dw_drv_spi_ops);
|
|
if (ret == RT_EOK)
|
|
{
|
|
static struct rt_spi_device spi_device0;
|
|
rt_spi_bus_attach_device(&spi_device0, SPI0_BUS_DEVICE0_NAME, SPI0_BUS_NAME, (void *)&dw_spi_device);
|
|
}
|
|
return ret;
|
|
}
|
|
INIT_BOARD_EXPORT(rt_hw_dw_spi_init);
|
|
|
|
#endif /* BSP_USING_DW_SPI */ |