/**************************************************************************//** * * @copyright (C) 2019 Nuvoton Technology Corp. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2021-1-13 Wayne First version * ******************************************************************************/ #include #if defined(NU_PKG_USING_SPINAND) #define LOG_TAG "spinand_flash" #define DBG_ENABLE #define DBG_SECTION_NAME LOG_TAG #define DBG_LEVEL DBG_INFO #define DBG_COLOR #include #include "spinand.h" const struct nu_spinand_info g_spinandflash_list[] = { /* Winbond */ { 0xEFAA21, 2048, 64, 0x6b, 0xff, 0xff, 0xff, 0x1, 1024, 64, 0, "Winbond 128MB: 2048+64@64@1024" }, /* Only tested */ #if 0 { 0xEFAA22, 2048, 64, 0x6b, 0xff, 0xff, 0xff, 0x1, 2048, 64, 0, "Winbond 256MB: 2048+64@64@1024" }, { 0xEFAB21, 2048, 64, 0x6b, 0xff, 0xff, 0xff, 0x1, 1024, 64, 1, "Winbond 256MB: 2048+64@64@1024, MCP" }, /* Not test and supporting yet. */ /* MXIC */ { 0x00C212, 2048, 64, 0x6b, 0x05, 0x01, 0x40, 0x1, 1024, 64, 0, "MXIC 128MB: 2048+64@64@1024" }, /* XTX */ { 0x0BE20B, 2048, 64, 0x6b, 0xff, 0xff, 0xff, 0x1, 2048, 64, 0, "XTX 256MB: 2048+64@64@2048" }, { 0x0BF20B, 2048, 64, 0x6b, 0xff, 0xff, 0xff, 0x1, 2048, 64, 0, "XTX 256MB: 2048+64@64@2048" }, { 0x0BE10B, 2048, 64, 0x6b, 0xff, 0xff, 0xff, 0x1, 1024, 64, 0, "XTX 256MB: 2048+64@64@1024" }, { 0x0BF10B, 2048, 64, 0x6b, 0xff, 0xff, 0xff, 0x1, 1024, 64, 0, "XTX 256MB: 2048+64@64@1024" }, /* ATO */ { 0x9B129B, 2048, 64, 0x6b, 0x0f, 0x1f, 0x01, 0x1, 1024, 64, 0, "ATO 128MB: 2048+64@64@1024" }, /* Micro */ { 0x2C242C, 2048, 128, 0x6b, 0x0f, 0x1f, 0x01, 0x1, 2048, 64, 0, "Micro 256MB: 2048+128@64@2048" }, /* GigaDevice */ { 0xB148C8, 2048, 128, 0x6b, 0x0f, 0x1f, 0x01, 0x1, 1024, 64, 0, "GD 128MB: 2048+128@64@1024" }, /* Unknown */ { 0x00C8D1, 2048, 128, 0x6b, 0x0f, 0x1f, 0x01, 0x1, 1024, 64, 0, "Unknown 128MB: 2048+128@64@1024" }, { 0x00C851, 2048, 128, 0x6b, 0x0f, 0x1f, 0x01, 0x1, 1024, 64, 0, "Unknown 128MB: 2048+128@64@1024" }, { 0x98E240, 2048, 128, 0x6b, 0x0f, 0x1f, 0x01, 0x1, 1024, 64, 0, "Unknown 128MB: 2048+128@64@1024" } #endif }; #define SPINAND_LIST_ELEMENT_NUM ( sizeof(g_spinandflash_list)/sizeof(struct nu_spinand_info) ) /* For 0xEFAA21 description: Data Area(2048-Byte) ----------------------------- |Sect-0|Sect-1|Sect-2|Sect-3| |(512B)|(512B)|(512B)|(512B)| ----------------------------- Spare Area(64-Byte) --------------------------------- |Spare-0|Spare-1|Spare-2|Spare-3| | (16B) | (16B) | (16B) | (16B) | --------------------------------- ----------------- Spare-0 ------------------- / \ ------------------------------------------------- | BBM | UD2 | UD1 | ECC Sect-0 | ECC Spare | | 0 1 | 2 3 | 4 5 6 7 | 8 9 A B C D | E F | ------------------------------------------------- | NO ECC | ECC PROTECTED | ECC 4-D | BBM: Bad block marker. UD1: User Data 1. UD2: User Data 2. ECC Sect-n: ECC for sector-n. ECC Spare: ECC for spare 4-D. ---------------- Spare-1 ------------------- / \ ----------------------------------------------- | UD2 | UD1 | ECC Sect-1 | ECC Spare | | 0 1 2 3 | 4 5 6 7 | 8 9 A B C D | E F | ----------------------------------------------- | NO ECC | ECC PROTECTED | ECC 14-1D | ---------------- Spare-2 ------------------- / \ ----------------------------------------------- | UD2 | UD1 | ECC Sect-2 | ECC Spare | | 0 1 2 3 | 4 5 6 7 | 8 9 A B C D | E F | ----------------------------------------------- | NO ECC | ECC PROTECTED | ECC 24-2D | ---------------- Spare-3 ------------------- / \ ----------------------------------------------- | UD2 | UD1 | ECC Sect-3 | ECC Spare | | 0 1 2 3 | 4 5 6 7 | 8 9 A B C D | E F | ----------------------------------------------- | NO ECC | ECC PROTECTED | ECC 34-3D | */ rt_uint8_t spinand_flash_data_layout[SPINAND_SPARE_LAYOUT_SIZE] = { #if defined(RT_USING_DFS_UFFS) /* For storing Seal-byte at 0x37. */ 0x04, 0x04, 0x14, 0x04, 0x24, 0x04, 0x34, 0x03, 0xFF, 0x00 #else 0x04, 0x04, 0x14, 0x04, 0x24, 0x04, 0x34, 0x04, 0xFF, 0x00 #endif }; rt_uint8_t spinand_flash_ecc_layout[SPINAND_SPARE_LAYOUT_SIZE] = { #if defined(RT_USING_DFS_UFFS) /* For storing Seal-byte at 0x37 and not report latest ECC part in Spare-3 */ 0x08, 0x08, 0x18, 0x08, 0x28, 0x08, /*0x38, 0x08,*/ 0xFF, 0x00 #else 0x08, 0x08, 0x18, 0x08, 0x28, 0x08, 0x38, 0x08, 0xFF, 0x00 #endif }; static rt_err_t spinand_info_read(struct rt_qspi_device *qspi); static rt_err_t spinand_die_select(struct rt_qspi_device *qspi, uint8_t select_die) { uint8_t au8Cmd[2] = { 0xC2, 0x0 }; au8Cmd[1] = select_die; return nu_qspi_send(qspi, &au8Cmd[0], sizeof(au8Cmd)); } static uint8_t spinand_isbusy(struct rt_qspi_device *qspi) { #define BUSY_CKECKING_TIMEOUT_MS 3000 volatile uint8_t SR = 0xFF; rt_err_t result; uint8_t au8Cmd[2] = { 0x0F, 0xC0 }; uint32_t u32CheckingDuration = rt_tick_from_millisecond(BUSY_CKECKING_TIMEOUT_MS); uint32_t u32Start = rt_tick_get(); do { result = nu_qspi_send_then_recv(qspi, &au8Cmd[0], sizeof(au8Cmd), (void *)&SR, 1); if (result != RT_EOK) goto timeout_spinand_isbusy; if ((rt_tick_get() - u32Start) >= u32CheckingDuration) { goto timeout_spinand_isbusy; } } while ((SR & 0x1) != 0x00); return 0; timeout_spinand_isbusy: LOG_E("Error: spinand timeout."); return 1; } static rt_err_t spinand_program_dataload( struct rt_qspi_device *qspi, uint8_t u8AddrH, uint8_t u8AddrL, uint8_t *pu8DataBuff, uint32_t u32DataCount, uint8_t *pu8SpareBuff, uint32_t u32SpareCount) { uint32_t volatile i = 0; uint8_t u8WECmd = 0x06; rt_err_t result = RT_EOK; struct rt_qspi_message qspi_messages[2] = {0}; /* 1-bit mode */ qspi_messages[0].instruction.content = 0x32; qspi_messages[0].instruction.qspi_lines = 1; qspi_messages[0].address.content = (u8AddrH << 8) | (u8AddrL); qspi_messages[0].address.size = 2 * 8; qspi_messages[0].address.qspi_lines = 1; /* 4-bit mode */ qspi_messages[0].qspi_data_lines = 4; qspi_messages[0].parent.cs_take = 1; qspi_messages[0].parent.cs_release = 0; qspi_messages[0].parent.send_buf = pu8DataBuff; qspi_messages[0].parent.length = u32DataCount; qspi_messages[0].parent.next = &qspi_messages[1].parent; qspi_messages[1].qspi_data_lines = 4; qspi_messages[1].parent.cs_take = 0; qspi_messages[1].parent.cs_release = 1; qspi_messages[1].parent.send_buf = pu8SpareBuff; qspi_messages[1].parent.length = u32SpareCount; if ((result = nu_qspi_send(qspi, &u8WECmd, sizeof(u8WECmd))) != RT_EOK) goto exit_spinand_program_dataload; result = nu_qspi_transfer_message(qspi, (struct rt_qspi_message *)&qspi_messages[0]); exit_spinand_program_dataload: return result; } static uint8_t spinand_status_register_read(struct rt_qspi_device *qspi, uint8_t u8SRSel) { uint8_t u8SR = 0; uint8_t au8Cmd[2]; switch (u8SRSel) { case 0x01: au8Cmd[0] = 0x05; au8Cmd[1] = 0xA0; break; case 0x02: au8Cmd[0] = 0x0F; au8Cmd[1] = 0xB0; break; case 0x03: au8Cmd[0] = 0x05; au8Cmd[1] = 0xC0; break; default: RT_ASSERT(0); break; } if (nu_qspi_send_then_recv(qspi, &au8Cmd[0], sizeof(au8Cmd), &u8SR, 1) != RT_EOK) RT_ASSERT(0); return u8SR; } static rt_err_t spinand_status_register_write(struct rt_qspi_device *qspi, uint8_t u8SRSel, uint8_t u8Value) { rt_err_t result = RT_EOK; uint8_t au8Cmd[3]; switch (u8SRSel) { case 0x01: au8Cmd[0] = 0x01; au8Cmd[1] = 0xA0; break; case 0x02: au8Cmd[0] = 0x01; au8Cmd[1] = 0xB0; break; case 0x03: au8Cmd[0] = 0x01; au8Cmd[1] = 0xC0; break; default: result = RT_EINVAL; goto exit_spinand_status_register_write; } au8Cmd[2] = u8Value; if ((result = nu_qspi_send(qspi, &au8Cmd[0], sizeof(au8Cmd))) != RT_EOK) goto exit_spinand_status_register_write; if (spinand_isbusy(qspi)) { result = RT_EIO; goto exit_spinand_status_register_write; } exit_spinand_status_register_write: return result; } static rt_err_t spinand_program_execute(struct rt_qspi_device *qspi, uint8_t u8Addr2, uint8_t u8Addr1, uint8_t u8Addr0) { rt_err_t result; uint8_t au8Cmd[4], u8SR; au8Cmd[0] = 0x10 ; au8Cmd[1] = u8Addr2; au8Cmd[2] = u8Addr1; au8Cmd[3] = u8Addr0; if ((result = nu_qspi_send(qspi, &au8Cmd, sizeof(au8Cmd))) != RT_EOK) goto exit_spinand_program_execute; if (spinand_isbusy(qspi)) { result = -RT_MTD_EIO; goto exit_spinand_program_execute; } u8SR = (spinand_status_register_read(SPINAND_FLASH_QSPI, 3) & 0x0C) >> 2; if (u8SR == 1) { result = -RT_MTD_EIO; LOG_E("Error write status!"); } exit_spinand_program_execute: return result; } static rt_err_t spinand_normal_read(struct rt_qspi_device *qspi, uint8_t u8AddrH, uint8_t u8AddrL, uint8_t *pu8Buff, uint32_t u32Count) { uint8_t au8Cmd[4]; au8Cmd[0] = 0x03; au8Cmd[1] = u8AddrH; au8Cmd[2] = u8AddrL; au8Cmd[3] = 0x00; return nu_qspi_send_then_recv(qspi, &au8Cmd[0], sizeof(au8Cmd), pu8Buff, u32Count); } static rt_err_t spinand_protect_set(struct rt_qspi_device *qspi, uint8_t u8Protect) { /* Read status register 1 */ uint8_t u8SR = spinand_status_register_read(qspi, 1); if (u8Protect) { /* protect */ u8SR |= 0x7C; } else { /* unprotect */ u8SR &= 0x83; } return spinand_status_register_write(qspi, 1, u8SR); } static uint8_t spinand_program_erase_isfail(struct rt_qspi_device *qspi) { /* Read status register 3 */ uint8_t u8SR = spinand_status_register_read(qspi, 3); return (u8SR & 0x0C) >> 2; /* Check P-Fail, E-Fail bit */ } static uint8_t spinand_hwecc_status_get(struct rt_qspi_device *qspi) { /* Read status register 3 */ uint8_t u8SR = spinand_status_register_read(qspi, 3); return (u8SR & 0x30) >> 4; /* ECC-1, ECC0 bit */ } static rt_err_t spinand_hwecc_set(struct rt_qspi_device *qspi, uint8_t u8Enable) { uint8_t u8SR = spinand_status_register_read(qspi, 2); // Read status register 2 if (u8Enable) { u8SR |= 0x10; // Enable ECC-E bit } else { u8SR &= 0xEF; // Disable ECC-E bit } return spinand_status_register_write(qspi, 2, u8SR); } static uint8_t spinand_hwecc_get(struct rt_qspi_device *qspi) { /* Read status register 2 */ uint8_t u8SR = spinand_status_register_read(qspi, 2); return (u8SR & 0x10) >> 4; } static rt_err_t spinand_read_dataload(struct rt_qspi_device *qspi, uint8_t u8Addr2, uint8_t u8Addr1, uint8_t u8Addr0) { rt_err_t result = RT_EOK; uint8_t au8Cmd[4]; uint8_t u8SR; au8Cmd[0] = 0x13 ; au8Cmd[1] = u8Addr2; au8Cmd[2] = u8Addr1; au8Cmd[3] = u8Addr0; if ((result = nu_qspi_send(qspi, &au8Cmd[0], sizeof(au8Cmd))) != RT_EOK) goto exit_spinand_read_dataload; if (spinand_isbusy(qspi)) { result = -RT_EIO; goto exit_spinand_read_dataload; } u8SR = spinand_hwecc_status_get(SPINAND_FLASH_QSPI); if ((u8SR != 0x00) && (u8SR != 0x01)) { result = -RT_MTD_EECC; LOG_E("Error ECC status error[0x%x].", u8SR); } exit_spinand_read_dataload: return result; } static uint8_t spinand_block_isbad(struct rt_qspi_device *qspi, uint32_t u32PageAddr) { rt_err_t result; uint8_t read_buf; again_spinand_block_isbad: result = spinand_read_dataload(qspi, (u32PageAddr >> 16) & 0xFF, (u32PageAddr >> 8) & 0xFF, u32PageAddr & 0xFF); // Read the first page of a block RT_ASSERT(result == RT_EOK); result = spinand_normal_read(qspi, (SPINAND_FLASH_PAGE_SIZE >> 8) & 0xff, SPINAND_FLASH_PAGE_SIZE & 0xff, &read_buf, 1); // Read bad block mark at 0x800 update at v.1.0.8 RT_ASSERT(result == RT_EOK); if (read_buf != 0xFF) { // update at v.1.0.7 return 1; } if (((u32PageAddr % (SPINAND_FLASH_PAGE_PER_BLOCK_NUM * SPINAND_FLASH_PAGE_SIZE)) == 0)) { /* Need check second page again. */ u32PageAddr++; goto again_spinand_block_isbad; } return 0; } static rt_err_t spinand_buffermode_set(struct rt_qspi_device *qspi, uint8_t u8Enable) { uint8_t u8SR = spinand_status_register_read(qspi, 2); // Read status register 2 if (u8Enable) { u8SR |= 0x08; // Enable BUF bit } else { u8SR &= 0xF7; // Disable BUF bit } return spinand_status_register_write(qspi, 2, u8SR); } static rt_err_t spinand_block_erase(struct rt_qspi_device *qspi, uint8_t u8Addr2, uint8_t u8Addr1, uint8_t u8Addr0) { rt_err_t result; uint8_t u8WECmd = 0x06; uint8_t au8EraseCmd[4], u8SR; au8EraseCmd[0] = 0xD8; au8EraseCmd[1] = u8Addr2; au8EraseCmd[2] = u8Addr1; au8EraseCmd[3] = u8Addr0; if ((result = nu_qspi_send(qspi, &u8WECmd, sizeof(u8WECmd))) != RT_EOK) goto exit_spinand_block_erase; if ((result = nu_qspi_send(qspi, &au8EraseCmd[0], sizeof(au8EraseCmd))) != RT_EOK) goto exit_spinand_block_erase; if (spinand_isbusy(qspi)) return -RT_EIO; u8SR = spinand_program_erase_isfail(SPINAND_FLASH_QSPI); if (u8SR != 0) { /* Fail to erase */ LOG_E("Fail to erase. Will mark it bad."); result = -RT_ERROR; goto exit_spinand_block_erase; } exit_spinand_block_erase: return result; } static rt_err_t spinand_block_markbad(struct rt_qspi_device *qspi, uint32_t u32PageAddr) { rt_err_t result = RT_EOK; uint8_t u8BadBlockMarker = 0xF0; result = spinand_block_erase(qspi, (u32PageAddr >> 16) & 0xFF, (u32PageAddr >> 8) & 0xFF, u32PageAddr & 0xFF); if (result != RT_EOK) return result; result = spinand_program_dataload(qspi, (SPINAND_FLASH_PAGE_SIZE >> 8) & 0xff, SPINAND_FLASH_PAGE_SIZE & 0xff, &u8BadBlockMarker, 1, 0, 0); if (result != RT_EOK) return result; return spinand_program_execute(qspi, (u32PageAddr >> 16) & 0xFF, (u32PageAddr >> 8) & 0xFF, u32PageAddr & 0xFF); } static rt_err_t spinand_read_quadoutput( struct rt_qspi_device *qspi, uint8_t u8AddrH, uint8_t u8AddrL, uint8_t *pu8DataBuff, uint32_t u32DataCount ) { struct rt_qspi_message qspi_messages = {0}; /* 1-bit mode */ qspi_messages.instruction.content = SPINAND_FLASH_QUADREAD_CMDID; qspi_messages.instruction.qspi_lines = 1; qspi_messages.address.content = (u8AddrH << 8) | (u8AddrL); qspi_messages.address.size = 2 * 8; qspi_messages.address.qspi_lines = 1; qspi_messages.dummy_cycles = SPINAND_FLASH_DUMMYBYTE * 8; //In bit /* 4-bit mode */ qspi_messages.qspi_data_lines = 4; qspi_messages.parent.cs_take = 1; qspi_messages.parent.cs_release = 1; qspi_messages.parent.recv_buf = pu8DataBuff; qspi_messages.parent.length = u32DataCount; qspi_messages.parent.next = RT_NULL; return nu_qspi_transfer_message(qspi, (struct rt_qspi_message *) &qspi_messages); } static rt_err_t spinand_jedecid_get(struct rt_qspi_device *qspi, uint32_t *pu32ID) { uint32_t u32JedecId = 0; uint32_t u32JedecId_real = 0; uint8_t u8Cmd = 0x9F; if (nu_qspi_send_then_recv(qspi, &u8Cmd, 1, &u32JedecId, 4) != RT_EOK) { return -RT_ERROR; } /* Reverse order. */ nu_set32_be((uint8_t *)&u32JedecId_real, u32JedecId); /* Only keep 3-bytes. */ u32JedecId_real &= 0x00ffffff; *pu32ID = u32JedecId_real; return RT_EOK; } static rt_err_t spinand_reset(struct rt_qspi_device *qspi) { rt_err_t result; uint8_t u8Cmd = 0xFF; if ((result = nu_qspi_send(qspi, &u8Cmd, 1)) != RT_EOK) goto exit_spinand_reset; if (spinand_isbusy(qspi)) { result = RT_EIO; goto exit_spinand_reset; } exit_spinand_reset: return result; } rt_err_t spinand_flash_init(struct rt_qspi_device *qspi) { rt_err_t result; if ((result = spinand_reset(qspi)) != RT_EOK) goto exit_spinand_init; if ((result = spinand_info_read(qspi)) != RT_EOK) goto exit_spinand_init; /* Un-protect */ if ((result = spinand_protect_set(qspi, 0)) != RT_EOK) goto exit_spinand_init; /* Enable BUF mode */ if ((result = spinand_buffermode_set(qspi, 1)) != RT_EOK) goto exit_spinand_init; /* Enable HWECC */ if ((result = spinand_hwecc_set(qspi, 1)) != RT_EOK) goto exit_spinand_init; /* Check HWECC */ if (!(spinand_hwecc_get(qspi))) goto exit_spinand_init; if (SPINAND_FLASH_MCP == 1) { /* Select die. */ if ((result = spinand_die_select(qspi, SPINAND_DIE_ID1)) != RT_EOK) goto exit_spinand_init; /* Unprotect */ if ((result = spinand_protect_set(qspi, 0)) != RT_EOK) goto exit_spinand_init; } LOG_I("Enabled BUF, HWECC. Unprotected."); exit_spinand_init: return -result; } struct spinand_ops spinand_ops_wb = { .block_erase = spinand_block_erase, .block_isbad = spinand_block_isbad, .block_markbad = spinand_block_markbad, .die_select = spinand_die_select, .jedecid_get = spinand_jedecid_get, .program_dataload = spinand_program_dataload, .program_execute = spinand_program_execute, .read_dataload = spinand_read_dataload, .read_quadoutput = spinand_read_quadoutput }; static rt_err_t spinand_info_read(struct rt_qspi_device *qspi) { int i; uint32_t u32JedecId = 0; if (spinand_jedecid_get(qspi, &u32JedecId) != RT_EOK) goto exit_spinand_info_read; for (i = 0 ; i < SPINAND_LIST_ELEMENT_NUM; i++) { if (u32JedecId == g_spinandflash_list[i].u32JEDECID) /* Match JEDECID? */ { rt_memcpy(SPINAND_FLASH_INFO, &g_spinandflash_list[i], sizeof(struct nu_spinand_info)); LOG_I("Found: [%08X] %s.", u32JedecId, SPINAND_FLASH_DESCRIPTION); switch (u32JedecId & 0xff0000) { case 0xEF0000: /* Winbond */ SPINAND_FLASH_OPS = &spinand_ops_wb; break; default: goto exit_spinand_info_read; } return RT_EOK; } } exit_spinand_info_read: LOG_E("Can't find the flash[%08X] in supported list.", u32JedecId); return -RT_ERROR; } #endif