mtd: nand: atmel: switch to mtd_ooblayout_ops
[cascardo/linux.git] / drivers / mtd / nand / atmel_nand.c
index bddcf83..eec8ca7 100644 (file)
@@ -36,7 +36,6 @@
 #include <linux/of.h>
 #include <linux/of_device.h>
 #include <linux/of_gpio.h>
-#include <linux/of_mtd.h>
 #include <linux/mtd/mtd.h>
 #include <linux/mtd/nand.h>
 #include <linux/mtd/partitions.h>
@@ -65,32 +64,51 @@ module_param(on_flash_bbt, int, 0);
 
 struct atmel_nand_caps {
        bool pmecc_correct_erase_page;
+       uint8_t pmecc_max_correction;
 };
 
-/* oob layout for large page size
+struct atmel_nand_nfc_caps {
+       uint32_t rb_mask;
+};
+
+/*
+ * oob layout for large page size
  * bad block info is on bytes 0 and 1
  * the bytes have to be consecutives to avoid
  * several NAND_CMD_RNDOUT during read
- */
-static struct nand_ecclayout atmel_oobinfo_large = {
-       .eccbytes = 4,
-       .eccpos = {60, 61, 62, 63},
-       .oobfree = {
-               {2, 58}
-       },
-};
-
-/* oob layout for small page size
+ *
+ * oob layout for small page size
  * bad block info is on bytes 4 and 5
  * the bytes have to be consecutives to avoid
  * several NAND_CMD_RNDOUT during read
  */
