Add quad mode support for QPIC SNAND

Md Sadre Alam <md.alam@oss.qualcomm.com> says:

The Qualcomm QPIC SPI-NAND controller supports both single (x1) and
quad (x4) transfer modes, however the current driver operates only in
single-bit mode. This series adds support for quad data transfers and
includes a vendor-specific fix required for Macronix SPI-NAND devices.

Link: https://patch.msgid.link/20260807-quad-v2-0-8ec821e2f22b@oss.qualcomm.com
This commit is contained in:
Mark Brown 2026-08-11 22:14:49 +01:00
commit 32c55bfb29
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0

View File

@ -46,14 +46,20 @@
#define SPINAND_RESET 0xff
#define SPINAND_READID 0x9f
#define SPINAND_FEATURE_ADDR 0xb0
#define SPINAND_GET_FEATURE 0x0f
#define SPINAND_SET_FEATURE 0x1f
#define SPINAND_READ_CACHE 0x0b
#define SPINAND_READ 0x13
#define SPINAND_READ_QUAD 0xeb
#define SPINAND_READ_MACRONIX 0x6b
#define SPINAND_ERASE 0xd8
#define SPINAND_WRITE_EN 0x06
#define SPINAND_PROGRAM_EXECUTE 0x10
#define SPINAND_PROGRAM_LOAD 0x84
#define SPINAND_PROGRAM_LOAD_QUAD 0x34
#define QUAD_WIDTH 0x4
#define ACC_FEATURE 0xe
#define BAD_BLOCK_MARKER_SIZE 0x2
#define OOB_BUF_SIZE 128
@ -114,6 +120,7 @@ struct qpic_spi_nand {
bool oob_rw;
bool page_rw;
bool raw_rw;
bool quad_mode;
};
static void qcom_spi_set_read_loc_first(struct qcom_nand_controller *snandc,
@ -1004,9 +1011,80 @@ static int qcom_spi_read_page_oob(struct qcom_nand_controller *snandc,
return qcom_spi_check_error(snandc);
}
static int qcom_spi_cmd_mapping(struct qcom_nand_controller *snandc,
const struct spi_mem_op *op, u32 *cmd)
{
u32 opcode = op->cmd.opcode;
u32 transfer_mode = SPI_TRANSFER_MODE_x1;
if (snandc->qspi->quad_mode && op->data.buswidth == QUAD_WIDTH)
transfer_mode = SPI_TRANSFER_MODE_x4;
switch (opcode) {
case SPINAND_RESET:
*cmd = (SPI_WP | SPI_HOLD | SPI_TRANSFER_MODE_x1 | OP_RESET_DEVICE);
break;
case SPINAND_READID:
*cmd = (SPI_WP | SPI_HOLD | SPI_TRANSFER_MODE_x1 | OP_FETCH_ID);
break;
case SPINAND_GET_FEATURE:
*cmd = (SPI_TRANSFER_MODE_x1 | SPI_WP | SPI_HOLD | ACC_FEATURE);
break;
case SPINAND_SET_FEATURE:
*cmd = (SPI_TRANSFER_MODE_x1 | SPI_WP | SPI_HOLD | ACC_FEATURE |
QPIC_SET_FEATURE);
break;
case SPINAND_READ:
case SPINAND_READ_QUAD:
case SPINAND_READ_CACHE:
case SPINAND_READ_MACRONIX:
if (snandc->qspi->raw_rw) {
*cmd = (PAGE_ACC | LAST_PAGE | transfer_mode |
SPI_WP | SPI_HOLD | OP_PAGE_READ);
} else {
*cmd = (PAGE_ACC | LAST_PAGE | transfer_mode |
SPI_WP | SPI_HOLD | OP_PAGE_READ_WITH_ECC);
}
break;
case SPINAND_ERASE:
*cmd = OP_BLOCK_ERASE | PAGE_ACC | LAST_PAGE | SPI_WP |
SPI_HOLD | SPI_TRANSFER_MODE_x1;
break;
case SPINAND_WRITE_EN:
*cmd = SPINAND_WRITE_EN;
break;
case SPINAND_PROGRAM_EXECUTE:
*cmd = (PAGE_ACC | LAST_PAGE | transfer_mode |
SPI_WP | SPI_HOLD | OP_PROGRAM_PAGE);
break;
case SPINAND_PROGRAM_LOAD:
*cmd = SPINAND_PROGRAM_LOAD;
break;
case SPINAND_PROGRAM_LOAD_QUAD:
*cmd = SPINAND_PROGRAM_LOAD_QUAD;
break;
default:
dev_err(snandc->dev, "Opcode not supported: %u\n", opcode);
return -EOPNOTSUPP;
}
return 0;
}
static int qcom_spi_read_page(struct qcom_nand_controller *snandc,
const struct spi_mem_op *op)
{
int ret;
u32 cmd;
/* Update the cached command for the cache-read opcode and bus width. */
ret = qcom_spi_cmd_mapping(snandc, op, &cmd);
if (ret < 0)
return ret;
snandc->qspi->cmd = cpu_to_le32(cmd);
if (snandc->qspi->page_rw && snandc->qspi->raw_rw)
return qcom_spi_read_page_raw(snandc, op);
@ -1268,65 +1346,18 @@ static int qcom_spi_program_execute(struct qcom_nand_controller *snandc,
return 0;
}
static int qcom_spi_cmd_mapping(struct qcom_nand_controller *snandc, u32 opcode, u32 *cmd)
{
switch (opcode) {
case SPINAND_RESET:
*cmd = (SPI_WP | SPI_HOLD | SPI_TRANSFER_MODE_x1 | OP_RESET_DEVICE);
break;
case SPINAND_READID:
*cmd = (SPI_WP | SPI_HOLD | SPI_TRANSFER_MODE_x1 | OP_FETCH_ID);
break;
case SPINAND_GET_FEATURE:
*cmd = (SPI_TRANSFER_MODE_x1 | SPI_WP | SPI_HOLD | ACC_FEATURE);
break;
case SPINAND_SET_FEATURE:
*cmd = (SPI_TRANSFER_MODE_x1 | SPI_WP | SPI_HOLD | ACC_FEATURE |
QPIC_SET_FEATURE);
break;
case SPINAND_READ:
if (snandc->qspi->raw_rw) {
*cmd = (PAGE_ACC | LAST_PAGE | SPI_TRANSFER_MODE_x1 |
SPI_WP | SPI_HOLD | OP_PAGE_READ);
} else {
*cmd = (PAGE_ACC | LAST_PAGE | SPI_TRANSFER_MODE_x1 |
SPI_WP | SPI_HOLD | OP_PAGE_READ_WITH_ECC);
}
break;
case SPINAND_ERASE:
*cmd = OP_BLOCK_ERASE | PAGE_ACC | LAST_PAGE | SPI_WP |
SPI_HOLD | SPI_TRANSFER_MODE_x1;
break;
case SPINAND_WRITE_EN:
*cmd = SPINAND_WRITE_EN;
break;
case SPINAND_PROGRAM_EXECUTE:
*cmd = (PAGE_ACC | LAST_PAGE | SPI_TRANSFER_MODE_x1 |
SPI_WP | SPI_HOLD | OP_PROGRAM_PAGE);
break;
case SPINAND_PROGRAM_LOAD:
*cmd = SPINAND_PROGRAM_LOAD;
break;
default:
dev_err(snandc->dev, "Opcode not supported: %u\n", opcode);
return -EOPNOTSUPP;
}
return 0;
}
static int qcom_spi_write_page(struct qcom_nand_controller *snandc,
const struct spi_mem_op *op)
{
int ret;
u32 cmd;
ret = qcom_spi_cmd_mapping(snandc, op->cmd.opcode, &cmd);
ret = qcom_spi_cmd_mapping(snandc, op, &cmd);
if (ret < 0)
return ret;
if (op->cmd.opcode == SPINAND_PROGRAM_LOAD)
if (op->cmd.opcode == SPINAND_PROGRAM_LOAD ||
op->cmd.opcode == SPINAND_PROGRAM_LOAD_QUAD)
snandc->qspi->data_buf = (u8 *)op->data.buf.out;
return 0;
@ -1338,7 +1369,7 @@ static int qcom_spi_send_cmdaddr(struct qcom_nand_controller *snandc,
u32 cmd;
int ret, opcode;
ret = qcom_spi_cmd_mapping(snandc, op->cmd.opcode, &cmd);
ret = qcom_spi_cmd_mapping(snandc, op, &cmd);
if (ret < 0)
return ret;
@ -1457,6 +1488,22 @@ static int qcom_spi_io_op(struct qcom_nand_controller *snandc, const struct spi_
val = le32_to_cpu(*(__le32 *)snandc->reg_read_buf);
val >>= 8;
memcpy(op->data.buf.in, &val, snandc->buf_count);
/*
* Track QUAD mode state from configuration register.
* When core layer reads register 0xB0 (CFG), check if
* QUAD enable bit (bit 0) is set and update our state
* accordingly for future READ/WRITE operations.
*/
if (op->addr.val == SPINAND_FEATURE_ADDR) {
bool quad_enabled = !!((u8)val & BIT(0));
if (snandc->qspi->quad_mode != quad_enabled) {
snandc->qspi->quad_mode = quad_enabled;
dev_info(snandc->dev, "SPI NAND QUAD mode: %s\n",
quad_enabled ? "enabled" : "disabled");
}
}
}
return 0;
@ -1497,7 +1544,8 @@ static bool qcom_spi_supports_op(struct spi_mem *mem, const struct spi_mem_op *o
return ((!op->addr.nbytes || op->addr.buswidth == 1) &&
(!op->dummy.nbytes || op->dummy.buswidth == 1) &&
(!op->data.nbytes || op->data.buswidth == 1));
(!op->data.nbytes || op->data.buswidth == 1 ||
op->data.buswidth == 4));
}
static int qcom_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
@ -1548,6 +1596,9 @@ static int qcom_spi_probe(struct platform_device *pdev)
if (!qspi)
return -ENOMEM;
/* Initialize QUAD mode state */
qspi->quad_mode = false;
ctlr = __devm_spi_alloc_controller(dev, sizeof(*snandc), false);
if (!ctlr)
return -ENOMEM;