spl: nand: sunxi: split 'load page' and 'read page' logic
authorBoris Brezillon <boris.brezillon@free-electrons.com>
Mon, 6 Jun 2016 08:17:01 +0000 (10:17 +0200)
committerScott Wood <oss@buserror.net>
Mon, 20 Jun 2016 00:12:01 +0000 (19:12 -0500)
Split the 'load page' and 'read page' logic in 2 different functions so
we can later load the page and test different ECC configs without the
penalty of reloading the same page in the NAND cache.

We also move common setup to a dedicated function (nand_apply_config()) to
avoid rewriting the same values in NFC registers each time we read a page.

These new functions are passed a pointer to an nfc_config struct to limit
the number of parameters.

Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
Acked-by: Hans de Goede <hdegoede@redhat.com>
drivers/mtd/nand/sunxi_nand_spl.c

index 55b3c8a3d19d1364b362048c3fa4110b07e816b7..b43f2afc22fea557bbe3ea0f77dedbe4131a7179 100644 (file)
@@ -66,6 +66,8 @@
 #define NFC_ROW_AUTO_INC           (1 << 27)
 #define NFC_SEND_CMD3              (1 << 28)
 #define NFC_SEND_CMD4              (1 << 29)
+#define NFC_RAW_CMD                (0 << 30)
+#define NFC_PAGE_CMD               (2 << 30)
 
 #define NFC_ST_CMD_INT_FLAG        (1 << 1)
 #define NFC_ST_DMA_INT_FLAG        (1 << 2)
@@ -78,9 +80,6 @@
 #define NFC_CMD_RNDOUT             0x05
 #define NFC_CMD_READSTART          0x30
 
-
-#define NFC_PAGE_CMD               (2 << 30)
-
 #define SUNXI_DMA_CFG_REG0              0x300
 #define SUNXI_DMA_SRC_START_ADDR_REG0   0x304
 #define SUNXI_DMA_DEST_START_ADDRR_REG0 0x308
 #define SUNXI_DMA_DDMA_PARA_REG_SRC_WAIT_CYC (0x0F << 0)
 #define SUNXI_DMA_DDMA_PARA_REG_SRC_BLK_SIZE (0x7F << 8)
 
+struct nfc_config {
+       int page_size;
+       int ecc_strength;
+       int ecc_size;
+       int addr_cycles;
+       int nseeds;
+       bool randomize;
+};
+
 /* minimal "boot0" style NAND support for Allwinner A20 */
 
 /* random seed used by linux */
@@ -175,50 +183,70 @@ void nand_init(void)
        writel(NFC_ST_CMD_INT_FLAG, SUNXI_NFC_BASE + NFC_ST);
 }
 
