mirror of
https://github.com/u-boot/u-boot.git
synced 2025-04-23 05:08:24 +00:00

Add spi nor flash controller driver for cv1800b SoC Signed-off-by: Kongyang Liu <seashell11234455@gmail.com> Reviewed-by: Leo Yu-Chi Liang <ycliang@andestech.com>
321 lines
8.5 KiB
C
321 lines
8.5 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright (c) 2024, Kongyang Liu <seashell11234455@gmail.com>
|
|
*/
|
|
|
|
#include <clk.h>
|
|
#include <dm.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <spi-mem.h>
|
|
#include <spi.h>
|
|
#include <spi_flash.h>
|
|
#include <wait_bit.h>
|
|
|
|
#define CV1800B_SPI_CTRL_SCK_DIV_MASK GENMASK(10, 0)
|
|
#define CV1800B_SPI_CTRL_CPHA BIT(12)
|
|
#define CV1800B_SPI_CTRL_CPOL BIT(13)
|
|
|
|
#define CV1800B_SPI_CE_MANUAL BIT(0)
|
|
#define CV1800B_SPI_CE_MANUAL_EN BIT(1)
|
|
#define CV1800B_SPI_CE_ENABLE (CV1800B_SPI_CE_MANUAL | \
|
|
CV1800B_SPI_CE_MANUAL_EN)
|
|
#define CV1800B_SPI_CE_DISABLE CV1800B_SPI_CE_MANUAL_EN
|
|
#define CV1800B_SPI_CE_HARDWARE 0
|
|
|
|
#define CV1800B_SPI_DLY_CTRL_NEG_SAMPLE BIT(14)
|
|
|
|
#define CV1800B_SPI_TRAN_MODE_RX BIT(0)
|
|
#define CV1800B_SPI_TRAN_MODE_TX BIT(1)
|
|
#define CV1800B_SPI_TRAN_FAST_MODE BIT(3)
|
|
#define CV1800B_SPI_TRAN_BUS_WIDTH_1_BIT 0x0
|
|
#define CV1800B_SPI_TRAN_BUS_WIDTH_2_BIT BIT(4)
|
|
#define CV1800B_SPI_TRAN_BUS_WIDTH_4_BIT BIT(5)
|
|
#define CV1800B_SPI_TRAN_ADDR_3_BYTES (3 << 8)
|
|
#define CV1800B_SPI_TRAN_ADDR_4_BYTES (4 << 8)
|
|
#define CV1800B_SPI_TRAN_WITH_CMD BIT(11)
|
|
#define CV1800B_SPI_TRAN_GO_BUSY BIT(15)
|
|
#define CV1800B_SPI_TRAN_DUMMY_CYC_MASK GENMASK(19, 16)
|
|
#define CV1800B_SPI_TRAN_DUMMY_CYC_OFFSET 16
|
|
#define CV1800B_SPI_TRAN_BYTE4_EN BIT(20)
|
|
#define CV1800B_SPI_TRAN_BYTE4_CMD BIT(21)
|
|
|
|
#define CV1800B_SPI_FF_PT_AVAILABLE_MASK GENMASK(3, 0)
|
|
|
|
#define CV1800B_SPI_INT_TRAN_DONE BIT(0)
|
|
#define CV1800B_SPI_INT_RD_FIFO BIT(2)
|
|
#define CV1800B_SPI_INT_WR_FIFO BIT(3)
|
|
|
|
#define CV1800B_FIFO_CAPACITY 8
|
|
#define CV1800B_DEFAULT_DIV 4
|
|
|
|
struct cv1800b_spif_regs {
|
|
u32 spi_ctrl;
|
|
u32 ce_ctrl;
|
|
u32 dly_ctrl;
|
|
u32 dmmr_ctrl;
|
|
u32 tran_csr;
|
|
u32 tran_num;
|
|
u32 ff_port;
|
|
u32 reserved0;
|
|
u32 ff_pt;
|
|
u32 reserved1;
|
|
u32 int_sts;
|
|
u32 int_en;
|
|
};
|
|
|
|
struct cv1800b_spi_priv {
|
|
struct cv1800b_spif_regs *regs;
|
|
uint clk_freq;
|
|
uint mode;
|
|
int div;
|
|
};
|
|
|
|
static int cv1800b_spi_probe(struct udevice *bus)
|
|
{
|
|
struct cv1800b_spi_priv *priv = dev_get_priv(bus);
|
|
struct clk clkdev;
|
|
int ret;
|
|
|
|
priv->regs = (struct cv1800b_spif_regs *)dev_read_addr_ptr(bus);
|
|
if (priv->regs == 0)
|
|
return -EINVAL;
|
|
|
|
ret = clk_get_by_index(bus, 0, &clkdev);
|
|
if (ret)
|
|
return ret;
|
|
priv->clk_freq = clk_get_rate(&clkdev);
|
|
|
|
/* DMMR mode is enabled by default, disable it */
|
|
writel(0, &priv->regs->dmmr_ctrl);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void cv1800b_spi_config_dmmr(struct cv1800b_spi_priv *priv, struct spi_nor *flash)
|
|
{
|
|
struct cv1800b_spif_regs *regs = priv->regs;
|
|
u32 read_cmd = flash->read_opcode;
|
|
u32 val;
|
|
|
|
val = CV1800B_SPI_TRAN_MODE_RX | CV1800B_SPI_TRAN_WITH_CMD;
|
|
|
|
switch (read_cmd) {
|
|
case SPINOR_OP_READ_4B:
|
|
case SPINOR_OP_READ_FAST_4B:
|
|
case SPINOR_OP_READ_1_1_2_4B:
|
|
case SPINOR_OP_READ_1_1_4_4B:
|
|
val |= CV1800B_SPI_TRAN_ADDR_4_BYTES |
|
|
CV1800B_SPI_TRAN_BYTE4_EN | CV1800B_SPI_TRAN_BYTE4_CMD;
|
|
break;
|
|
case SPINOR_OP_READ:
|
|
case SPINOR_OP_READ_FAST:
|
|
case SPINOR_OP_READ_1_1_2:
|
|
case SPINOR_OP_READ_1_1_4:
|
|
val |= CV1800B_SPI_TRAN_ADDR_3_BYTES;
|
|
break;
|
|
}
|
|
|
|
switch (read_cmd) {
|
|
case SPINOR_OP_READ_FAST:
|
|
case SPINOR_OP_READ_FAST_4B:
|
|
val |= CV1800B_SPI_TRAN_FAST_MODE;
|
|
break;
|
|
}
|
|
|
|
switch (read_cmd) {
|
|
case SPINOR_OP_READ_1_1_2:
|
|
case SPINOR_OP_READ_1_1_2_4B:
|
|
val |= CV1800B_SPI_TRAN_BUS_WIDTH_2_BIT;
|
|
break;
|
|
case SPINOR_OP_READ_1_1_4:
|
|
case SPINOR_OP_READ_1_1_4_4B:
|
|
val |= CV1800B_SPI_TRAN_BUS_WIDTH_4_BIT;
|
|
break;
|
|
}
|
|
|
|
val |= (flash->read_dummy & CV1800B_SPI_TRAN_DUMMY_CYC_MASK)
|
|
<< CV1800B_SPI_TRAN_DUMMY_CYC_OFFSET;
|
|
writel(val, ®s->tran_csr);
|
|
}
|
|
|
|
static void cv1800b_set_clk_div(struct cv1800b_spi_priv *priv, u32 div)
|
|
{
|
|
struct cv1800b_spif_regs *regs = priv->regs;
|
|
u32 neg_sample = 0;
|
|
|
|
clrsetbits_le32(®s->spi_ctrl, CV1800B_SPI_CTRL_SCK_DIV_MASK, div);
|
|
|
|
if (div < CV1800B_DEFAULT_DIV)
|
|
neg_sample = CV1800B_SPI_DLY_CTRL_NEG_SAMPLE;
|
|
clrsetbits_le32(®s->dly_ctrl, CV1800B_SPI_DLY_CTRL_NEG_SAMPLE, neg_sample);
|
|
}
|
|
|
|
static int cv1800b_spi_transfer(struct cv1800b_spi_priv *priv,
|
|
u8 *din, const u8 *dout, uint len, ulong flags)
|
|
{
|
|
struct cv1800b_spif_regs *regs = priv->regs;
|
|
u32 tran_csr;
|
|
u32 xfer_size, off;
|
|
u32 fifo_cnt;
|
|
u32 interrupt_mask;
|
|
|
|
if (din) {
|
|
/* Slow down on receiving */
|
|
cv1800b_set_clk_div(priv, CV1800B_DEFAULT_DIV);
|
|
interrupt_mask = CV1800B_SPI_INT_RD_FIFO;
|
|
} else {
|
|
interrupt_mask = CV1800B_SPI_INT_WR_FIFO;
|
|
}
|
|
|
|
writel(0, ®s->ff_pt);
|
|
writel(len, ®s->tran_num);
|
|
|
|
tran_csr = CV1800B_SPI_TRAN_GO_BUSY;
|
|
if (din) {
|
|
tran_csr |= CV1800B_SPI_TRAN_MODE_RX;
|
|
} else {
|
|
tran_csr |= CV1800B_SPI_TRAN_MODE_TX;
|
|
if (!(flags & SPI_XFER_BEGIN) && (priv->mode & SPI_TX_QUAD))
|
|
tran_csr |= CV1800B_SPI_TRAN_BUS_WIDTH_4_BIT;
|
|
}
|
|
writel(tran_csr, ®s->tran_csr);
|
|
|
|
wait_for_bit_le32(®s->int_sts, interrupt_mask, true, 3000, false);
|
|
|
|
off = 0;
|
|
while (off < len) {
|
|
xfer_size = min_t(u32, len - off, CV1800B_FIFO_CAPACITY);
|
|
|
|
fifo_cnt = readl(®s->ff_pt) & CV1800B_SPI_FF_PT_AVAILABLE_MASK;
|
|
if (din)
|
|
xfer_size = min(xfer_size, fifo_cnt);
|
|
else
|
|
xfer_size = min(xfer_size, CV1800B_FIFO_CAPACITY - fifo_cnt);
|
|
|
|
while (xfer_size--) {
|
|
if (din)
|
|
din[off++] = readb(®s->ff_port);
|
|
else
|
|
writeb(dout[off++], ®s->ff_port);
|
|
}
|
|
}
|
|
|
|
wait_for_bit_le32(®s->int_sts, CV1800B_SPI_INT_TRAN_DONE, true, 3000, false);
|
|
writel(0, ®s->ff_pt);
|
|
clrbits_le32(®s->int_sts, CV1800B_SPI_INT_TRAN_DONE | interrupt_mask);
|
|
|
|
if (din)
|
|
cv1800b_set_clk_div(priv, priv->div);
|
|
return 0;
|
|
}
|
|
|
|
static int cv1800b_spi_xfer(struct udevice *dev, unsigned int bitlen,
|
|
const void *dout, void *din, unsigned long flags)
|
|
{
|
|
struct udevice *bus = dev->parent;
|
|
struct cv1800b_spi_priv *priv = dev_get_priv(bus);
|
|
struct cv1800b_spif_regs *regs = priv->regs;
|
|
|
|
if (bitlen == 0)
|
|
goto out;
|
|
|
|
if (bitlen % 8) {
|
|
flags |= SPI_XFER_END;
|
|
goto out;
|
|
}
|
|
|
|
if (flags & SPI_XFER_BEGIN)
|
|
writel(CV1800B_SPI_CE_DISABLE, ®s->ce_ctrl);
|
|
|
|
if (din || dout)
|
|
cv1800b_spi_transfer(priv, din, dout, bitlen / 8, flags);
|
|
|
|
out:
|
|
if (flags & SPI_XFER_END)
|
|
writel(CV1800B_SPI_CE_ENABLE, ®s->ce_ctrl);
|
|
return 0;
|
|
}
|
|
|
|
static int cv1800b_spi_set_speed(struct udevice *bus, uint speed)
|
|
{
|
|
struct cv1800b_spi_priv *priv = dev_get_priv(bus);
|
|
|
|
priv->div = DIV_ROUND_CLOSEST(priv->clk_freq, speed * 2) - 1;
|
|
if (priv->div <= 0)
|
|
priv->div = CV1800B_DEFAULT_DIV;
|
|
|
|
cv1800b_set_clk_div(priv, priv->div);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cv1800b_spi_set_mode(struct udevice *bus, uint mode)
|
|
{
|
|
struct cv1800b_spi_priv *priv = dev_get_priv(bus);
|
|
struct cv1800b_spif_regs *regs = priv->regs;
|
|
u32 val = 0;
|
|
|
|
if (mode & SPI_CPHA)
|
|
val |= CV1800B_SPI_CTRL_CPHA;
|
|
if (mode & SPI_CPOL)
|
|
val |= CV1800B_SPI_CTRL_CPOL;
|
|
clrsetbits_le32(®s->spi_ctrl, CV1800B_SPI_CTRL_CPHA | CV1800B_SPI_CTRL_CPOL, val);
|
|
|
|
priv->mode = mode;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cv1800b_spi_exec_op(struct spi_slave *slave, const struct spi_mem_op *op)
|
|
{
|
|
struct udevice *bus = slave->dev->parent;
|
|
struct cv1800b_spi_priv *priv = dev_get_priv(bus);
|
|
struct cv1800b_spif_regs *regs = priv->regs;
|
|
struct spi_nor *flash = dev_get_uclass_priv(slave->dev);
|
|
u32 old_tran_csr;
|
|
|
|
if (!(op->data.nbytes > 0 && op->data.dir == SPI_MEM_DATA_IN) ||
|
|
!(op->addr.nbytes > 0 && op->addr.nbytes <= 4))
|
|
return -ENOTSUPP;
|
|
|
|
old_tran_csr = readl(®s->tran_csr);
|
|
writel(CV1800B_SPI_CE_HARDWARE, ®s->ce_ctrl);
|
|
|
|
cv1800b_spi_config_dmmr(priv, flash);
|
|
|
|
writel(1, ®s->dmmr_ctrl);
|
|
memcpy(op->data.buf.in, (void *)priv->regs + op->addr.val, op->data.nbytes);
|
|
writel(0, ®s->dmmr_ctrl);
|
|
|
|
writel(CV1800B_SPI_CE_ENABLE, ®s->ce_ctrl);
|
|
writel(old_tran_csr, ®s->tran_csr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct spi_controller_mem_ops cv1800b_spi_mem_ops = {
|
|
.exec_op = cv1800b_spi_exec_op,
|
|
};
|
|
|
|
static const struct dm_spi_ops cv1800b_spi_ops = {
|
|
.xfer = cv1800b_spi_xfer,
|
|
.mem_ops = &cv1800b_spi_mem_ops,
|
|
.set_speed = cv1800b_spi_set_speed,
|
|
.set_mode = cv1800b_spi_set_mode,
|
|
};
|
|
|
|
static const struct udevice_id cv1800b_spi_ids[] = {
|
|
{ .compatible = "sophgo,cv1800b-spif" },
|
|
{ }
|
|
};
|
|
|
|
U_BOOT_DRIVER(cv1800b_spi) = {
|
|
.name = "cv1800b_spif",
|
|
.id = UCLASS_SPI,
|
|
.of_match = cv1800b_spi_ids,
|
|
.ops = &cv1800b_spi_ops,
|
|
.priv_auto = sizeof(struct cv1800b_spi_priv),
|
|
.probe = cv1800b_spi_probe,
|
|
};
|