diff options
Diffstat (limited to 'arch_msm7k/nand.c')
-rw-r--r-- | arch_msm7k/nand.c | 630 |
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); +} + |