mtd: rawnand: Handle the double bytes in NV-DDR mode
authorMiquel Raynal <miquel.raynal@bootlin.com>
Wed, 5 May 2021 21:37:42 +0000 (23:37 +0200)
committerMiquel Raynal <miquel.raynal@bootlin.com>
Wed, 26 May 2021 08:43:56 +0000 (10:43 +0200)
As explained in chapter "NV-DDR / NV-DDR2 / NV-DDR3 and Repeat Bytes" of
the ONFI specification, with some commands (mainly the commands which do
not transfer actual data) the data bytes are repeated twice and it is
the responsibility of the receiver to discard them properly. The
concerned commands are: SET_FEATURES, READ_ID, GET_FEATURES,
READ_STATUS, READ_STATUS_ENHANCED, ODT_CONFIGURE. Hence, in the NAND
core we are only impacted by the implementation of READ_ID, GET_FEATURES
and READ_STATUS.

The logic is the same for all:
2/ Check if it is relevant to read all data bytes twice.
1/ Allocate a buffer with twice the requested size (may be done
   statically).
2/ Update the instruction structure to read these extra bytes in the
   allocated buffer.
3/ Copy the even bytes into the original buffer. The performance hit is
   negligible on such small data transfers anyway and we don't really
   care about performances at this stage anyway.
4/ Free the allocated buffer, if any.

Note: nand_data_read_op() is also impacted because it is theoretically
possible to run the command/address cycles first, and, as another
operation, do the data transfers. In this case we can easily identify
the impacted commands because the force_8bit flag will be set (due to
the same reason: their data does not go through the same pipeline).

Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Link: https://lore.kernel.org/linux-mtd/20210505213750.257417-15-miquel.raynal@bootlin.com
drivers/mtd/nand/raw/nand_base.c

index cdb3ddb34cd677a3a41a8af9a999def4ea7c3a8e..b8a515a74379f08b1e2439c7d2d5cb1a53c55853 100644 (file)
@@ -1601,7 +1601,7 @@ int nand_readid_op(struct nand_chip *chip, u8 addr, void *buf,
                   unsigned int len)
 {
        unsigned int i;
-       u8 *id = buf;
+       u8 *id = buf, *ddrbuf = NULL;
 
        if (len && !buf)
                return -EINVAL;
@@ -1616,12 +1616,31 @@ int nand_readid_op(struct nand_chip *chip, u8 addr, void *buf,
                        NAND_OP_8BIT_DATA_IN(len, buf, 0),
                };
                struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs);
+               int ret;
+
+               /* READ_ID data bytes are received twice in NV-DDR mode */
+               if (len && nand_interface_is_nvddr(conf)) {
+                       ddrbuf = kzalloc(len * 2, GFP_KERNEL);
+                       if (!ddrbuf)
+                               return -ENOMEM;
+
+                       instrs[2].ctx.data.len *= 2;
+                       instrs[2].ctx.data.buf.in = ddrbuf;
+               }
 
                /* Drop the DATA_IN instruction if len is set to 0. */
                if (!len)
                        op.ninstrs--;
 
-               return nand_exec_op(chip, &op);
+               ret = nand_exec_op(chip, &op);
+               if (!ret && len && nand_interface_is_nvddr(conf)) {
+                       for (i = 0; i < len; i++)
+                               id[i] = ddrbuf[i * 2];
+               }
+
+               kfree(ddrbuf);
+
+               return ret;
        }
 
        chip->legacy.cmdfunc(chip, NAND_CMD_READID, addr, -1);
@@ -1649,17 +1668,29 @@ int nand_status_op(struct nand_chip *chip, u8 *status)
        if (nand_has_exec_op(chip)) {
                const struct nand_interface_config *conf =
                        nand_get_interface_config(chip);
+               u8 ddrstatus[2];
                struct nand_op_instr instrs[] = {
                        NAND_OP_CMD(NAND_CMD_STATUS,
                                    NAND_COMMON_TIMING_NS(conf, tADL_min)),
                        NAND_OP_8BIT_DATA_IN(1, status, 0),
                };
                struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs);
