u-boot/drivers/spi/cvitek-spif.c
Yilin Sun 3c3fcb59c0
CV1800B: Added CVITEK-SPIF driver.
Signed-off-by: Yilin Sun <imi415@imi.moe>
2023-09-10 21:39:58 +08:00

499 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2023 Yilin Sun <imi415@imi.moe>
*/
#include <common.h>
#include <clk.h>
#include <dm.h>
#include <dm/device_compat.h>
#include <spi.h>
#include <spi-mem.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
#include <linux/ioport.h>
#include <linux/sizes.h>
#define CVI_SPIF_BUS_WIDTH_1 0x00
#define CVI_SPIF_BUS_WIDTH_2 0x01
#define CVI_SPIF_BUS_WIDTH_4 0x02
#define CVI_SPIF_FIFO_DEPTH 8U
/**
* SPIF SPI_CTRL register
*
*/
#define CVI_SPIF_SPI_CTRL_SCK_DIV_SHIFT 0
#define CVI_SPIF_SPI_CTRL_SCK_DIV_MASK GENMASK(10, 0) /* Clock divider, assume F/(x+1) */
#define CVI_SPIF_SPI_CTRL_CPHA_SHIFT 12
#define CVI_SPIF_SPI_CTRL_CPHA_MASK BIT(12) /* Clock phase */
#define CVI_SPIF_SPI_CTRL_CPOL_SHIFT 13
#define CVI_SPIF_SPI_CTRL_CPOL_MASK BIT(13) /* Clock polarity */
#define CVI_SPIF_SPI_CTRL_HOLD_OL_MASK BIT(14) /* HOLD output low?*/
#define CVI_SPIF_SPI_CTRL_WP_OL_MASK BIT(15) /* WP output low? */
#define CVI_SPIF_SPI_CTRL_FRAME_LEN_MASK BIT(16) /* Frame length */
#define CVI_SPIF_SPI_CTRL_LSB_FIRST_MASK BIT(20) /* LSB first SPI */
#define CVI_SPIF_SPI_CTRL_SRST_MASK BIT(21) /* Software Reset? */
/**
* SPIF CE_CTRL register
*
*/
#define CVI_SPIF_CE_CTRL_CEMANUAL_MASK BIT(0) /* Manual CE control value */
#define CVI_SPIF_CE_CTRL_CEMANUAL_EN_MASK BIT(1) /* Manual CE control enable */
/**
* SPIF DLY_CTRL register
*
*/
#define CVI_SPIF_DLY_CTRL_CET_MASK GENMASK(9, 8)
#define CVI_SPIF_DLY_CTRL_NEG_SAMPLE_MASK BIT(14)
/**
* SPIF DMMR register
*
*/
#define CVI_SPIF_DMMR_EN_MASK BIT(0) /* Direct memory-mapped mode enable */
/**
* SPIF TRANS_CSR register
*
*/
#define CVI_SPIF_TRANS_CSR_MODE_SHIFT 0
#define CVI_SPIF_TRANS_CSR_MODE_MASK GENMASK(1, 0) /* Mode, 01: Read, 10: Write */
#define CVI_SPIF_TRANS_CSR_MODE(x) \
((x << CVI_SPIF_TRANS_CSR_MODE_SHIFT) & CVI_SPIF_TRANS_CSR_MODE_MASK)
#define CVI_SPIF_TRANS_CSR_CONT_READ_MASK BIT(2) /* Continous mode read? */
#define CVI_SPIF_TRANS_CSR_FAST_MODE_MASK BIT(3) /* Fast mode (which)? */
#define CVI_SPIF_TRANS_CSR_BUS_WIDTH_SHIFT 4
#define CVI_SPIF_TRANS_CSR_BUS_WIDTH_MASK GENMASK(5, 4) /* Bus width, 2^x bits */
#define CVI_SPIF_TRANS_CSR_BUS_WIDTH(x) \
((x << CVI_SPIF_TRANS_CSR_BUS_WIDTH_SHIFT) & CVI_SPIF_TRANS_CSR_BUS_WIDTH_MASK)
#define CVI_SPIF_TRANS_CSR_DMA_EN_MASK BIT(6) /* DMA request enable? */
#define CVI_SPIF_TRANS_CSR_MISO_LVL_MASK BIT(7) /* Read MISO level? */
#define CVI_SPIF_TRANS_CSR_ADDR_BYTES_MASK GENMASK(10, 8) /* Address bytes? */
#define CVI_SPIF_TRANS_CSR_WITH_CMD_MASK BIT(11) /* ??? */
#define CVI_SPIF_TRANS_CSR_FIFO_TRIG_LVL_SHIFT 12
#define CVI_SPIF_TRANS_CSR_FIFO_TRIG_LVL_MASK GENMASK(13, 12) /* FIFO (IRQ?) trigger level, 2^x bytes */
#define CVI_SPIF_TRANS_CSR_FIFO_TRIG_LVL(x) \
((x << CVI_SPIF_TRANS_CSR_FIFO_TRIG_LVL_SHIFT) & CVI_SPIF_TRANS_CSR_FIFO_TRIG_LVL_MASK)
#define CVI_SPIF_TRANS_CSR_GO_BUSY_MASK BIT(15) /* Write 1 to GO? Self-cleared bit? */
#define CVI_SPIF_TRANS_CSR_DUMMY_MASK GENMASK(19, 16) /* Dummy count? */
#define CVI_SPIF_TRANS_CSR_4B_ADDR_MASK BIT(20) /* 4 bytes address */
#define CVI_SPIF_TRANS_CSR_4B_CMD_MASK BIT(21) /* 4 bytes command */
/**
* SPIF INT_STS register
*
*/
#define CVI_SPIF_INT_STS_TRANS_DONE_MASK BIT(0)
#define CVI_SPIF_INT_STS_RD_FIFO_MASK BIT(2)
/*
* Note: All registers are mapped into the XIP space:
* Read access:
* - If DMMR_EN = 1: Memory contents
* - If DMMR_EN = 0: Register contents
* Write access: Register contents (DMMR_EN ignored)
*
*/
struct cvitek_spif_regs {
u32 spi_ctrl; /* Offset: 0x00 */
u32 ce_ctrl; /* Offset: 0x04 */
u32 dly_ctrl; /* Offset: 0x08 */
u32 dmmr; /* Offset: 0x0C */
u32 trans_csr; /* Offset: 0x10 */
u32 trans_num; /* Offset: 0x14 */
u32 ff_port; /* Offset: 0x18 */
u32 reserved0; /* Offset: 0x1C */
u32 ff_pt; /* Offset: 0x20 */
u32 reserved1; /* Offset: 0x24 */
u32 int_sts; /* Offset: 0x28 */
u32 int_en; /* Offset: 0x2C */
};
struct cvitek_spif_xfer {
u8 width; /* Bus width, 0: 1bit, 1: 2bits, 2: 4bits */
union {
u8 *in;
const u8 *out;
} data;
u32 length; /* Length in bytes */
};
struct cvitek_spif_priv {
struct cvitek_spif_regs *regs;
u32 clk_rate;
unsigned int cs;
};
static int cvitek_spif_claim_bus(struct udevice *dev)
{
struct cvitek_spif_priv *priv = dev_get_priv(dev->parent);
struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(dev);
priv->cs = slave_plat->cs;
if(priv->cs > 0)
return -ENODEV;
clrbits_le32(&priv->regs->ce_ctrl, CVI_SPIF_CE_CTRL_CEMANUAL_EN_MASK);
dev_dbg(dev, "claim_bus cs: %d\n", priv->cs);
return 0;
}
static int cvitek_spif_release_bus(struct udevice *dev)
{
struct cvitek_spif_priv *priv = dev_get_priv(dev->parent);
clrbits_le32(&priv->regs->ce_ctrl, CVI_SPIF_CE_CTRL_CEMANUAL_EN_MASK);
dev_dbg(dev, "release_bus cs: %d\n", priv->cs);
return 0;
}
static int cvitek_spif_set_speed(struct udevice *bus, uint speed)
{
struct cvitek_spif_priv *priv = dev_get_priv(bus);
u8 div_val = DIV_ROUND_UP(priv->clk_rate, speed) - 1;
u32 dly_ctrl = CVI_SPIF_DLY_CTRL_CET_MASK;
clrsetbits_le32(&priv->regs->spi_ctrl,
CVI_SPIF_SPI_CTRL_SCK_DIV_MASK,
(div_val << CVI_SPIF_SPI_CTRL_SCK_DIV_SHIFT));
if(speed > 30000000)
dly_ctrl |= CVI_SPIF_DLY_CTRL_NEG_SAMPLE_MASK;
writel(dly_ctrl, &priv->regs->dly_ctrl);
dev_dbg(bus, "set_speed to %dHz, div: %d\n", speed, div_val);
return 0;
}
static int cvitek_spif_set_mode(struct udevice *bus, uint mode)
{
struct cvitek_spif_priv *priv = dev_get_priv(bus);
u32 mode_mask = 0U;
if(mode & SPI_CPHA)
mode_mask |= CVI_SPIF_SPI_CTRL_CPHA_MASK;
if(mode & SPI_CPOL)
mode_mask |= CVI_SPIF_SPI_CTRL_CPOL_MASK;
clrsetbits_le32(&priv->regs->spi_ctrl,
(CVI_SPIF_SPI_CTRL_CPOL_MASK | CVI_SPIF_SPI_CTRL_CPHA_MASK),
mode_mask);
dev_dbg(bus, "set_mode to %d\n", mode);
return 0;
}
static int cvitek_spif_read(struct cvitek_spif_priv *priv, struct cvitek_spif_xfer *xfer)
{
u32 trans_ctrl;
u32 i;
u8 width;
switch(xfer->width)
{
case 1:
width = CVI_SPIF_BUS_WIDTH_1;
break;
case 2:
width = CVI_SPIF_BUS_WIDTH_2;
break;
case 4:
width = CVI_SPIF_BUS_WIDTH_4;
break;
default:
return -EINVAL;
}
trans_ctrl |= CVI_SPIF_TRANS_CSR_FIFO_TRIG_LVL(3);
trans_ctrl |= CVI_SPIF_TRANS_CSR_BUS_WIDTH(width);
trans_ctrl |= CVI_SPIF_TRANS_CSR_MODE(1); /* RX mode */
/* Set up transfer mode */
writel(trans_ctrl, &priv->regs->trans_csr);
/* Write transfer length in bytes */
writel(xfer->length, &priv->regs->trans_num);
/* Reset FIFO pointer */
writel(0U, &priv->regs->ff_pt);
/* Start transfer */
setbits_le32(&priv->regs->trans_csr, CVI_SPIF_TRANS_CSR_GO_BUSY_MASK);
for(i = 0; i < xfer->length; i++)
{
/* Wait for FIFO not empty */
while((readl(&priv->regs->ff_pt) & 0x0F) == 0U)
;
xfer->data.in[i] = readb(&priv->regs->ff_port);
}
while((readl(&priv->regs->int_sts) & CVI_SPIF_INT_STS_TRANS_DONE_MASK) == 0U)
;
/* Clear interrupt status */
writeb(0U, &priv->regs->int_sts);
return 0;
}
static int cvitek_spif_write(struct cvitek_spif_priv *priv, struct cvitek_spif_xfer *xfer)
{
u32 trans_ctrl;
u32 i;
u8 width;
switch(xfer->width)
{
case 1:
width = CVI_SPIF_BUS_WIDTH_1;
break;
case 2:
width = CVI_SPIF_BUS_WIDTH_2;
break;
case 4:
width = CVI_SPIF_BUS_WIDTH_4;
break;
default:
return -EINVAL;
}
trans_ctrl |= CVI_SPIF_TRANS_CSR_FIFO_TRIG_LVL(3);
trans_ctrl |= CVI_SPIF_TRANS_CSR_BUS_WIDTH(width);
trans_ctrl |= CVI_SPIF_TRANS_CSR_MODE(2); /* TX mode */
/* Set up transfer mode */
writel(trans_ctrl, &priv->regs->trans_csr);
/* Write transfer length in bytes */
writel(xfer->length, &priv->regs->trans_num);
/* Reset FIFO pointer */
writel(0U, &priv->regs->ff_pt);
/* Start transfer */
setbits_le32(&priv->regs->trans_csr, CVI_SPIF_TRANS_CSR_GO_BUSY_MASK);
for(i = 0; i < xfer->length; i++)
{
/* Wait for FIFO not full */
while((readl(&priv->regs->ff_pt) & 0x0F) >= CVI_SPIF_FIFO_DEPTH)
;
writeb(xfer->data.out[i], &priv->regs->ff_port);
}
while((readl(&priv->regs->int_sts) & CVI_SPIF_INT_STS_TRANS_DONE_MASK) == 0U)
;
/* Clear interrupt status */
writeb(0U, &priv->regs->int_sts);
return 0;
}
static int cvitek_spif_exec_op(struct spi_slave *slave, const struct spi_mem_op *op)
{
struct cvitek_spif_priv *priv = dev_get_priv(slave->dev->parent);
struct cvitek_spif_xfer xfer;
u8 buf[4];
int ret;
int i;
/* Use manual CE control mode */
writel((CVI_SPIF_CE_CTRL_CEMANUAL_MASK | CVI_SPIF_CE_CTRL_CEMANUAL_EN_MASK), &priv->regs->ce_ctrl);
clrbits_le32(&priv->regs->ce_ctrl, CVI_SPIF_CE_CTRL_CEMANUAL_MASK);
if(op->cmd.nbytes)
{
if(op->cmd.nbytes == 1U)
buf[0] = op->cmd.opcode & 0xFFU;
else
{
buf[0] = (op->cmd.opcode >> 8U) & 0xFFU;
buf[1] = op->cmd.opcode & 0xFFU;
}
xfer.data.out = buf;
xfer.length = op->cmd.nbytes;
xfer.width = op->cmd.buswidth;
ret = cvitek_spif_write(priv, &xfer);
if(ret < 0)
return ret;
}
if(op->addr.nbytes)
{
if(op->addr.nbytes == 3U)
{
buf[0] = (op->addr.val >> 16U) & 0xFFU;
buf[1] = (op->addr.val >> 8U) & 0xFFU;
buf[2] = op->addr.val & 0xFFU;
}
else if(op->addr.nbytes == 4U)
{
buf[0] = (op->addr.val >> 24U) & 0xFFU;
buf[1] = (op->addr.val >> 16U) & 0xFFU;
buf[2] = (op->addr.val >> 8U) & 0xFFU;
buf[3] = op->addr.val & 0xFFU;
}
else
return -EINVAL;
xfer.data.out = buf;
xfer.length = op->addr.nbytes;
xfer.width = op->addr.buswidth;
ret = cvitek_spif_write(priv, &xfer);
if(ret < 0)
return ret;
}
if(op->dummy.nbytes)
{
xfer.data.in = buf;
xfer.length = 1;
xfer.width = op->dummy.buswidth;
for(i = 0; i < op->dummy.nbytes; i++)
{
ret = cvitek_spif_write(priv, &xfer);
if(ret < 0)
return ret;
}
}
if(op->data.nbytes && (op->data.dir != SPI_MEM_NO_DATA))
{
xfer.length = op->data.nbytes;
xfer.width = op->data.buswidth;
if(op->data.dir == SPI_MEM_DATA_IN)
{
xfer.data.in = op->data.buf.in;
ret = cvitek_spif_read(priv, &xfer);
}
if(op->data.dir == SPI_MEM_DATA_OUT)
{
xfer.data.out = op->data.buf.out;
ret = cvitek_spif_write(priv, &xfer);
}
if(ret < 0)
return ret;
}
setbits_le32(&priv->regs->ce_ctrl, CVI_SPIF_CE_CTRL_CEMANUAL_MASK);
return 0;
}
static int cvitek_spif_probe(struct udevice *bus)
{
int ret;
struct clk clk;
struct cvitek_spif_priv *priv = dev_get_priv(bus);
priv->regs = dev_read_addr_ptr(bus);
if(!priv->regs)
return -EINVAL;
ret = clk_get_by_index(bus, 0, &clk);
if(ret < 0)
return ret;
ret = clk_enable(&clk);
if(ret)
{
dev_err(bus, "failed to enable clock\n");
return ret;
}
priv->clk_rate = clk_get_rate(&clk);
if(!priv->clk_rate)
{
dev_err(bus, "failed to get clock rate\n");
clk_disable(&clk);
return -EINVAL;
}
/* Disable direct memory-mapped mode */
writeb(0U, &priv->regs->dmmr);
dev_dbg(bus, "cvitek-spi probed on addr %p\r\n", priv->regs);
return 0;
}
static const struct spi_controller_mem_ops cvitek_spif_mem_ops = {
.exec_op = cvitek_spif_exec_op,
};
static const struct dm_spi_ops cvitek_spif_ops = {
.claim_bus = cvitek_spif_claim_bus,
.release_bus = cvitek_spif_release_bus,
.set_speed = cvitek_spif_set_speed,
.set_mode = cvitek_spif_set_mode,
.mem_ops = &cvitek_spif_mem_ops,
};
static const struct udevice_id cvitek_spif_ids[] = {
{ .compatible = "cvitek,cvitek-spif" },
{ }
};
U_BOOT_DRIVER(cvitek_spif) = {
.name = "cvitek_spif",
.id = UCLASS_SPI,
.of_match = cvitek_spif_ids,
.ops = &cvitek_spif_ops,
.priv_auto = sizeof(struct cvitek_spif_priv),
.probe = cvitek_spif_probe,
};