Re: [PATCH v6 6/8] spi: spi-qpic: add driver for QCOM SPI NAND flash Interface

Miquel Raynal
2024-05-22T12:33:38Z

Hi,

> >> +static int qcom_spi_ooblayout_ecc(struct mtd_info *mtd, int section,
> >> + struct mtd_oob_region *oobregion)
> >> +{
> >> + struct nand_device *nand = mtd_to_nanddev(mtd);
> >> + struct qcom_nand_controller *snandc = nand_to_qcom_snand(nand);
> >> + struct qpic_ecc *qecc = snandc->qspi->ecc;
> >> +
> >> + if (section > 1)
> >> + return -ERANGE;
> >> +
> >> + if (!section) {
> >> + oobregion->length = (qecc->bytes * (qecc->steps - 1)) + qecc->bbm_size;
> >> + oobregion->offset = 0;
> >
> > No, offset 0 is for the BBM. This is wrong.
> > The whole oob layout looks really really wrong.
> >
> > ECC bytes are where the ECC engine puts its bytes in the OOB area.
> > Free bytes start after the BBM and fill the gaps until the end of the
> > area, except where there are ECC bytes.
> QPIC NAND controller having its own page layout with ecc and without ecc.
> The same layout we are using in raw nand driver as well, so i used the
> same here. The below info is already there in qcom raw nand driver file
> in page layout info.
>
> QPIC NAND controller layout as below:
>
> Layout with ECC enabled:
>
> |----------------------| |---------------------------------|
> | xx.......yy| | *********xx.......yy|
> | DATA xx..ECC..yy| | DATA **SPARE**xx..ECC..yy|
> | (516) xx.......yy| | (516-n*4) **(n*4)**xx.......yy|
> | xx.......yy| | *********xx.......yy|
> |----------------------| |---------------------------------|
> codeword 1,2..n-1 codeword n
> <---(528/532 Bytes)--> <-------(528/532 Bytes)--------->
>
> n = Number of codewords in the page
> . = ECC bytes
> * = Spare/free bytes
> x = Unused byte(s)
> y = Reserved byte(s)
>
> 2K page: n = 4, spare = 16 bytes
> 4K page: n = 8, spare = 32 bytes
> 8K page: n = 16, spare = 64 bytes
>
> the qcom nand controller operates at a sub page/codeword level. each
> codeword is 528 and 532 bytes for 4 bit and 8 bit ECC modes respectively.
> the number of ECC bytes vary based on the ECC strength and the bus width.
>
> the first n - 1 codewords contains 516 bytes of user data, the remaining
> 12/16 bytes consist of ECC and reserved data. The nth codeword contains
> both user data and spare(oobavail) bytes that sum up to 516 bytes.
>
> When we access a page with ECC enabled, the reserved bytes(s) are not
> accessible at all. When reading, we fill up these unreadable positions
> with 0xffs. When writing, the controller skips writing the inaccessible
> bytes.
>
> Layout with ECC disabled:
>
> |------------------------------| |---------------------------------------|
> | yy xx.......| | bb *********xx.......|
> | DATA1 yy DATA2 xx..ECC..| | DATA1 bb DATA2 **SPARE**xx..ECC..|
> | (size1) yy (size2) xx.......| | (size1) bb (size2) **(n*4)**xx.......|
> | yy xx.......| | bb *********xx.......|
> |------------------------------| |---------------------------------------|
> codeword 1,2..n-1 codeword n
> <-------(528/532 Bytes)------> <-----------(528/532 Bytes)----------->
>
> n = Number of codewords in the page
> . = ECC bytes
> * = Spare/free bytes
> x = Unused byte(s)
> y = Dummy Bad Bock byte(s)
> b = Real Bad Block byte(s)
> size1/size2 = function of codeword size and 'n'
>
> when the ECC block is disabled, one reserved byte (or two for 16 bit bus
> width) is now accessible. For the first n - 1 codewords, these are dummy Bad
> Block Markers. In the last codeword, this position contains the real BBM
>
> In order to have a consistent layout between RAW and ECC modes, we assume
> the following OOB layout arrangement:
>
> |-----------| |--------------------|
> |yyxx.......| |bb*********xx.......|
> |yyxx..ECC..| |bb*FREEOOB*xx..ECC..|
> |yyxx.......| |bb*********xx.......|
> |yyxx.......| |bb*********xx.......|
> |-----------| |--------------------|
> first n - 1 nth OOB region
> OOB regions
>
> n = Number of codewords in the page
> . = ECC bytes
> * = FREE OOB bytes
> y = Dummy bad block byte(s) (inaccessible when ECC enabled)
> x = Unused byte(s)
> b = Real bad block byte(s) (inaccessible when ECC enabled)
>
> This layout is read as is when ECC is disabled. When ECC is enabled, the
> inaccessible Bad Block byte(s) are ignored when we write to a page/oob,
> and assumed as 0xffs when we read a page/oob. The ECC, unused and
> dummy/real bad block bytes are grouped as ecc bytes (i.e, ecc->bytes is
> the sum of the three).

Thanks for the detailed explanation (which would benefit from being
added somewhere in a comment, maybe at the top of the file).

Unfortunately, these ooblayout callbacks do work on a flat <oob>
layout, not on the hardware ECC engine layout. So whatever the real
physical position of the bad block marker within the NAND array, these
markers will always be at offset 0 and 1 in the OOB final buffer.

Same applies to the spare and ECC bytes. These layouts are totally
wrong and must be fixed. If the layouts are the same in both raw/spi
cases, maybe they should be part of the common file?

> >> + } else {
> >> + oobregion->length = qecc->ecc_bytes_hw + qecc->spare_bytes;
> >> + oobregion->offset = mtd->oobsize - oobregion->length;
> >> + }
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static int qcom_spi_ooblayout_free(struct mtd_info *mtd, int section,
> >> + struct mtd_oob_region *oobregion)
> >> +{
> >> + struct nand_device *nand = mtd_to_nanddev(mtd);
> >> + struct qcom_nand_controller *snandc = nand_to_qcom_snand(nand);
> >> + struct qpic_ecc *qecc = snandc->qspi->ecc;
> >> +
> >> + if (section)
> >> + return -ERANGE;
> >> +
> >> + oobregion->length = qecc->steps * 4;
> >> + oobregion->offset = ((qecc->steps - 1) * qecc->bytes) + qecc->bbm_size;
> >> +
> >> + return 0;
> >> +}
> >> +
> >
> > ...
> >
> >> +static int qcom_spi_ecc_prepare_io_req_pipelined(struct nand_device *nand,
> >> + struct nand_page_io_req *req)
> >> +{
> >> + struct qcom_nand_controller *snandc = nand_to_qcom_snand(nand);
> >> + struct qpic_ecc *ecc_cfg = nand_to_ecc_ctx(nand);
> >> + struct mtd_info *mtd = nanddev_to_mtd(nand);
> >> +
> >> + snandc->qspi->ecc = ecc_cfg;
> >> + snandc->qspi->pagesize = mtd->writesize;
> >> + snandc->qspi->raw_rw = false;
> >> + snandc->qspi->oob_rw = false;
> >> + snandc->qspi->page_rw = false;
> >> +
> >> + if (req->datalen)
> >> + snandc->qspi->page_rw = true;
> >> +
> >> + if (req->ooblen) {
> >> + snandc->qspi->oob_rw = true;
> >> + if (req->ooblen == BAD_BLOCK_MARKER_SIZE)
> >> + snandc->qspi->read_last_cw = true;
> >
> > ???
> As per QPIC controller layout , the actual babd block marker will
> be present in last code word. Thats why i have added this check.
> to read only last codeword for bad block check.

You need to comply with the request. If ooblen is != 0, you need to
read the codeword(s) where the oob is. Please don't try to be smarter
than that. Checking the _value_ of ooblen is an optimization I don't
think is worth.

> >
> >> + }
> >> +
> >> + if (req->mode == MTD_OPS_RAW)
> >> + snandc->qspi->raw_rw = true;
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static int qcom_spi_ecc_finish_io_req_pipelined(struct nand_device *nand,
> >> + struct nand_page_io_req *req)
> >> +{
> >> + struct qcom_nand_controller *snandc = nand_to_qcom_snand(nand);
> >> + struct mtd_info *mtd = nanddev_to_mtd(nand);
> >> +
> >> + if (req->mode == MTD_OPS_RAW || req->type != NAND_PAGE_READ)
> >> + return 0;
> >> +
> >> + if (snandc->qspi->ecc_stats.failed)
> >> + mtd->ecc_stats.failed += snandc->qspi->ecc_stats.failed;
> >> + mtd->ecc_stats.corrected += snandc->qspi->ecc_stats.corrected;
> >
> > Seems strange
> In flash error check for each code word i am updating the error value.
> So on finishing on io i am assigning that error to mtd variables so that
> upper layer check for error.

You don't clear the qspi ecc_stats so this cannot work properly.

Plus, I would welcome an else statement for incrementing the corrected
field.

> >
> >> +
> >> + if (snandc->qspi->ecc_stats.failed)
> >> + return -EBADMSG;
> >> + else
> >> + return snandc->qspi->ecc_stats.bitflips;
> >> +}
> >> +
> >> +static struct nand_ecc_engine_ops qcom_spi_ecc_engine_ops_pipelined = {
> >> + .init_ctx = qcom_spi_ecc_init_ctx_pipelined,
> >> + .cleanup_ctx = qcom_spi_ecc_cleanup_ctx_pipelined,
> >> + .prepare_io_req = qcom_spi_ecc_prepare_io_req_pipelined,
> >> + .finish_io_req = qcom_spi_ecc_finish_io_req_pipelined,
> >> +};
> >> +
> >
> > ...
> >
> >> +static int qcom_spi_read_page_raw(struct qcom_nand_controller *snandc,
> >> + const struct spi_mem_op *op)
> >> +{
> >> + struct qpic_ecc *ecc_cfg = snandc->qspi->ecc;
> >> + u8 *data_buf = NULL, *oob_buf = NULL;
> >> + int ret, cw;
> >> + u32 num_cw = snandc->qspi->num_cw;
> >> +
> >> + if (snandc->qspi->page_rw)
> >
> > I don't like this indirection very much. Can't you simplify this and
> > just follow the spi-mem op instead of constantly trying to add
> > additional stuff?
> This indirection needed due to QPIC controller will not take all the instruction
> one-by-one , once we will set CMD_EXEC = 1, then it will execute all the instruction
> at once.

The spi_mem_op structure already describes the whole operation. Why do
you split the operation in sub routines if you can't actually do that?

> >
> > The hardware is already quite complex, but it feels like your adding
> > yet another pile of unnecessary complexity.
> Yes hardware is complex. let me check if i can further optimize as per spi-mem op
> as you suggested.
> >
> >> + data_buf = op->data.buf.in;
> >> +
> >> + if (snandc->qspi->oob_rw)
> >> + oob_buf = op->data.buf.in;

...

> >> +static int qcom_spi_write_page_cache(struct qcom_nand_controller *snandc,
> >> + const struct spi_mem_op *op)
> >> +{
> >> + struct qpic_snand_op s_op = {};
> >> + u32 cmd;
> >> +
> >> + cmd = qcom_spi_cmd_mapping(snandc, op->cmd.opcode);
> >
> > I've asked for switch cases to return an error in case they could not
> > handle the request. If you don't check the returned values, it
> > does not make any sense.
> Ok, will fix in next patch.
> >
> >> + s_op.cmd_reg = cmd;
> >> +
> >> + if (op->cmd.opcode == SPINAND_PROGRAM_LOAD) {
> >> + if (snandc->qspi->page_rw)
> >> + snandc->qspi->data_buf = (u8 *)op->data.buf.out;
> >
> > What you do here does not write anything in a page cache.
> No here just updating the buffer , actual write will happen in program_execute.
> This is due to QPIC controller will not take all the instruction one-by-one.
> once we will set CMD_EXEC = 1, then it will execute all the instruction
> at once. So accumulating all the instruction and then executing at once in
> program_execute.
> >
> > I also don't understand why you would have to check against the
> > SPINAND_PROGRAM_LOAD opcode.
> Because the actual write will happen in program_execute. and here
> PROGRAM_EXECUTE command will also land, so that added the check.
> >
> >> + }
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static int qcom_spi_send_cmdaddr(struct qcom_nand_controller *snandc,
> >> + const struct spi_mem_op *op)
> >> +{
> >> + struct qpic_snand_op s_op = {};
> >> + u32 cmd;
> >> + int ret, opcode;
> >> +
> >> + cmd = qcom_spi_cmd_mapping(snandc, op->cmd.opcode);
> >> +
> >> + s_op.cmd_reg = cmd;
> >> + s_op.addr1_reg = op->addr.val;
> >> + s_op.addr2_reg = 0;
> >> +
> >> + opcode = op->cmd.opcode;
> >> +
> >> + switch (opcode) {
> >> + case SPINAND_WRITE_EN:
> >> + return 0;
> >> + case SPINAND_PROGRAM_EXECUTE:
> >> + s_op.addr1_reg = op->addr.val << 16;
> >> + s_op.addr2_reg = op->addr.val >> 16 & 0xff;
> >> + snandc->qspi->addr1 = s_op.addr1_reg;
> >> + snandc->qspi->addr2 = s_op.addr2_reg;
> >> + snandc->qspi->cmd = cmd;
> >> + return qcom_spi_program_execute(snandc, op);
> >> + case SPINAND_READ:
> >> + s_op.addr1_reg = (op->addr.val << 16);
> >> + s_op.addr2_reg = op->addr.val >> 16 & 0xff;
> >> + snandc->qspi->addr1 = s_op.addr1_reg;
> >> + snandc->qspi->addr2 = s_op.addr2_reg;
> >> + snandc->qspi->cmd = cmd;
> >> + return 0;
> >> + case SPINAND_ERASE:
> >> + s_op.addr2_reg = (op->addr.val >> 16) & 0xffff;
> >> + s_op.addr1_reg = op->addr.val;
> >> + snandc->qspi->addr1 = (s_op.addr1_reg << 16);
> >> + snandc->qspi->addr2 = s_op.addr2_reg;
> >> + snandc->qspi->cmd = cmd;
> >> + qcom_spi_block_erase(snandc);
> >> + return 0;
> >> + default:
> >> + break;
> >> + }
> >> +
> >> + snandc->buf_count = 0;
> >> + snandc->buf_start = 0;
> >> + qcom_clear_read_regs(snandc);
> >> + qcom_clear_bam_transaction(snandc);
> >> +
> >> + snandc->regs->cmd = s_op.cmd_reg;
> >> + snandc->regs->exec = 1;
> >> + snandc->regs->addr0 = s_op.addr1_reg;
> >> + snandc->regs->addr1 = s_op.addr2_reg;
> >> +
> >> + qcom_write_reg_dma(snandc, &snandc->regs->cmd, NAND_FLASH_CMD, 3, NAND_BAM_NEXT_SGL);
> >> + qcom_write_reg_dma(snandc, &snandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL);
> >> +
> >> + ret = qcom_submit_descs(snandc);

And you really don't want to check the validity of the opcode with what
you support before submitting the descriptors?

> >> + if (ret)
> >> + dev_err(snandc->dev, "failure in sbumitting cmd descriptor\n");
> >
> > typo
> Ok , will fix in next patch.
> >
> >> +
> >> + return ret;
> >> +}
> >> +
> >> +static int qcom_spi_io_op(struct qcom_nand_controller *snandc, const struct spi_mem_op *op)
> >> +{
> >> + int ret, val, opcode;
> >> + bool copy = false, copy_ftr = false;
> >> +
> >> + ret = qcom_spi_send_cmdaddr(snandc, op);
> >> + if (ret)
> >> + return ret;
> >> +
> >> + snandc->buf_count = 0;
> >> + snandc->buf_start = 0;
> >> + qcom_clear_read_regs(snandc);
> >> + qcom_clear_bam_transaction(snandc);
> >> + opcode = op->cmd.opcode;
> >> +
> >> + switch (opcode) {
> >> + case SPINAND_READID:
> >> + snandc->buf_count = 4;
> >> + qcom_read_reg_dma(snandc, NAND_READ_ID, 1, NAND_BAM_NEXT_SGL);
> >> + copy = true;
> >> + break;
> >> + case SPINAND_GET_FEATURE:
> >> + snandc->buf_count = 4;
> >> + qcom_read_reg_dma(snandc, NAND_FLASH_FEATURES, 1, NAND_BAM_NEXT_SGL);
> >> + copy_ftr = true;
> >> + break;
> >> + case SPINAND_SET_FEATURE:
> >> + snandc->regs->flash_feature = *(u32 *)op->data.buf.out;
> >> + qcom_write_reg_dma(snandc, &snandc->regs->flash_feature,
> >> + NAND_FLASH_FEATURES, 1, NAND_BAM_NEXT_SGL);
> >> + break;
> >> + case SPINAND_RESET:
> >> + return 0;
> >> + case SPINAND_PROGRAM_EXECUTE:
> >> + return 0;
> >> + case SPINAND_WRITE_EN:
> >> + return 0;
> >> + case SPINAND_ERASE:
> >> + return 0;
> >> + case SPINAND_READ:
> >> + return 0;
> >
> > You can stack the cases
> Ok
> >
> >> + default:
> >> + return -EOPNOTSUPP;
> >> + }
> >> +
> >> + ret = qcom_submit_descs(snandc);
> >> + if (ret)
> >> + dev_err(snandc->dev, "failure in submitting descriptor for:%d\n", opcode);
> >> +
> >> + if (copy) {
> >> + qcom_nandc_dev_to_mem(snandc, true);
> >> + memcpy(op->data.buf.in, snandc->reg_read_buf, snandc->buf_count);
> >> + }
> >> +
> >> + if (copy_ftr) {
> >> + qcom_nandc_dev_to_mem(snandc, true);
> >> + val = le32_to_cpu(*(__le32 *)snandc->reg_read_buf);
> >> + val >>= 8;
> >> + memcpy(op->data.buf.in, &val, snandc->buf_count);
> >> + }
> >> +
> >> + return ret;
> >> +}

Thanks,
Miquèl