/** @file Copyright (c) 2008 - 2009, Apple Inc. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "Flash.h" NAND_PART_INFO_TABLE gNandPartInfoTable[1] = { { 0x2C, 0xBA, 17, 11 } }; NAND_FLASH_INFO *gNandFlashInfo = NULL; UINT8 *gEccCode; UINTN gNum512BytesChunks = 0; // // Device path for SemiHosting. It contains our autogened Caller ID GUID. // typedef struct { VENDOR_DEVICE_PATH Guid; EFI_DEVICE_PATH_PROTOCOL End; } FLASH_DEVICE_PATH; FLASH_DEVICE_PATH gDevicePath = { { { HARDWARE_DEVICE_PATH, HW_VENDOR_DP, { sizeof (VENDOR_DEVICE_PATH), 0 } }, EFI_CALLER_ID_GUID }, { END_DEVICE_PATH_TYPE, END_ENTIRE_DEVICE_PATH_SUBTYPE, { sizeof (EFI_DEVICE_PATH_PROTOCOL), 0} } }; //Actual page address = Column address + Page address + Block address. UINTN GetActualPageAddressInBytes ( UINTN BlockIndex, UINTN PageIndex ) { //BlockAddressStart = Start of the Block address in actual NAND //PageAddressStart = Start of the Page address in actual NAND return ((BlockIndex << gNandFlashInfo->BlockAddressStart) + (PageIndex << gNandFlashInfo->PageAddressStart)); } VOID NandSendCommand ( UINT8 Command ) { MmioWrite16(GPMC_NAND_COMMAND_0, Command); } VOID NandSendAddress ( UINT8 Address ) { MmioWrite16(GPMC_NAND_ADDRESS_0, Address); } UINT16 NandReadStatus ( VOID ) { //Send READ STATUS command NandSendCommand(READ_STATUS_CMD); //Read status. return MmioRead16(GPMC_NAND_DATA_0); } VOID NandSendAddressCycles ( UINTN Address ) { //Column address NandSendAddress(Address & 0xff); Address >>= 8; //Column address NandSendAddress(Address & 0x07); Address >>= 3; //Page and Block address NandSendAddress(Address & 0xff); Address >>= 8; //Block address NandSendAddress(Address & 0xff); Address >>= 8; //Block address NandSendAddress(Address & 0x01); } VOID GpmcInit ( VOID ) { //Enable Smart-idle mode. MmioWrite32 (GPMC_SYSCONFIG, SMARTIDLEMODE); //Set IRQSTATUS and IRQENABLE to the reset value MmioWrite32 (GPMC_IRQSTATUS, 0x0); MmioWrite32 (GPMC_IRQENABLE, 0x0); //Disable GPMC timeout control. MmioWrite32 (GPMC_TIMEOUT_CONTROL, TIMEOUTDISABLE); //Set WRITEPROTECT bit to enable write access. MmioWrite32 (GPMC_CONFIG, WRITEPROTECT_HIGH); //NOTE: Following GPMC_CONFIGi_0 register settings are taken from u-boot memory dump. MmioWrite32 (GPMC_CONFIG1_0, DEVICETYPE_NAND | DEVICESIZE_X16); MmioWrite32 (GPMC_CONFIG2_0, CSRDOFFTIME | CSWROFFTIME); MmioWrite32 (GPMC_CONFIG3_0, ADVRDOFFTIME | ADVWROFFTIME); MmioWrite32 (GPMC_CONFIG4_0, OEONTIME | OEOFFTIME | WEONTIME | WEOFFTIME); MmioWrite32 (GPMC_CONFIG5_0, RDCYCLETIME | WRCYCLETIME | RDACCESSTIME | PAGEBURSTACCESSTIME); MmioWrite32 (GPMC_CONFIG6_0, WRACCESSTIME | WRDATAONADMUXBUS | CYCLE2CYCLEDELAY | CYCLE2CYCLESAMECSEN); MmioWrite32 (GPMC_CONFIG7_0, MASKADDRESS_128MB | CSVALID | BASEADDRESS); } EFI_STATUS NandDetectPart ( VOID ) { UINT8 NandInfo = 0; UINT8 PartInfo[5]; UINTN Index; BOOLEAN Found = FALSE; //Send READ ID command NandSendCommand(READ_ID_CMD); //Send one address cycle. NandSendAddress(0); //Read 5-bytes to idenfity code programmed into the NAND flash devices. //BYTE 0 = Manufacture ID //Byte 1 = Device ID //Byte 2, 3, 4 = Nand part specific information (Page size, Block size etc) for (Index = 0; Index < sizeof(PartInfo); Index++) { PartInfo[Index] = MmioRead16(GPMC_NAND_DATA_0); } //Check if the ManufactureId and DeviceId are part of the currently supported nand parts. for (Index = 0; Index < sizeof(gNandPartInfoTable)/sizeof(NAND_PART_INFO_TABLE); Index++) { if (gNandPartInfoTable[Index].ManufactureId == PartInfo[0] && gNandPartInfoTable[Index].DeviceId == PartInfo[1]) { gNandFlashInfo->BlockAddressStart = gNandPartInfoTable[Index].BlockAddressStart; gNandFlashInfo->PageAddressStart = gNandPartInfoTable[Index].PageAddressStart; Found = TRUE; break; } } if (Found == FALSE) { DEBUG ((EFI_D_ERROR, "Nand part is not currently supported. Manufacture id: %x, Device id: %x\n", PartInfo[0], PartInfo[1])); return EFI_NOT_FOUND; } //Populate NAND_FLASH_INFO based on the result of READ ID command. gNandFlashInfo->ManufactureId = PartInfo[0]; gNandFlashInfo->DeviceId = PartInfo[1]; NandInfo = PartInfo[3]; if (PAGE_SIZE(NandInfo) == PAGE_SIZE_2K_VAL) { gNandFlashInfo->PageSize = PAGE_SIZE_2K; } else { DEBUG ((EFI_D_ERROR, "Unknown Page size.\n")); return EFI_DEVICE_ERROR; } if (SPARE_AREA_SIZE(NandInfo) == SPARE_AREA_SIZE_64B_VAL) { gNandFlashInfo->SparePageSize = SPARE_AREA_SIZE_64B; } else { DEBUG ((EFI_D_ERROR, "Unknown Spare area size.\n")); return EFI_DEVICE_ERROR; } if (BLOCK_SIZE(NandInfo) == BLOCK_SIZE_128K_VAL) { gNandFlashInfo->BlockSize = BLOCK_SIZE_128K; } else { DEBUG ((EFI_D_ERROR, "Unknown Block size.\n")); return EFI_DEVICE_ERROR; } if (ORGANIZATION(NandInfo) == ORGANIZATION_X8) { gNandFlashInfo->Organization = 0; } else if (ORGANIZATION(NandInfo) == ORGANIZATION_X16) { gNandFlashInfo->Organization = 1; } //Calculate total number of blocks. gNandFlashInfo->NumPagesPerBlock = DivU64x32(gNandFlashInfo->BlockSize, gNandFlashInfo->PageSize); return EFI_SUCCESS; } VOID NandConfigureEcc ( VOID ) { //Define ECC size 0 and size 1 to 512 bytes MmioWrite32 (GPMC_ECC_SIZE_CONFIG, (ECCSIZE0_512BYTES | ECCSIZE1_512BYTES)); } VOID NandEnableEcc ( VOID ) { //Clear all the ECC result registers and select ECC result register 1 MmioWrite32 (GPMC_ECC_CONTROL, (ECCCLEAR | ECCPOINTER_REG1)); //Enable ECC engine on CS0 MmioWrite32 (GPMC_ECC_CONFIG, (ECCENABLE | ECCCS_0 | ECC16B)); } VOID NandDisableEcc ( VOID ) { //Turn off ECC engine. MmioWrite32 (GPMC_ECC_CONFIG, ECCDISABLE); } VOID NandCalculateEcc ( VOID ) { UINTN Index; UINTN EccResultRegister; UINTN EccResult; //Capture 32-bit ECC result for each 512-bytes chunk. //In our case PageSize is 2K so read ECC1-ECC4 result registers and //generate total of 12-bytes of ECC code for the particular page. EccResultRegister = GPMC_ECC1_RESULT; for (Index = 0; Index < gNum512BytesChunks; Index++) { EccResult = MmioRead32 (EccResultRegister); //Calculate ECC code from 32-bit ECC result value. //NOTE: Following calculation is not part of TRM. We got this information //from Beagleboard mailing list. gEccCode[Index * 3] = EccResult & 0xFF; gEccCode[(Index * 3) + 1] = (EccResult >> 16) & 0xFF; gEccCode[(Index * 3) + 2] = (((EccResult >> 20) & 0xF0) | ((EccResult >> 8) & 0x0F)); //Point to next ECC result register. EccResultRegister += 4; } } EFI_STATUS NandReadPage ( IN UINTN BlockIndex, IN UINTN PageIndex, OUT VOID *Buffer, OUT UINT8 *SpareBuffer ) { UINTN Address; UINTN Index; UINTN NumMainAreaWords = (gNandFlashInfo->PageSize/2); UINTN NumSpareAreaWords = (gNandFlashInfo->SparePageSize/2); UINT16 *MainAreaWordBuffer = Buffer; UINT16 *SpareAreaWordBuffer = (UINT16 *)SpareBuffer; UINTN Timeout = MAX_RETRY_COUNT; //Generate device address in bytes to access specific block and page index Address = GetActualPageAddressInBytes(BlockIndex, PageIndex); //Send READ command NandSendCommand(PAGE_READ_CMD); //Send 5 Address cycles to access specific device address NandSendAddressCycles(Address); //Send READ CONFIRM command NandSendCommand(PAGE_READ_CONFIRM_CMD); //Poll till device is busy. while (Timeout) { if ((NandReadStatus() & NAND_READY) == NAND_READY) { break; } Timeout--; } if (Timeout == 0) { DEBUG ((EFI_D_ERROR, "Read page timed out.\n")); return EFI_TIMEOUT; } //Reissue READ command NandSendCommand(PAGE_READ_CMD); //Enable ECC engine. NandEnableEcc(); //Read data into the buffer. for (Index = 0; Index < NumMainAreaWords; Index++) { *MainAreaWordBuffer++ = MmioRead16(GPMC_NAND_DATA_0); } //Read spare area into the buffer. for (Index = 0; Index < NumSpareAreaWords; Index++) { *SpareAreaWordBuffer++ = MmioRead16(GPMC_NAND_DATA_0); } //Calculate ECC. NandCalculateEcc(); //Turn off ECC engine. NandDisableEcc(); //Perform ECC correction. //Need to implement.. return EFI_SUCCESS; } EFI_STATUS NandWritePage ( IN UINTN BlockIndex, IN UINTN PageIndex, OUT VOID *Buffer, IN UINT8 *SpareBuffer ) { UINTN Address; UINT16 *MainAreaWordBuffer = Buffer; UINT16 *SpareAreaWordBuffer = (UINT16 *)SpareBuffer; UINTN Index; UINTN NandStatus; UINTN Timeout = MAX_RETRY_COUNT; //Generate device address in bytes to access specific block and page index Address = GetActualPageAddressInBytes(BlockIndex, PageIndex); //Send SERIAL DATA INPUT command NandSendCommand(PROGRAM_PAGE_CMD); //Send 5 Address cycles to access specific device address NandSendAddressCycles(Address); //Enable ECC engine. NandEnableEcc(); //Data input from Buffer for (Index = 0; Index < (gNandFlashInfo->PageSize/2); Index++) { MmioWrite16(GPMC_NAND_DATA_0, *MainAreaWordBuffer++); //After each write access, device has to wait to accept data. //Currently we may not be programming proper timing parameters to //the GPMC_CONFIGi_0 registers and we would need to figure that out. //Without following delay, page programming fails. gBS->Stall(1); } //Calculate ECC. NandCalculateEcc(); //Turn off ECC engine. NandDisableEcc(); //Prepare Spare area buffer with ECC codes. SetMem(SpareBuffer, gNandFlashInfo->SparePageSize, 0xFF); CopyMem(&SpareBuffer[ECC_POSITION], gEccCode, gNum512BytesChunks * 3); //Program spare area with calculated ECC. for (Index = 0; Index < (gNandFlashInfo->SparePageSize/2); Index++) { MmioWrite16(GPMC_NAND_DATA_0, *SpareAreaWordBuffer++); } //Send PROGRAM command NandSendCommand(PROGRAM_PAGE_CONFIRM_CMD); //Poll till device is busy. NandStatus = 0; while (Timeout) { NandStatus = NandReadStatus(); if ((NandStatus & NAND_READY) == NAND_READY) { break; } Timeout--; } if (Timeout == 0) { DEBUG ((EFI_D_ERROR, "Program page timed out.\n")); return EFI_TIMEOUT; } //Bit0 indicates Pass/Fail status if (NandStatus & NAND_FAILURE) { return EFI_DEVICE_ERROR; } return EFI_SUCCESS; } EFI_STATUS NandEraseBlock ( IN UINTN BlockIndex ) { UINTN Address; UINTN NandStatus; UINTN Timeout = MAX_RETRY_COUNT; //Generate device address in bytes to access specific block and page index Address = GetActualPageAddressInBytes(BlockIndex, 0); //Send ERASE SETUP command NandSendCommand(BLOCK_ERASE_CMD); //Send 3 address cycles to device to access Page address and Block address Address >>= 11; //Ignore column addresses NandSendAddress(Address & 0xff); Address >>= 8; NandSendAddress(Address & 0xff); Address >>= 8; NandSendAddress(Address & 0xff); //Send ERASE CONFIRM command NandSendCommand(BLOCK_ERASE_CONFIRM_CMD); //Poll till device is busy. NandStatus = 0; while (Timeout) { NandStatus = NandReadStatus(); if ((NandStatus & NAND_READY) == NAND_READY) { break; } Timeout--; gBS->Stall(1); } if (Timeout == 0) { DEBUG ((EFI_D_ERROR, "Erase block timed out for Block: %d.\n", BlockIndex)); return EFI_TIMEOUT; } //Bit0 indicates Pass/Fail status if (NandStatus & NAND_FAILURE) { return EFI_DEVICE_ERROR; } return EFI_SUCCESS; } EFI_STATUS NandReadBlock ( IN UINTN StartBlockIndex, IN UINTN EndBlockIndex, OUT VOID *Buffer, OUT VOID *SpareBuffer ) { UINTN BlockIndex; UINTN PageIndex; EFI_STATUS Status = EFI_SUCCESS; for (BlockIndex = StartBlockIndex; BlockIndex <= EndBlockIndex; BlockIndex++) { //For each block read number of pages for (PageIndex = 0; PageIndex < gNandFlashInfo->NumPagesPerBlock; PageIndex++) { Status = NandReadPage(BlockIndex, PageIndex, Buffer, SpareBuffer); if (EFI_ERROR(Status)) { return Status; } Buffer = ((UINT8 *)Buffer + gNandFlashInfo->PageSize); } } return Status; } EFI_STATUS NandWriteBlock ( IN UINTN StartBlockIndex, IN UINTN EndBlockIndex, OUT VOID *Buffer, OUT VOID *SpareBuffer ) { UINTN BlockIndex; UINTN PageIndex; EFI_STATUS Status = EFI_SUCCESS; for (BlockIndex = StartBlockIndex; BlockIndex <= EndBlockIndex; BlockIndex++) { //Page programming. for (PageIndex = 0; PageIndex < gNandFlashInfo->NumPagesPerBlock; PageIndex++) { Status = NandWritePage(BlockIndex, PageIndex, Buffer, SpareBuffer); if (EFI_ERROR(Status)) { return Status; } Buffer = ((UINT8 *)Buffer + gNandFlashInfo->PageSize); } } return Status; } EFI_STATUS EFIAPI NandFlashReset ( IN EFI_BLOCK_IO_PROTOCOL *This, IN BOOLEAN ExtendedVerification ) { UINTN BusyStall = 50; // microSeconds UINTN ResetBusyTimeout = (1000000 / BusyStall); // 1 Second //Send RESET command to device. NandSendCommand(RESET_CMD); //Wait for 1ms before we check status register. gBS->Stall(1000); //Check BIT#5 & BIT#6 in Status register to make sure RESET is done. while ((NandReadStatus() & NAND_RESET_STATUS) != NAND_RESET_STATUS) { //In case of extended verification, wait for extended amount of time //to make sure device is reset. if (ExtendedVerification) { if (ResetBusyTimeout == 0) { return EFI_DEVICE_ERROR; } gBS->Stall(BusyStall); ResetBusyTimeout--; } } return EFI_SUCCESS; } EFI_STATUS EFIAPI NandFlashReadBlocks ( IN EFI_BLOCK_IO_PROTOCOL *This, IN UINT32 MediaId, IN EFI_LBA Lba, IN UINTN BufferSize, OUT VOID *Buffer ) { UINTN NumBlocks; UINTN EndBlockIndex; EFI_STATUS Status; UINT8 *SpareBuffer = NULL; if (Buffer == NULL) { Status = EFI_INVALID_PARAMETER; goto exit; } if (Lba > LAST_BLOCK) { Status = EFI_INVALID_PARAMETER; goto exit; } if ((BufferSize % gNandFlashInfo->BlockSize) != 0) { Status = EFI_BAD_BUFFER_SIZE; goto exit; } NumBlocks = DivU64x32(BufferSize, gNandFlashInfo->BlockSize); EndBlockIndex = ((UINTN)Lba + NumBlocks) - 1; SpareBuffer = (UINT8 *)AllocatePool(gNandFlashInfo->SparePageSize); if (SpareBuffer == NULL) { Status = EFI_OUT_OF_RESOURCES; goto exit; } //Read block Status = NandReadBlock((UINTN)Lba, EndBlockIndex, Buffer, SpareBuffer); if (EFI_ERROR(Status)) { DEBUG((EFI_D_ERROR, "Read block fails: %x\n", Status)); goto exit; } exit: if (SpareBuffer != NULL) { FreePool (SpareBuffer); } return Status; } EFI_STATUS EFIAPI NandFlashWriteBlocks ( IN EFI_BLOCK_IO_PROTOCOL *This, IN UINT32 MediaId, IN EFI_LBA Lba, IN UINTN BufferSize, IN VOID *Buffer ) { UINTN BlockIndex; UINTN NumBlocks; UINTN EndBlockIndex; EFI_STATUS Status; UINT8 *SpareBuffer = NULL; if (Buffer == NULL) { Status = EFI_INVALID_PARAMETER; goto exit; } if (Lba > LAST_BLOCK) { Status = EFI_INVALID_PARAMETER; goto exit; } if ((BufferSize % gNandFlashInfo->BlockSize) != 0) { Status = EFI_BAD_BUFFER_SIZE; goto exit; } NumBlocks = DivU64x32(BufferSize, gNandFlashInfo->BlockSize); EndBlockIndex = ((UINTN)Lba + NumBlocks) - 1; SpareBuffer = (UINT8 *)AllocatePool(gNandFlashInfo->SparePageSize); if (SpareBuffer == NULL) { Status = EFI_OUT_OF_RESOURCES; goto exit; } // Erase block for (BlockIndex = (UINTN)Lba; BlockIndex <= EndBlockIndex; BlockIndex++) { Status = NandEraseBlock(BlockIndex); if (EFI_ERROR(Status)) { DEBUG((EFI_D_ERROR, "Erase block failed. Status: %x\n", Status)); goto exit; } } // Program data Status = NandWriteBlock((UINTN)Lba, EndBlockIndex, Buffer, SpareBuffer); if (EFI_ERROR(Status)) { DEBUG((EFI_D_ERROR, "Block write fails: %x\n", Status)); goto exit; } exit: if (SpareBuffer != NULL) { FreePool (SpareBuffer); } return Status; } EFI_STATUS EFIAPI NandFlashFlushBlocks ( IN EFI_BLOCK_IO_PROTOCOL *This ) { return EFI_SUCCESS; } EFI_BLOCK_IO_MEDIA gNandFlashMedia = { SIGNATURE_32('n','a','n','d'), // MediaId FALSE, // RemovableMedia TRUE, // MediaPresent FALSE, // LogicalPartition FALSE, // ReadOnly FALSE, // WriteCaching 0, // BlockSize 2, // IoAlign 0, // Pad 0 // LastBlock }; EFI_BLOCK_IO_PROTOCOL BlockIo = { EFI_BLOCK_IO_INTERFACE_REVISION, // Revision &gNandFlashMedia, // *Media NandFlashReset, // Reset NandFlashReadBlocks, // ReadBlocks NandFlashWriteBlocks, // WriteBlocks NandFlashFlushBlocks // FlushBlocks }; EFI_STATUS NandFlashInitialize ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; gNandFlashInfo = (NAND_FLASH_INFO *)AllocateZeroPool (sizeof(NAND_FLASH_INFO)); //Initialize GPMC module. GpmcInit(); //Reset NAND part NandFlashReset(&BlockIo, FALSE); //Detect NAND part and populate gNandFlashInfo structure Status = NandDetectPart (); if (EFI_ERROR(Status)) { DEBUG((EFI_D_ERROR, "Nand part id detection failure: Status: %x\n", Status)); return Status; } //Count total number of 512Bytes chunk based on the page size. if (gNandFlashInfo->PageSize == PAGE_SIZE_512B) { gNum512BytesChunks = 1; } else if (gNandFlashInfo->PageSize == PAGE_SIZE_2K) { gNum512BytesChunks = 4; } else if (gNandFlashInfo->PageSize == PAGE_SIZE_4K) { gNum512BytesChunks = 8; } gEccCode = (UINT8 *)AllocatePool(gNum512BytesChunks * 3); if (gEccCode == NULL) { return EFI_OUT_OF_RESOURCES; } //Configure ECC NandConfigureEcc (); //Patch EFI_BLOCK_IO_MEDIA structure. gNandFlashMedia.BlockSize = gNandFlashInfo->BlockSize; gNandFlashMedia.LastBlock = LAST_BLOCK; //Publish BlockIO. Status = gBS->InstallMultipleProtocolInterfaces ( &ImageHandle, &gEfiBlockIoProtocolGuid, &BlockIo, &gEfiDevicePathProtocolGuid, &gDevicePath, NULL ); return Status; }