]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
mtd: rawnand: sunxi: introduce maximize variable user data length
authorRichard Genoud <richard.genoud@bootlin.com>
Tue, 17 Mar 2026 14:24:37 +0000 (15:24 +0100)
committerMiquel Raynal <miquel.raynal@bootlin.com>
Wed, 25 Mar 2026 14:27:30 +0000 (15:27 +0100)
In Allwinner SoCs, user data can be added in OOB before each ECC data.
For older SoCs like A10, the user data size was the size of a register
(4 bytes) and was mandatory before each ECC step.
So, the A10 OOB Layout is:
[4Bytes USER_DATA_STEP0] [ECC_STEP0 bytes]
[4bytes USER_DATA_STEP1] [ECC_STEP1 bytes]
...
NB: the BBM is stored at the beginning of the USER_DATA_STEP0.

Now, for H6/H616 NAND flash controller, this user data can have a
different size for each step.
So, we are maximizing the user data length to use as many OOB bytes as
possible.

Fixes: 88fd4e4deae8 ("mtd: rawnand: sunxi: Add support for H616 nand controller")
Signed-off-by: Richard Genoud <richard.genoud@bootlin.com>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
drivers/mtd/nand/raw/sunxi_nand.c

index 3d2580d39e702d2dd25f70c5d9cf999e0f742d2d..02647565c8ba9233a087a75b46c34d94088c0cd6 100644 (file)
 
 /*
  * On A10/A23, this is the size of the NDFC User Data Register, containing the
- * mandatory user data bytes following the ECC for each ECC step.
+ * mandatory user data bytes preceding the ECC for each ECC step.
  * Thus, for each ECC step, we need the ECC bytes + USER_DATA_SZ.
- * Those bits are currently unsused, and kept as default value 0xffffffff.
  *
  * On H6/H616, this size became configurable, from 0 bytes to 32, via the
  * USER_DATA_LEN registers.
@@ -249,6 +248,7 @@ struct sunxi_nand_hw_ecc {
  * @timing_ctl: TIMING_CTL register value for this NAND chip
  * @nsels: number of CS lines required by the NAND chip
  * @sels: array of CS lines descriptions
+ * @user_data_bytes: array of user data lengths for all ECC steps
  */
 struct sunxi_nand_chip {
        struct list_head node;
@@ -257,6 +257,7 @@ struct sunxi_nand_chip {
        unsigned long clk_rate;
        u32 timing_cfg;
        u32 timing_ctl;
+       u8 *user_data_bytes;
        int nsels;
        struct sunxi_nand_chip_sel sels[] __counted_by(nsels);
 };
@@ -823,12 +824,50 @@ static inline u32 sunxi_nfc_buf_to_user_data(const u8 *buf)
        return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
 }
 
+static u8 sunxi_nfc_user_data_sz(struct sunxi_nand_chip *sunxi_nand, int step)
+{
+       if (!sunxi_nand->user_data_bytes)
+               return USER_DATA_SZ;
+
+       return sunxi_nand->user_data_bytes[step];
+}
+
 static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8 *oob,