+               int ret;
+
+               /* The status data byte will be received twice in NV-DDR mode */
+               if (status && nand_interface_is_nvddr(conf)) {
+                       instrs[1].ctx.data.len *= 2;
+                       instrs[1].ctx.data.buf.in = ddrstatus;
+               }
 
                if (!status)
                        op.ninstrs--;
 
-               return nand_exec_op(chip, &op);
+               ret = nand_exec_op(chip, &op);
+               if (!ret && status && nand_interface_is_nvddr(conf))
+                       *status = ddrstatus[0];
+
+               return ret;
        }
 
        chip->legacy.cmdfunc(chip, NAND_CMD_STATUS, -1, -1);
@@ -1822,7 +1853,7 @@ static int nand_set_features_op(struct nand_chip *chip, u8 feature,
 static int nand_get_features_op(struct nand_chip *chip, u8 feature,
                                void *data)
 {
-       u8 *params = data;
+       u8 *params = data, ddrbuf[ONFI_SUBFEATURE_PARAM_LEN * 2];
        int i;
 
        if (nand_has_exec_op(chip)) {
@@ -1838,8 +1869,21 @@ static int nand_get_features_op(struct nand_chip *chip, u8 feature,
                                             data, 0),
                };
                struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs);
+               int ret;
 
-               return nand_exec_op(chip, &op);
+               /* GET_FEATURE data bytes are received twice in NV-DDR mode */
+               if (nand_interface_is_nvddr(conf)) {
+                       instrs[3].ctx.data.len *= 2;
+                       instrs[3].ctx.data.buf.in = ddrbuf;
+               }
+
+               ret = nand_exec_op(chip, &op);
+               if (nand_interface_is_nvddr(conf)) {
+                       for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; i++)
+                               params[i] = ddrbuf[i * 2];
+               }
+
+               return ret;
        }
 
        chip->legacy.cmdfunc(chip, NAND_CMD_GET_FEATURES, feature, -1);
@@ -1925,17 +1969,50 @@ int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len,
                return -EINVAL;
 
        if (nand_has_exec_op(chip)) {
+               const struct nand_interface_config *conf =
+                       nand_get_interface_config(chip);
                struct nand_op_instr instrs[] = {
                        NAND_OP_DATA_IN(len, buf, 0),
                };
                struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs);
+               u8 *ddrbuf = NULL;
+               int ret, i;
 
                instrs[0].ctx.data.force_8bit = force_8bit;
 
-               if (check_only)
-                       return nand_check_op(chip, &op);
+               /*
+                * Parameter payloads (ID, status, features, etc) do not go
+                * through the same pipeline as regular data, hence the
+                * force_8bit flag must be set and this also indicates that in
+                * case NV-DDR timings are being used the data will be received
+                * twice.
+                */
+               if (force_8bit && nand_interface_is_nvddr(conf)) {
+                       ddrbuf = kzalloc(len * 2, GFP_KERNEL);
+                       if (!ddrbuf)
+                               return -ENOMEM;
 
-               return nand_exec_op(chip, &op);
+                       instrs[0].ctx.data.len *= 2;
+                       instrs[0].ctx.data.buf.in = ddrbuf;
+               }
+
+               if (check_only) {
+                       ret = nand_check_op(chip, &op);
+                       kfree(ddrbuf);
+                       return ret;
+               }
+
+               ret = nand_exec_op(chip, &op);
+               if (!ret && force_8bit && nand_interface_is_nvddr(conf)) {
+                       u8 *dst = buf;
+
+                       for (i = 0; i < len; i++)
+                               dst[i] = ddrbuf[i * 2];
+               }
+
+               kfree(ddrbuf);
+
+               return ret;
        }
 
        if (check_only)