diff --git a/.build-test-rules.yml b/.build-test-rules.yml index 2f646f4820..bd98e5dd4d 100644 --- a/.build-test-rules.yml +++ b/.build-test-rules.yml @@ -79,6 +79,11 @@ spi_nand_flash/test_app: - if: IDF_VERSION_MAJOR < 5 reason: The spi_nand_flash component is compatible with IDF version v5.0 and above, due to a change in the f_mkfs API in versions above v5.0, which is not supported in older IDF versions. +spi_nand_flash/host_test: + disable: + - if: IDF_VERSION_MAJOR < 5 + reason: The spi_nand_flash component is compatible with IDF version v5.0 and above, due to a change in the f_mkfs API in versions above v5.0, which is not supported in older IDF versions. + thorvg/examples/thorvg-example: enable: - if: (IDF_VERSION_MAJOR == 5 and IDF_VERSION_MINOR >= 3) and (IDF_TARGET in ["esp32p4"]) diff --git a/spi_nand_flash/CMakeLists.txt b/spi_nand_flash/CMakeLists.txt index db55dcf50b..9001d56bba 100644 --- a/spi_nand_flash/CMakeLists.txt +++ b/spi_nand_flash/CMakeLists.txt @@ -1,17 +1,32 @@ +idf_build_get_property(target IDF_TARGET) + +set(reqs fatfs) +set(inc diskio include) +set(priv_inc priv_include) set(srcs "src/nand.c" - "src/nand_winbond.c" - "src/nand_gigadevice.c" - "src/nand_alliance.c" - "src/nand_micron.c" - "src/nand_impl.c" - "src/nand_impl_wrap.c" - "src/nand_diag_api.c" - "src/spi_nand_oper.c" "src/dhara_glue.c" - "vfs/vfs_fat_spinandflash.c" + "src/nand_impl_wrap.c" "diskio/diskio_nand.c") -set(reqs fatfs) +if(${target} STREQUAL "linux") + +list(APPEND reqs esp_partition) +list(APPEND srcs "src/nand_impl_linux.c") + +else() + +list(APPEND srcs "src/nand_winbond.c" + "src/nand_gigadevice.c" + "src/nand_alliance.c" + "src/nand_micron.c" + "src/nand_impl.c" + "src/nand_impl_wrap.c" + "src/nand_diag_api.c" + "src/spi_nand_oper.c" + "vfs/vfs_fat_spinandflash.c") + +set(priv_reqs vfs) +list(APPEND inc vfs) if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER "5.3") list(APPEND reqs esp_driver_spi) @@ -19,10 +34,11 @@ else() list(APPEND reqs driver) endif() -set(priv_reqs vfs) +endif() + idf_component_register(SRCS ${srcs} - INCLUDE_DIRS include vfs diskio - PRIV_INCLUDE_DIRS "priv_include" + INCLUDE_DIRS ${inc} + PRIV_INCLUDE_DIRS ${priv_inc} REQUIRES ${reqs} PRIV_REQUIRES ${priv_reqs}) diff --git a/spi_nand_flash/diskio/diskio_nand.c b/spi_nand_flash/diskio/diskio_nand.c index e0ead6ad45..ef8800294c 100644 --- a/spi_nand_flash/diskio/diskio_nand.c +++ b/spi_nand_flash/diskio/diskio_nand.c @@ -133,11 +133,12 @@ DRESULT ff_nand_ioctl(BYTE pdrv, BYTE cmd, void *buff) break; } #if FF_USE_TRIM - case CTRL_TRIM: + case CTRL_TRIM: { DWORD start_sector = *((DWORD *)buff); DWORD end_sector = *((DWORD *)buff + 1) + 1; DWORD sector_count = end_sector - start_sector; return ff_nand_trim(pdrv, start_sector, sector_count); + } #endif //FF_USE_TRIM default: return RES_ERROR; diff --git a/spi_nand_flash/host_test/CMakeLists.txt b/spi_nand_flash/host_test/CMakeLists.txt new file mode 100644 index 0000000000..ccb678b1b9 --- /dev/null +++ b/spi_nand_flash/host_test/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(COMPONENTS main) + +project(nand_flash_host_test) diff --git a/spi_nand_flash/host_test/README.md b/spi_nand_flash/host_test/README.md new file mode 100644 index 0000000000..37c142df16 --- /dev/null +++ b/spi_nand_flash/host_test/README.md @@ -0,0 +1,2 @@ +| Supported Targets | Linux | +| ----------------- | ----- | diff --git a/spi_nand_flash/host_test/main/CMakeLists.txt b/spi_nand_flash/host_test/main/CMakeLists.txt new file mode 100644 index 0000000000..faca86c6ee --- /dev/null +++ b/spi_nand_flash/host_test/main/CMakeLists.txt @@ -0,0 +1,8 @@ +idf_component_register(SRCS "test_nand_flash.cpp" "test_app_main.cpp" + REQUIRES fatfs + WHOLE_ARCHIVE + ) + +# Currently 'main' for IDF_TARGET=linux is defined in freertos component. +# Since we are using a freertos mock here, need to let Catch2 provide 'main'. +target_link_libraries(${COMPONENT_LIB} PRIVATE Catch2WithMain) diff --git a/spi_nand_flash/host_test/main/idf_component.yml b/spi_nand_flash/host_test/main/idf_component.yml new file mode 100644 index 0000000000..ffd61fddb2 --- /dev/null +++ b/spi_nand_flash/host_test/main/idf_component.yml @@ -0,0 +1,5 @@ +dependencies: + espressif/catch2: "^3.4.0" + espressif/spi_nand_flash: + version: '*' + override_path: '../../' diff --git a/spi_nand_flash/host_test/main/test_app_main.cpp b/spi_nand_flash/host_test/main/test_app_main.cpp new file mode 100644 index 0000000000..c6130ffaa0 --- /dev/null +++ b/spi_nand_flash/host_test/main/test_app_main.cpp @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + + +extern "C" void app_main(void) +{ + int argc = 1; + const char *argv[2] = { + "target_test_main", + NULL + }; + + auto result = Catch::Session().run(argc, argv); + if (result != 0) { + printf("Test failed with result %d\n", result); + } else { + printf("Test passed.\n"); + } + fflush(stdout); + exit(result); +} diff --git a/spi_nand_flash/host_test/main/test_nand_flash.cpp b/spi_nand_flash/host_test/main/test_nand_flash.cpp new file mode 100644 index 0000000000..80627b5b29 --- /dev/null +++ b/spi_nand_flash/host_test/main/test_nand_flash.cpp @@ -0,0 +1,180 @@ +/* + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "ff.h" +#include "esp_partition.h" +#include "diskio_impl.h" +#include "diskio_nand.h" +#include "spi_nand_flash.h" +#include "nand_private/nand_impl_wrap.h" + +#include + +#define PATTERN_SEED 0x12345678 + +TEST_CASE("Create volume, open file, write and read back data", "[fatfs, spi_nand_flash]") +{ + FRESULT fr_result; + BYTE pdrv; + FATFS fs; + FIL file; + UINT bw; + + esp_err_t esp_result; + spi_nand_flash_config_t nand_flash_config; + spi_nand_flash_device_t *device_handle; + REQUIRE(spi_nand_flash_init_device(&nand_flash_config, &device_handle) == ESP_OK); + + // Get a physical drive + esp_result = ff_diskio_get_drive(&pdrv); + REQUIRE(esp_result == ESP_OK); + + // Register physical drive as wear-levelled partition + esp_result = ff_diskio_register_nand(pdrv, device_handle); + + // Create FAT volume on the entire disk + LBA_t part_list[] = {100, 0, 0, 0}; + BYTE work_area[FF_MAX_SS]; + + fr_result = f_fdisk(pdrv, part_list, work_area); + REQUIRE(fr_result == FR_OK); + + char drv[3] = {(char)('0' + pdrv), ':', 0}; + const MKFS_PARM opt = {(BYTE)(FM_ANY), 0, 0, 0, 0}; + fr_result = f_mkfs(drv, &opt, work_area, sizeof(work_area)); // Use default volume + REQUIRE(fr_result == FR_OK); + + // Mount the volume + fr_result = f_mount(&fs, drv, 0); + REQUIRE(fr_result == FR_OK); + + // Open, write and read data + fr_result = f_open(&file, "0:/test.txt", FA_OPEN_ALWAYS | FA_READ | FA_WRITE); + REQUIRE(fr_result == FR_OK); + + // Generate data + uint32_t data_size = 1000; + + char *data = (char *) malloc(data_size); + char *read = (char *) malloc(data_size); + + for (uint32_t i = 0; i < data_size; i += sizeof(i)) { + *((uint32_t *)(data + i)) = i; + } + + // Write generated data + fr_result = f_write(&file, data, data_size, &bw); + REQUIRE(fr_result == FR_OK); + REQUIRE(bw == data_size); + + // Move to beginning of file + fr_result = f_lseek(&file, 0); + REQUIRE(fr_result == FR_OK); + + // Read written data + fr_result = f_read(&file, read, data_size, &bw); + REQUIRE(fr_result == FR_OK); + REQUIRE(bw == data_size); + + REQUIRE(memcmp(data, read, data_size) == 0); + + // Close file + fr_result = f_close(&file); + REQUIRE(fr_result == FR_OK); + + // Unmount default volume + fr_result = f_mount(0, drv, 0); + REQUIRE(fr_result == FR_OK); + + // Clear + free(read); + free(data); + ff_diskio_unregister(pdrv); + ff_diskio_clear_pdrv_nand(device_handle); + spi_nand_flash_deinit_device(device_handle); + esp_partition_unload_all(); +} + +TEST_CASE("verify mark_bad_block works", "[spi_nand_flash]") +{ + spi_nand_flash_config_t nand_flash_config; + spi_nand_flash_device_t *device_handle; + REQUIRE(spi_nand_flash_init_device(&nand_flash_config, &device_handle) == ESP_OK); + + uint32_t sector_num, sector_size; + REQUIRE(spi_nand_flash_get_capacity(device_handle, §or_num) == 0); + REQUIRE(spi_nand_flash_get_sector_size(device_handle, §or_size) == 0); + + uint32_t test_block = 15; + if (test_block < sector_num) { + bool is_bad_status = false; + // Verify if test_block is not bad block + REQUIRE(nand_wrap_is_bad(device_handle, test_block, &is_bad_status) == 0); + REQUIRE(is_bad_status == false); + // mark test_block as a bad block + REQUIRE(nand_wrap_mark_bad(device_handle, test_block) == 0); + // Verify if test_block is marked as bad block + REQUIRE(nand_wrap_is_bad(device_handle, test_block, &is_bad_status) == 0); + REQUIRE(is_bad_status == true); + } + + spi_nand_flash_deinit_device(device_handle); + esp_partition_unload_all(); +} + +static void fill_buffer(uint32_t seed, uint8_t *dst, size_t count) +{ + srand(seed); + for (size_t i = 0; i < count; ++i) { + uint32_t val = rand(); + memcpy(dst + i * sizeof(uint32_t), &val, sizeof(val)); + } +} + +TEST_CASE("verify nand_prog, nand_read, nand_copy, nand_is_free works", "[spi_nand_flash]") +{ + spi_nand_flash_config_t nand_flash_config; + spi_nand_flash_device_t *device_handle; + REQUIRE(spi_nand_flash_init_device(&nand_flash_config, &device_handle) == ESP_OK); + + uint32_t sector_num, sector_size, block_size; + REQUIRE(spi_nand_flash_get_capacity(device_handle, §or_num) == 0); + REQUIRE(spi_nand_flash_get_sector_size(device_handle, §or_size) == 0); + REQUIRE(spi_nand_flash_get_block_size(device_handle, &block_size) == 0); + + uint8_t *pattern_buf = (uint8_t *)heap_caps_malloc(sector_size, MALLOC_CAP_DEFAULT); + REQUIRE(pattern_buf != NULL); + uint8_t *temp_buf = (uint8_t *)heap_caps_malloc(sector_size, MALLOC_CAP_DEFAULT); + REQUIRE(temp_buf != NULL); + + fill_buffer(PATTERN_SEED, pattern_buf, sector_size / sizeof(uint32_t)); + + bool is_page_free = true; + uint32_t test_block = 20; + uint32_t test_page = test_block * (block_size / sector_size); //(block_num * pages_per_block) + uint32_t dst_page = test_page + 1; + if (test_page < sector_num) { + // Verify if test_page is free + REQUIRE(nand_wrap_is_free(device_handle, test_page, &is_page_free) == 0); + REQUIRE(is_page_free == true); + // Write/program test_page + REQUIRE(nand_wrap_prog(device_handle, test_page, pattern_buf) == 0); + // Verify if test_page is used/programmed + REQUIRE(nand_wrap_is_free(device_handle, test_page, &is_page_free) == 0); + REQUIRE(is_page_free == false); + + REQUIRE(nand_wrap_read(device_handle, test_page, 0, sector_size, temp_buf) == 0); + REQUIRE(nand_wrap_copy(device_handle, test_page, dst_page) == 0); + REQUIRE(nand_wrap_read(device_handle, dst_page, 0, sector_size, temp_buf) == 0); + } + free(pattern_buf); + free(temp_buf); + spi_nand_flash_deinit_device(device_handle); + esp_partition_unload_all(); +} diff --git a/spi_nand_flash/host_test/partition_table.csv b/spi_nand_flash/host_test/partition_table.csv new file mode 100644 index 0000000000..2e4bcf3944 --- /dev/null +++ b/spi_nand_flash/host_test/partition_table.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1M, +storage, data, fat, , 8M, diff --git a/spi_nand_flash/host_test/pytest_nand_flash_linux.py b/spi_nand_flash/host_test/pytest_nand_flash_linux.py new file mode 100644 index 0000000000..19f0d0c573 --- /dev/null +++ b/spi_nand_flash/host_test/pytest_nand_flash_linux.py @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 +import pytest +from pytest_embedded import Dut + + +@pytest.mark.linux +@pytest.mark.host_test +def test_nand_flash_linux(dut: Dut) -> None: + dut.expect_exact('All tests passed', timeout=120) diff --git a/spi_nand_flash/host_test/sdkconfig.defaults b/spi_nand_flash/host_test/sdkconfig.defaults new file mode 100644 index 0000000000..b7749b56ce --- /dev/null +++ b/spi_nand_flash/host_test/sdkconfig.defaults @@ -0,0 +1,8 @@ +CONFIG_IDF_TARGET="linux" +CONFIG_COMPILER_CXX_EXCEPTIONS=y +CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n +CONFIG_PARTITION_TABLE_OFFSET=0x8000 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partition_table.csv" +CONFIG_MMU_PAGE_SIZE=0X10000 +CONFIG_ESP_PARTITION_ENABLE_STATS=y diff --git a/spi_nand_flash/include/spi_nand_flash.h b/spi_nand_flash/include/spi_nand_flash.h index a11f1ed395..c118221373 100644 --- a/spi_nand_flash/include/spi_nand_flash.h +++ b/spi_nand_flash/include/spi_nand_flash.h @@ -10,8 +10,10 @@ #include #include "esp_err.h" +#ifndef CONFIG_IDF_TARGET_LINUX #include "driver/spi_common.h" #include "driver/spi_master.h" +#endif #ifdef __cplusplus extern "C" { @@ -21,7 +23,9 @@ extern "C" { @note The spi_device_handle_t must be initialized with the flag SPI_DEVICE_HALFDUPLEX */ struct spi_nand_flash_config_t { +#ifndef CONFIG_IDF_TARGET_LINUX spi_device_handle_t device_handle; ///< SPI Device for this nand chip. +#endif uint8_t gc_factor; ///< The gc factor controls the number of blocks to spare block ratio. ///< Lower values will reduce the available space but increase performance }; diff --git a/spi_nand_flash/priv_include/nand.h b/spi_nand_flash/priv_include/nand.h index c73b07adf6..ca81404bd3 100644 --- a/spi_nand_flash/priv_include/nand.h +++ b/spi_nand_flash/priv_include/nand.h @@ -10,6 +10,10 @@ #include #include "spi_nand_flash.h" +#ifdef CONFIG_IDF_TARGET_LINUX +#include "freertos/FreeRTOS.h" +#include "esp_partition.h" +#endif #include "freertos/semphr.h" #ifdef __cplusplus @@ -40,6 +44,10 @@ typedef struct { uint8_t log2_ppb; //is power of 2, log2_ppb shift ((1< #include "esp_check.h" #include "spi_nand_flash.h" +#include "nand.h" + +#ifndef CONFIG_IDF_TARGET_LINUX #include "spi_nand_oper.h" #include "nand_impl.h" -#include "nand.h" #include "nand_flash_devices.h" #include "nand_flash_chip.h" #include "esp_vfs_fat_nand.h" +#endif //CONFIG_IDF_TARGET_LINUX static const char *TAG = "nand_flash"; +#ifdef CONFIG_IDF_TARGET_LINUX + +static esp_err_t detect_chip(spi_nand_flash_device_t *dev) +{ + esp_err_t ret = ESP_OK; + const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, "storage"); + dev->chip.page_size = (1 << dev->chip.log2_page_size); + + dev->chip.emulated_page_oob = 64; + if (dev->chip.page_size == 512) { + dev->chip.emulated_page_oob = 16; + } else if (dev->chip.page_size == 2048) { + dev->chip.emulated_page_oob = 64; + } else if (dev->chip.page_size == 4096) { + dev->chip.emulated_page_oob = 128; + } + dev->chip.emulated_page_size = dev->chip.page_size + dev->chip.emulated_page_oob; + dev->chip.block_size = (1 << dev->chip.log2_ppb) * dev->chip.emulated_page_size; + dev->chip.num_blocks = partition->size / dev->chip.block_size; + dev->chip.erase_block_delay_us = 3000; + dev->chip.program_page_delay_us = 630; + dev->chip.read_page_delay_us = 60; + dev->partition = partition; + return ret; +} + +static esp_err_t unprotect_chip(spi_nand_flash_device_t *dev) +{ + return ESP_OK; +} + +#else + static esp_err_t detect_chip(spi_nand_flash_device_t *dev) { uint8_t manufacturer_id; @@ -60,10 +96,13 @@ static esp_err_t unprotect_chip(spi_nand_flash_device_t *dev) return ret; } +#endif //CONFIG_IDF_TARGET_LINUX esp_err_t spi_nand_flash_init_device(spi_nand_flash_config_t *config, spi_nand_flash_device_t **handle) { +#ifndef CONFIG_IDF_TARGET_LINUX ESP_RETURN_ON_FALSE(config->device_handle != NULL, ESP_ERR_INVALID_ARG, TAG, "Spi device pointer can not be NULL"); +#endif //CONFIG_IDF_TARGET_LINUX if (!config->gc_factor) { config->gc_factor = 45; diff --git a/spi_nand_flash/src/nand_impl_linux.c b/spi_nand_flash/src/nand_impl_linux.c new file mode 100644 index 0000000000..3e5708e649 --- /dev/null +++ b/spi_nand_flash/src/nand_impl_linux.c @@ -0,0 +1,162 @@ +/* + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_check.h" +#include "esp_err.h" +#include "spi_nand_flash.h" +#include "nand.h" +#include "esp_partition.h" + +static const char *TAG = "nand_linux"; + +esp_err_t nand_is_bad(spi_nand_flash_device_t *handle, uint32_t block, bool *is_bad_status) +{ + uint16_t bad_block_indicator = 0xFFFF; + esp_err_t ret = ESP_OK; + uint32_t block_offset = block * handle->chip.block_size; + + // Read the first 2 bytes on the OOB of the first page in the block. This should be 0xFFFF for a good block + ESP_GOTO_ON_ERROR(esp_partition_read(handle->partition, block_offset + handle->chip.page_size, (uint8_t *) &bad_block_indicator, 2), + fail, TAG, ""); + + ESP_LOGD(TAG, "is_bad, block=%"PRIu32", page=%"PRIu32",indicator = %04x", block, block_offset, bad_block_indicator); + if (bad_block_indicator == 0xFFFF) { + *is_bad_status = false; + } else { + *is_bad_status = true; + } + return ret; + +fail: + ESP_LOGE(TAG, "Error in nand_is_bad %d", ret); + return ret; +} + +esp_err_t nand_mark_bad(spi_nand_flash_device_t *handle, uint32_t block) +{ + esp_err_t ret = ESP_OK; + + uint32_t first_block_page = block * (1 << handle->chip.log2_ppb); + uint16_t bad_block_indicator = 0; + ESP_LOGD(TAG, "mark_bad, block=%"PRIu32", page=%"PRIu32",indicator = %04x", block, first_block_page, bad_block_indicator); + + ESP_GOTO_ON_ERROR(esp_partition_erase_range(handle->partition, block * handle->chip.block_size, handle->chip.block_size), + fail, TAG, ""); + + ESP_GOTO_ON_ERROR(esp_partition_write(handle->partition, block * handle->chip.block_size + handle->chip.page_size, + (const uint8_t *) &bad_block_indicator, 2), fail, TAG, ""); + + return ret; +fail: + ESP_LOGE(TAG, "Error in nand_mark_bad %d", ret); + return ret; +} + +esp_err_t nand_erase_block(spi_nand_flash_device_t *handle, uint32_t block) +{ + ESP_LOGD(TAG, "erase_block, block=%"PRIu32",", block); + esp_err_t ret = ESP_OK; + + uint32_t address = block * handle->chip.block_size; + + ESP_GOTO_ON_ERROR(esp_partition_erase_range(handle->partition, address, handle->chip.block_size), + fail, TAG, ""); + return ret; + +fail: + ESP_LOGE(TAG, "Error in nand_erase %x", ret); + return ret; +} + +esp_err_t nand_erase_chip(spi_nand_flash_device_t *handle) +{ + esp_err_t ret = ESP_OK; + + for (int i = 0; i < handle->chip.num_blocks; i++) { + ESP_GOTO_ON_ERROR(nand_erase_block(handle, i), + end, TAG, ""); + } + return ret; + +end: + ESP_LOGE(TAG, "Error in nand_erase_chip %d", ret); + return ret; +} + +esp_err_t nand_prog(spi_nand_flash_device_t *handle, uint32_t page, const uint8_t *data) +{ + ESP_LOGV(TAG, "prog, page=%"PRIu32",", page); + esp_err_t ret = ESP_OK; + uint16_t used_marker = 0; + uint32_t data_offset = page * handle->chip.emulated_page_size; + + ESP_GOTO_ON_ERROR(esp_partition_write(handle->partition, data_offset, data, handle->chip.page_size), fail, TAG, ""); + ESP_GOTO_ON_ERROR(esp_partition_write(handle->partition, data_offset + handle->chip.page_size + 2, + (uint8_t *)&used_marker, 2), fail, TAG, ""); + + return ret; +fail: + ESP_LOGE(TAG, "Error in nand_prog %d", ret); + return ret; +} + +esp_err_t nand_is_free(spi_nand_flash_device_t *handle, uint32_t page, bool *is_free_status) +{ + esp_err_t ret = ESP_OK; + uint16_t used_marker = 0xFF; + + ESP_GOTO_ON_ERROR(esp_partition_read(handle->partition, page * handle->chip.emulated_page_size + handle->chip.page_size + 2, (uint8_t *)&used_marker, 2), + fail, TAG, ""); + + ESP_LOGD(TAG, "is free, page=%"PRIu32", used_marker=%04x,", page, used_marker); + if (used_marker == 0xFFFF) { + *is_free_status = true; + } else { + *is_free_status = false; + } + return ret; +fail: + ESP_LOGE(TAG, "Error in nand_is_free %d", ret); + return ret; +} + +esp_err_t nand_read(spi_nand_flash_device_t *handle, uint32_t page, size_t offset, size_t length, uint8_t *data) +{ + ESP_LOGV(TAG, "read, page=%"PRIu32", offset=%ld, length=%ld", page, offset, length); + assert(page < handle->chip.num_blocks * (1 << handle->chip.log2_ppb)); + esp_err_t ret = ESP_OK; + + ESP_GOTO_ON_ERROR(esp_partition_read(handle->partition, page * handle->chip.emulated_page_size + offset, data, length), + fail, TAG, ""); + + return ret; +fail: + ESP_LOGE(TAG, "Error in nand_read %d", ret); + return ret; +} + +esp_err_t nand_copy(spi_nand_flash_device_t *handle, uint32_t src, uint32_t dst) +{ + ESP_LOGD(TAG, "copy, src=%"PRIu32", dst=%"PRIu32"", src, dst); + esp_err_t ret = ESP_OK; + uint32_t dst_offset = dst * handle->chip.emulated_page_size; + uint32_t src_offset = src * handle->chip.emulated_page_size; + ESP_GOTO_ON_ERROR(esp_partition_read(handle->partition, (size_t)src_offset, (void *)handle->read_buffer, handle->chip.page_size), + fail, TAG, ""); + ESP_GOTO_ON_ERROR(esp_partition_write(handle->partition, (size_t)dst_offset, (void *)handle->read_buffer, handle->chip.page_size), fail, TAG, ""); + + return ret; +fail: + ESP_LOGE(TAG, "Error in nand_copy 0x%x", ret); + return ret; +} + +esp_err_t nand_get_ecc_status(spi_nand_flash_device_t *handle, uint32_t page) +{ + esp_err_t ret = ESP_OK; + return ret; +}