-                                               int step, bool bbm, int page)
+                                               int step, bool bbm, int page,
+                                               unsigned int user_data_sz)
 {
+       struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
        struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+       u32 user_data;
 
-       sunxi_nfc_user_data_to_buf(readl(nfc->regs + NFC_REG_USER_DATA(nfc, step)), oob);
+       if (!nfc->caps->reg_user_data_len) {
+               /*
+                * For A10, the user data for step n is in the nth
+                * REG_USER_DATA
+                */
+               user_data = readl(nfc->regs + NFC_REG_USER_DATA(nfc, step));
+               sunxi_nfc_user_data_to_buf(user_data, oob);
+       } else {
+               /*
+                * For H6 NAND controller, the user data for all steps is
+                * contained in 32 user data registers, but not at a specific
+                * offset for each step, they are just concatenated.
+                */
+               unsigned int user_data_off = 0;
+               unsigned int reg_off;
+               u8 *ptr = oob;
+               unsigned int i;
+
+               for (i = 0; i < step; i++)
+                       user_data_off += sunxi_nfc_user_data_sz(sunxi_nand, i);
+
+               user_data_off /= 4;
+               for (i = 0; i < user_data_sz / 4; i++, ptr += 4) {
+                       reg_off = NFC_REG_USER_DATA(nfc, user_data_off + i);
+                       user_data = readl(nfc->regs + reg_off);
+                       sunxi_nfc_user_data_to_buf(user_data, ptr);
+               }
+       }
 
        /* De-randomize the Bad Block Marker. */
        if (bbm && (nand->options & NAND_NEED_SCRAMBLING))
@@ -887,17 +926,46 @@ static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struct nand_chip *nand,
                                                bool bbm, int page)
 {
        struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
-       u8 user_data[USER_DATA_SZ];
+       struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+       unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
+       u8 *user_data = NULL;
 
        /* Randomize the Bad Block Marker. */
        if (bbm && (nand->options & NAND_NEED_SCRAMBLING)) {
-               memcpy(user_data, oob, sizeof(user_data));
+               user_data = kmalloc(user_data_sz, GFP_KERNEL);
+               memcpy(user_data, oob, user_data_sz);
                sunxi_nfc_randomize_bbm(nand, page, user_data);
                oob = user_data;
        }
 
-       writel(sunxi_nfc_buf_to_user_data(oob),
-              nfc->regs + NFC_REG_USER_DATA(nfc, step));
+       if (!nfc->caps->reg_user_data_len) {
+               /*
+                * For A10, the user data for step n is in the nth
+                * REG_USER_DATA
+                */
+               writel(sunxi_nfc_buf_to_user_data(oob),
+                      nfc->regs + NFC_REG_USER_DATA(nfc, step));
+       } else {
+               /*
+                * For H6 NAND controller, the user data for all steps is
+                * contained in 32 user data registers, but not at a specific
+                * offset for each step, they are just concatenated.
+                */
+               unsigned int user_data_off = 0;
+               const u8 *ptr = oob;
+               unsigned int i;
+
+               for (i = 0; i < step; i++)
+                       user_data_off += sunxi_nfc_user_data_sz(sunxi_nand, i);
+
+               user_data_off /= 4;
+               for (i = 0; i < user_data_sz / 4; i++, ptr += 4) {
+                       writel(sunxi_nfc_buf_to_user_data(ptr),
+                              nfc->regs + NFC_REG_USER_DATA(nfc, user_data_off + i));
+               }
+       }
+
+       kfree(user_data);
 }
 
 static void sunxi_nfc_hw_ecc_update_stats(struct nand_chip *nand,
@@ -918,6 +986,8 @@ static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
                                    bool *erased)
 {
        struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+       struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+       unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
        struct nand_ecc_ctrl *ecc = &nand->ecc;
        u32 tmp;
 
@@ -940,7 +1010,7 @@ static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
                        memset(data, pattern, ecc->size);
 
                if (oob)
-                       memset(oob, pattern, ecc->bytes + USER_DATA_SZ);
+                       memset(oob, pattern, ecc->bytes + user_data_sz);
 
                return 0;
        }
@@ -955,12 +1025,15 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
                                       u8 *oob, int oob_off,
                                       int *cur_off,
                                       unsigned int *max_bitflips,
-                                      bool bbm, bool oob_required, int page)
+                                      int step, bool oob_required, int page)
 {
        struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+       struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+       unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
        struct nand_ecc_ctrl *ecc = &nand->ecc;
        int raw_mode = 0;
        u32 pattern_found;
+       bool bbm = !step;
        bool erased;
        int ret;
        /* From the controller point of view, we are at step 0 */
@@ -978,8 +1051,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
        if (ret)
                return ret;
 
-       sunxi_nfc_reset_user_data_len(nfc);
-       sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, nfc_step);
+       sunxi_nfc_set_user_data_len(nfc, user_data_sz, nfc_step);
        sunxi_nfc_randomizer_config(nand, page, false);
        sunxi_nfc_randomizer_enable(nand);
        writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | NFC_ECC_OP,
@@ -990,7 +1062,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
        if (ret)
                return ret;
 
