aboutsummaryrefslogtreecommitdiff
path: root/arch_msm7k/nand.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch_msm7k/nand.c')
-rw-r--r--arch_msm7k/nand.c630
1 files changed, 630 insertions, 0 deletions
diff --git a/arch_msm7k/nand.c b/arch_msm7k/nand.c
new file mode 100644
index 0000000..ea123f0
--- /dev/null
+++ b/arch_msm7k/nand.c
@@ -0,0 +1,630 @@
+/*
+ * Copyright (c) 2008, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <boot/boot.h>
+#include <boot/flash.h>
+
+#include <msm7k/dmov.h>
+#include <msm7k/nand.h>
+#include <msm7k/shared.h>
+
+#define VERBOSE 0
+
+typedef struct dmov_ch dmov_ch;
+struct dmov_ch
+{
+ volatile unsigned cmd;
+ volatile unsigned result;
+ volatile unsigned status;
+ volatile unsigned config;
+};
+
+static void dmov_prep_ch(dmov_ch *ch, unsigned id)
+{
+ ch->cmd = DMOV_CMD_PTR(id);
+ ch->result = DMOV_RSLT(id);
+ ch->status = DMOV_STATUS(id);
+ ch->config = DMOV_CONFIG(id);
+}
+
+#define SRC_CRCI_NAND_CMD CMD_SRC_CRCI(DMOV_NAND_CRCI_CMD)
+#define DST_CRCI_NAND_CMD CMD_DST_CRCI(DMOV_NAND_CRCI_CMD)
+#define SRC_CRCI_NAND_DATA CMD_SRC_CRCI(DMOV_NAND_CRCI_DATA)
+#define DST_CRCI_NAND_DATA CMD_DST_CRCI(DMOV_NAND_CRCI_DATA)
+
+static unsigned CFG0, CFG1;
+
+#define CFG1_WIDE_FLASH (1U << 1)
+
+#define paddr(n) ((unsigned) (n))
+
+static int dmov_exec_cmdptr(unsigned id, unsigned *ptr)
+{
+ dmov_ch ch;
+ unsigned n;
+
+ dmov_prep_ch(&ch, id);
+
+ writel(DMOV_CMD_PTR_LIST | DMOV_CMD_ADDR(paddr(ptr)), ch.cmd);
+
+ while(!(readl(ch.status) & DMOV_STATUS_RSLT_VALID)) ;
+
+ n = readl(ch.status);
+ while(DMOV_STATUS_RSLT_COUNT(n)) {
+ n = readl(ch.result);
+ if(n != 0x80000002) {
+ dprintf("ERROR: result: %x\n", n);
+ dprintf("ERROR: flush: %x %x %x %x\n",
+ readl(DMOV_FLUSH0(DMOV_NAND_CHAN)),
+ readl(DMOV_FLUSH1(DMOV_NAND_CHAN)),
+ readl(DMOV_FLUSH2(DMOV_NAND_CHAN)),
+ readl(DMOV_FLUSH3(DMOV_NAND_CHAN)));
+ }
+ n = readl(ch.status);
+ }
+
+ return 0;
+}
+
+static unsigned flash_maker = 0;
+static unsigned flash_device = 0;
+
+static void flash_read_id(dmov_s *cmdlist, unsigned *ptrlist)
+{
+ dmov_s *cmd = cmdlist;
+ unsigned *ptr = ptrlist;
+ unsigned *data = ptrlist + 4;
+
+ data[0] = 0 | 4;
+ data[1] = NAND_CMD_FETCH_ID;
+ data[2] = 1;
+ data[3] = 0;
+ data[4] = 0;
+
+ cmd[0].cmd = 0 | CMD_OCB;
+ cmd[0].src = paddr(&data[0]);
+ cmd[0].dst = NAND_FLASH_CHIP_SELECT;
+ cmd[0].len = 4;
+
+ cmd[1].cmd = DST_CRCI_NAND_CMD;
+ cmd[1].src = paddr(&data[1]);
+ cmd[1].dst = NAND_FLASH_CMD;
+ cmd[1].len = 4;
+
+ cmd[2].cmd = 0;
+ cmd[2].src = paddr(&data[2]);
+ cmd[2].dst = NAND_EXEC_CMD;
+ cmd[2].len = 4;
+
+ cmd[3].cmd = SRC_CRCI_NAND_DATA;
+ cmd[3].src = NAND_FLASH_STATUS;
+ cmd[3].dst = paddr(&data[3]);
+ cmd[3].len = 4;
+
+ cmd[4].cmd = CMD_OCU | CMD_LC;
+ cmd[4].src = NAND_READ_ID;
+ cmd[4].dst = paddr(&data[4]);
+ cmd[4].len = 4;
+
+ ptr[0] = (paddr(cmd) >> 3) | CMD_PTR_LP;
+
+ dmov_exec_cmdptr(DMOV_NAND_CHAN, ptr);
+
+#if VERBOSE
+ dprintf("status: %x\n", data[3]);
+#endif
+ dprintf("nandid: %x maker %b device %b\n",
+ data[4], data[4] & 0xff, (data[4] >> 8) & 0xff);
+
+ flash_maker = data[4] & 0xff;
+ flash_device = (data[4] >> 8) & 0xff;
+}
+
+static int flash_erase_block(dmov_s *cmdlist, unsigned *ptrlist, unsigned page)
+{
+ dmov_s *cmd = cmdlist;
+ unsigned *ptr = ptrlist;
+ unsigned *data = ptrlist + 4;
+
+ /* only allow erasing on block boundaries */
+ if(page & 63) return -1;
+
+ data[0] = NAND_CMD_BLOCK_ERASE;
+ data[1] = page;
+ data[2] = 0;
+ data[3] = 0 | 4;
+ data[4] = 1;
+ data[5] = 0xeeeeeeee;
+ data[6] = CFG0 & (~(7 << 6)); /* CW_PER_PAGE = 0 */
+ data[7] = CFG1;
+
+ cmd[0].cmd = DST_CRCI_NAND_CMD | CMD_OCB;
+ cmd[0].src = paddr(&data[0]);
+ cmd[0].dst = NAND_FLASH_CMD;
+ cmd[0].len = 16;
+
+ cmd[1].cmd = 0;
+ cmd[1].src = paddr(&data[6]);
+ cmd[1].dst = NAND_DEV0_CFG0;
+ cmd[1].len = 8;
+
+ cmd[2].cmd = 0;
+ cmd[2].src = paddr(&data[4]);
+ cmd[2].dst = NAND_EXEC_CMD;
+ cmd[2].len = 4;
+
+ cmd[3].cmd = SRC_CRCI_NAND_DATA | CMD_OCU | CMD_LC;
+ cmd[3].src = NAND_FLASH_STATUS;
+ cmd[3].dst = paddr(&data[5]);
+ cmd[3].len = 4;
+
+ ptr[0] = (paddr(cmd) >> 3) | CMD_PTR_LP;
+
+ dmov_exec_cmdptr(DMOV_NAND_CHAN, ptr);
+
+#if VERBOSE
+ dprintf("status: %x\n", data[5]);
+#endif
+
+ /* we fail if there was an operation error, a mpu error, or the
+ ** erase success bit was not set.
+ */
+ if(data[5] & 0x110) return -1;
+ if(!(data[5] & 0x80)) return -1;
+
+ return 0;
+}
+
+struct data_flash_io {
+ unsigned cmd;
+ unsigned addr0;
+ unsigned addr1;
+ unsigned chipsel;
+ unsigned cfg0;
+ unsigned cfg1;
+ unsigned exec;
+ unsigned ecc_cfg;
+ unsigned ecc_cfg_save;
+ struct {
+ unsigned flash_status;
+ unsigned buffer_status;
+ } result[4];
+};
+
+static int _flash_read_page(dmov_s *cmdlist, unsigned *ptrlist, unsigned page, void *_addr, void *_spareaddr)
+{
+ dmov_s *cmd = cmdlist;
+ unsigned *ptr = ptrlist;
+ struct data_flash_io *data = (void*) (ptrlist + 4);
+ unsigned addr = (unsigned) _addr;
+ unsigned spareaddr = (unsigned) _spareaddr;
+ unsigned n;
+
+ data->cmd = NAND_CMD_PAGE_READ_ECC;
+ data->addr0 = page << 16;
+ data->addr1 = (page >> 16) & 0xff;
+ data->chipsel = 0 | 4; /* flash0 + undoc bit */
+
+ /* GO bit for the EXEC register */
+ data->exec = 1;
+
+ data->cfg0 = CFG0;
+ data->cfg1 = CFG1;
+
+ data->ecc_cfg = 0x203;
+
+ /* save existing ecc config */
+ cmd->cmd = CMD_OCB;
+ cmd->src = NAND_EBI2_ECC_BUF_CFG;
+ cmd->dst = paddr(&data->ecc_cfg_save);
+ cmd->len = 4;
+ cmd++;
+
+ for(n = 0; n < 4; n++) {
+ /* write CMD / ADDR0 / ADDR1 / CHIPSEL regs in a burst */
+ cmd->cmd = DST_CRCI_NAND_CMD;
+ cmd->src = paddr(&data->cmd);
+ cmd->dst = NAND_FLASH_CMD;
+ cmd->len = ((n == 0) ? 16 : 4);
+ cmd++;
+
+ if (n == 0) {
+ /* block on cmd ready, set configuration */
+ cmd->cmd = 0;
+ cmd->src = paddr(&data->cfg0);
+ cmd->dst = NAND_DEV0_CFG0;
+ cmd->len = 8;
+ cmd++;
+
+ /* set our ecc config */
+ cmd->cmd = 0;
+ cmd->src = paddr(&data->ecc_cfg);
+ cmd->dst = NAND_EBI2_ECC_BUF_CFG;
+ cmd->len = 4;
+ cmd++;
+ }
+ /* kick the execute register */
+ cmd->cmd = 0;
+ cmd->src = paddr(&data->exec);
+ cmd->dst = NAND_EXEC_CMD;
+ cmd->len = 4;
+ cmd++;
+
+ /* block on data ready, then read the status register */
+ cmd->cmd = SRC_CRCI_NAND_DATA;
+ cmd->src = NAND_FLASH_STATUS;
+ cmd->dst = paddr(&data->result[n]);
+ cmd->len = 8;
+ cmd++;
+
+ /* read data block */
+ cmd->cmd = 0;
+ cmd->src = NAND_FLASH_BUFFER;
+ cmd->dst = addr + n * 516;
+ cmd->len = ((n < 3) ? 516 : 500);
+ cmd++;
+ }
+
+ /* read extra data */
+ cmd->cmd = 0;
+ cmd->src = NAND_FLASH_BUFFER + 500;
+ cmd->dst = spareaddr;
+ cmd->len = 16;
+ cmd++;
+
+ /* restore saved ecc config */
+ cmd->cmd = CMD_OCU | CMD_LC;
+ cmd->src = paddr(&data->ecc_cfg_save);
+ cmd->dst = NAND_EBI2_ECC_BUF_CFG;
+ cmd->len = 4;
+
+ ptr[0] = (paddr(cmdlist) >> 3) | CMD_PTR_LP;
+
+ dmov_exec_cmdptr(DMOV_NAND_CHAN, ptr);
+
+#if VERBOSE
+ dprintf("read page %d: status: %x %x %x %x\n",
+ page, data[5], data[6], data[7], data[8]);
+ for(n = 0; n < 4; n++) {
+ ptr = (unsigned*)(addr + 512 * n);
+ dprintf("data%d: %x %x %x %x\n", n, ptr[0], ptr[1], ptr[2], ptr[3]);
+ ptr = (unsigned*)(spareaddr + 16 * n);
+ dprintf("spare data%d %x %x %x %x\n", n, ptr[0], ptr[1], ptr[2], ptr[3]);
+ }
+#endif
+
+ /* if any of the writes failed (0x10), or there was a
+ ** protection violation (0x100), we lose
+ */
+ for(n = 0; n < 4; n++) {
+ if (data->result[n].flash_status & 0x110) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int _flash_write_page(dmov_s *cmdlist, unsigned *ptrlist, unsigned page,
+ const void *_addr, const void *_spareaddr)
+{
+ dmov_s *cmd = cmdlist;
+ unsigned *ptr = ptrlist;
+ struct data_flash_io *data = (void*) (ptrlist + 4);
+ unsigned addr = (unsigned) _addr;
+ unsigned spareaddr = (unsigned) _spareaddr;
+ unsigned n;
+
+ data->cmd = NAND_CMD_PRG_PAGE;
+ data->addr0 = page << 16;
+ data->addr1 = (page >> 16) & 0xff;
+ data->chipsel = 0 | 4; /* flash0 + undoc bit */
+
+ data->cfg0 = CFG0;
+ data->cfg1 = CFG1;
+
+ /* GO bit for the EXEC register */
+ data->exec = 1;
+
+ data->ecc_cfg = 0x203;
+
+ /* save existing ecc config */
+ cmd->cmd = CMD_OCB;
+ cmd->src = NAND_EBI2_ECC_BUF_CFG;
+ cmd->dst = paddr(&data->ecc_cfg_save);
+ cmd->len = 4;
+ cmd++;
+
+ for(n = 0; n < 4; n++) {
+ /* write CMD / ADDR0 / ADDR1 / CHIPSEL regs in a burst */
+ cmd->cmd = DST_CRCI_NAND_CMD;
+ cmd->src = paddr(&data->cmd);
+ cmd->dst = NAND_FLASH_CMD;
+ cmd->len = ((n == 0) ? 16 : 4);
+ cmd++;
+
+ if (n == 0) {
+ /* set configuration */
+ cmd->cmd = 0;
+ cmd->src = paddr(&data->cfg0);
+ cmd->dst = NAND_DEV0_CFG0;
+ cmd->len = 8;
+ cmd++;
+
+ /* set our ecc config */
+ cmd->cmd = 0;
+ cmd->src = paddr(&data->ecc_cfg);
+ cmd->dst = NAND_EBI2_ECC_BUF_CFG;
+ cmd->len = 4;
+ cmd++;
+ }
+
+ /* write data block */
+ cmd->cmd = 0;
+ cmd->src = addr + n * 516;
+ cmd->dst = NAND_FLASH_BUFFER;
+ cmd->len = ((n < 3) ? 516 : 510);
+ cmd++;
+
+ if (n == 3) {
+ /* write extra data */
+ cmd->cmd = 0;
+ cmd->src = spareaddr;
+ cmd->dst = NAND_FLASH_BUFFER + 500;
+ cmd->len = 16;
+ cmd++;
+ }
+
+ /* kick the execute register */
+ cmd->cmd = 0;
+ cmd->src = paddr(&data->exec);
+ cmd->dst = NAND_EXEC_CMD;
+ cmd->len = 4;
+ cmd++;
+
+ /* block on data ready, then read the status register */
+ cmd->cmd = SRC_CRCI_NAND_DATA;
+ cmd->src = NAND_FLASH_STATUS;
+ cmd->dst = paddr(&data->result[n]);
+ cmd->len = 8;
+ cmd++;
+ }
+
+ /* restore saved ecc config */
+ cmd->cmd = CMD_OCU | CMD_LC;
+ cmd->src = paddr(&data->ecc_cfg_save);
+ cmd->dst = NAND_EBI2_ECC_BUF_CFG;
+ cmd->len = 4;
+
+ ptr[0] = (paddr(cmdlist) >> 3) | CMD_PTR_LP;
+
+ dmov_exec_cmdptr(DMOV_NAND_CHAN, ptr);
+
+#if VERBOSE
+ dprintf("write page %d: status: %x %x %x %x\n",
+ page, data[5], data[6], data[7], data[8]);
+#endif
+
+ /* if any of the writes failed (0x10), or there was a
+ ** protection violation (0x100), or the program success
+ ** bit (0x80) is unset, we lose
+ */
+ for(n = 0; n < 4; n++) {
+ if(data->result[n].flash_status & 0x110) return -1;
+ if(!(data->result[n].flash_status & 0x80)) return -1;
+ }
+
+ return 0;
+}
+
+unsigned nand_cfg0;
+unsigned nand_cfg1;
+
+static int flash_read_config(dmov_s *cmdlist, unsigned *ptrlist)
+{
+ cmdlist[0].cmd = CMD_OCB;
+ cmdlist[0].src = NAND_DEV0_CFG0;
+ cmdlist[0].dst = paddr(&CFG0);
+ cmdlist[0].len = 4;
+
+ cmdlist[1].cmd = CMD_OCU | CMD_LC;
+ cmdlist[1].src = NAND_DEV0_CFG1;
+ cmdlist[1].dst = paddr(&CFG1);
+ cmdlist[1].len = 4;
+
+ *ptrlist = (paddr(cmdlist) >> 3) | CMD_PTR_LP;
+
+ dmov_exec_cmdptr(DMOV_NAND_CHAN, ptrlist);
+
+ if((CFG0 == 0) || (CFG1 == 0)) {
+ return -1;
+ }
+
+ dprintf("nandcfg: %x %x (initial)\n", CFG0, CFG1);
+
+ CFG0 = (3 << 6) /* 4 codeword per page for 2k nand */
+ | (516 << 9) /* 516 user data bytes */
+ | (10 << 19) /* 10 parity bytes */
+ | (5 << 27) /* 5 address cycles */
+ | (1 << 30) /* Read status before data */
+ | (1 << 31) /* Send read cmd */
+ /* 0 spare bytes for 16 bit nand or 1 spare bytes for 8 bit */
+ | ((nand_cfg1 & CFG1_WIDE_FLASH) ? (0 << 23) : (1 << 23));
+ CFG1 = (0 << 0) /* Enable ecc */
+ | (7 << 2) /* 8 recovery cycles */
+ | (0 << 5) /* Allow CS deassertion */
+ | (465 << 6) /* Bad block marker location */
+ | (0 << 16) /* Bad block in user data area */
+ | (2 << 17) /* 6 cycle tWB/tRB */
+ | (nand_cfg1 & CFG1_WIDE_FLASH); /* preserve wide flash flag */
+
+ dprintf("nandcfg: %x %x (used)\n", CFG0, CFG1);
+
+ return 0;
+}
+
+static unsigned *flash_ptrlist;
+static dmov_s *flash_cmdlist;
+static void *flash_spare;
+static void *flash_data;
+
+int flash_init(void)
+{
+ flash_ptrlist = alloc(1024);
+ flash_cmdlist = alloc(1024);
+ flash_data = alloc(2048);
+ flash_spare = alloc(64);
+
+ if(flash_read_config(flash_cmdlist, flash_ptrlist)) {
+ dprintf("ERROR: could not read CFG0/CFG1 state\n");
+ for(;;);
+ }
+
+ flash_read_id(flash_cmdlist, flash_ptrlist);
+
+ return 0;
+}
+
+int flash_erase(ptentry *ptn)
+{
+ unsigned block = ptn->start;
+ unsigned count = ptn->length;
+
+ while(count-- > 0) {
+ if(flash_erase_block(flash_cmdlist, flash_ptrlist, block * 64)) {
+ dprintf("cannot erase @ %d (bad block?)\n", block);
+ }
+ block++;
+ }
+ return 0;
+}
+
+int flash_read_ext(ptentry *ptn, unsigned extra_per_page, unsigned offset, void *data, unsigned bytes)
+{
+ unsigned page = (ptn->start * 64) + (offset / 2048);
+ unsigned lastpage = (ptn->start + ptn->length) * 64;
+ unsigned count = (bytes + 2047 + extra_per_page) / (2048 + extra_per_page);
+ unsigned *spare = (unsigned*) flash_spare;
+ unsigned errors = 0;
+ unsigned char *image = data;
+
+ if(offset & 2047)
+ return -1;
+
+ while(page < lastpage) {
+ if(count == 0) {
+ dprintf("flash_read_image: success (%d errors)\n", errors);
+ return 0;
+ }
+
+ if(_flash_read_page(flash_cmdlist, flash_ptrlist, page++, image, spare)) {
+ errors++;
+ continue;
+ }
+ image += 2048;
+ memcpy(image, spare, extra_per_page);
+ image += extra_per_page;
+ count -= 1;
+ }
+
+ /* could not find enough valid pages before we hit the end */
+ dprintf("flash_read_image: failed (%d errors)\n", errors);
+ return 0xffffffff;
+}
+
+int flash_write(ptentry *ptn, unsigned extra_per_page, const void *data, unsigned bytes)
+{
+ unsigned page = ptn->start * 64;
+ unsigned lastpage = (ptn->start + ptn->length) * 64;
+ unsigned *spare = (unsigned*) flash_spare;
+ const unsigned char *image = data;
+ unsigned wsize = 2048 + extra_per_page;
+ unsigned n;
+ int r;
+
+ for(n = 0; n < 16; n++) spare[n] = 0xffffffff;
+
+ while(bytes > 0) {
+ if(bytes < wsize) {
+ dprintf("flash_write_image: image undersized (%d < %d)\n", bytes, wsize);
+ return -1;
+ }
+ if(page >= lastpage) {
+ dprintf("flash_write_image: out of space\n");
+ return -1;
+ }
+
+ if((page & 63) == 0) {
+ if(flash_erase_block(flash_cmdlist, flash_ptrlist, page)) {
+ dprintf("flash_write_image: bad block @ %d\n", page >> 6);
+ page += 64;
+ continue;
+ }
+ }
+
+ if(extra_per_page) {
+ r = _flash_write_page(flash_cmdlist, flash_ptrlist, page++, image, image + 2048);
+ } else {
+ r = _flash_write_page(flash_cmdlist, flash_ptrlist, page++, image, spare);
+ }
+ if(r) {
+ dprintf("flash_write_image: write failure @ page %d (src %d)\n", page, image - (const unsigned char *)data);
+ image -= (page & 63) * wsize;
+ bytes += (page & 63) * wsize;
+ page &= ~63;
+ if(flash_erase_block(flash_cmdlist, flash_ptrlist, page)) {
+ dprintf("flash_write_image: erase failure @ page %d\n", page);
+ }
+ dprintf("flash_write_image: restart write @ page %d (src %d)\n", page, image - (const unsigned char *)data);
+ page += 64;
+ continue;
+ }
+
+ image += wsize;
+ bytes -= wsize;
+ }
+
+ /* erase any remaining pages in the partition */
+ page = (page + 63) & (~63);
+ while(page < lastpage){
+ if(flash_erase_block(flash_cmdlist, flash_ptrlist, page)) {
+ dprintf("flash_write_image: bad block @ %d\n", page >> 6);
+ }
+ page += 64;
+ }
+
+ dprintf("flash_write_image: success\n");
+ return 0;
+}
+
+static int flash_read_page(unsigned page, void *data, void *extra)
+{
+ return _flash_read_page(flash_cmdlist, flash_ptrlist,
+ page, data, extra);
+}
+