-static int nand_read_page(int page_size, int ecc_strength, int ecc_page_size,
-       int addr_cycles, uint32_t real_addr, dma_addr_t dst)
+static void nand_apply_config(const struct nfc_config *conf)
 {
-       uint32_t val;
-       int i, ecc_off = 0;
-       uint16_t ecc_mode = 0;
-       uint16_t rand_seed;
-       uint32_t page;
-       uint16_t column;
-       static const u8 strengths[] = { 16, 24, 28, 32, 40, 48, 56, 60, 64 };
+       u32 val;
 
-       for (i = 0; i < ARRAY_SIZE(strengths); i++) {
-               if (ecc_strength == strengths[i]) {
-                       ecc_mode = i;
-                       break;
-               }
+       val = readl(SUNXI_NFC_BASE + NFC_CTL);
+       val &= ~NFC_CTL_PAGE_SIZE_MASK;
+       writel(val | NFC_CTL_RAM_METHOD | NFC_CTL_PAGE_SIZE(conf->page_size),
+              SUNXI_NFC_BASE + NFC_CTL);
+       writel(conf->ecc_size, SUNXI_NFC_BASE + NFC_CNT);
+       writel(conf->page_size, SUNXI_NFC_BASE + NFC_SPARE_AREA);
+}
+
+static int nand_load_page(const struct nfc_config *conf, u32 offs)
+{
+       int page = offs / conf->page_size;
+
+       writel((NFC_CMD_RNDOUTSTART << NFC_RANDOM_READ_CMD1_OFFSET) |
+              (NFC_CMD_RNDOUT << NFC_RANDOM_READ_CMD0_OFFSET) |
+              (NFC_CMD_READSTART << NFC_READ_CMD_OFFSET),
+              SUNXI_NFC_BASE + NFC_RCMD_SET);
+       writel(((page & 0xFFFF) << 16), SUNXI_NFC_BASE + NFC_ADDR_LOW);
+       writel((page >> 16) & 0xFF, SUNXI_NFC_BASE + NFC_ADDR_HIGH);
+       writel(NFC_ST_CMD_INT_FLAG, SUNXI_NFC_BASE + NFC_ST);
+       writel(NFC_SEND_CMD1 | NFC_SEND_CMD2 | NFC_RAW_CMD | NFC_WAIT_FLAG |
+              ((conf->addr_cycles - 1) << NFC_ADDR_NUM_OFFSET) | NFC_SEND_ADR,
+              SUNXI_NFC_BASE + NFC_CMD);
+
+       if (!check_value(SUNXI_NFC_BASE + NFC_ST, NFC_ST_CMD_INT_FLAG,
+                        DEFAULT_TIMEOUT_US)) {
+               printf("Error while initializing dma interrupt\n");
+               return -EIO;
        }
 
-       /* HW ECC always request ECC bytes for 1024 bytes blocks */
-       ecc_off = DIV_ROUND_UP(ecc_strength * fls(8 * 1024), 8);
-       /* HW ECC always work with even numbers of ECC bytes */
-       ecc_off += (ecc_off & 1);
-       ecc_off += 4; /* prepad */
+       return 0;
+}
+
+static int nand_read_page(const struct nfc_config *conf, u32 offs,
+                         void *dest, int len)
+{
+       dma_addr_t dst = (dma_addr_t)dest;
+       int nsectors = len / conf->ecc_size;
+       u16 rand_seed;
+       u32 val;
+       int page;
+
+       page = offs / conf->page_size;
 
-       page = real_addr / page_size;
-       column = real_addr % page_size;
+       if (offs % conf->page_size || len % conf->ecc_size ||
+           len > conf->page_size || len < 0)
+               return -EINVAL;
 
        /* clear ecc status */
        writel(0, SUNXI_NFC_BASE + NFC_ECC_ST);
 
        /* Choose correct seed */
-       rand_seed = random_seed[page % 128];
+       rand_seed = random_seed[page % conf->nseeds];
 
-       writel((rand_seed << 16) | NFC_ECC_RANDOM_EN | NFC_ECC_EN
-               | NFC_ECC_PIPELINE | (ecc_mode << 12),
+       writel((rand_seed << 16) | (conf->ecc_strength << 12) |
+               (conf->randomize ? NFC_ECC_RANDOM_EN : 0) |
+               (conf->ecc_size == 512 ? NFC_ECC_BLOCK_SIZE : 0) |
+               NFC_ECC_EN | NFC_ECC_PIPELINE | NFC_ECC_EXCEPTION,
                SUNXI_NFC_BASE + NFC_ECC_CTL);
 
-       val = readl(SUNXI_NFC_BASE + NFC_CTL);
-       writel(val | NFC_CTL_RAM_METHOD, SUNXI_NFC_BASE + NFC_CTL);
-
-       writel(page_size + (column / ecc_page_size) * ecc_off,
-              SUNXI_NFC_BASE + NFC_SPARE_AREA);
-
-       flush_dcache_range(dst, ALIGN(dst + ecc_page_size, ARCH_DMA_MINALIGN));
+       flush_dcache_range(dst, ALIGN(dst + conf->ecc_size, ARCH_DMA_MINALIGN));
 
        /* SUNXI_DMA */
        writel(0x0, SUNXI_DMA_BASE + SUNXI_DMA_CFG_REG0); /* clr dma cmd */
@@ -227,38 +255,27 @@ static int nand_read_page(int page_size, int ecc_strength, int ecc_page_size,
               SUNXI_DMA_BASE + SUNXI_DMA_SRC_START_ADDR_REG0);
        /* read to RAM */
        writel(dst, SUNXI_DMA_BASE + SUNXI_DMA_DEST_START_ADDRR_REG0);
-       writel(SUNXI_DMA_DDMA_PARA_REG_SRC_WAIT_CYC
-                       | SUNXI_DMA_DDMA_PARA_REG_SRC_BLK_SIZE,
-                       SUNXI_DMA_BASE + SUNXI_DMA_DDMA_PARA_REG0);
-       writel(ecc_page_size,
-              SUNXI_DMA_BASE + SUNXI_DMA_DDMA_BC_REG0); /* 1kB */
-       writel(SUNXI_DMA_DDMA_CFG_REG_LOADING
-               | SUNXI_DMA_DDMA_CFG_REG_DMA_DEST_DATA_WIDTH_32
-               | SUNXI_DMA_DDMA_CFG_REG_DDMA_DST_DRQ_TYPE_DRAM
-               | SUNXI_DMA_DDMA_CFG_REG_DMA_SRC_DATA_WIDTH_32
-               | SUNXI_DMA_DDMA_CFG_REG_DMA_SRC_ADDR_MODE_IO
-               | SUNXI_DMA_DDMA_CFG_REG_DDMA_SRC_DRQ_TYPE_NFC,
-               SUNXI_DMA_BASE + SUNXI_DMA_CFG_REG0);
-
-       writel((NFC_CMD_RNDOUTSTART << NFC_RANDOM_READ_CMD1_OFFSET)
-               | (NFC_CMD_RNDOUT << NFC_RANDOM_READ_CMD0_OFFSET)
-               | (NFC_CMD_READSTART | NFC_READ_CMD_OFFSET), SUNXI_NFC_BASE
-                       + NFC_RCMD_SET);
-       writel(1, SUNXI_NFC_BASE + NFC_SECTOR_NUM);
-       writel(((page & 0xFFFF) << 16) | column,
-              SUNXI_NFC_BASE + NFC_ADDR_LOW);
-       writel((page >> 16) & 0xFF, SUNXI_NFC_BASE + NFC_ADDR_HIGH);
+       writel(SUNXI_DMA_DDMA_PARA_REG_SRC_WAIT_CYC |
+              SUNXI_DMA_DDMA_PARA_REG_SRC_BLK_SIZE,
+              SUNXI_DMA_BASE + SUNXI_DMA_DDMA_PARA_REG0);
+       writel(len, SUNXI_DMA_BASE + SUNXI_DMA_DDMA_BC_REG0);
+       writel(SUNXI_DMA_DDMA_CFG_REG_LOADING |
+              SUNXI_DMA_DDMA_CFG_REG_DMA_DEST_DATA_WIDTH_32 |
+              SUNXI_DMA_DDMA_CFG_REG_DDMA_DST_DRQ_TYPE_DRAM |
+              SUNXI_DMA_DDMA_CFG_REG_DMA_SRC_DATA_WIDTH_32 |
+              SUNXI_DMA_DDMA_CFG_REG_DMA_SRC_ADDR_MODE_IO |
+              SUNXI_DMA_DDMA_CFG_REG_DDMA_SRC_DRQ_TYPE_NFC,
+              SUNXI_DMA_BASE + SUNXI_DMA_CFG_REG0);
+
+       writel(nsectors, SUNXI_NFC_BASE + NFC_SECTOR_NUM);
        writel(NFC_ST_DMA_INT_FLAG, SUNXI_NFC_BASE + NFC_ST);
-       writel(NFC_SEND_CMD1 | NFC_SEND_CMD2 | NFC_DATA_TRANS |
-               NFC_PAGE_CMD | NFC_WAIT_FLAG |
-               ((addr_cycles - 1) << NFC_ADDR_NUM_OFFSET) |
-               NFC_SEND_ADR | NFC_DATA_SWAP_METHOD,
-               SUNXI_NFC_BASE + NFC_CMD);
+       writel(NFC_DATA_TRANS | NFC_PAGE_CMD | NFC_DATA_SWAP_METHOD,
+              SUNXI_NFC_BASE + NFC_CMD);
 
        if (!check_value(SUNXI_NFC_BASE + NFC_ST, NFC_ST_DMA_INT_FLAG,
                         DEFAULT_TIMEOUT_US)) {
                printf("Error while initializing dma interrupt\n");
-               return -1;
+               return -EIO;
        }
        writel(NFC_ST_DMA_INT_FLAG, SUNXI_NFC_BASE + NFC_ST);
 
@@ -266,29 +283,55 @@ static int nand_read_page(int page_size, int ecc_strength, int ecc_page_size,
                                 SUNXI_DMA_DDMA_CFG_REG_LOADING,
                                 DEFAULT_TIMEOUT_US)) {
                printf("Error while waiting for dma transfer to finish\n");
-               return -1;
+               return -EIO;
        }
 
        invalidate_dcache_range(dst,
-                               ALIGN(dst + ecc_page_size, ARCH_DMA_MINALIGN));
+                               ALIGN(dst + conf->ecc_size, ARCH_DMA_MINALIGN));
 
-       if (readl(SUNXI_NFC_BASE + NFC_ECC_ST))
-               return -1;
+       val = readl(SUNXI_NFC_BASE + NFC_ECC_ST);
 
-       return 0;
+       /* ECC error detected. */
+       if (val & 0xffff)
+               return -EIO;
+
+       /*
+        * Return 1 if the page is empty.
+        * We consider the page as empty if the first ECC block is marked
+        * empty.
+        */
+       return (val & 0x10000) ? 1 : 0;
 }
 
 static int nand_read_ecc(int page_size, int ecc_strength, int ecc_page_size,
        int addr_cycles, uint32_t offs, uint32_t size, void *dest)
 {
        void *end = dest + size;
+       static const u8 strengths[] = { 16, 24, 28, 32, 40, 48, 56, 60, 64 };
+       struct nfc_config conf = {
+               .page_size = page_size,
+               .ecc_size = ecc_page_size,
+               .addr_cycles = addr_cycles,
+               .nseeds = ARRAY_SIZE(random_seed),
+               .randomize = true,
+       };
+       int i;
 
-       clrsetbits_le32(SUNXI_NFC_BASE + NFC_CTL, NFC_CTL_PAGE_SIZE_MASK,
-                       NFC_CTL_PAGE_SIZE(page_size));
+       for (i = 0; i < ARRAY_SIZE(strengths); i++) {
+               if (ecc_strength == strengths[i]) {
+                       conf.ecc_strength = i;
+                       break;
+               }
+       }
+
+
+       nand_apply_config(&conf);
+
+       for ( ;dest < end; dest += ecc_page_size, offs += page_size) {
+               if (nand_load_page(&conf, offs))
+                       return -1;
 
-       for ( ;dest < end; dest += ecc_page_size, offs += ecc_page_size) {
-               if (nand_read_page(page_size, ecc_strength, ecc_page_size,
-                                  addr_cycles, offs, (dma_addr_t)dest))
+               if (nand_read_page(&conf, offs, dest, page_size))
                        return -1;
        }