diff --git a/tests/drivers/mtd_spi_nor/Makefile b/tests/drivers/mtd_spi_nor/Makefile new file mode 100644 index 0000000000000..427f779ce1fcd --- /dev/null +++ b/tests/drivers/mtd_spi_nor/Makefile @@ -0,0 +1,11 @@ +BOARD ?= nrf52840dk + +include ../Makefile.drivers_common + +USEMODULE += embunit +USEMODULE += mtd_spi_nor + +CFLAGS += -DTHREAD_STACKSIZE_MAIN=4096 +CFLAGS += -DISR_STACKSIZE=2048 + +include $(RIOTBASE)/Makefile.include diff --git a/tests/drivers/mtd_spi_nor/Makefile.ci b/tests/drivers/mtd_spi_nor/Makefile.ci new file mode 100644 index 0000000000000..72db76ccb5cc5 --- /dev/null +++ b/tests/drivers/mtd_spi_nor/Makefile.ci @@ -0,0 +1,3 @@ +BOARD_INSUFFICIENT_MEMORY := \ + atmega8 \ + # diff --git a/tests/drivers/mtd_spi_nor/README.md b/tests/drivers/mtd_spi_nor/README.md new file mode 100644 index 0000000000000..224e3d416c8ae --- /dev/null +++ b/tests/drivers/mtd_spi_nor/README.md @@ -0,0 +1,41 @@ +# Test Program for the MTD SPI NOR Flash Driver + +This program performs a number of tests with the MTD SPI NOR Flash driver, +including reading, writing and erasing of the chip. + +This test will destroy the data present on the chip! + +# Usage + +## nRF52830DK +The test is designed to work with the built-in MX25F6435F flash of the +Nordic Semiconductor nRF52840DK development board. +Currently the nRF52840DK Flash definitions do not have the security options enabled. + +To run the test you simply have to compile and run the test: +``` +make flash term +``` + +The tests should conclude with "OK (4 tests)". + +## nRF52840DK with different flash chips + +The default SPI Device for using different Flash chips is SPI(0), which is present on +the Arduino Uno style headers. The default pin for Chip Select is P1.5. The SPI Device and +Chip Select pin can be changed with CFLAGS: +``` +CFLAGS+="-DFLASH_SPI_DEV=1 -DFLASH_SPI_CS='GPIO_PIN(1,5)'" make flash term +``` + +The Device under Test can be changed with CFLAGS as well: +``` +CFLAGS+="-DTEST_MX25F12873F" make flash term +``` + +# Tested/Supported Chips + +## Macronix + +- M25F6435F (built-in on nRF52840DK) +- M25F12873F diff --git a/tests/drivers/mtd_spi_nor/flash_dut.c b/tests/drivers/mtd_spi_nor/flash_dut.c new file mode 100644 index 0000000000000..ca7d7d81b862b --- /dev/null +++ b/tests/drivers/mtd_spi_nor/flash_dut.c @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2024 Technische Universität Hamburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ +/** + * @{ + * + * @file + * @brief Definitions for different Flash chips that can be tested + * + * @author Christopher Büchse + */ + +#include "timex.h" +#include "mtd_spi_nor.h" + +#ifdef TEST_MX25L12873F + +#define MX25L12873F_PAGE_SIZE (256) +#define MX25L12873F_PAGES_PER_SECTOR (16) +#define MX25L12873F_SECTOR_COUNT (4096) +#define MX25L12873F_FLAGS (SPI_NOR_F_SECT_4K | SPI_NOR_F_SECT_32K | \ + SPI_NOR_F_SECT_64K | SPI_NOR_F_MX_SECUR) +#define MX25L12873F_SPI_CLK SPI_CLK_10MHZ +#define MX25L12873F_SPI_MODE SPI_MODE_0 + +#ifndef FLASH_SPI_CS +#define FLASH_SPI_CS GPIO_PIN(1, 5) +#endif + +#ifndef FLASH_SPI_DEV +#define FLASH_SPI_DEV 0 +#endif + +static const mtd_spi_nor_params_t _mx25l12873f_flash_nor_params = { + .opcode = &mtd_spi_nor_opcode_macronix, + .wait_chip_erase = 50LU * US_PER_SEC, + .wait_32k_erase = 240LU *US_PER_MS, + .wait_sector_erase = 40LU * US_PER_MS, + .wait_chip_wake_up = 35LU * US_PER_MS, + .clk = MX25L12873F_SPI_CLK, + .flag = MX25L12873F_FLAGS, + .spi = SPI_DEV(FLASH_SPI_DEV), + .mode = MX25L12873F_SPI_MODE, + .cs = FLASH_SPI_CS, + .wp = GPIO_UNDEF, + .hold = GPIO_UNDEF, +}; + +static mtd_spi_nor_t _mx25l12873f_nor_dev = { + .base = { + .driver = &mtd_spi_nor_driver, + .page_size = MX25L12873F_PAGE_SIZE, + .pages_per_sector = MX25L12873F_PAGES_PER_SECTOR, + .sector_count = MX25L12873F_SECTOR_COUNT, + }, + .params = &_mx25l12873f_flash_nor_params, +}; + +MTD_XFA_ADD(_mx25l12873f_nor_dev, 10); + +#endif /* TEST_MX25L12873F */ +/** @} */ diff --git a/tests/drivers/mtd_spi_nor/main.c b/tests/drivers/mtd_spi_nor/main.c new file mode 100644 index 0000000000000..ffeffc2178877 --- /dev/null +++ b/tests/drivers/mtd_spi_nor/main.c @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2024 Technische Universität Hamburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @{ + * + * @file + * @brief Test for the MTD SPI NOR Driver + * + * @author Christopher Büchse + */ + +#include +#include + +#include "busy_wait.h" +#include "embUnit.h" + +#include "mtd_spi_nor.h" +#include "../../drivers/mtd_spi_nor/include/mtd_spi_nor_defines.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#define ENABLE_TRACE 0 +#define TRACE(...) DEBUG(__VA_ARGS__) + +#define TEST_MTD mtd_dev_get(MTD_NUMOF-1) // Make sure to get the last device + +// This function was copied from drivers/mtd_spi_nor/mtd_spi_nor.c +static inline spi_t _get_spi(const mtd_spi_nor_t *dev) +{ + return dev->params->spi; +} + +// This function was copied from drivers/mtd_spi_nor/mtd_spi_nor.c +static void mtd_spi_cmd(const mtd_spi_nor_t *dev, uint8_t opcode) +{ + if (IS_ACTIVE(TRACE)) { + TRACE("mtd_spi_cmd: %p, %02x\n", + (void *)dev, (unsigned int)opcode); + } + spi_transfer_byte(_get_spi(dev), dev->params->cs, false, opcode); +} + +// This function was copied from drivers/mtd_spi_nor/mtd_spi_nor.c +static void mtd_spi_cmd_read(const mtd_spi_nor_t *dev, uint8_t opcode, void *dest, uint32_t count) +{ + if (IS_ACTIVE(TRACE)) { + TRACE("mtd_spi_cmd_read: %p, %02x, %p, %" PRIu32 "\n", + (void *)dev, (unsigned int)opcode, dest, count); + } + spi_transfer_regs(_get_spi(dev), dev->params->cs, opcode, NULL, dest, count); +} + +// This function was copied from drivers/mtd_spi_nor/mtd_spi_nor.c +static void mtd_spi_cmd_write(const mtd_spi_nor_t *dev, uint8_t opcode, + const void *src, uint32_t count) +{ + if (IS_ACTIVE(TRACE)) { + TRACE("mtd_spi_cmd_write: %p, %02x, %p, %" PRIu32 "\n", + (void *)dev, (unsigned int)opcode, src, count); + } + spi_transfer_regs(_get_spi(dev), dev->params->cs, opcode, + (void *)src, NULL, count); +} + +// This function was copied from drivers/mtd_spi_nor/mtd_spi_nor.c +static inline void wait_for_write_enable_set(const mtd_spi_nor_t *dev) +{ + do { + uint8_t status; + mtd_spi_cmd_read(dev, dev->params->opcode->rdsr, &status, sizeof(status)); + + if (IS_ACTIVE(TRACE)) { + TRACE("mtd_spi_nor: wait device status = 0x%02x, waiting WEL-Flag\n", + (unsigned int)status); + } + if (status & SPI_NOR_STATUS_WEL) { + break; + } + thread_yield(); + } while (1); +} + +// This function was copied from drivers/mtd_spi_nor/mtd_spi_nor.c +static void mtd_spi_acquire(const mtd_spi_nor_t *dev) +{ + spi_acquire(_get_spi(dev), dev->params->cs, + dev->params->mode, dev->params->clk); +} + +// This function was copied from drivers/mtd_spi_nor/mtd_spi_nor.c +static void mtd_spi_release(const mtd_spi_nor_t *dev) +{ + spi_release(_get_spi(dev)); +} + +static void setup(void) +{ +} + +static void teardown(void) +{ +} + +static void test_mtd_init(void) +{ + DEBUG("test_mtd_init: Initializing the Device\n"); + + int ret = mtd_init(TEST_MTD); + TEST_ASSERT_EQUAL_INT(0, ret); +} + +static void test_mtd_erase(void) +{ + DEBUG("test_mtd_erase: Erasing the first sector\n"); + + int ret = mtd_erase(TEST_MTD, 0, TEST_MTD->page_size*TEST_MTD->pages_per_sector); + TEST_ASSERT_EQUAL_INT(0, ret); +} + +static void test_mtd_blank(void) +{ + return; + + DEBUG("test_mtd_blank: Blank Testing the Device... (this may take a while)\n"); + + int ret = 0; + uint32_t page_count = TEST_MTD->pages_per_sector * TEST_MTD->sector_count; + + uint8_t buffer[TEST_MTD->page_size]; + + for (uint32_t page = 0; page < page_count; page++) { + ret = mtd_read_page(TEST_MTD, buffer, page, 0, TEST_MTD->page_size); + TEST_ASSERT_EQUAL_INT(0, ret); + + uint8_t sum = 0xFF; + for (uint32_t i = 0; i < TEST_MTD->page_size; i++) { + sum &= buffer[i]; + } + TEST_ASSERT_EQUAL_INT(0xFF, sum); + } +} + +static void test_mtd_block_protect(void) +{ + int ret; + const char test_data[16] = "BLOCK TEST"; + const uint8_t bp_flags = SPI_NOR_STATUS_BP0 | SPI_NOR_STATUS_BP1 | \ + SPI_NOR_STATUS_BP2 | SPI_NOR_STATUS_BP3; + + uint8_t status_reg; + mtd_spi_nor_t *dev = (mtd_spi_nor_t *)TEST_MTD; + + if (!(dev->params->flag & SPI_NOR_F_MX_SECUR || dev->params->flag & SPI_NOR_F_ISSI_SECUR)) { + DEBUG("test_mtd_block_protect: No security features enabled, skip test.\n"); + return; + } + + DEBUG("test_mtd_block_protect: Checking the Block Protect Functions\n"); + + mtd_spi_acquire(dev); + + // check that the Status Register is Writable + mtd_spi_cmd_read(dev, dev->params->opcode->rdsr, &status_reg, sizeof(status_reg)); + if (status_reg & SPI_NOR_STATUS_SRWD) { + status_reg &= !SPI_NOR_STATUS_SRWD; + } + + // Mask the status bits + status_reg &= !(SPI_NOR_STATUS_WEL | SPI_NOR_STATUS_WIP); + + // Send WREN command to write to the Status Register + mtd_spi_cmd(dev, dev->params->opcode->wren); + wait_for_write_enable_set(dev); + + // read Status Register again + mtd_spi_cmd_read(dev, dev->params->opcode->rdsr, &status_reg, sizeof(status_reg)); + TEST_ASSERT_EQUAL_INT(0, status_reg & SPI_NOR_STATUS_SRWD); + TEST_ASSERT_EQUAL_INT(SPI_NOR_STATUS_WEL, status_reg & SPI_NOR_STATUS_WEL); + + // protect all blocks + status_reg |= bp_flags; + mtd_spi_cmd_write(dev, 0x01, &status_reg, sizeof(status_reg)); // Opcode WRSR + + busy_wait_us(500); + + // check that the Block Protection was set + mtd_spi_cmd_read(dev, dev->params->opcode->rdsr, &status_reg, sizeof(status_reg)); + TEST_ASSERT_EQUAL_INT(bp_flags, status_reg & bp_flags); + + mtd_spi_cmd(dev, dev->params->opcode->wren); + wait_for_write_enable_set(dev); + busy_wait_us(50000); + + mtd_spi_release(dev); + + // Perform a write test to check if the P_FAIL flag is correctly handled + ret = mtd_write_page_raw(TEST_MTD, test_data, 0x00000000, 0x00, sizeof(test_data)); + TEST_ASSERT_EQUAL_INT(-EIO, ret); + + // Perform an erase test to check if the E_FAIL flag is correctly handled + ret = mtd_erase(TEST_MTD, 0x00000000, dev->base.page_size * dev->base.pages_per_sector); + TEST_ASSERT_EQUAL_INT(-EIO, ret); + + // Revert everything back to the original state + mtd_spi_acquire(dev); + + // Send WREN command to write to the Status Register + mtd_spi_cmd(dev, dev->params->opcode->wren); + wait_for_write_enable_set(dev); + + // Mask the status bits and Block Protection Flags + status_reg &= !(SPI_NOR_STATUS_WEL | SPI_NOR_STATUS_WIP); + status_reg &= !bp_flags; + + mtd_spi_cmd_write(dev, 0x01, &status_reg, sizeof(status_reg)); // Opcode: WRSR + mtd_spi_release(dev); +} + +Test *tests_mtd_spi_nor_tests(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_mtd_init), + new_TestFixture(test_mtd_erase), + new_TestFixture(test_mtd_blank), + + new_TestFixture(test_mtd_block_protect), + }; + + EMB_UNIT_TESTCALLER(mtd_mtd_spi_nor_tests, setup, teardown, fixtures); + + return (Test *)&mtd_mtd_spi_nor_tests; +} + +int main(void) +{ + TESTS_START(); + TESTS_RUN(tests_mtd_spi_nor_tests()); + TESTS_END(); + return 0; +} +/** @} */