-static struct nand_ecclayout atmel_oobinfo_small = {
-       .eccbytes = 4,
-       .eccpos = {0, 1, 2, 3},
-       .oobfree = {
-               {6, 10}
-       },
+static int atmel_ooblayout_ecc_sp(struct mtd_info *mtd, int section,
+                                 struct mtd_oob_region *oobregion)
+{
+       if (section)
+               return -ERANGE;
+
+       oobregion->length = 4;
+       oobregion->offset = 0;
+
+       return 0;
+}
+
+static int atmel_ooblayout_free_sp(struct mtd_info *mtd, int section,
+                                  struct mtd_oob_region *oobregion)
+{
+       if (section)
+               return -ERANGE;
+
+       oobregion->offset = 6;
+       oobregion->length = mtd->oobsize - oobregion->offset;
+
+       return 0;
+}
+
+static const struct mtd_ooblayout_ops atmel_ooblayout_sp_ops = {
+       .ecc = atmel_ooblayout_ecc_sp,
+       .free = atmel_ooblayout_free_sp,
 };
 
 struct atmel_nfc {
@@ -111,6 +129,7 @@ struct atmel_nfc {
        /* Point to the sram bank which include readed data via NFC */
        void                    *data_in_sram;
        bool                    will_write_sram;
+       const struct atmel_nand_nfc_caps *caps;
 };
 static struct atmel_nfc        nand_nfc;
 
@@ -140,6 +159,7 @@ struct atmel_nand_host {
        int                     pmecc_cw_len;   /* Length of codeword */
 
        void __iomem            *pmerrloc_base;
+       void __iomem            *pmerrloc_el_base;
        void __iomem            *pmecc_rom_base;
 
        /* lookup table for alpha_to and index_of */
@@ -156,8 +176,6 @@ struct atmel_nand_host {
        int                     *pmecc_delta;
 };
 
-static struct nand_ecclayout atmel_pmecc_oobinfo;
-
 /*
  * Enable NAND.
  */
@@ -427,14 +445,13 @@ err_buf:
 static void atmel_read_buf(struct mtd_info *mtd, u8 *buf, int len)
 {
        struct nand_chip *chip = mtd_to_nand(mtd);
-       struct atmel_nand_host *host = nand_get_controller_data(chip);
 
        if (use_dma && len > mtd->oobsize)
                /* only use DMA for bigger than oob size: better performances */
                if (atmel_nand_dma_op(mtd, buf, len, 1) == 0)
                        return;
 
-       if (host->board.bus_width_16)
+       if (chip->options & NAND_BUSWIDTH_16)
                atmel_read_buf16(mtd, buf, len);
        else
                atmel_read_buf8(mtd, buf, len);
@@ -443,14 +460,13 @@ static void atmel_read_buf(struct mtd_info *mtd, u8 *buf, int len)
 static void atmel_write_buf(struct mtd_info *mtd, const u8 *buf, int len)
 {
        struct nand_chip *chip = mtd_to_nand(mtd);
-       struct atmel_nand_host *host = nand_get_controller_data(chip);
 
        if (use_dma && len > mtd->oobsize)
                /* only use DMA for bigger than oob size: better performances */
                if (atmel_nand_dma_op(mtd, (void *)buf, len, 0) == 0)
                        return;
 
-       if (host->board.bus_width_16)
+       if (chip->options & NAND_BUSWIDTH_16)
                atmel_write_buf16(mtd, buf, len);
        else
                atmel_write_buf8(mtd, buf, len);
@@ -468,6 +484,7 @@ static void atmel_write_buf(struct mtd_info *mtd, const u8 *buf, int len)
  *                8-bits                13-bytes                 14-bytes
  *               12-bits                20-bytes                 21-bytes
  *               24-bits                39-bytes                 42-bytes
+ *               32-bits                52-bytes                 56-bytes
  */
 static int pmecc_get_ecc_bytes(int cap, int sector_size)
 {
@@ -475,22 +492,6 @@ static int pmecc_get_ecc_bytes(int cap, int sector_size)
        return (m * cap + 7) / 8;
 }
 
-static void pmecc_config_ecc_layout(struct nand_ecclayout *layout,
-                                   int oobsize, int ecc_len)
-{
-       int i;
-
-       layout->eccbytes = ecc_len;
-
-       /* ECC will occupy the last ecc_len bytes continuously */
-       for (i = 0; i < ecc_len; i++)
-               layout->eccpos[i] = oobsize - ecc_len + i;
-
-       layout->oobfree[0].offset = PMECC_OOB_RESERVED_BYTES;
-       layout->oobfree[0].length =
-               oobsize - ecc_len - layout->oobfree[0].offset;
-}
-
 static void __iomem *pmecc_get_alpha_to(struct atmel_nand_host *host)
 {
        int table_size;
@@ -813,7 +814,7 @@ static void pmecc_correct_data(struct mtd_info *mtd, uint8_t *buf, uint8_t *ecc,
        sector_size = host->pmecc_sector_size;
 
        while (err_nbr) {
-               tmp = pmerrloc_readl_el_relaxed(host->pmerrloc_base, i) - 1;
+               tmp = pmerrloc_readl_el_relaxed(host->pmerrloc_el_base, i) - 1;
                byte_pos = tmp / 8;
                bit_pos  = tmp % 8;
 
@@ -825,17 +826,20 @@ static void pmecc_correct_data(struct mtd_info *mtd, uint8_t *buf, uint8_t *ecc,
                        *(buf + byte_pos) ^= (1 << bit_pos);
 
                        pos = sector_num * host->pmecc_sector_size + byte_pos;
-                       dev_info(host->dev, "Bit flip in data area, byte_pos: %d, bit_pos: %d, 0x%02x -> 0x%02x\n",
+                       dev_dbg(host->dev, "Bit flip in data area, byte_pos: %d, bit_pos: %d, 0x%02x -> 0x%02x\n",
                                pos, bit_pos, err_byte, *(buf + byte_pos));
                } else {
+                       struct mtd_oob_region oobregion;
+
                        /* Bit flip in OOB area */
                        tmp = sector_num * nand_chip->ecc.bytes
                                        + (byte_pos - sector_size);
                        err_byte = ecc[tmp];
                        ecc[tmp] ^= (1 << bit_pos);
 
-                       pos = tmp + nand_chip->ecc.layout->eccpos[0];
-                       dev_info(host->dev, "Bit flip in OOB, oob_byte_pos: %d, bit_pos: %d, 0x%02x -> 0x%02x\n",
+                       mtd_ooblayout_ecc(mtd, 0, &oobregion);
+                       pos = tmp + oobregion.offset;
+                       dev_dbg(host->dev, "Bit flip in OOB, oob_byte_pos: %d, bit_pos: %d, 0x%02x -> 0x%02x\n",
                                pos, bit_pos, err_byte, ecc[tmp]);
                }
 
@@ -855,17 +859,6 @@ static int pmecc_correction(struct mtd_info *mtd, u32 pmecc_stat, uint8_t *buf,
        uint8_t *buf_pos;
        int max_bitflips = 0;
 
-       /* If can correct bitfilps from erased page, do the normal check */
-       if (host->caps->pmecc_correct_erase_page)
-               goto normal_check;
-
-       for (i = 0; i < nand_chip->ecc.total; i++)
-               if (ecc[i] != 0xff)
-                       goto normal_check;
-       /* Erased page, return OK */
-       return 0;
-
-normal_check:
        for (i = 0; i < nand_chip->ecc.steps; i++) {
                err_nbr = 0;
                if (pmecc_stat & 0x1) {
@@ -876,16 +869,30 @@ normal_check:
                        pmecc_get_sigma(mtd);
 
                        err_nbr = pmecc_err_location(mtd);
-                       if (err_nbr == -1) {
+                       if (err_nbr >= 0) {
+                               pmecc_correct_data(mtd, buf_pos, ecc, i,
+                                                  nand_chip->ecc.bytes,
+                                                  err_nbr);
+                       } else if (!host->caps->pmecc_correct_erase_page) {
+                               u8 *ecc_pos = ecc + (i * nand_chip->ecc.bytes);
+
+                               /* Try to detect erased pages */
+                               err_nbr = nand_check_erased_ecc_chunk(buf_pos,
+                                                       host->pmecc_sector_size,
+                                                       ecc_pos,
+                                                       nand_chip->ecc.bytes,
+                                                       NULL, 0,
+                                                       nand_chip->ecc.strength);
+                       }
+
+                       if (err_nbr < 0) {
                                dev_err(host->dev, "PMECC: Too many errors\n");
                                mtd->ecc_stats.failed++;
                                return -EIO;
-                       } else {
-                               pmecc_correct_data(mtd, buf_pos, ecc, i,
-                                       nand_chip->ecc.bytes, err_nbr);
-                               mtd->ecc_stats.corrected += err_nbr;
-                               max_bitflips = max_t(int, max_bitflips, err_nbr);
                        }
+
+                       mtd->ecc_stats.corrected += err_nbr;
+                       max_bitflips = max_t(int, max_bitflips, err_nbr);
                }
                pmecc_stat >>= 1;
        }
@@ -923,7 +930,6 @@ static int atmel_nand_pmecc_read_page(struct mtd_info *mtd,
        struct atmel_nand_host *host = nand_get_controller_data(chip);
        int eccsize = chip->ecc.size * chip->ecc.steps;
        uint8_t *oob = chip->oob_poi;
-       uint32_t *eccpos = chip->ecc.layout->eccpos;
        uint32_t stat;
        unsigned long end_time;
        int bitflips = 0;
@@ -945,7 +951,11 @@ static int atmel_nand_pmecc_read_page(struct mtd_info *mtd,
 
        stat = pmecc_readl_relaxed(host->ecc, ISR);
        if (stat != 0) {
-               bitflips = pmecc_correction(mtd, stat, buf, &oob[eccpos[0]]);
+               struct mtd_oob_region oobregion;
+
+               mtd_ooblayout_ecc(mtd, 0, &oobregion);
+               bitflips = pmecc_correction(mtd, stat, buf,
+                                           &oob[oobregion.offset]);
                if (bitflips < 0)
                        /* uncorrectable errors */
                        return 0;
@@ -959,8 +969,8 @@ static int atmel_nand_pmecc_write_page(struct mtd_info *mtd,
                int page)
 {
        struct atmel_nand_host *host = nand_get_controller_data(chip);
-       uint32_t *eccpos = chip->ecc.layout->eccpos;
-       int i, j;
+       struct mtd_oob_region oobregion = { };
+       int i, j, section = 0;
        unsigned long end_time;
 
        if (!host->nfc || !host->nfc->write_by_sram) {
@@ -979,11 +989,14 @@ static int atmel_nand_pmecc_write_page(struct mtd_info *mtd,
 
        for (i = 0; i < chip->ecc.steps; i++) {
                for (j = 0; j < chip->ecc.bytes; j++) {
-                       int pos;
+                       if (!oobregion.length)
+                               mtd_ooblayout_ecc(mtd, section, &oobregion);
 
-                       pos = i * chip->ecc.bytes + j;
-                       chip->oob_poi[eccpos[pos]] =
+                       chip->oob_poi[oobregion.offset] =
                                pmecc_readb_ecc_relaxed(host->ecc, i, j);
+                       oobregion.length--;
+                       oobregion.offset++;
+                       section++;
                }
        }
        chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
@@ -995,8 +1008,9 @@ static void atmel_pmecc_core_init(struct mtd_info *mtd)
 {
        struct nand_chip *nand_chip = mtd_to_nand(mtd);
        struct atmel_nand_host *host = nand_get_controller_data(nand_chip);
+       int eccbytes = mtd_ooblayout_count_eccbytes(mtd);
        uint32_t val = 0;
-       struct nand_ecclayout *ecc_layout;
+       struct mtd_oob_region oobregion;
 
        pmecc_writel(host->ecc, CTRL, PMECC_CTRL_RST);
        pmecc_writel(host->ecc, CTRL, PMECC_CTRL_DISABLE);
@@ -1017,6 +1031,9 @@ static void atmel_pmecc_core_init(struct mtd_info *mtd)
        case 24:
                val = PMECC_CFG_BCH_ERR24;
                break;
+       case 32:
+               val = PMECC_CFG_BCH_ERR32;
+               break;
        }
 
        if (host->pmecc_sector_size == 512)
@@ -1043,11 +1060,11 @@ static void atmel_pmecc_core_init(struct mtd_info *mtd)
                | PMECC_CFG_AUTO_DISABLE);
        pmecc_writel(host->ecc, CFG, val);
 
-       ecc_layout = nand_chip->ecc.layout;
        pmecc_writel(host->ecc, SAREA, mtd->oobsize - 1);
-       pmecc_writel(host->ecc, SADDR, ecc_layout->eccpos[0]);
+       mtd_ooblayout_ecc(mtd, 0, &oobregion);
+       pmecc_writel(host->ecc, SADDR, oobregion.offset);
        pmecc_writel(host->ecc, EADDR,
-                       ecc_layout->eccpos[ecc_layout->eccbytes - 1]);
+                    oobregion.offset + eccbytes - 1);
        /* See datasheet about PMECC Clock Control Register */
        pmecc_writel(host->ecc, CLK, 2);
        pmecc_writel(host->ecc, IDR, 0xff);
@@ -1078,6 +1095,9 @@ static int pmecc_choose_ecc(struct atmel_nand_host *host,
 
        /* If device tree doesn't specify, use NAND's minimum ECC parameters */
        if (host->pmecc_corr_cap == 0) {
+               if (*cap > host->caps->pmecc_max_correction)
+                       return -EINVAL;
+
                /* use the most fitable ecc bits (the near bigger one ) */
                if (*cap <= 2)
                        host->pmecc_corr_cap = 2;
@@ -1089,6 +1109,8 @@ static int pmecc_choose_ecc(struct atmel_nand_host *host,
                        host->pmecc_corr_cap = 12;
                else if (*cap <= 24)
                        host->pmecc_corr_cap = 24;
+               else if (*cap <= 32)
+                       host->pmecc_corr_cap = 32;
                else
                        return -EINVAL;
        }
@@ -1205,6 +1227,8 @@ static int atmel_pmecc_nand_init_params(struct platform_device *pdev,
                err_no = PTR_ERR(host->pmerrloc_base);
                goto err;
        }
+       host->pmerrloc_el_base = host->pmerrloc_base + ATMEL_PMERRLOC_SIGMAx +
+               (host->caps->pmecc_max_correction + 1) * 4;
 
        if (!host->has_no_lookup_table) {
                regs_rom = platform_get_resource(pdev, IORESOURCE_MEM, 3);
@@ -1262,11 +1286,8 @@ static int atmel_pmecc_nand_init_params(struct platform_device *pdev,
                        err_no = -EINVAL;
                        goto err;
                }
-               pmecc_config_ecc_layout(&atmel_pmecc_oobinfo,
-                                       mtd->oobsize,
-                                       nand_chip->ecc.total);
 
-               nand_chip->ecc.layout = &atmel_pmecc_oobinfo;
+               mtd_set_ooblayout(mtd, &nand_ooblayout_lp_ops);
                break;
        default:
                dev_warn(host->dev,
@@ -1341,12 +1362,12 @@ static int atmel_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip,
 {
        int eccsize = chip->ecc.size;
        int eccbytes = chip->ecc.bytes;
-       uint32_t *eccpos = chip->ecc.layout->eccpos;
        uint8_t *p = buf;
        uint8_t *oob = chip->oob_poi;
        uint8_t *ecc_pos;
        int stat;
        unsigned int max_bitflips = 0;
+       struct mtd_oob_region oobregion = {};
 
        /*
         * Errata: ALE is incorrectly wired up to the ECC controller
@@ -1364,19 +1385,20 @@ static int atmel_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip,
        chip->read_buf(mtd, p, eccsize);
 
        /* move to ECC position if needed */
-       if (eccpos[0] != 0) {
-               /* This only works on large pages
-                * because the ECC controller waits for
-                * NAND_CMD_RNDOUTSTART after the
-                * NAND_CMD_RNDOUT.
-                * anyway, for small pages, the eccpos[0] == 0
+       mtd_ooblayout_ecc(mtd, 0, &oobregion);
+       if (oobregion.offset != 0) {
+               /*
+                * This only works on large pages because the ECC controller
+                * waits for NAND_CMD_RNDOUTSTART after the NAND_CMD_RNDOUT.
+                * Anyway, for small pages, the first ECC byte is at offset
+                * 0 in the OOB area.
                 */
                chip->cmdfunc(mtd, NAND_CMD_RNDOUT,
-                               mtd->writesize + eccpos[0], -1);
+                             mtd->writesize + oobregion.offset, -1);
        }
 
        /* the ECC controller needs to read the ECC just after the data */
-       ecc_pos = oob + eccpos[0];
+       ecc_pos = oob + oobregion.offset;
        chip->read_buf(mtd, ecc_pos, eccbytes);
 
        /* check if there's an error */
@@ -1486,60 +1508,17 @@ static void atmel_nand_hwctl(struct mtd_info *mtd, int mode)
                ecc_writel(host->ecc, CR, ATMEL_ECC_RST);
 }
 
-static const struct of_device_id atmel_nand_dt_ids[];
-
-static int atmel_of_init_port(struct atmel_nand_host *host,
-                             struct device_node *np)
+static int atmel_of_init_ecc(struct atmel_nand_host *host,
+                            struct device_node *np)
 {
-       u32 val;
        u32 offset[2];
-       int ecc_mode;
-       struct atmel_nand_data *board = &host->board;
-       enum of_gpio_flags flags = 0;
-
-       host->caps = (struct atmel_nand_caps *)
-               of_match_device(atmel_nand_dt_ids, host->dev)->data;
-
-       if (of_property_read_u32(np, "atmel,nand-addr-offset", &val) == 0) {
-               if (val >= 32) {
-                       dev_err(host->dev, "invalid addr-offset %u\n", val);
-                       return -EINVAL;
-               }
-               board->ale = val;
-       }
-
-       if (of_property_read_u32(np, "atmel,nand-cmd-offset", &val) == 0) {
-               if (val >= 32) {
-                       dev_err(host->dev, "invalid cmd-offset %u\n", val);
-                       return -EINVAL;
-               }
-               board->cle = val;
-       }
-
-       ecc_mode = of_get_nand_ecc_mode(np);
-
-       board->ecc_mode = ecc_mode < 0 ? NAND_ECC_SOFT : ecc_mode;
-
-       board->on_flash_bbt = of_get_nand_on_flash_bbt(np);
-
-       board->has_dma = of_property_read_bool(np, "atmel,nand-has-dma");
-
-       if (of_get_nand_bus_width(np) == 16)
-               board->bus_width_16 = 1;
-
-       board->rdy_pin = of_get_gpio_flags(np, 0, &flags);
-       board->rdy_pin_active_low = (flags == OF_GPIO_ACTIVE_LOW);
-
-       board->enable_pin = of_get_gpio(np, 1);
-       board->det_pin = of_get_gpio(np, 2);
+       u32 val;
 
        host->has_pmecc = of_property_read_bool(np, "atmel,has-pmecc");
 
-       /* load the nfc driver if there is */
-       of_platform_populate(np, NULL, NULL, host->dev);
-
-       if (!(board->ecc_mode == NAND_ECC_HW) || !host->has_pmecc)
-               return 0;       /* Not using PMECC */
+       /* Not using PMECC */
+       if (!(host->nand_chip.ecc.mode == NAND_ECC_HW) || !host->has_pmecc)
+               return 0;
 
        /* use PMECC, get correction capability, sector size and lookup
         * table offset.
@@ -1547,10 +1526,16 @@ static int atmel_of_init_port(struct atmel_nand_host *host,
         * them from NAND ONFI parameters.
         */
        if (of_property_read_u32(np, "atmel,pmecc-cap", &val) == 0) {
-               if ((val != 2) && (val != 4) && (val != 8) && (val != 12) &&
-                               (val != 24)) {
+               if (val > host->caps->pmecc_max_correction) {
+                       dev_err(host->dev,
+                               "Required ECC strength too high: %u max %u\n",
+                               val, host->caps->pmecc_max_correction);
+                       return -EINVAL;
+               }
+               if ((val != 2)  && (val != 4)  && (val != 8) &&
+                   (val != 12) && (val != 24) && (val != 32)) {
                        dev_err(host->dev,
-                               "Unsupported PMECC correction capability: %d; should be 2, 4, 8, 12 or 24\n",
+                               "Required ECC strength not supported: %u\n",
                                val);
                        return -EINVAL;
                }
@@ -1560,7 +1545,7 @@ static int atmel_of_init_port(struct atmel_nand_host *host,
        if (of_property_read_u32(np, "atmel,pmecc-sector-size", &val) == 0) {
                if ((val != 512) && (val != 1024)) {
                        dev_err(host->dev,
-                               "Unsupported PMECC sector size: %d; should be 512 or 1024 bytes\n",
+                               "Required ECC sector size not supported: %u\n",
                                val);
                        return -EINVAL;
                }
@@ -1574,16 +1559,64 @@ static int atmel_of_init_port(struct atmel_nand_host *host,
                /* Will build a lookup table and initialize the offset later */
                return 0;
        }
+
        if (!offset[0] && !offset[1]) {
                dev_err(host->dev, "Invalid PMECC lookup table offset\n");
                return -EINVAL;
        }
+
        host->pmecc_lookup_table_offset_512 = offset[0];
        host->pmecc_lookup_table_offset_1024 = offset[1];
 
        return 0;
 }
 
+static int atmel_of_init_port(struct atmel_nand_host *host,
+                             struct device_node *np)
+{
+       u32 val;
+       struct atmel_nand_data *board = &host->board;
+       enum of_gpio_flags flags = 0;
+
+       host->caps = (struct atmel_nand_caps *)
+               of_device_get_match_data(host->dev);
+
+       if (of_property_read_u32(np, "atmel,nand-addr-offset", &val) == 0) {
+               if (val >= 32) {
+                       dev_err(host->dev, "invalid addr-offset %u\n", val);
+                       return -EINVAL;
+               }
+               board->ale = val;
+       }
+
+       if (of_property_read_u32(np, "atmel,nand-cmd-offset", &val) == 0) {
+               if (val >= 32) {
+                       dev_err(host->dev, "invalid cmd-offset %u\n", val);
+                       return -EINVAL;
+               }
+               board->cle = val;
+       }
+
+       board->has_dma = of_property_read_bool(np, "atmel,nand-has-dma");
+
+       board->rdy_pin = of_get_gpio_flags(np, 0, &flags);
+       board->rdy_pin_active_low = (flags == OF_GPIO_ACTIVE_LOW);
+
+       board->enable_pin = of_get_gpio(np, 1);
+       board->det_pin = of_get_gpio(np, 2);
+
+       /* load the nfc driver if there is */
+       of_platform_populate(np, NULL, NULL, host->dev);
+
+       /*
+        * Initialize ECC mode to NAND_ECC_SOFT so that we have a correct value
+        * even if the nand-ecc-mode property is not defined.
+        */
+       host->nand_chip.ecc.mode = NAND_ECC_SOFT;
+
+       return 0;
+}
+
 static int atmel_hw_nand_init_params(struct platform_device *pdev,
                                         struct atmel_nand_host *host)
 {
@@ -1609,19 +1642,19 @@ static int atmel_hw_nand_init_params(struct platform_device *pdev,
        /* set ECC page size and oob layout */
        switch (mtd->writesize) {
        case 512:
-               nand_chip->ecc.layout = &atmel_oobinfo_small;
+               mtd_set_ooblayout(mtd, &atmel_ooblayout_sp_ops);
                ecc_writel(host->ecc, MR, ATMEL_ECC_PAGESIZE_528);
                break;
        case 1024:
-               nand_chip->ecc.layout = &atmel_oobinfo_large;
+               mtd_set_ooblayout(mtd, &nand_ooblayout_lp_ops);
                ecc_writel(host->ecc, MR, ATMEL_ECC_PAGESIZE_1056);
                break;
        case 2048:
-               nand_chip->ecc.layout = &atmel_oobinfo_large;
+               mtd_set_ooblayout(mtd, &nand_ooblayout_lp_ops);
                ecc_writel(host->ecc, MR, ATMEL_ECC_PAGESIZE_2112);
                break;
        case 4096:
-               nand_chip->ecc.layout = &atmel_oobinfo_large;
+               mtd_set_ooblayout(mtd, &nand_ooblayout_lp_ops);
                ecc_writel(host->ecc, MR, ATMEL_ECC_PAGESIZE_4224);
                break;
        default:
@@ -1677,9 +1710,9 @@ static irqreturn_t hsmc_interrupt(int irq, void *dev_id)
                nfc_writel(host->nfc->hsmc_regs, IDR, NFC_SR_XFR_DONE);
                ret = IRQ_HANDLED;
        }
-       if (pending & NFC_SR_RB_EDGE) {
+       if (pending & host->nfc->caps->rb_mask) {
                complete(&host->nfc->comp_ready);
-               nfc_writel(host->nfc->hsmc_regs, IDR, NFC_SR_RB_EDGE);
+               nfc_writel(host->nfc->hsmc_regs, IDR, host->nfc->caps->rb_mask);
                ret = IRQ_HANDLED;
        }
        if (pending & NFC_SR_CMD_DONE) {
@@ -1697,7 +1730,7 @@ static void nfc_prepare_interrupt(struct atmel_nand_host *host, u32 flag)
        if (flag & NFC_SR_XFR_DONE)
                init_completion(&host->nfc->comp_xfer_done);
 
-       if (flag & NFC_SR_RB_EDGE)
+       if (flag & host->nfc->caps->rb_mask)
                init_completion(&host->nfc->comp_ready);
 
        if (flag & NFC_SR_CMD_DONE)
@@ -1715,7 +1748,7 @@ static int nfc_wait_interrupt(struct atmel_nand_host *host, u32 flag)
        if (flag & NFC_SR_XFR_DONE)
                comp[index++] = &host->nfc->comp_xfer_done;
 
-       if (flag & NFC_SR_RB_EDGE)
+       if (flag & host->nfc->caps->rb_mask)
                comp[index++] = &host->nfc->comp_ready;
 
        if (flag & NFC_SR_CMD_DONE)
@@ -1783,7 +1816,7 @@ static int nfc_device_ready(struct mtd_info *mtd)
                dev_err(host->dev, "Lost the interrupt flags: 0x%08x\n",
                                mask & status);
 
-       return status & NFC_SR_RB_EDGE;
+       return status & host->nfc->caps->rb_mask;
 }
 
 static void nfc_select_chip(struct mtd_info *mtd, int chip)
@@ -1956,8 +1989,8 @@ static void nfc_nand_command(struct mtd_info *mtd, unsigned int command,
                }
                /* fall through */
        default:
-               nfc_prepare_interrupt(host, NFC_SR_RB_EDGE);
-               nfc_wait_interrupt(host, NFC_SR_RB_EDGE);
+               nfc_prepare_interrupt(host, host->nfc->caps->rb_mask);
+               nfc_wait_interrupt(host, host->nfc->caps->rb_mask);
        }
 }
 
@@ -2125,6 +2158,11 @@ static int atmel_nand_probe(struct platform_device *pdev)
        } else {
                memcpy(&host->board, dev_get_platdata(&pdev->dev),
                       sizeof(struct atmel_nand_data));
+               nand_chip->ecc.mode = host->board.ecc_mode;
+
+               /* 16-bit bus width */
+               if (host->board.bus_width_16)
+                       nand_chip->options |= NAND_BUSWIDTH_16;
        }
 
         /* link the private data structures */
@@ -2166,11 +2204,8 @@ static int atmel_nand_probe(struct platform_device *pdev)
                nand_chip->cmd_ctrl = atmel_nand_cmd_ctrl;
        }
 
-       nand_chip->ecc.mode = host->board.ecc_mode;
        nand_chip->chip_delay = 40;             /* 40us command delay time */
 
-       if (host->board.bus_width_16)   /* 16-bit bus width */
-               nand_chip->options |= NAND_BUSWIDTH_16;
 
        nand_chip->read_buf = atmel_read_buf;
        nand_chip->write_buf = atmel_write_buf;
@@ -2203,11 +2238,6 @@ static int atmel_nand_probe(struct platform_device *pdev)
                }
        }
 
-       if (host->board.on_flash_bbt || on_flash_bbt) {
-               dev_info(&pdev->dev, "Use On Flash BBT\n");
-               nand_chip->bbt_options |= NAND_BBT_USE_FLASH;
-       }
-
        if (!host->board.has_dma)
                use_dma = 0;
 
@@ -2234,6 +2264,18 @@ static int atmel_nand_probe(struct platform_device *pdev)
                goto err_scan_ident;
        }
 
+       if (host->board.on_flash_bbt || on_flash_bbt)
+               nand_chip->bbt_options |= NAND_BBT_USE_FLASH;
+
+       if (nand_chip->bbt_options & NAND_BBT_USE_FLASH)
+               dev_info(&pdev->dev, "Use On Flash BBT\n");
+
+       if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) {
+               res = atmel_of_init_ecc(host, pdev->dev.of_node);
+               if (res)
+                       goto err_hw_ecc;
+       }
+
        if (nand_chip->ecc.mode == NAND_ECC_HW) {
                if (host->has_pmecc)
                        res = atmel_pmecc_nand_init_params(pdev, host);
@@ -2304,17 +2346,34 @@ static int atmel_nand_remove(struct platform_device *pdev)
        return 0;
 }
 
+/*
+ * AT91RM9200 does not have PMECC or PMECC Errloc peripherals for
+ * BCH ECC. Combined with the "atmel,has-pmecc", it is used to describe
+ * devices from the SAM9 family that have those.
+ */
 static const struct atmel_nand_caps at91rm9200_caps = {
        .pmecc_correct_erase_page = false,
+       .pmecc_max_correction = 24,
 };
 
 static const struct atmel_nand_caps sama5d4_caps = {
        .pmecc_correct_erase_page = true,
+       .pmecc_max_correction = 24,
+};
+
+/*
+ * The PMECC Errloc controller starting in SAMA5D2 is not compatible,
+ * as the increased correction strength requires more registers.
+ */
+static const struct atmel_nand_caps sama5d2_caps = {
+       .pmecc_correct_erase_page = true,
+       .pmecc_max_correction = 32,
 };
 
 static const struct of_device_id atmel_nand_dt_ids[] = {
        { .compatible = "atmel,at91rm9200-nand", .data = &at91rm9200_caps },
        { .compatible = "atmel,sama5d4-nand", .data = &sama5d4_caps },
+       { .compatible = "atmel,sama5d2-nand", .data = &sama5d2_caps },
        { /* sentinel */ }
 };
 
@@ -2354,6 +2413,11 @@ static int atmel_nand_nfc_probe(struct platform_device *pdev)
                }
        }
 
+       nfc->caps = (const struct atmel_nand_nfc_caps *)
+               of_device_get_match_data(&pdev->dev);
+       if (!nfc->caps)
+               return -ENODEV;
+
        nfc_writel(nfc->hsmc_regs, IDR, 0xffffffff);
        nfc_readl(nfc->hsmc_regs, SR);  /* clear the NFC_SR */
 
@@ -2382,8 +2446,17 @@ static int atmel_nand_nfc_remove(struct platform_device *pdev)
        return 0;
 }
 
+static const struct atmel_nand_nfc_caps sama5d3_nfc_caps = {
+       .rb_mask = NFC_SR_RB_EDGE0,
+};
+
+static const struct atmel_nand_nfc_caps sama5d4_nfc_caps = {
+       .rb_mask = NFC_SR_RB_EDGE3,
+};
+
 static const struct of_device_id atmel_nand_nfc_match[] = {
-       { .compatible = "atmel,sama5d3-nfc" },
+       { .compatible = "atmel,sama5d3-nfc", .data = &sama5d3_nfc_caps },
+       { .compatible = "atmel,sama5d4-nfc", .data = &sama5d4_nfc_caps },
        { /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, atmel_nand_nfc_match);