@@ -256,12 +256,38 @@ struct sunxi_nfc_timings {
s32 tRHW[SUNXI_NFC_TIMING_STEPS];
};
+struct sunxi_nfc_ecc_read;
+
+/* OOB geometry and interpretation; callbacks may issue supplementary reads. */
+struct sunxi_nfc_oob_ops {
+ /* Reserve OOB bytes for nsteps before ecc->steps is initialized. */
+ unsigned int (*min_user_data)(struct nand_chip *nand, int nsteps);
+ /* Install the user-data layout for the selected ECC geometry. */
+ int (*init_user_data)(struct nand_chip *nand, int nsteps);
+ /* Optional symmetric conversion of the on-flash bad-block marker. */
+ void (*transform_bbm)(struct nand_chip *nand, int page, u8 *bbm);
+ /*
+ * first_step is page-relative; status bits use transfer-local slots.
+ * PIO uses slot zero even when first_step is nonzero.
+ * Return zero on success or a negative I/O error.
+ */
+ int (*read_batch)(struct nand_chip *nand, struct sunxi_nfc_ecc_read *read,
+ u32 status, u32 pattern_found, int first_step, int nsteps);
+ /*
+ * Finish remaining OOB; return max bitflips or a negative I/O error.
+ * cur_off is an absolute NAND column, or NULL if unknown.
+ */
+ int (*finish_read)(struct nand_chip *nand, struct sunxi_nfc_ecc_read *read,
+ int *cur_off);
+};
+
/**
* struct sunxi_nand_chip - stores NAND chip device related information
*
* @node: used to store NAND chips into a list
* @nand: base NAND chip structure
* @ecc: ECC controller structure
+ * @oob_ops: OOB geometry and ECC-result interpretation
* @clk_rate: clk_rate required for this NAND chip
* @timing_cfg: TIMING_CFG register value for this NAND chip
* @timing_ctl: TIMING_CTL register value for this NAND chip
@@ -273,6 +299,7 @@ struct sunxi_nand_chip {
struct list_head node;
struct nand_chip nand;
struct sunxi_nand_hw_ecc ecc;
+ const struct sunxi_nfc_oob_ops *oob_ops;
unsigned long clk_rate;
u32 timing_cfg;
u32 timing_ctl;
@@ -908,6 +935,7 @@ static u8 sunxi_nfc_user_data_sz(struct sunxi_nand_chip *sunxi_nand, int step)
return sunxi_nand->user_data_bytes[step];
}
+/* PIO uses register zero; DMA concatenates user data within each transfer. */
static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8 *oob,
int step, bool bbm, int page,
unsigned int user_data_sz)
@@ -946,8 +974,8 @@ static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8 *oob,
}
/* De-randomize the Bad Block Marker. */
- if (bbm && (nand->options & NAND_NEED_SCRAMBLING))
- sunxi_nfc_randomize_bbm(nand, page, oob);
+ if (bbm && sunxi_nand->oob_ops->transform_bbm)
+ sunxi_nand->oob_ops->transform_bbm(nand, page, oob);
}
/*
@@ -1007,9 +1035,9 @@ static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struct nand_chip *nand,
u8 user_data[SUNXI_NFC_MAX_USER_DATA_SZ] = {};
/* Randomize the Bad Block Marker. */
- if (bbm && (nand->options & NAND_NEED_SCRAMBLING)) {
+ if (bbm && sunxi_nand->oob_ops->transform_bbm) {
memcpy(user_data, oob, user_data_sz);
- sunxi_nfc_randomize_bbm(nand, page, user_data);
+ sunxi_nand->oob_ops->transform_bbm(nand, page, user_data);
oob = user_data;
}
@@ -1055,6 +1083,41 @@ static void sunxi_nfc_hw_ecc_update_stats(struct nand_chip *nand,
}
}
+/* Return whether the chunk is erased, and account for its ECC result. */
+static int sunxi_nfc_hw_ecc_read_error(struct nand_chip *nand,
+ u8 *data, int data_off,
+ u8 *oob, int oob_off,
+ unsigned int user_data_sz,
+ unsigned int *max_bitflips)
+{
+ struct nand_ecc_ctrl *ecc = &nand->ecc;
+ unsigned int oob_len = ecc->bytes + user_data_sz;
+ int ret;
+
+ /* Check the physical representation for bitflips in erased pages. */
+ if (nand->options & NAND_NEED_SCRAMBLING)
+ nand_change_read_column_op(nand, data_off, data, ecc->size, false);
+
+ nand_change_read_column_op(nand, oob_off, oob, oob_len, false);
+
+ ret = nand_check_erased_ecc_chunk(data, ecc->size, oob, oob_len, NULL, 0,
+ ecc->strength);
+
+ sunxi_nfc_hw_ecc_update_stats(nand, max_bitflips, ret);
+
+ return ret >= 0;
+}
+
+/* Shared across a read's transfers; buf remains the full-page buffer base. */
+struct sunxi_nfc_ecc_read {
+ u8 *buf;
+ int page;
+ unsigned int max_bitflips;
+ bool oob_required;
+ bool dma;
+ bool has_erased_chunk;
+};
+
static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
int step, u32 status, u32 pattern_found,
bool *erased)
@@ -1094,98 +1157,6 @@ static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
return NFC_ECC_ERR_CNT(step, tmp);
}
-static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
- u8 *data, int data_off,
- u8 *oob, int oob_off,
- int *cur_off,
- unsigned int *max_bitflips,
- 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 */
- const int nfc_step = 0;
-
- if (*cur_off != data_off)
- nand_change_read_column_op(nand, data_off, NULL, 0, false);
-
- sunxi_nfc_randomizer_read_buf(nand, NULL, ecc->size, false, page);
-
- if (data_off + ecc->size != oob_off)
- nand_change_read_column_op(nand, oob_off, NULL, 0, false);
-
- ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
- if (ret)
- return ret;
-
- 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,
- nfc->regs + NFC_REG_CMD);
-
- ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, false, 0);
- sunxi_nfc_randomizer_disable(nand);
- if (ret)
- return ret;
-
- *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);
-
- ret = sunxi_nfc_hw_ecc_correct(nand, data, oob_required ? oob : NULL,
- nfc_step, readl(nfc->regs + NFC_REG_ECC_ST),
- pattern_found, &erased);
- if (erased)
- return 1;
-
- if (ret < 0) {
- /*
- * Re-read the data with the randomizer disabled to identify
- * bitflips in erased pages.
- */
- if (nand->options & NAND_NEED_SCRAMBLING)
- nand_change_read_column_op(nand, data_off, data,
- ecc->size, false);
- else
- memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE,
- ecc->size);
-
- nand_change_read_column_op(nand, oob_off, oob,
- ecc->bytes + user_data_sz, false);
-
- ret = nand_check_erased_ecc_chunk(data, ecc->size, oob,
- ecc->bytes + user_data_sz,
- NULL, 0, ecc->strength);
- if (ret >= 0)
- raw_mode = 1;
- } else {
- memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE, ecc->size);
-
- 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,
- true, page);
-
- sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, nfc_step,
- bbm, page, user_data_sz);
- }
- }
-
- sunxi_nfc_hw_ecc_update_stats(nand, max_bitflips, ret);
-
- return raw_mode;
-}
-
/*
* Returns the offset of the OOB for each step.
* (it includes the user data before the ECC data.)
@@ -1241,18 +1212,166 @@ static void sunxi_nfc_hw_ecc_read_extra_oob(struct nand_chip *nand,
*cur_off = mtd->oobsize + mtd->writesize;
}
-static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf,
- int oob_required, int page,
- int nchunks)
+/* Interpret one PIO step or a completed DMA batch before reusing its status. */
+static int sunxi_nfc_oob_read_batch(struct nand_chip *nand,
+ struct sunxi_nfc_ecc_read *read,
+ u32 status, u32 pattern_found,
+ int first_step, int nsteps)
{
- 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;
- unsigned int max_bitflips = 0;
- int ret, i, raw_mode = 0;
+ bool randomized = nand->options & NAND_NEED_SCRAMBLING;
+ unsigned int user_data_sz;
+ int i, ret;
+
+ for (i = 0; i < nsteps; i++) {
+ int step = first_step + i;
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, step);
+ u8 *data = read->buf + step * ecc->size;
+ u8 *oob = nand->oob_poi + oob_off;
+ bool erased;
+ int bitflips;
+
+ user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
+ bitflips = sunxi_nfc_hw_ecc_correct(nand,
+ !read->dma || randomized ? data : NULL,
+ read->oob_required ? oob : NULL,
+ i, status, pattern_found, &erased);
+ if (bitflips < 0) {
+ /* Preserve PIO data before the raw erased-page check. */
+ if (!read->dma && !randomized)
+ memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE, ecc->size);
+ continue;
+ }
+
+ if (erased) {
+ read->has_erased_chunk = true;
+ continue;
+ }
+
+ if (!read->dma)
+ memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE, ecc->size);
+
+ if (read->oob_required) {
+ /* Keep the existing PIO and DMA representations of parity. */
+ if (read->dma) {
+ nand_change_read_column_op(nand, mtd->writesize + oob_off,
+ oob, ecc->bytes + user_data_sz, false);
+ } else {
+ nand_change_read_column_op(nand, mtd->writesize + oob_off,
+ NULL, 0, false);
+ sunxi_nfc_randomizer_read_buf(nand, oob,
+ ecc->bytes + user_data_sz,
+ true, read->page);
+ }
+
+ sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i, !step,
+ read->page, user_data_sz);
+ }
+
+ sunxi_nfc_hw_ecc_update_stats(nand, &read->max_bitflips, bitflips);
+ }
+
+ if (!(status & NFC_ECC_ERR_MSK(nfc)))
+ return 0;
+
+ /* Finish consuming the batch's corrected OOB before raw error rereads. */
+ for (i = 0; i < nsteps; i++) {
+ int step = first_step + i;
+ int data_off, oob_off;
+
+ if (!(status & NFC_ECC_ERR(i)))
+ continue;
+
+ data_off = step * ecc->size;
+ oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, step);
+ user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
+ ret = sunxi_nfc_hw_ecc_read_error(nand, read->buf + data_off, data_off,
+ nand->oob_poi + oob_off,
+ mtd->writesize + oob_off, user_data_sz,
+ &read->max_bitflips);
+ if (ret < 0)
+ return ret;
+ if (ret)
+ read->has_erased_chunk = true;
+ }
+
+ return 0;
+}
+
+static int sunxi_nfc_oob_finish_read(struct nand_chip *nand,
+ struct sunxi_nfc_ecc_read *read, int *cur_off)
+{
+ if (read->oob_required)
+ sunxi_nfc_hw_ecc_read_extra_oob(nand, nand->oob_poi, cur_off,
+ !read->has_erased_chunk, read->page);
+
+ return read->max_bitflips;
+}
+
+static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
+ struct sunxi_nfc_ecc_read *read,
+ int step, int *cur_off)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ struct nand_ecc_ctrl *ecc = &nand->ecc;
+ int data_off = step * ecc->size;
+ int oob_off = nand_to_mtd(nand)->writesize + sunxi_get_oob_offset(sunxi_nand, ecc, step);
+ int page = read->page;
+ u32 pattern_found;
+ int ret;
+
+ if (*cur_off != data_off)
+ nand_change_read_column_op(nand, data_off, NULL, 0, false);
+
+ sunxi_nfc_randomizer_read_buf(nand, NULL, ecc->size, false, page);
+
+ if (data_off + ecc->size != oob_off)
+ nand_change_read_column_op(nand, oob_off, NULL, 0, false);
+
+ ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
+ if (ret)
+ return ret;
+
+ sunxi_nfc_set_user_data_len(nfc, user_data_sz, 0);
+ sunxi_nfc_randomizer_config(nand, page, false);
+ sunxi_nfc_randomizer_enable(nand);
+ writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | NFC_ECC_OP,
+ nfc->regs + NFC_REG_CMD);
+
+ ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, false, 0);
+ sunxi_nfc_randomizer_disable(nand);
+ if (ret)
+ return ret;
+
+ *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);
+
+ return sunxi_nand->oob_ops->read_batch(nand, read,
+ readl(nfc->regs + NFC_REG_ECC_ST),
+ pattern_found, step, 1);
+}
+
+static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, u8 *buf,
+ int oob_required, int page, int nchunks)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ struct nand_ecc_ctrl *ecc = &nand->ecc;
+ struct sunxi_nfc_ecc_read read = {
+ .buf = buf,
+ .page = page,
+ .oob_required = oob_required,
+ .dma = true,
+ };
struct scatterlist sg;
+ int ret, i;
u32 status, pattern_found, wait;
ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
@@ -1299,83 +1418,12 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
status = readl(nfc->regs + NFC_REG_ECC_ST);
pattern_found = readl(nfc->regs + nfc->caps->reg_pat_found);
pattern_found = field_get(NFC_ECC_PAT_FOUND_MSK(nfc), pattern_found);
+ ret = sunxi_nand->oob_ops->read_batch(nand, &read, status, pattern_found,
+ 0, nchunks);
+ if (ret)
+ return ret;
- for (i = 0; i < nchunks; i++) {
- int data_off = i * ecc->size;
- 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;
-
- ret = sunxi_nfc_hw_ecc_correct(nand, randomized ? data : NULL,
- oob_required ? oob : NULL,
- i, status, pattern_found,
- &erased);
-
- /* ECC errors are handled in the second loop. */
- if (ret < 0)
- continue;
-
- if (oob_required && !erased) {
- /* TODO: use DMA to retrieve OOB */
- nand_change_read_column_op(nand,
- mtd->writesize + oob_off,
- oob, ecc->bytes + user_data_sz, false);
-
- sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i, !i,
- page, user_data_sz);
- }
-
- if (erased)
- raw_mode = 1;
-
- sunxi_nfc_hw_ecc_update_stats(nand, &max_bitflips, ret);
- }
-
- if (status & NFC_ECC_ERR_MSK(nfc)) {
- for (i = 0; i < nchunks; i++) {
- int data_off = i * ecc->size;
- 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;
-
- if (!(status & NFC_ECC_ERR(i)))
- continue;
-
- /*
- * Re-read the data with the randomizer disabled to
- * identify bitflips in erased pages.
- * TODO: use DMA to read page in raw mode
- */
- if (randomized)
- nand_change_read_column_op(nand, data_off,
- data, ecc->size,
- false);
-
- /* TODO: use DMA to retrieve OOB */
- nand_change_read_column_op(nand,
- mtd->writesize + oob_off,
- oob, ecc->bytes + user_data_sz, false);
-
- ret = nand_check_erased_ecc_chunk(data, ecc->size, oob,
- ecc->bytes + user_data_sz,
- NULL, 0,
- ecc->strength);
- if (ret >= 0)
- raw_mode = 1;
-
- sunxi_nfc_hw_ecc_update_stats(nand, &max_bitflips, ret);
- }
- }
-
- if (oob_required)
- sunxi_nfc_hw_ecc_read_extra_oob(nand, nand->oob_poi,
- NULL, !raw_mode,
- page);
-
- return max_bitflips;
+ return sunxi_nand->oob_ops->finish_read(nand, &read, NULL);
}
static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
@@ -1452,11 +1500,13 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
{
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;
+ struct sunxi_nfc_ecc_read read = {
+ .buf = buf,
+ .page = page,
+ .oob_required = oob_required,
+ };
int ret, i, cur_off = 0;
- bool raw_mode = false;
sunxi_nfc_select_chip(nand, nand->cur_cs);
@@ -1466,28 +1516,15 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
sunxi_nfc_reset_user_data_len(nfc);
for (i = 0; i < ecc->steps; i++) {
- int data_off = i * ecc->size;
- 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);
+ ret = sunxi_nfc_hw_ecc_read_chunk(nand, &read, i, &cur_off);
if (ret < 0)
return ret;
- else if (ret)
- raw_mode = true;
}
- if (oob_required)
- sunxi_nfc_hw_ecc_read_extra_oob(nand, nand->oob_poi, &cur_off,
- !raw_mode, page);
-
+ ret = sunxi_nand->oob_ops->finish_read(nand, &read, &cur_off);
sunxi_nfc_hw_ecc_disable(nand);
- return max_bitflips;
+ return ret;
}
static int sunxi_nfc_hw_ecc_read_page_dma(struct nand_chip *nand, u8 *buf,
@@ -1513,11 +1550,12 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand,
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;
+ struct sunxi_nfc_ecc_read read = {
+ .buf = bufpoi,
+ .page = page,
+ };
int ret, i, cur_off = 0;
- unsigned int max_bitflips = 0;
sunxi_nfc_select_chip(nand, nand->cur_cs);
@@ -1528,23 +1566,15 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *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 = 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,
- false, page);
+ ret = sunxi_nfc_hw_ecc_read_chunk(nand, &read, i, &cur_off);
if (ret < 0)
return ret;
}
+ ret = read.max_bitflips;
sunxi_nfc_hw_ecc_disable(nand);
- return max_bitflips;
+ return ret;
}
static int sunxi_nfc_hw_ecc_read_subpage_dma(struct nand_chip *nand,
@@ -2066,6 +2096,58 @@ static int sunxi_nfc_maximize_user_data(struct nand_chip *nand, uint32_t oobsize
return 0;
}
+static unsigned int sunxi_nfc_oob_min_user_data(struct nand_chip *nand, int nsteps)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ unsigned int size = 0;
+ int i;
+
+ /* Variable lengths reserve the first word, which contains the BBM. */
+ if (nfc->caps->reg_user_data_len)
+ return USER_DATA_SZ;
+
+ for (i = 0; i < nsteps; i++)
+ size += sunxi_nfc_user_data_sz(sunxi_nand, i);
+
+ /* Preserve the older platforms' extra BBM reservation and ECC strength. */
+ if (nfc->caps->legacy_max_strength)
+ size += 2;
+
+ return size;
+}
+
+static int sunxi_nfc_oob_init_user_data(struct nand_chip *nand, int nsteps)
+{
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+
+ if (!nfc->caps->reg_user_data_len)
+ return 0;
+
+ /* Prioritize ECC strength, then use the remaining space for user data. */
+ return sunxi_nfc_maximize_user_data(nand, nand_to_mtd(nand)->oobsize,
+ nand->ecc.bytes, nsteps);
+}
+
+/*
+ * Both modes use the same layout and ECC policy. Scrambled transfers also
+ * compensate the first two user bytes to keep the on-flash BBM unmodified.
+ */
+static const struct sunxi_nfc_oob_ops sunxi_nfc_plain_oob_ops = {
+ .min_user_data = sunxi_nfc_oob_min_user_data,
+ .init_user_data = sunxi_nfc_oob_init_user_data,
+ .read_batch = sunxi_nfc_oob_read_batch,
+ .finish_read = sunxi_nfc_oob_finish_read,
+};
+
+static const struct sunxi_nfc_oob_ops sunxi_nfc_scrambled_plain_bbm_oob_ops = {
+ .min_user_data = sunxi_nfc_oob_min_user_data,
+ .init_user_data = sunxi_nfc_oob_init_user_data,
+ .transform_bbm = sunxi_nfc_randomize_bbm,
+ .read_batch = sunxi_nfc_oob_read_batch,
+ .finish_read = sunxi_nfc_oob_finish_read,
+};
+
static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
struct nand_ecc_ctrl *ecc,
struct device_node *np)
@@ -2090,33 +2172,7 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
ecc->size = 1024;
nsectors = mtd->writesize / ecc->size;
- 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 {
- /*
- * User-data lengths are encoded in four-byte units. Reserve
- * the first word because it contains the two BBM bytes.
- */
- bytes -= USER_DATA_SZ;
- }
+ bytes -= sunxi_nand->oob_ops->min_user_data(nand, nsectors);
/*
* Once all user data has been subtracted, the rest can be used
@@ -2175,20 +2231,12 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
nsectors = mtd->writesize / ecc->size;
- /*
- * 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) {
- ret = sunxi_nfc_maximize_user_data(nand, mtd->oobsize,
- ecc->bytes, nsectors);
- if (ret)
- return ret;
- }
+ ret = sunxi_nand->oob_ops->init_user_data(nand, nsectors);
+ if (ret)
+ return ret;
- if (total_user_data_sz == 0)
- for (i = 0; i < nsectors; i++)
- total_user_data_sz += sunxi_nfc_user_data_sz(sunxi_nand, i);
+ 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;
@@ -2232,6 +2280,7 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
static int sunxi_nand_attach_chip(struct nand_chip *nand)
{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
const struct nand_ecc_props *requirements =
nanddev_get_ecc_requirements(&nand->base);
struct nand_ecc_ctrl *ecc = &nand->ecc;
@@ -2254,6 +2303,9 @@ static int sunxi_nand_attach_chip(struct nand_chip *nand)
if (!ecc->size || !ecc->strength)
return -EINVAL;
+ sunxi_nand->oob_ops = nand->options & NAND_NEED_SCRAMBLING ?
+ &sunxi_nfc_scrambled_plain_bbm_oob_ops : &sunxi_nfc_plain_oob_ops;
+
switch (ecc->engine_type) {
case NAND_ECC_ENGINE_TYPE_ON_HOST:
ret = sunxi_nand_hw_ecc_ctrl_init(nand, ecc, np);