| /* |
| * 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); |
| } |
| |