-       *cur_off = oob_off + ecc->bytes + USER_DATA_SZ;
+       *cur_off = oob_off + ecc->bytes + user_data_sz;
 
        pattern_found = readl(nfc->regs + nfc->caps->reg_pat_found);
        pattern_found = field_get(NFC_ECC_PAT_FOUND_MSK(nfc), pattern_found);
@@ -1014,10 +1086,10 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
                                      ecc->size);
 
                nand_change_read_column_op(nand, oob_off, oob,
-                                          ecc->bytes + USER_DATA_SZ, false);
+                                          ecc->bytes + user_data_sz, false);
 
                ret = nand_check_erased_ecc_chunk(data, ecc->size, oob,
-                                                 ecc->bytes + USER_DATA_SZ,
+                                                 ecc->bytes + user_data_sz,
                                                  NULL, 0, ecc->strength);
                if (ret >= 0)
                        raw_mode = 1;
@@ -1027,11 +1099,11 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
                if (oob_required) {
                        nand_change_read_column_op(nand, oob_off, NULL, 0,
                                                   false);
-                       sunxi_nfc_randomizer_read_buf(nand, oob, ecc->bytes + USER_DATA_SZ,
+                       sunxi_nfc_randomizer_read_buf(nand, oob, ecc->bytes + user_data_sz,
                                                      true, page);
 
                        sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, nfc_step,
-                                                           bbm, page);
+                                                           bbm, page, user_data_sz);
                }
        }
 
@@ -1040,13 +1112,42 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
        return raw_mode;
 }
 
+/*
+ * Returns the offset of the OOB for each step.
+ * (it includes the user data before the ECC data.)
+ */
+static int sunxi_get_oob_offset(struct sunxi_nand_chip *sunxi_nand,
+                               struct nand_ecc_ctrl *ecc, int step)
+{
+       int ecc_off = step * ecc->bytes;
+       int i;
+
+       for (i = 0; i < step; i++)
+               ecc_off += sunxi_nfc_user_data_sz(sunxi_nand, i);
+
+       return ecc_off;
+}
+
+/*
+ * Returns the offset of the ECC for each step.
+ * So, it's the same as sunxi_get_oob_offset(),
+ * but it skips the next user data.
+ */
+static int sunxi_get_ecc_offset(struct sunxi_nand_chip *sunxi_nand,
+                               struct nand_ecc_ctrl *ecc, int step)
+{
+       return sunxi_get_oob_offset(sunxi_nand, ecc, step) +
+               sunxi_nfc_user_data_sz(sunxi_nand, step);
+}
+
 static void sunxi_nfc_hw_ecc_read_extra_oob(struct nand_chip *nand,
                                            u8 *oob, int *cur_off,
                                            bool randomize, int page)
 {
+       struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
        struct mtd_info *mtd = nand_to_mtd(nand);
        struct nand_ecc_ctrl *ecc = &nand->ecc;
-       int offset = ((ecc->bytes + USER_DATA_SZ) * ecc->steps);
+       int offset = sunxi_get_oob_offset(sunxi_nand, ecc, ecc->steps);
        int len = mtd->oobsize - offset;
 
        if (len <= 0)
@@ -1071,6 +1172,7 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
                                            int nchunks)
 {
        bool randomized = nand->options & NAND_NEED_SCRAMBLING;
+       struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
        struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
        struct mtd_info *mtd = nand_to_mtd(nand);
        struct nand_ecc_ctrl *ecc = &nand->ecc;
@@ -1090,7 +1192,8 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
 
        sunxi_nfc_hw_ecc_enable(nand);
        sunxi_nfc_reset_user_data_len(nfc);
-       sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, 0);
+       for (i = 0; i < nchunks; i++)
+               sunxi_nfc_set_user_data_len(nfc, sunxi_nfc_user_data_sz(sunxi_nand, i), i);
        sunxi_nfc_randomizer_config(nand, page, false);
        sunxi_nfc_randomizer_enable(nand);
 
