spi: airoha: add dma support

This patch speed up cache reading/writing/updating opearions.
It was tested on en7523/an7581 and some other Airoha chips.

It will speed up
 * page reading/writing without oob
 * page reading/writing with oob
 * oob reading/writing (significant for UBI scanning)

The only know issue appears in a very specific conditions for en7523 family
chips only.

Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
This commit is contained in:
Mikhail Kshevetskiy
2025-11-18 20:07:41 +01:00
committed by Michael Trimarchi
parent ba04299ff7
commit a7c3319bc0
+309
View File
@@ -141,12 +141,14 @@
#define SPI_NFI_CUS_SEC_SIZE_EN BIT(16)
#define REG_SPI_NFI_RD_CTL2 0x0510
#define REG_SPI_NFI_RD_CTL3 0x0514
#define REG_SPI_NFI_PG_CTL1 0x0524
#define SPI_NFI_PG_LOAD_CMD GENMASK(15, 8)
#define REG_SPI_NFI_PG_CTL2 0x0528
#define REG_SPI_NFI_NOR_PROG_ADDR 0x052c
#define REG_SPI_NFI_NOR_RD_ADDR 0x0534
@@ -219,6 +221,8 @@ struct airoha_snand_priv {
u8 sec_num;
u8 spare_size;
} nfi_cfg;
u8 *txrx_buf;
};
static int airoha_snand_set_fifo_op(struct airoha_snand_priv *priv,
@@ -614,6 +618,302 @@ static bool airoha_snand_supports_op(struct spi_slave *slave,
(!op->data.nbytes || op->data.buswidth == 1);
}
static int airoha_snand_dirmap_create(struct spi_mem_dirmap_desc *desc)
{
struct spi_slave *slave = desc->slave;
struct udevice *bus = slave->dev->parent;
struct airoha_snand_priv *priv = dev_get_priv(bus);
if (!priv->txrx_buf)
return -EINVAL;
if (desc->info.offset + desc->info.length > U32_MAX)
return -EINVAL;
if (!airoha_snand_supports_op(desc->slave, &desc->info.op_tmpl))
return -EOPNOTSUPP;
return 0;
}
static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc,
u64 offs, size_t len, void *buf)
{
struct spi_mem_op *op = &desc->info.op_tmpl;
struct spi_slave *slave = desc->slave;
struct udevice *bus = slave->dev->parent;
struct airoha_snand_priv *priv = dev_get_priv(bus);
u8 *txrx_buf = priv->txrx_buf;
dma_addr_t dma_addr;
u32 val, rd_mode;
int err;
switch (op->cmd.opcode) {
case SPI_NAND_OP_READ_FROM_CACHE_DUAL:
rd_mode = 1;
break;
case SPI_NAND_OP_READ_FROM_CACHE_QUAD:
rd_mode = 2;
break;
default:
rd_mode = 0;
break;
}
err = airoha_snand_set_mode(priv, SPI_MODE_DMA);
if (err < 0)
return err;
err = airoha_snand_nfi_config(priv);
if (err)
goto error_dma_mode_off;
dma_addr = dma_map_single(txrx_buf, SPI_NAND_CACHE_SIZE,
DMA_FROM_DEVICE);
/* set dma addr */
err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_STRADDR,
dma_addr);
if (err)
goto error_dma_unmap;
/* set cust sec size */
val = priv->nfi_cfg.sec_size * priv->nfi_cfg.sec_num;
val = FIELD_PREP(SPI_NFI_READ_DATA_BYTE_NUM, val);
err = regmap_update_bits(priv->regmap_nfi,
REG_SPI_NFI_SNF_MISC_CTL2,
SPI_NFI_READ_DATA_BYTE_NUM, val);
if (err)
goto error_dma_unmap;
/* set read command */
err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_RD_CTL2,
op->cmd.opcode);
if (err)
goto error_dma_unmap;
/* set read mode */
err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_SNF_MISC_CTL,
FIELD_PREP(SPI_NFI_DATA_READ_WR_MODE, rd_mode));
if (err)
goto error_dma_unmap;
/* set read addr: zero page offset + descriptor read offset */
err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_RD_CTL3,
desc->info.offset);
if (err)
goto error_dma_unmap;
/* set nfi read */
err = regmap_update_bits(priv->regmap_nfi, REG_SPI_NFI_CNFG,
SPI_NFI_OPMODE,
FIELD_PREP(SPI_NFI_OPMODE, 6));
if (err)
goto error_dma_unmap;
err = regmap_set_bits(priv->regmap_nfi, REG_SPI_NFI_CNFG,
SPI_NFI_READ_MODE | SPI_NFI_DMA_MODE);
if (err)
goto error_dma_unmap;
err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_CMD, 0x0);
if (err)
goto error_dma_unmap;
/* trigger dma reading */
err = regmap_clear_bits(priv->regmap_nfi, REG_SPI_NFI_CON,
SPI_NFI_RD_TRIG);
if (err)
goto error_dma_unmap;
err = regmap_set_bits(priv->regmap_nfi, REG_SPI_NFI_CON,
SPI_NFI_RD_TRIG);
if (err)
goto error_dma_unmap;
err = regmap_read_poll_timeout(priv->regmap_nfi,
REG_SPI_NFI_SNF_STA_CTL1, val,
(val & SPI_NFI_READ_FROM_CACHE_DONE),
0, 1 * MSEC_PER_SEC);
if (err)
goto error_dma_unmap;
/*
* SPI_NFI_READ_FROM_CACHE_DONE bit must be written at the end
* of dirmap_read operation even if it is already set.
*/
err = regmap_update_bits(priv->regmap_nfi, REG_SPI_NFI_SNF_STA_CTL1,
SPI_NFI_READ_FROM_CACHE_DONE,
SPI_NFI_READ_FROM_CACHE_DONE);
if (err)
goto error_dma_unmap;
err = regmap_read_poll_timeout(priv->regmap_nfi, REG_SPI_NFI_INTR,
val, (val & SPI_NFI_AHB_DONE), 0,
1 * MSEC_PER_SEC);
if (err)
goto error_dma_unmap;
/* DMA read need delay for data ready from controller to DRAM */
udelay(1);
dma_unmap_single(dma_addr, SPI_NAND_CACHE_SIZE, DMA_FROM_DEVICE);
err = airoha_snand_set_mode(priv, SPI_MODE_MANUAL);
if (err < 0)
return err;
memcpy(buf, txrx_buf + offs, len);
return len;
error_dma_unmap:
dma_unmap_single(dma_addr, SPI_NAND_CACHE_SIZE, DMA_FROM_DEVICE);
error_dma_mode_off:
airoha_snand_set_mode(priv, SPI_MODE_MANUAL);
return err;
}
static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc,
u64 offs, size_t len, const void *buf)
{
struct spi_slave *slave = desc->slave;
struct udevice *bus = slave->dev->parent;
struct airoha_snand_priv *priv = dev_get_priv(bus);
u8 *txrx_buf = priv->txrx_buf;
dma_addr_t dma_addr;
u32 wr_mode, val, opcode;
int err;
opcode = desc->info.op_tmpl.cmd.opcode;
switch (opcode) {
case SPI_NAND_OP_PROGRAM_LOAD_SINGLE:
case SPI_NAND_OP_PROGRAM_LOAD_RAMDOM_SINGLE:
wr_mode = 0;
break;
case SPI_NAND_OP_PROGRAM_LOAD_QUAD:
case SPI_NAND_OP_PROGRAM_LOAD_RAMDON_QUAD:
wr_mode = 2;
break;
default:
/* unknown opcode */
return -EOPNOTSUPP;
}
memcpy(txrx_buf + offs, buf, len);
err = airoha_snand_set_mode(priv, SPI_MODE_DMA);
if (err < 0)
return err;
err = airoha_snand_nfi_config(priv);
if (err)
goto error_dma_mode_off;
dma_addr = dma_map_single(txrx_buf, SPI_NAND_CACHE_SIZE,
DMA_TO_DEVICE);
/* set dma addr */
err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_STRADDR,
dma_addr);
if (err)
goto error_dma_unmap;
val = FIELD_PREP(SPI_NFI_PROG_LOAD_BYTE_NUM,
priv->nfi_cfg.sec_size * priv->nfi_cfg.sec_num);
err = regmap_update_bits(priv->regmap_nfi,
REG_SPI_NFI_SNF_MISC_CTL2,
SPI_NFI_PROG_LOAD_BYTE_NUM, val);
if (err)
goto error_dma_unmap;
/* set write command */
err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_PG_CTL1,
FIELD_PREP(SPI_NFI_PG_LOAD_CMD, opcode));
if (err)
goto error_dma_unmap;
/* set write mode */
err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_SNF_MISC_CTL,
FIELD_PREP(SPI_NFI_DATA_READ_WR_MODE, wr_mode));
if (err)
goto error_dma_unmap;
/* set write addr: zero page offset + descriptor write offset */
err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_PG_CTL2,
desc->info.offset);
if (err)
goto error_dma_unmap;
err = regmap_clear_bits(priv->regmap_nfi, REG_SPI_NFI_CNFG,
SPI_NFI_READ_MODE);
if (err)
goto error_dma_unmap;
err = regmap_update_bits(priv->regmap_nfi, REG_SPI_NFI_CNFG,
SPI_NFI_OPMODE,
FIELD_PREP(SPI_NFI_OPMODE, 3));
if (err)
goto error_dma_unmap;
err = regmap_set_bits(priv->regmap_nfi, REG_SPI_NFI_CNFG,
SPI_NFI_DMA_MODE);
if (err)
goto error_dma_unmap;
err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_CMD, 0x80);
if (err)
goto error_dma_unmap;
/* trigger dma writing */
err = regmap_clear_bits(priv->regmap_nfi, REG_SPI_NFI_CON,
SPI_NFI_WR_TRIG);
if (err)
goto error_dma_unmap;
err = regmap_set_bits(priv->regmap_nfi, REG_SPI_NFI_CON,
SPI_NFI_WR_TRIG);
if (err)
goto error_dma_unmap;
err = regmap_read_poll_timeout(priv->regmap_nfi, REG_SPI_NFI_INTR,
val, (val & SPI_NFI_AHB_DONE), 0,
1 * MSEC_PER_SEC);
if (err)
goto error_dma_unmap;
err = regmap_read_poll_timeout(priv->regmap_nfi,
REG_SPI_NFI_SNF_STA_CTL1, val,
(val & SPI_NFI_LOAD_TO_CACHE_DONE),
0, 1 * MSEC_PER_SEC);
if (err)
goto error_dma_unmap;
/*
* SPI_NFI_LOAD_TO_CACHE_DONE bit must be written at the end
* of dirmap_write operation even if it is already set.
*/
err = regmap_update_bits(priv->regmap_nfi, REG_SPI_NFI_SNF_STA_CTL1,
SPI_NFI_LOAD_TO_CACHE_DONE,
SPI_NFI_LOAD_TO_CACHE_DONE);
if (err)
goto error_dma_unmap;
dma_unmap_single(dma_addr, SPI_NAND_CACHE_SIZE, DMA_TO_DEVICE);
err = airoha_snand_set_mode(priv, SPI_MODE_MANUAL);
if (err < 0)
return err;
return len;
error_dma_unmap:
dma_unmap_single(dma_addr, SPI_NAND_CACHE_SIZE, DMA_TO_DEVICE);
error_dma_mode_off:
airoha_snand_set_mode(priv, SPI_MODE_MANUAL);
return err;
}
static int airoha_snand_exec_op(struct spi_slave *slave,
const struct spi_mem_op *op)
{
@@ -696,6 +996,12 @@ static int airoha_snand_probe(struct udevice *dev)
struct airoha_snand_priv *priv = dev_get_priv(dev);
int ret;
priv->txrx_buf = memalign(ARCH_DMA_MINALIGN, SPI_NAND_CACHE_SIZE);
if (!priv->txrx_buf) {
dev_err(dev, "failed to alloacate memory for dirmap\n");
return -ENOMEM;
}
ret = regmap_init_mem_index(dev_ofnode(dev), &priv->regmap_ctrl, 0);
if (ret) {
dev_err(dev, "failed to init spi ctrl regmap\n");
@@ -769,6 +1075,9 @@ static int airoha_snand_nfi_setup(struct spi_slave *slave,
static const struct spi_controller_mem_ops airoha_snand_mem_ops = {
.supports_op = airoha_snand_supports_op,
.exec_op = airoha_snand_exec_op,
.dirmap_create = airoha_snand_dirmap_create,
.dirmap_read = airoha_snand_dirmap_read,
.dirmap_write = airoha_snand_dirmap_write,
};
static const struct dm_spi_ops airoha_snfi_spi_ops = {