+static inline void
+ar934x_nfc_enable_hwecc(struct ar934x_nfc *nfc)
+{
+ nfc->ctrl_reg |= AR934X_NFC_CTRL_ECC_EN;
+ nfc->ctrl_reg &= ~AR934X_NFC_CTRL_CUSTOM_SIZE_EN;
+}
+
+static inline void
+ar934x_nfc_disable_hwecc(struct ar934x_nfc *nfc)
+{
+ nfc->ctrl_reg &= ~AR934X_NFC_CTRL_ECC_EN;
+ nfc->ctrl_reg |= AR934X_NFC_CTRL_CUSTOM_SIZE_EN;
+}
+
+static int
+ar934x_nfc_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
+ int page)
+{
+ struct ar934x_nfc *nfc = mtd_to_ar934x_nfc(mtd);
+ int err;
+
+ nfc_dbg(nfc, "read_oob: page:%d\n", page);
+
+ err = ar934x_nfc_send_read(nfc, NAND_CMD_READ0, mtd->writesize, page,
+ mtd->oobsize);
+ if (err)
+ return err;
+
+ memcpy(chip->oob_poi, nfc->buf, mtd->oobsize);
+
+ return 0;
+}
+
+static int
+ar934x_nfc_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
+ int page)
+{
+ struct ar934x_nfc *nfc = mtd_to_ar934x_nfc(mtd);
+
+ nfc_dbg(nfc, "write_oob: page:%d\n", page);
+
+ memcpy(nfc->buf, chip->oob_poi, mtd->oobsize);
+
+ return ar934x_nfc_send_write(nfc, NAND_CMD_PAGEPROG, mtd->writesize,
+ page, mtd->oobsize);
+}
+
+static int
+ar934x_nfc_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
+ u8 *buf, int oob_required, int page)
+{
+ struct ar934x_nfc *nfc = mtd_to_ar934x_nfc(mtd);
+ int len;
+ int err;
+
+ nfc_dbg(nfc, "read_page_raw: page:%d oob:%d\n", page, oob_required);
+
+ len = mtd->writesize;
+ if (oob_required)
+ len += mtd->oobsize;
+
+ err = ar934x_nfc_send_read(nfc, NAND_CMD_READ0, 0, page, len);
+ if (err)
+ return err;
+
+ memcpy(buf, nfc->buf, mtd->writesize);
+
+ if (oob_required)
+ memcpy(chip->oob_poi, &nfc->buf[mtd->writesize], mtd->oobsize);
+
+ return 0;
+}
+
+static int
+ar934x_nfc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
+ u8 *buf, int oob_required, int page)
+{
+ struct ar934x_nfc *nfc = mtd_to_ar934x_nfc(mtd);
+ u32 ecc_ctrl;
+ int max_bitflips = 0;
+ bool ecc_failed;
+ bool ecc_corrected;
+ int err;
+
+ nfc_dbg(nfc, "read_page: page:%d oob:%d\n", page, oob_required);
+
+ ar934x_nfc_enable_hwecc(nfc);
+ err = ar934x_nfc_send_read(nfc, NAND_CMD_READ0, 0, page,
+ mtd->writesize);
+ ar934x_nfc_disable_hwecc(nfc);
+
+ if (err)
+ return err;
+
+ /* TODO: optimize to avoid memcpy */
+ memcpy(buf, nfc->buf, mtd->writesize);
+
+ /* read the ECC status */
+ ecc_ctrl = ar934x_nfc_rr(nfc, AR934X_NFC_REG_ECC_CTRL);
+ ecc_failed = ecc_ctrl & AR934X_NFC_ECC_CTRL_ERR_UNCORRECT;
+ ecc_corrected = ecc_ctrl & AR934X_NFC_ECC_CTRL_ERR_CORRECT;
+
+ if (oob_required || ecc_failed) {
+ err = ar934x_nfc_send_read(nfc, NAND_CMD_READ0, mtd->writesize,
+ page, mtd->oobsize);
+ if (err)
+ return err;
+
+ if (oob_required)
+ memcpy(chip->oob_poi, nfc->buf, mtd->oobsize);
+ }
+
+ if (ecc_failed) {
+ /*
+ * The hardware ECC engine reports uncorrectable errors
+ * on empty pages. Check the ECC bytes and the data. If
+ * both contains 0xff bytes only, dont report a failure.
+ *
+ * TODO: prebuild a buffer with 0xff bytes and use memcmp
+ * for better performance?
+ */
+ if (!is_all_ff(&nfc->buf[nfc->ecc_oob_pos], chip->ecc.total) ||
+ !is_all_ff(buf, mtd->writesize))
+ mtd->ecc_stats.failed++;
+ } else if (ecc_corrected) {
+ /*
+ * The hardware does not report the exact count of the
+ * corrected bitflips, use assumptions based on the
+ * threshold.
+ */
+ if (ecc_ctrl & AR934X_NFC_ECC_CTRL_ERR_OVER) {
+ /*
+ * The number of corrected bitflips exceeds the
+ * threshold. Assume the maximum.
+ */
+ max_bitflips = chip->ecc.strength * chip->ecc.steps;
+ } else {
+ max_bitflips = nfc->ecc_thres * chip->ecc.steps;
+ }
+
+ mtd->ecc_stats.corrected += max_bitflips;
+ }
+
+ return max_bitflips;
+}
+
+static int
+ar934x_nfc_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
+ const u8 *buf, int oob_required)
+{
+ struct ar934x_nfc *nfc = mtd_to_ar934x_nfc(mtd);
+ int page;
+ int len;
+
+ page = nfc->seqin_page_addr;
+
+ nfc_dbg(nfc, "write_page_raw: page:%d oob:%d\n", page, oob_required);
+
+ memcpy(nfc->buf, buf, mtd->writesize);
+ len = mtd->writesize;
+
+ if (oob_required) {
+ memcpy(&nfc->buf[mtd->writesize], chip->oob_poi, mtd->oobsize);
+ len += mtd->oobsize;
+ }
+
+ return ar934x_nfc_send_write(nfc, NAND_CMD_PAGEPROG, 0, page, len);
+}
+
+static int
+ar934x_nfc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
+ const u8 *buf, int oob_required)
+{
+ struct ar934x_nfc *nfc = mtd_to_ar934x_nfc(mtd);
+ int page;
+ int err;
+
+ page = nfc->seqin_page_addr;
+
+ nfc_dbg(nfc, "write_page: page:%d oob:%d\n", page, oob_required);
+
+ /* write OOB first */
+ if (oob_required &&
+ !is_all_ff(chip->oob_poi, mtd->oobsize)) {
+ err = ar934x_nfc_write_oob(mtd, chip, page);
+ if (err)
+ return err;
+ }
+
+ /* TODO: optimize to avoid memcopy */
+ memcpy(nfc->buf, buf, mtd->writesize);
+
+ ar934x_nfc_enable_hwecc(nfc);
+ err = ar934x_nfc_send_write(nfc, NAND_CMD_PAGEPROG, 0, page,
+ mtd->writesize);
+ ar934x_nfc_disable_hwecc(nfc);
+
+ return err;
+}
+