@@ -1125,7 +1228,8 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
 
        for (i = 0; i < nchunks; i++) {
                int data_off = i * ecc->size;
-               int oob_off = i * (ecc->bytes + USER_DATA_SZ);
+               unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, i);
+               int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
                u8 *data = buf + data_off;
                u8 *oob = nand->oob_poi + oob_off;
                bool erased;
@@ -1143,10 +1247,10 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
                        /* TODO: use DMA to retrieve OOB */
                        nand_change_read_column_op(nand,
                                                   mtd->writesize + oob_off,
-                                                  oob, ecc->bytes + USER_DATA_SZ, false);
+                                                  oob, ecc->bytes + user_data_sz, false);
 
-                       sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i,
-                                                           !i, page);
+                       sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i, !i,
+                                                           page, user_data_sz);
                }
 
                if (erased)
@@ -1158,7 +1262,8 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
        if (status & NFC_ECC_ERR_MSK(nfc)) {
                for (i = 0; i < nchunks; i++) {
                        int data_off = i * ecc->size;
-                       int oob_off = i * (ecc->bytes + USER_DATA_SZ);
+                       unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, i);
+                       int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
                        u8 *data = buf + data_off;
                        u8 *oob = nand->oob_poi + oob_off;
 
@@ -1178,10 +1283,10 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
                        /* TODO: use DMA to retrieve OOB */
                        nand_change_read_column_op(nand,
                                                   mtd->writesize + oob_off,
-                                                  oob, ecc->bytes + USER_DATA_SZ, false);
+                                                  oob, ecc->bytes + user_data_sz, false);
 
                        ret = nand_check_erased_ecc_chunk(data, ecc->size, oob,
-                                                         ecc->bytes + USER_DATA_SZ,
+                                                         ecc->bytes + user_data_sz,
                                                          NULL, 0,
                                                          ecc->strength);
                        if (ret >= 0)
@@ -1202,11 +1307,14 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
 static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
                                        const u8 *data, int data_off,
                                        const u8 *oob, int oob_off,
-                                       int *cur_off, bool bbm,
+                                       int *cur_off, int step,
                                        int page)
 {
        struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+       struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+       unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
        struct nand_ecc_ctrl *ecc = &nand->ecc;
+       bool bbm = !step;
        int ret;
        /* From the controller point of view, we are at step 0 */
        const int nfc_step = 0;
@@ -1225,8 +1333,7 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
 
        sunxi_nfc_randomizer_config(nand, page, false);
        sunxi_nfc_randomizer_enable(nand);
-       sunxi_nfc_reset_user_data_len(nfc);
-       sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, nfc_step);
+       sunxi_nfc_set_user_data_len(nfc, user_data_sz, nfc_step);
        sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, nfc_step, bbm, page);
 
        writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD |
@@ -1238,7 +1345,7 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
        if (ret)
                return ret;
 
-       *cur_off = oob_off + ecc->bytes + USER_DATA_SZ;
+       *cur_off = oob_off + ecc->bytes + user_data_sz;
 
        return 0;
 }
@@ -1248,8 +1355,9 @@ static void sunxi_nfc_hw_ecc_write_extra_oob(struct nand_chip *nand,
                                             int page)
 {
        struct mtd_info *mtd = nand_to_mtd(nand);
+       struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
        struct nand_ecc_ctrl *ecc = &nand->ecc;
-       int offset = ((ecc->bytes + USER_DATA_SZ) * ecc->steps);
+       int offset = sunxi_get_oob_offset(sunxi_nand, ecc, ecc->steps);
        int len = mtd->oobsize - offset;
 
        if (len <= 0)
@@ -1268,6 +1376,8 @@ static void sunxi_nfc_hw_ecc_write_extra_oob(struct nand_chip *nand,
 static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
                                      int oob_required, int page)
 {
+       struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+       struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
        struct mtd_info *mtd = nand_to_mtd(nand);
        struct nand_ecc_ctrl *ecc = &nand->ecc;
        unsigned int max_bitflips = 0;
@@ -1280,16 +1390,17 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
 
        sunxi_nfc_hw_ecc_enable(nand);
 
+       sunxi_nfc_reset_user_data_len(nfc);
        for (i = 0; i < ecc->steps; i++) {
                int data_off = i * ecc->size;
-               int oob_off = i * (ecc->bytes + USER_DATA_SZ);
+               int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
                u8 *data = buf + data_off;
                u8 *oob = nand->oob_poi + oob_off;
 
                ret = sunxi_nfc_hw_ecc_read_chunk(nand, data, data_off, oob,
                                                  oob_off + mtd->writesize,
                                                  &cur_off, &max_bitflips,
-                                                 !i, oob_required, page);
+                                                 i, oob_required, page);
                if (ret < 0)
                        return ret;
                else if (ret)
@@ -1327,6 +1438,8 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand,
                                         u32 data_offs, u32 readlen,
                                         u8 *bufpoi, int page)
 {
+       struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+       struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
        struct mtd_info *mtd = nand_to_mtd(nand);
        struct nand_ecc_ctrl *ecc = &nand->ecc;
        int ret, i, cur_off = 0;
@@ -1338,17 +1451,18 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand,
 
        sunxi_nfc_hw_ecc_enable(nand);
 
+       sunxi_nfc_reset_user_data_len(nfc);
        for (i = data_offs / ecc->size;
             i < DIV_ROUND_UP(data_offs + readlen, ecc->size); i++) {
                int data_off = i * ecc->size;
-               int oob_off = i * (ecc->bytes + USER_DATA_SZ);
+               int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
                u8 *data = bufpoi + data_off;
                u8 *oob = nand->oob_poi + oob_off;
 
                ret = sunxi_nfc_hw_ecc_read_chunk(nand, data, data_off,
                                                  oob,
                                                  oob_off + mtd->writesize,
-                                                 &cur_off, &max_bitflips, !i,
+                                                 &cur_off, &max_bitflips, i,
                                                  false, page);
                if (ret < 0)
                        return ret;
@@ -1383,6 +1497,8 @@ static int sunxi_nfc_hw_ecc_write_page(struct nand_chip *nand,
                                       const uint8_t *buf, int oob_required,
                                       int page)
 {
+       struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+       struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
        struct mtd_info *mtd = nand_to_mtd(nand);
        struct nand_ecc_ctrl *ecc = &nand->ecc;
        int ret, i, cur_off = 0;
@@ -1393,15 +1509,16 @@ static int sunxi_nfc_hw_ecc_write_page(struct nand_chip *nand,
 
        sunxi_nfc_hw_ecc_enable(nand);
 
+       sunxi_nfc_reset_user_data_len(nfc);
        for (i = 0; i < ecc->steps; i++) {
                int data_off = i * ecc->size;
-               int oob_off = i * (ecc->bytes + USER_DATA_SZ);
+               int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
                const u8 *data = buf + data_off;
                const u8 *oob = nand->oob_poi + oob_off;
 
                ret = sunxi_nfc_hw_ecc_write_chunk(nand, data, data_off, oob,
                                                   oob_off + mtd->writesize,
-                                                  &cur_off, !i, page);
+                                                  &cur_off, i, page);
                if (ret)
                        return ret;
        }
@@ -1420,6 +1537,8 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct nand_chip *nand,
                                          const u8 *buf, int oob_required,
                                          int page)
 {
+       struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+       struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
        struct mtd_info *mtd = nand_to_mtd(nand);
        struct nand_ecc_ctrl *ecc = &nand->ecc;
        int ret, i, cur_off = 0;
@@ -1430,16 +1549,17 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct nand_chip *nand,
 
        sunxi_nfc_hw_ecc_enable(nand);
 
+       sunxi_nfc_reset_user_data_len(nfc);
        for (i = data_offs / ecc->size;
             i < DIV_ROUND_UP(data_offs + data_len, ecc->size); i++) {
                int data_off = i * ecc->size;
-               int oob_off = i * (ecc->bytes + USER_DATA_SZ);
+               int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
                const u8 *data = buf + data_off;
                const u8 *oob = nand->oob_poi + oob_off;
 
                ret = sunxi_nfc_hw_ecc_write_chunk(nand, data, data_off, oob,
                                                   oob_off + mtd->writesize,
-                                                  &cur_off, !i, page);
+                                                  &cur_off, i, page);
                if (ret)
                        return ret;
        }
@@ -1455,6 +1575,7 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
                                           int page)
 {
        struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+       struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
        struct nand_ecc_ctrl *ecc = &nand->ecc;
        struct scatterlist sg;
        u32 wait;
@@ -1473,10 +1594,12 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
 
        sunxi_nfc_reset_user_data_len(nfc);
        for (i = 0; i < ecc->steps; i++) {
-               const u8 *oob = nand->oob_poi + (i * (ecc->bytes + USER_DATA_SZ));
+               unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, i);
+               int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
+               const u8 *oob = nand->oob_poi + oob_off;
 
                sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, i, !i, page);
-               sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, i);
+               sunxi_nfc_set_user_data_len(nfc, user_data_sz, i);
        }
 
        nand_prog_page_begin_op(nand, page, 0, NULL, 0);
@@ -1740,11 +1863,12 @@ static int sunxi_nand_ooblayout_ecc(struct mtd_info *mtd, int section,
 {
        struct nand_chip *nand = mtd_to_nand(mtd);
        struct nand_ecc_ctrl *ecc = &nand->ecc;
+       struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
 
        if (section >= ecc->steps)
                return -ERANGE;
 
-       oobregion->offset = section * (ecc->bytes + USER_DATA_SZ) + USER_DATA_SZ;
+       oobregion->offset = sunxi_get_ecc_offset(sunxi_nand, ecc, section);
        oobregion->length = ecc->bytes;
 
        return 0;
@@ -1755,6 +1879,8 @@ static int sunxi_nand_ooblayout_free(struct mtd_info *mtd, int section,
 {
        struct nand_chip *nand = mtd_to_nand(mtd);
        struct nand_ecc_ctrl *ecc = &nand->ecc;
+       struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+       unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, section);
 
        /*
         * The controller does not provide access to OOB bytes
@@ -1765,18 +1891,18 @@ static int sunxi_nand_ooblayout_free(struct mtd_info *mtd, int section,
 
        /*
         * The first 2 bytes are used for BB markers, hence we
-        * only have USER_DATA_SZ - 2 bytes available in the first user data
+        * only have user_data_sz - 2 bytes available in the first user data
         * section.
         */
        if (section == 0) {
                oobregion->offset = 2;
-               oobregion->length = USER_DATA_SZ - 2;
+               oobregion->length = user_data_sz - 2;
 
                return 0;
        }
 
-       oobregion->offset = section * (ecc->bytes + USER_DATA_SZ);
-       oobregion->length = USER_DATA_SZ;
+       oobregion->offset = sunxi_get_ecc_offset(sunxi_nand, ecc, section);
+       oobregion->length = user_data_sz;
 
        return 0;
 }
@@ -1786,6 +1912,43 @@ static const struct mtd_ooblayout_ops sunxi_nand_ooblayout_ops = {
        .free = sunxi_nand_ooblayout_free,
 };
 
+static void sunxi_nand_detach_chip(struct nand_chip *nand)
+{
+       struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+       struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+
+       devm_kfree(nfc->dev, sunxi_nand->user_data_bytes);
+       sunxi_nand->user_data_bytes = NULL;
+}
+
+static int sunxi_nfc_maximize_user_data(struct nand_chip *nand, uint32_t oobsize,
+                                       int ecc_bytes, int nsectors)
+{
+       struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+       struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+       const struct sunxi_nfc_caps *c = nfc->caps;
+       int remaining_bytes = oobsize - (ecc_bytes * nsectors);
+       int i, step;
+
+       sunxi_nand->user_data_bytes = devm_kzalloc(nfc->dev, nsectors,
+                                                  GFP_KERNEL);
+       if (!sunxi_nand->user_data_bytes)
+               return -ENOMEM;
+
+       for (step = 0; (step < nsectors) && (remaining_bytes > 0); step++) {
+               for (i = 0; i < c->nuser_data_tab; i++) {
+                       if (c->user_data_len_tab[i] > remaining_bytes)
+                               break;
+                       sunxi_nand->user_data_bytes[step] = c->user_data_len_tab[i];
+               }
+               remaining_bytes -= sunxi_nand->user_data_bytes[step];
+               if (sunxi_nand->user_data_bytes[step] == 0)
+                       break;
+       }
+
+       return 0;
+}
+
 static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
                                       struct nand_ecc_ctrl *ecc,
                                       struct device_node *np)
@@ -1795,33 +1958,50 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
        const u8 *strengths = nfc->caps->ecc_strengths;
        struct mtd_info *mtd = nand_to_mtd(nand);
        struct nand_device *nanddev = mtd_to_nanddev(mtd);
+       int total_user_data_sz = 0;
        int nsectors;
        int ecc_mode;
        int i;
 
        if (nanddev->ecc.user_conf.flags & NAND_ECC_MAXIMIZE_STRENGTH) {
-               int bytes;
+               int bytes = mtd->oobsize;
 
                ecc->size = 1024;
                nsectors = mtd->writesize / ecc->size;
 
-               /*
-                * The 2 BBM bytes should not be removed from the grand total,
-                * because they are part of the USER_DATA_SZ.
-                * But we can't modify that for older platform since it may
-                * result in a stronger ECC at the end, and break the
-                * compatibility.
-                */
-               if (nfc->caps->legacy_max_strength)
-                       bytes = (mtd->oobsize - 2) / nsectors;
-               else
-                       bytes = mtd->oobsize / nsectors;
+               if (!nfc->caps->reg_user_data_len) {
+                       /*
+                        * If there's a fixed user data length, subtract it before
+                        * computing the max ECC strength
+                        */
+
+                       for (i = 0; i < nsectors; i++)
+                               total_user_data_sz += sunxi_nfc_user_data_sz(sunxi_nand, i);
+
+                       /*
+                        * The 2 BBM bytes should not be removed from the grand total,
+                        * because they are part of the USER_DATA_SZ.
+                        * But we can't modify that for older platform since it may
+                        * result in a stronger ECC at the end, and break the
+                        * compatibility.
+                        */
+                       if (nfc->caps->legacy_max_strength)
+                               bytes -= 2;
+
+                       bytes -= total_user_data_sz;
+               } else {
+                       /*
+                        * remove at least the BBM size before computing the
+                        * max ECC
+                        */
+                       bytes -= 2;
+               }
 
                /*
-                * USER_DATA_SZ non-ECC bytes are added before each ECC bytes
-                * section, they contain the 2 BBM bytes
+                * Once all user data has been subtracted, the rest can be used
+                * for ECC bytes
                 */
-               bytes -= USER_DATA_SZ;
+               bytes /= nsectors;
 
                /* and bytes has to be even. */
                if (bytes % 2)
@@ -1874,7 +2054,19 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
 
        nsectors = mtd->writesize / ecc->size;
 
-       if (mtd->oobsize < ((ecc->bytes + USER_DATA_SZ) * nsectors))
+       /*
+        * The rationale for variable data length is to prioritize maximum ECC
+        * strength, and then use the remaining space for user data.
+        */
+       if (nfc->caps->reg_user_data_len)
+               sunxi_nfc_maximize_user_data(nand, mtd->oobsize, ecc->bytes,
+                                            nsectors);
+
+       if (total_user_data_sz == 0)
+               for (i = 0; i < nsectors; i++)
+                       total_user_data_sz += sunxi_nfc_user_data_sz(sunxi_nand, i);
+
+       if (mtd->oobsize < (ecc->bytes * nsectors + total_user_data_sz))
                return -EINVAL;
 
        ecc->read_oob = sunxi_nfc_hw_ecc_read_oob;
@@ -2104,6 +2296,7 @@ static int sunxi_nfc_exec_op(struct nand_chip *nand,
 
 static const struct nand_controller_ops sunxi_nand_controller_ops = {
        .attach_chip = sunxi_nand_attach_chip,
+       .detach_chip = sunxi_nand_detach_chip,
        .setup_interface = sunxi_nfc_setup_interface,
        .exec_op = sunxi_nfc_exec_op,
 };