From 1f1006bf2a36db4c097c6c5d13ddef832e56f0d2 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 4 Sep 2026 12:22:21 +0300 Subject: [PATCH 1/6] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BD=D0=BE=D1=81=D0=B8=D0=BC?= =?UTF-8?q?=D1=8B=D0=B9=20=D0=B4=D1=80=D0=B0=D0=B9=D0=B2=D0=B5=D1=80=20par?= =?UTF-8?q?allel=20NAND?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 26 +++ README.md | 88 ++++++++ docs/EMPTY_BOARD_PINOUT.md | 81 ++++++++ docs/MEMORY_GAS.md | 177 ++++++++++++++++ include/parallel_nand.h | 68 +++++++ include/parallel_nand_gas.h | 84 ++++++++ include/parallel_nand_pcan_gas.h | 11 + include/parallel_nand_port.h | 28 +++ ports/stm32f407ve/README.md | 26 +++ .../parallel_nand_port_stm32f407ve.c | 93 +++++++++ .../parallel_nand_port_stm32f407ve.h | 8 + ports/stm32g474ce/README.md | 21 ++ .../parallel_nand_port_stm32g474ce.c | 134 ++++++++++++ .../parallel_nand_port_stm32g474ce.h | 8 + ports/tms320f2812/README.md | 21 ++ .../parallel_nand_port_tms320f2812.c | 65 ++++++ .../parallel_nand_port_tms320f2812.h | 8 + src/parallel_nand.c | 191 ++++++++++++++++++ src/parallel_nand_gas.c | 151 ++++++++++++++ src/parallel_nand_pcan_gas.c | 43 ++++ tests/test_parallel_nand.c | 97 +++++++++ 21 files changed, 1429 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 docs/EMPTY_BOARD_PINOUT.md create mode 100644 docs/MEMORY_GAS.md create mode 100644 include/parallel_nand.h create mode 100644 include/parallel_nand_gas.h create mode 100644 include/parallel_nand_pcan_gas.h create mode 100644 include/parallel_nand_port.h create mode 100644 ports/stm32f407ve/README.md create mode 100644 ports/stm32f407ve/parallel_nand_port_stm32f407ve.c create mode 100644 ports/stm32f407ve/parallel_nand_port_stm32f407ve.h create mode 100644 ports/stm32g474ce/README.md create mode 100644 ports/stm32g474ce/parallel_nand_port_stm32g474ce.c create mode 100644 ports/stm32g474ce/parallel_nand_port_stm32g474ce.h create mode 100644 ports/tms320f2812/README.md create mode 100644 ports/tms320f2812/parallel_nand_port_tms320f2812.c create mode 100644 ports/tms320f2812/parallel_nand_port_tms320f2812.h create mode 100644 src/parallel_nand.c create mode 100644 src/parallel_nand_gas.c create mode 100644 src/parallel_nand_pcan_gas.c create mode 100644 tests/test_parallel_nand.c diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0960b91 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.16) +project(parallel_nand C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) + +add_library(parallel_nand + src/parallel_nand.c + src/parallel_nand_gas.c +) +target_include_directories(parallel_nand PUBLIC + include +) + +add_library(parallel_nand_pcan_gas src/parallel_nand_pcan_gas.c) +target_include_directories(parallel_nand_pcan_gas PUBLIC + include + ../set-protocol/include +) +target_link_libraries(parallel_nand_pcan_gas PUBLIC parallel_nand) + +add_executable(test_parallel_nand tests/test_parallel_nand.c) +target_link_libraries(test_parallel_nand PRIVATE parallel_nand) + +enable_testing() +add_test(NAME parallel_nand_core COMMAND test_parallel_nand) diff --git a/README.md b/README.md new file mode 100644 index 0000000..ebc4a27 --- /dev/null +++ b/README.md @@ -0,0 +1,88 @@ +# parallel-nand + +Переносимый C99-драйвер асинхронной parallel NAND x8 и окно управления через +общее адресное пространство GAS. Первый профиль — Micron `MT29F1G08ABADA`: +2048+64 байта, 64 страницы в блоке, 1024 блока. + +Подготовлены аппаратные порты: + +- `TMS320F2812` — XINTF Zone 6; +- `STM32F407VET6` — аппаратный FSMC NAND Bank 2; +- `STM32G474CEU6` — GPIO bit-bang для корпуса UFQFPN-48. + +Ядро не зависит от HAL и транспорта GUI. STM32 HAL используется только в +конкретных портах. `parallel_nand_gas` реализует переносимую 16-битную карту +GAS и компилируется в том числе TI C28x, где `char` имеет 16 бит. + +## Подключение + +В проект добавить: + +```text +c/parallel-nand/src/parallel_nand.c +c/parallel-nand/src/parallel_nand_gas.c +``` + +Пути заголовков: + +```text +c/parallel-nand/include +c/parallel-nand/ports/<нужный-порт> +``` + +И один аппаратный порт, например: + +```text +c/parallel-nand/ports/stm32g474ce/parallel_nand_port_stm32g474ce.c +``` + +Минимальная инициализация: + +```c +static parallel_nand_t nand; +static parallel_nand_gas_t nand_gas; + +void memory_init(void) +{ + int rc = parallel_nand_init(&nand, + parallel_nand_port_stm32g474ce(), + ¶llel_nand_mt29f1g08_geometry); + if (rc != PNAND_OK) { + /* Оставить диагностику доступной GUI и сообщить ошибку платы. */ + } + + parallel_nand_gas_init(&nand_gas, &nand, PNAND_GAS_DEFAULT_BASE); +} +``` + +Диспетчер общего адресного пространства вызывает +`parallel_nand_gas_read()`/`parallel_nand_gas_write()` для адресов +`0xE000..0xE51F`. Для STM32 с `c/set-protocol/pcan_gas` дополнительно включить +`src/parallel_nand_pcan_gas.c`, создать `pcan_gas_region_t` и вызвать +`parallel_nand_pcan_gas_region_init()`. Этот адаптер намеренно не нужен TMS. +Для полного дампа предпочтительны пакетные `READ_REGISTERS/WRITE_REGISTERS`, +а classic CAN GAS оставлен для совместимости и коротких диагностических чтений. + +Доступ GUI и алгоритм полного дампа описаны в +[docs/MEMORY_GAS.md](docs/MEMORY_GAS.md). Электрические соединения — в README +соответствующего порта и [docs/EMPTY_BOARD_PINOUT.md](docs/EMPTY_BOARD_PINOUT.md). + +## Текущие границы + +- Реализовано безопасное чтение, RESET, READ ID, STATUS и bad-block marker. +- PROGRAM/ERASE намеренно отсутствуют; ядро включает аппаратную защиту `WP#`. +- ECC пока не исправляет данные. GUI получает физические main/OOB и должен + помечать дамп как raw. +- В полном дампе bad-блоки не пропускаются: сохраняется физический порядок. +- Для другой NAND нужно передать другую `parallel_nand_geometry_t` и проверить + команды/маркер по её datasheet. + +## Проверка первого запуска + +1. Запустить `parallel_nand_init()`. +2. Через GAS проверить `0xE000 = 0x4E44`. +3. Записать `2` в `0xE004` (`READ_ID`). +4. Проверить `0xE00B = 0x002C`, `0xE00C = 0x00F1`. +5. Записать номер страницы в `0xE005/0xE006`, затем `3` в `0xE004`. +6. Дождаться `READY | PAGE_VALID` в `0xE002`. +7. Прочитать main из `0xE100..0xE4FF`, OOB из `0xE500..0xE51F`. diff --git a/docs/EMPTY_BOARD_PINOUT.md b/docs/EMPTY_BOARD_PINOUT.md new file mode 100644 index 0000000..5094b1f --- /dev/null +++ b/docs/EMPTY_BOARD_PINOUT.md @@ -0,0 +1,81 @@ +# Подключение parallel NAND на пустой плате + +Документ рассчитан на асинхронную NAND x8 `MT29F1G08ABADA` 3,3 В в корпусе +TSOP-48 (суффикс `WP`). На второй фотографии NAND — длинная микросхема справа +с маркировкой `29F1G08ABADA`. Микросхема Micron `D9MDK` сверху — DDR2 SDRAM, +не NAND. На первой фотографии `AM29LV800BT` — parallel NOR, а не NAND. + +Уточнение по ёмкости с фотографий: + +- `K6R4008V1D` — SRAM 4 Mbit (512 KiB), не Flash; +- `AM29LV800BT` — parallel NOR 8 Mbit (1 MiB); +- `MT29F1G08ABADA` — parallel NAND 1 Gbit, то есть 128 MiB main area. + +Поэтому текущий NAND-профиль рассчитан на 1-Gbit чип со второй платы. Для +другой плотности меняется `parallel_nand_geometry_t`; GAS и GUI-команды +остаются теми же. + +Не переносите номера выводов TSOP-48 на вариант `H4` VFBGA-63. Для BGA нужно +использовать ball map конкретного полного part number. + +## TSOP-48, x8 + +| NAND | Вывод | Назначение | +|---|---:|---| +| `R/B#` | 7 | open-drain Ready/Busy, подтяжка 4,7–10 кОм к 3,3 В | +| `RE#` | 8 | строб чтения от МК | +| `CE#` | 9 | выбор кристалла от МК | +| `VCC` | 12, 37 | питание 3,3 В | +| `VSS` | 13, 36 | земля | +| `CLE` | 16 | фиксация команды | +| `ALE` | 17 | фиксация адреса | +| `WE#` | 18 | строб команды/адреса от МК | +| `WP#` | 19 | для read-only 10 кОм на GND; для записи — GPIO и pull-down | +| `I/O0` | 29 | двунаправленная шина, бит 0 | +| `I/O1` | 30 | двунаправленная шина, бит 1 | +| `I/O2` | 31 | двунаправленная шина, бит 2 | +| `I/O3` | 32 | двунаправленная шина, бит 3 | +| `I/O4` | 41 | двунаправленная шина, бит 4 | +| `I/O5` | 42 | двунаправленная шина, бит 5 | +| `I/O6` | 43 | двунаправленная шина, бит 6 | +| `I/O7` | 44 | двунаправленная шина, бит 7 | +| `VCC1` | 34, 39 | соединить с 3,3 В для совместимости корпуса/ONFI | +| `VSS1` | 25, 48 | соединить с GND для совместимости корпуса/ONFI | +| `DNU` | 38, 47 | оставить неподключёнными | +| `NC` | остальные | не подключать | + +У каждого VCC/VCC1 поставить 100 нФ на ближайший VSS/VSS1; рядом с NAND — +общий 1–4,7 мкФ. Сигналы не должны превышать питание NAND. Для трасс длиннее +нескольких сантиметров предусмотреть последовательные резисторы 22–47 Ом у +источника на `WE#`, `RE#`, `CLE`, `ALE` и при необходимости на данных. + +## Соединение с МК + +| NAND | TMS320F2812 | STM32F407VET6 | STM32G474CEU6 | +|---|---|---|---| +| `I/O0..7` | `XD0..XD7` | `PD14,PD15,PD0,PD1,PE7..PE10` | `PB0..PB7` | +| `CLE` | `XA0` | `PD11/FSMC_A16` | `PA0` | +| `ALE` | `XA1` | `PD12/FSMC_A17` | `PA1` | +| `WE#` | `XWE` | `PD5/FSMC_NWE` | `PA2` | +| `RE#` | `XRD` | `PD4/FSMC_NOE` | `PA3` | +| `CE#` | `XZCS6AND7` | `PD7/FSMC_NE2` | `PA4` | +| `WP#` | GND через 10 кОм | GND через 10 кОм | `PA5` и pull-down 10 кОм | +| `R/B#` | `GPIOE0` | `PD6/FSMC_NWAIT` | `PA6` | + +Подробные номера выводов корпуса МК и ограничения находятся в README каждого +каталога `ports/`. Перед изготовлением платы нужно сверить выбранный корпус и +полный part number NAND с актуальным datasheet. + +## Первый запуск + +1. Не устанавливать NAND и проверить 3,3 В, отсутствие КЗ и уровни `CE#/WE#/RE#`. +2. Установить NAND, оставить `WP#` в нуле и выполнить RESET `FFh`. +3. Прочитать ID `90h`: ожидаемые первые байты для профиля — `2C F1`. +4. Считать одну страницу два раза и сравнить main/OOB побайтно. +5. Только после устойчивого чтения запускать полный дамп через GAS. + +## Источники для проверки footprint + +- [Micron: каталог SLC NAND](https://www.micron.com/products/storage/nand-flash/slc-nand/part-catalog) +- [MT29F1G08ABADA: signal assignments и геометрия](https://helpdesk.trx.pl/pliki/SERWER/Micron-MT29F1G08ABADAWP-IT%20D-datasheet.pdf) +- [Micron FBGA part decoder](https://in.micron.com/sales-support/design-tools/fbga-parts-decoder) diff --git a/docs/MEMORY_GAS.md b/docs/MEMORY_GAS.md new file mode 100644 index 0000000..b760002 --- /dev/null +++ b/docs/MEMORY_GAS.md @@ -0,0 +1,177 @@ +# Работа с parallel NAND через GAS + +## Архитектура + +Внешняя NAND значительно больше общего адресного пространства GAS. Например, +`MT29F1G08ABADA` содержит 128 МиБ main area, а GAS имеет только 65536 +16-битных регистров. Поэтому NAND представлена не линейным диапазоном, а +банковым окном одной физической страницы. + +```text +SETGUI + │ GUI protocol v1: READ_REGISTERS / WRITE_REGISTERS + ▼ +карта GAS 0x0000..0xFFFF + │ 0xE000: управление, 0xE100: main, 0xE500: OOB + ▼ +parallel_nand_gas + ▼ +общее ядро parallel_nand + ├─ TMS320F2812: XINTF + ├─ STM32F407VE: FSMC + └─ STM32G474CE: GPIO +``` + +GUI работает одинаково со всеми МК. Тип процессора влияет только на выбранный +файл из `ports/`. + +Встроенный проект должен направлять весь диапазон `0xE000..0xE51F` в +`parallel_nand_gas_read()` и `parallel_nand_gas_write()`. Их аргументы имеют +тип `unsigned int`: на C28x это нативное 16-битное слово, а на ARM функции +явно оставляют только младшие 16 бит. Так карта и wire-формат совпадают без +предположения, что C `char` всегда восьмибитный. + +Для STM32, использующего карту `pcan_gas`, готов адаптер: + +```c +#include "parallel_nand_pcan_gas.h" + +static pcan_gas_region_t nand_region; +parallel_nand_pcan_gas_region_init(&nand_region, &nand_gas); +``` + +Для TMS320F2812 переносимый обработчик вызывается прямо из существующего GAS +диспетчера: + +```c +if (address >= nand_gas.base && + address < nand_gas.base + PNAND_GAS_REGION_REGS) { + return parallel_nand_gas_read(&nand_gas, address, value); +} +``` + +Если тип результата диспетчера отличается, статусы `PNAND_GAS_OK`, +`PNAND_GAS_NO_REG`, `PNAND_GAS_READ_ONLY` и `PNAND_GAS_REJECTED` +преобразуются в его локальные статусы так же, как это сделано в +`parallel_nand_pcan_gas.c`. + +## Транспорт до GUI + +Карта GAS не привязана к физическому каналу. Рекомендуемый путь для полного +дампа — используемые SETGUI сообщения `WRITE_REGISTERS (0x0A)` и +`READ_REGISTERS (0x09)`: они позволяют передавать крупные блоки поверх USB, +UART, RS-485 или Ethernet. Адаптер `pcan_gas` нужен, когда теми же регистрами +обмениваются по classic CAN; там один кадр несёт только четыре регистра, и +полный 128-MiB дамп будет значительно медленнее. + +Устройство не должно собирать весь дамп в RAM. Буферизуется только одна +страница. Из-за переносимости на C28x каждый octet хранится в `unsigned short`, +поэтому статические буферы GAS занимают примерно 4,2 KiB RAM и на TMS, и на +ARM. + +## Карта GAS + +Базовый адрес по умолчанию — `0xE000`. Его можно изменить при вызове +`parallel_nand_gas_init()`; все смещения ниже останутся прежними. + +| Адрес | Доступ | Название | Значение | +|---:|:---:|---|---| +| `E000` | R | `SIGNATURE` | `0x4E44` (`ND`) | +| `E001` | R | `VERSION` | версия окна, сейчас 1 | +| `E002` | R | `STATUS` | состояние операции | +| `E003` | R | `ERROR` | код `PNAND_*`, 0 = нет ошибки | +| `E004` | W | `COMMAND` | команда окна | +| `E005` | R/W | `PAGE_LO` | младшие 16 бит физической страницы | +| `E006` | R/W | `PAGE_HI` | старшие 16 бит физической страницы | +| `E007` | R | `PAGE_MAIN` | main bytes, для MT29 = 2048 | +| `E008` | R | `PAGE_OOB` | OOB bytes, для MT29 = 64 | +| `E009` | R | `PAGES_PER_BLOCK` | для MT29 = 64 | +| `E00A` | R | `BLOCKS` | для MT29 = 1024 | +| `E00B` | R | `MANUFACTURER_ID` | ожидается `0x2C` | +| `E00C` | R | `DEVICE_ID` | ожидается `0xF1` | +| `E00D` | R | `BAD_BLOCK` | 1 для физически плохого блока | +| `E00E` | R | `GENERATION_LO` | счётчик загруженных страниц, low | +| `E00F` | R | `GENERATION_HI` | счётчик загруженных страниц, high | +| `E100..E4FF` | R | `PAGE_DATA` | 1024 регистра main, два байта LE | +| `E500..E51F` | R | `PAGE_OOB` | 32 регистра OOB, два байта LE | + +Между управляющими регистрами и окнами данных есть зарезервированные адреса. +Чтение блока через них не выполняется: каждый запрос должен начинаться внутри +существующего участка. + +## STATUS + +| Бит | Маска | Смысл | +|---:|---:|---| +| 0 | `0x0001` | `READY` | +| 1 | `0x0002` | `BUSY` | +| 2 | `0x0004` | `PAGE_VALID` | +| 3 | `0x0008` | `BAD_BLOCK` | +| 15 | `0x8000` | `ERROR`, подробность в `E003` | + +GUI читает окно данных только при `READY=1`, `PAGE_VALID=1`, `ERROR=0`. + +## COMMAND + +| Значение | Команда | Результат | +|---:|---|---| +| 1 | `RESET` | сброс NAND и ожидание Ready | +| 2 | `READ_ID` | обновляет `E00B/E00C` | +| 3 | `READ_PAGE` | загружает main/OOB выбранной страницы | + +Запись номера страницы и запуск — две отдельные операции. Сначала GUI пишет +`PAGE_LO/PAGE_HI`, затем записывает `READ_PAGE` в `COMMAND`. Нельзя отправлять +один блок, начинающийся с `COMMAND`: команда выполнилась бы до обновления номера +страницы. + +## Последовательность полного дампа + +1. Прочитать `E000..E00F`, проверить подпись, версию и геометрию. +2. Выполнить `READ_ID`; для микросхемы с фотографии проверить `2C F1`. +3. Рассчитать `page_count = PAGES_PER_BLOCK * BLOCKS`. +4. Для каждой физической страницы от 0 до `page_count - 1`: + - записать номер страницы в `E005/E006`; + - записать `3` в `E004`; + - опрашивать `E002`, пока не установлен `READY`; + - проверить `ERROR`, `BAD_BLOCK` и изменение `GENERATION`; + - прочитать `E100..E4FF`; + - прочитать `E500..E51F`; + - сразу дописать данные и метаданные в файлы. +5. Не пропускать bad-блоки: иначе файл потеряет соответствие физическим + страницам микросхемы. + +GUI protocol v1 допускает payload до 512 байт. Ответ `READ_REGISTERS` содержит +четырёхбайтовый заголовок диапазона, поэтому за один запрос следует читать не +более 254 GAS-регистров. Страница main читается пятью запросами: четыре раза по +254 регистра и последний раз 8 регистров. OOB читается одним запросом на 32 +регистра. + +## Возобновление + +GUI хранит номер последней полностью записанной страницы. После разрыва связи +он повторно проверяет подпись/ID/геометрию и продолжает со следующей страницы. +Страница считается готовой только после записи main, OOB и записи её статуса в +метаданные. + +Рекомендуемые выходные файлы: + +- `dump.bin` — 2048 байт main каждой физической страницы; +- `dump.oob` — 64 байта OOB каждой физической страницы; +- `dump.json` — ID, геометрия, bad-блоки, ошибки и последняя готовая страница. + +Для `MT29F1G08ABADA` размеры завершённого дампа: `dump.bin` = 134217728 байт, +`dump.oob` = 4194304 байта. + +## ECC + +Текущая версия шаблона отдаёт raw main/OOB и не заявляет исправление ошибок. +Статус bad-блока определяется по первому байту OOB первых двух страниц блока. +Перед использованием дампа как рабочего образа нужно добавить ECC-политику для +точного чипа и формата OOB. Само окно GAS при этом менять не требуется: можно +добавить новые флаги состояния в свободные биты `STATUS`. + +## PROGRAM/ERASE + +Запись и стирание не включены намеренно. Они требуют отдельного разрешения, +проверки ECC, защиты загрузочных блоков и двухфазного подтверждения через GAS. +До добавления этих функций `WP#` удерживается активным аппаратным портом. diff --git a/include/parallel_nand.h b/include/parallel_nand.h new file mode 100644 index 0000000..8773ede --- /dev/null +++ b/include/parallel_nand.h @@ -0,0 +1,68 @@ +#ifndef PARALLEL_NAND_H +#define PARALLEL_NAND_H + +#include "parallel_nand_port.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PNAND_MT29F1G08_PAGE_MAIN 2048UL +#define PNAND_MT29F1G08_PAGE_OOB 64UL +#define PNAND_MT29F1G08_PAGES_PER_BLOCK 64UL +#define PNAND_MT29F1G08_BLOCKS 1024UL +#define PNAND_MT29F1G08_COLUMN_CYCLES 2U +#define PNAND_MT29F1G08_ROW_CYCLES 2U +#define PNAND_MT29F1G08_PAGE_TOTAL \ + (PNAND_MT29F1G08_PAGE_MAIN + PNAND_MT29F1G08_PAGE_OOB) + +typedef enum { + PNAND_OK = 0, + PNAND_ERROR_ARGUMENT = -1, + PNAND_ERROR_PORT = -2, + PNAND_ERROR_TIMEOUT = -3, + PNAND_ERROR_RANGE = -4, + PNAND_ERROR_BUFFER = -5 +} parallel_nand_result_t; + +typedef struct { + unsigned long page_main_size; + unsigned long page_oob_size; + unsigned long pages_per_block; + unsigned long block_count; + unsigned int column_cycles; + unsigned int row_cycles; + unsigned long ready_timeout_us; +} parallel_nand_geometry_t; + +typedef struct { + const parallel_nand_port_t *port; + parallel_nand_geometry_t geometry; +} parallel_nand_t; + +extern const parallel_nand_geometry_t parallel_nand_mt29f1g08_geometry; + +int parallel_nand_init(parallel_nand_t *nand, + const parallel_nand_port_t *port, + const parallel_nand_geometry_t *geometry); +int parallel_nand_reset(parallel_nand_t *nand); +int parallel_nand_read_status(parallel_nand_t *nand, pnand_octet_t *status); +int parallel_nand_read_id(parallel_nand_t *nand, pnand_octet_t address, + pnand_octet_t *id, unsigned int count); +int parallel_nand_read_page(parallel_nand_t *nand, unsigned long page, + pnand_octet_t *main_data, + unsigned long main_capacity, + pnand_octet_t *oob, + unsigned long oob_capacity); +int parallel_nand_read_page_slice(parallel_nand_t *nand, unsigned long page, + unsigned long column, pnand_octet_t *out, + unsigned long count); +int parallel_nand_is_bad_block(parallel_nand_t *nand, unsigned long block, + int *is_bad); +unsigned long parallel_nand_page_count(const parallel_nand_t *nand); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/parallel_nand_gas.h b/include/parallel_nand_gas.h new file mode 100644 index 0000000..50238cc --- /dev/null +++ b/include/parallel_nand_gas.h @@ -0,0 +1,84 @@ +#ifndef PARALLEL_NAND_GAS_H +#define PARALLEL_NAND_GAS_H + +#include "parallel_nand.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PNAND_GAS_DEFAULT_BASE 0xE000U +#define PNAND_GAS_REGION_REGS 0x0520U +#define PNAND_GAS_DATA_OFFSET 0x0100U +#define PNAND_GAS_DATA_REGS 1024U +#define PNAND_GAS_OOB_OFFSET 0x0500U +#define PNAND_GAS_OOB_REGS 32U + +enum { + PNAND_GAS_REG_SIGNATURE = 0x0000, + PNAND_GAS_REG_VERSION = 0x0001, + PNAND_GAS_REG_STATUS = 0x0002, + PNAND_GAS_REG_ERROR = 0x0003, + PNAND_GAS_REG_COMMAND = 0x0004, + PNAND_GAS_REG_PAGE_LO = 0x0005, + PNAND_GAS_REG_PAGE_HI = 0x0006, + PNAND_GAS_REG_PAGE_MAIN = 0x0007, + PNAND_GAS_REG_PAGE_OOB = 0x0008, + PNAND_GAS_REG_PAGES_PER_BLOCK = 0x0009, + PNAND_GAS_REG_BLOCKS = 0x000A, + PNAND_GAS_REG_MANUFACTURER_ID = 0x000B, + PNAND_GAS_REG_DEVICE_ID = 0x000C, + PNAND_GAS_REG_BAD_BLOCK = 0x000D, + PNAND_GAS_REG_GENERATION_LO = 0x000E, + PNAND_GAS_REG_GENERATION_HI = 0x000F +}; + +enum { + PNAND_GAS_CMD_NONE = 0, + PNAND_GAS_CMD_RESET = 1, + PNAND_GAS_CMD_READ_ID = 2, + PNAND_GAS_CMD_READ_PAGE = 3 +}; + +#define PNAND_GAS_STATUS_READY 0x0001U +#define PNAND_GAS_STATUS_BUSY 0x0002U +#define PNAND_GAS_STATUS_PAGE_VALID 0x0004U +#define PNAND_GAS_STATUS_BAD_BLOCK 0x0008U +#define PNAND_GAS_STATUS_ERROR 0x8000U + +typedef struct { + parallel_nand_t *nand; + unsigned int base; + unsigned int status; + int error; + unsigned long selected_page; + unsigned long generation; + unsigned long cached_block; + int cached_bad_block; + int block_cache_valid; + pnand_octet_t id[8]; + pnand_octet_t main_data[PNAND_MT29F1G08_PAGE_MAIN]; + pnand_octet_t oob[PNAND_MT29F1G08_PAGE_OOB]; +} parallel_nand_gas_t; + +void parallel_nand_gas_init(parallel_nand_gas_t *gas, + parallel_nand_t *nand, unsigned int base); + +/* Portable GAS callbacks. Values and addresses are exactly 16 bits on wire. */ +typedef enum { + PNAND_GAS_OK = 0, + PNAND_GAS_NO_REG, + PNAND_GAS_READ_ONLY, + PNAND_GAS_REJECTED +} parallel_nand_gas_result_t; + +parallel_nand_gas_result_t parallel_nand_gas_read( + parallel_nand_gas_t *gas, unsigned int address, unsigned int *value); +parallel_nand_gas_result_t parallel_nand_gas_write( + parallel_nand_gas_t *gas, unsigned int address, unsigned int value); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/parallel_nand_pcan_gas.h b/include/parallel_nand_pcan_gas.h new file mode 100644 index 0000000..939cd71 --- /dev/null +++ b/include/parallel_nand_pcan_gas.h @@ -0,0 +1,11 @@ +#ifndef PARALLEL_NAND_PCAN_GAS_H +#define PARALLEL_NAND_PCAN_GAS_H + +#include "parallel_nand_gas.h" +#include "pcan_gas.h" + +/* Adapter for targets where c/set-protocol pcan_gas is available. */ +void parallel_nand_pcan_gas_region_init(pcan_gas_region_t *region, + parallel_nand_gas_t *gas); + +#endif diff --git a/include/parallel_nand_port.h b/include/parallel_nand_port.h new file mode 100644 index 0000000..a9e73c1 --- /dev/null +++ b/include/parallel_nand_port.h @@ -0,0 +1,28 @@ +#ifndef PARALLEL_NAND_PORT_H +#define PARALLEL_NAND_PORT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Один octet хранится в целом типе: это важно для C28x с 16-битным char. */ +typedef unsigned short pnand_octet_t; + +typedef struct { + int (*init)(void); + void (*select)(void); + void (*deselect)(void); + void (*write_command)(pnand_octet_t value); + void (*write_address)(pnand_octet_t value); + void (*write_data)(pnand_octet_t value); + pnand_octet_t (*read_data)(void); + int (*is_ready)(void); + void (*set_write_protect)(int enabled); + void (*delay_us)(unsigned long us); +} parallel_nand_port_t; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ports/stm32f407ve/README.md b/ports/stm32f407ve/README.md new file mode 100644 index 0000000..0decb57 --- /dev/null +++ b/ports/stm32f407ve/README.md @@ -0,0 +1,26 @@ +# Порт STM32F407VET6 + +Используется аппаратный FSMC NAND Bank 2, шина x8. + +| NAND | STM32F407VE | LQFP-100 | +|---|---|---:| +| `I/O0` | `PD14/FSMC_D0` | 61 | +| `I/O1` | `PD15/FSMC_D1` | 62 | +| `I/O2` | `PD0/FSMC_D2` | 81 | +| `I/O3` | `PD1/FSMC_D3` | 82 | +| `I/O4` | `PE7/FSMC_D4` | 38 | +| `I/O5` | `PE8/FSMC_D5` | 39 | +| `I/O6` | `PE9/FSMC_D6` | 40 | +| `I/O7` | `PE10/FSMC_D7` | 41 | +| `RE#` | `PD4/FSMC_NOE` | 85 | +| `WE#` | `PD5/FSMC_NWE` | 86 | +| `R/B#` | `PD6/FSMC_NWAIT` | 87 | +| `CE#` | `PD7/FSMC_NE2` | 88 | +| `CLE` | `PD11/FSMC_A16` | 58 | +| `ALE` | `PD12/FSMC_A17` | 59 | + +Порт использует STM32Cube HAL. В `stm32f4xx_hal_conf.h` включить +`HAL_NAND_MODULE_ENABLED`, добавить `stm32f4xx_hal_nand.c` и +`stm32f4xx_ll_fsmc.c`, включить FSMC и не создавать второй +`NAND_HandleTypeDef` для Bank 2. Начальные тайминги +консервативны; финальные значения подтвердить осциллографом. diff --git a/ports/stm32f407ve/parallel_nand_port_stm32f407ve.c b/ports/stm32f407ve/parallel_nand_port_stm32f407ve.c new file mode 100644 index 0000000..50290c2 --- /dev/null +++ b/ports/stm32f407ve/parallel_nand_port_stm32f407ve.c @@ -0,0 +1,93 @@ +#include "parallel_nand_port_stm32f407ve.h" +#include "stm32f4xx_hal.h" +#include "stm32f4xx_hal_nand.h" + +#define PNAND_F407_BANK2_BASE 0x70000000UL +#define PNAND_F407_CMD_AREA (1UL << 16) +#define PNAND_F407_ADDR_AREA (1UL << 17) + +static NAND_HandleTypeDef hnand; + +static int f407_init(void) +{ + GPIO_InitTypeDef io; + FSMC_NAND_PCC_TimingTypeDef common; + FSMC_NAND_PCC_TimingTypeDef attribute; + + __HAL_RCC_GPIOD_CLK_ENABLE(); + __HAL_RCC_GPIOE_CLK_ENABLE(); + __HAL_RCC_FSMC_CLK_ENABLE(); + + io.Mode = GPIO_MODE_AF_PP; + io.Pull = GPIO_NOPULL; + io.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + io.Alternate = GPIO_AF12_FSMC; + io.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5 | + GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_11 | GPIO_PIN_12 | + GPIO_PIN_14 | GPIO_PIN_15; + HAL_GPIO_Init(GPIOD, &io); + io.Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10; + HAL_GPIO_Init(GPIOE, &io); + + hnand.Instance = FSMC_NAND_DEVICE; + hnand.Init.NandBank = FSMC_NAND_BANK2; + hnand.Init.Waitfeature = FSMC_NAND_PCC_WAIT_FEATURE_DISABLE; + hnand.Init.MemoryDataWidth = FSMC_NAND_PCC_MEM_BUS_WIDTH_8; + hnand.Init.EccComputation = FSMC_NAND_ECC_DISABLE; + hnand.Init.ECCPageSize = FSMC_NAND_ECC_PAGE_SIZE_2048BYTE; + hnand.Init.TCLRSetupTime = 2U; + hnand.Init.TARSetupTime = 2U; + + common.SetupTime = 2U; + common.WaitSetupTime = 5U; + common.HoldSetupTime = 3U; + common.HiZSetupTime = 2U; + attribute = common; + return HAL_NAND_Init(&hnand, &common, &attribute) == HAL_OK ? 0 : -1; +} + +static void f407_select(void) { /* FSMC_NCE2 активируется доступом. */ } +static void f407_deselect(void) { } +static void f407_write_command(pnand_octet_t value) +{ + *(__IO uint8_t *)(PNAND_F407_BANK2_BASE | PNAND_F407_CMD_AREA) = + (uint8_t)value; +} +static void f407_write_address(pnand_octet_t value) +{ + *(__IO uint8_t *)(PNAND_F407_BANK2_BASE | PNAND_F407_ADDR_AREA) = + (uint8_t)value; +} +static void f407_write_data(pnand_octet_t value) +{ + *(__IO uint8_t *)PNAND_F407_BANK2_BASE = (uint8_t)value; +} +static pnand_octet_t f407_read_data(void) +{ + return *(__IO uint8_t *)PNAND_F407_BANK2_BASE; +} +static int f407_is_ready(void) +{ + return HAL_GPIO_ReadPin(GPIOD, GPIO_PIN_6) == GPIO_PIN_SET; +} +static void f407_set_wp(int enabled) +{ + /* WP# аппаратный в базовой схеме. */ + (void)enabled; +} +static void f407_delay_us(unsigned long us) +{ + uint32_t cycles = (SystemCoreClock / 3000000UL) * (uint32_t)us; + while (cycles-- != 0U) __NOP(); +} + +static const parallel_nand_port_t f407_port = { + f407_init, f407_select, f407_deselect, + f407_write_command, f407_write_address, f407_write_data, + f407_read_data, f407_is_ready, f407_set_wp, f407_delay_us +}; + +const parallel_nand_port_t *parallel_nand_port_stm32f407ve(void) +{ + return &f407_port; +} diff --git a/ports/stm32f407ve/parallel_nand_port_stm32f407ve.h b/ports/stm32f407ve/parallel_nand_port_stm32f407ve.h new file mode 100644 index 0000000..36c1e8d --- /dev/null +++ b/ports/stm32f407ve/parallel_nand_port_stm32f407ve.h @@ -0,0 +1,8 @@ +#ifndef PARALLEL_NAND_PORT_STM32F407VE_H +#define PARALLEL_NAND_PORT_STM32F407VE_H + +#include "parallel_nand_port.h" + +const parallel_nand_port_t *parallel_nand_port_stm32f407ve(void); + +#endif diff --git a/ports/stm32g474ce/README.md b/ports/stm32g474ce/README.md new file mode 100644 index 0000000..24e07e6 --- /dev/null +++ b/ports/stm32g474ce/README.md @@ -0,0 +1,21 @@ +# Порт STM32G474CEU6 + +У UFQFPN-48 нет выведенного FSMC, поэтому используется GPIO bit-bang. + +| NAND | STM32G474CEU6 | UFQFPN-48 | +|---|---|---:| +| `I/O0..I/O7` | `PB0..PB7` | 17, 18, 19, 41, 42, 43, 44, 45 | +| `CLE` | `PA0` | 8 | +| `ALE` | `PA1` | 9 | +| `WE#` | `PA2` | 10 | +| `RE#` | `PA3` | 11 | +| `CE#` | `PA4` | 12 | +| `WP#` | `PA5` | 13 | +| `R/B#` | `PA6` | 14 | + +`R/B#` требует внешней подтяжки 4,7–10 кОм к 3,3 В. Порт использует STM32Cube +HAL только для инициализации, а циклы NAND выполняет прямым доступом к GPIO. + +Выбранные `PB3/PB4` нельзя одновременно использовать для trace/JTAG; SWD на +`PA13/PA14` остаётся доступен. Если эти пины нужны проекту, измените таблицу и +операции шины: текущая оптимизация рассчитана на непрерывные `PB0..PB7`. diff --git a/ports/stm32g474ce/parallel_nand_port_stm32g474ce.c b/ports/stm32g474ce/parallel_nand_port_stm32g474ce.c new file mode 100644 index 0000000..3f18b64 --- /dev/null +++ b/ports/stm32g474ce/parallel_nand_port_stm32g474ce.c @@ -0,0 +1,134 @@ +#include "parallel_nand_port_stm32g474ce.h" +#include "stm32g4xx_hal.h" + +/* Пустая плата STM32G474CEU6: PB0..PB7 образуют непрерывную x8 шину. */ +#define DATA_MASK 0x00FFU +#define CLE_PIN GPIO_PIN_0 +#define ALE_PIN GPIO_PIN_1 +#define WE_PIN GPIO_PIN_2 +#define RE_PIN GPIO_PIN_3 +#define CE_PIN GPIO_PIN_4 +#define WP_PIN GPIO_PIN_5 +#define RB_PIN GPIO_PIN_6 + +static int data_is_output; + +static void data_input(void) +{ + if (data_is_output) { + GPIOB->MODER &= ~0x0000FFFFUL; + data_is_output = 0; + } +} + +static void data_output(void) +{ + if (!data_is_output) { + GPIOB->MODER = (GPIOB->MODER & ~0x0000FFFFUL) | 0x00005555UL; + data_is_output = 1; + } +} + +static void short_delay(void) +{ + __NOP(); __NOP(); __NOP(); __NOP(); + __NOP(); __NOP(); __NOP(); __NOP(); +} + +static int g474_init(void) +{ + GPIO_InitTypeDef io; + __HAL_RCC_GPIOA_CLK_ENABLE(); + __HAL_RCC_GPIOB_CLK_ENABLE(); + + io.Pin = DATA_MASK; + io.Mode = GPIO_MODE_OUTPUT_PP; + io.Pull = GPIO_NOPULL; + io.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + HAL_GPIO_Init(GPIOB, &io); + data_is_output = 1; + data_input(); + + /* Set inactive levels before enabling the pins as outputs. */ + GPIOA->BSRR = WE_PIN | RE_PIN | CE_PIN | + ((uint32_t)(CLE_PIN | ALE_PIN | WP_PIN) << 16); + io.Pin = CLE_PIN | ALE_PIN | WE_PIN | RE_PIN | CE_PIN | WP_PIN; + io.Mode = GPIO_MODE_OUTPUT_PP; + io.Pull = GPIO_NOPULL; + io.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + HAL_GPIO_Init(GPIOA, &io); + io.Pin = RB_PIN; + io.Mode = GPIO_MODE_INPUT; + io.Pull = GPIO_NOPULL; + HAL_GPIO_Init(GPIOA, &io); + return 0; +} + +static void g474_select(void) { GPIOA->BSRR = (uint32_t)CE_PIN << 16; } +static void g474_deselect(void) { GPIOA->BSRR = CE_PIN; } + +static void write_cycle(pnand_octet_t value) +{ + data_output(); + GPIOB->BSRR = (uint32_t)DATA_MASK << 16; + GPIOB->BSRR = (uint32_t)value & DATA_MASK; + short_delay(); + GPIOA->BSRR = (uint32_t)WE_PIN << 16; + short_delay(); + GPIOA->BSRR = WE_PIN; + short_delay(); +} + +static void g474_write_command(pnand_octet_t value) +{ + GPIOA->BSRR = CLE_PIN | ((uint32_t)ALE_PIN << 16); + write_cycle(value); + GPIOA->BSRR = (uint32_t)CLE_PIN << 16; +} + +static void g474_write_address(pnand_octet_t value) +{ + GPIOA->BSRR = ALE_PIN | ((uint32_t)CLE_PIN << 16); + write_cycle(value); + GPIOA->BSRR = (uint32_t)ALE_PIN << 16; +} + +static void g474_write_data(pnand_octet_t value) +{ + GPIOA->BSRR = ((uint32_t)(CLE_PIN | ALE_PIN) << 16); + write_cycle(value); +} + +static pnand_octet_t g474_read_data(void) +{ + pnand_octet_t value; + data_input(); + GPIOA->BSRR = ((uint32_t)(CLE_PIN | ALE_PIN | RE_PIN) << 16); + short_delay(); + value = (pnand_octet_t)(GPIOB->IDR & DATA_MASK); + GPIOA->BSRR = RE_PIN; + short_delay(); + return value; +} + +static int g474_is_ready(void) { return (GPIOA->IDR & RB_PIN) != 0U; } +static void g474_set_wp(int enabled) +{ + GPIOA->BSRR = enabled ? ((uint32_t)WP_PIN << 16) : WP_PIN; +} +static void g474_delay_us(unsigned long us) +{ + uint32_t cycles = (SystemCoreClock / 3000000UL) * (uint32_t)us; + while (cycles-- != 0U) __NOP(); +} + +static const parallel_nand_port_t g474_port = { + g474_init, g474_select, g474_deselect, + g474_write_command, g474_write_address, g474_write_data, + g474_read_data, g474_is_ready, g474_set_wp, g474_delay_us +}; + +const parallel_nand_port_t *parallel_nand_port_stm32g474ce(void) +{ + return &g474_port; +} diff --git a/ports/stm32g474ce/parallel_nand_port_stm32g474ce.h b/ports/stm32g474ce/parallel_nand_port_stm32g474ce.h new file mode 100644 index 0000000..409e52d --- /dev/null +++ b/ports/stm32g474ce/parallel_nand_port_stm32g474ce.h @@ -0,0 +1,8 @@ +#ifndef PARALLEL_NAND_PORT_STM32G474CE_H +#define PARALLEL_NAND_PORT_STM32G474CE_H + +#include "parallel_nand_port.h" + +const parallel_nand_port_t *parallel_nand_port_stm32g474ce(void); + +#endif diff --git a/ports/tms320f2812/README.md b/ports/tms320f2812/README.md new file mode 100644 index 0000000..7a33632 --- /dev/null +++ b/ports/tms320f2812/README.md @@ -0,0 +1,21 @@ +# Порт TMS320F2812 + +Порт использует XINTF Zone 6: + +- `XD0..XD7` — NAND `I/O0..I/O7`; +- `XA0` — `CLE`; +- `XA1` — `ALE`; +- `XWE` — `WE#`; +- `XRD` — `RE#`; +- `XZCS6AND7` — `CE#`; +- `GPIOE0/XINT1` — `R/B#` с внешней подтяжкой 4,7–10 кОм. + +Адреса C28x задаются в 16-битных словах: data `0x100000`, command +`0x100001`, address `0x100002`. Используется только младший байт XINTF. + +`WP#` в базовом варианте аппаратный: для режима только чтения соединить с GND +через 10 кОм; если позже понадобится PROGRAM/ERASE, заменить на GPIO и реализовать +`tms_set_wp()`. + +Задержка в порте приблизительная. После выбора частоты CPU проверить импульсы +осциллографом и при необходимости изменить `PNAND_TMS_CPU_MHZ`. diff --git a/ports/tms320f2812/parallel_nand_port_tms320f2812.c b/ports/tms320f2812/parallel_nand_port_tms320f2812.c new file mode 100644 index 0000000..b1dda6d --- /dev/null +++ b/ports/tms320f2812/parallel_nand_port_tms320f2812.c @@ -0,0 +1,65 @@ +#include "parallel_nand_port_tms320f2812.h" +#include "DSP281x_Device.h" + +/* C28x addresses are 16-bit word addresses. XA0/XA1 drive CLE/ALE. */ +#define PNAND_TMS_DATA_REG (*(volatile unsigned int *)0x100000UL) +#define PNAND_TMS_CMD_REG (*(volatile unsigned int *)0x100001UL) +#define PNAND_TMS_ADDR_REG (*(volatile unsigned int *)0x100002UL) + +#ifndef PNAND_TMS_CPU_MHZ +#define PNAND_TMS_CPU_MHZ 150UL +#endif + +static void tms_delay_us(unsigned long us) +{ + volatile unsigned long loops = us * (PNAND_TMS_CPU_MHZ / 5UL); + while (loops-- != 0UL) asm(" NOP"); +} + +static int tms_init(void) +{ + EALLOW; + GpioMuxRegs.GPEMUX.bit.XINT1_XBIO_GPIOE0 = 0; + GpioMuxRegs.GPEDIR.bit.GPIOE0 = 0; + + XintfRegs.XINTCNF2.bit.XTIMCLK = 1; + XintfRegs.XINTCNF2.bit.WRBUFF = 0; + XintfRegs.XINTCNF2.bit.CLKOFF = 0; + XintfRegs.XINTCNF2.bit.CLKMODE = 1; + XintfRegs.XTIMING6.bit.XWRLEAD = 3; + XintfRegs.XTIMING6.bit.XWRACTIVE = 7; + XintfRegs.XTIMING6.bit.XWRTRAIL = 3; + XintfRegs.XTIMING6.bit.XRDLEAD = 3; + XintfRegs.XTIMING6.bit.XRDACTIVE = 7; + XintfRegs.XTIMING6.bit.XRDTRAIL = 3; + XintfRegs.XTIMING6.bit.USEREADY = 0; + XintfRegs.XTIMING6.bit.READYMODE = 0; + XintfRegs.XTIMING6.bit.XSIZE = 3; + EDIS; + asm(" RPT #7 || NOP"); + return 0; +} + +static void tms_select(void) { /* XZCS6AND7 формируется каждым доступом. */ } +static void tms_deselect(void) { } +static void tms_write_command(pnand_octet_t v) { PNAND_TMS_CMD_REG = v & 0xFFU; } +static void tms_write_address(pnand_octet_t v) { PNAND_TMS_ADDR_REG = v & 0xFFU; } +static void tms_write_data(pnand_octet_t v) { PNAND_TMS_DATA_REG = v & 0xFFU; } +static pnand_octet_t tms_read_data(void) { return PNAND_TMS_DATA_REG & 0xFFU; } +static int tms_is_ready(void) { return GpioDataRegs.GPEDAT.bit.GPIOE0 != 0; } +static void tms_set_wp(int enabled) +{ + /* Шаблон платы фиксирует WP# аппаратно. См. README порта. */ + (void)enabled; +} + +static const parallel_nand_port_t tms_port = { + tms_init, tms_select, tms_deselect, + tms_write_command, tms_write_address, tms_write_data, + tms_read_data, tms_is_ready, tms_set_wp, tms_delay_us +}; + +const parallel_nand_port_t *parallel_nand_port_tms320f2812(void) +{ + return &tms_port; +} diff --git a/ports/tms320f2812/parallel_nand_port_tms320f2812.h b/ports/tms320f2812/parallel_nand_port_tms320f2812.h new file mode 100644 index 0000000..ecddb38 --- /dev/null +++ b/ports/tms320f2812/parallel_nand_port_tms320f2812.h @@ -0,0 +1,8 @@ +#ifndef PARALLEL_NAND_PORT_TMS320F2812_H +#define PARALLEL_NAND_PORT_TMS320F2812_H + +#include "parallel_nand_port.h" + +const parallel_nand_port_t *parallel_nand_port_tms320f2812(void); + +#endif diff --git a/src/parallel_nand.c b/src/parallel_nand.c new file mode 100644 index 0000000..62158c4 --- /dev/null +++ b/src/parallel_nand.c @@ -0,0 +1,191 @@ +#include "parallel_nand.h" + +#define PNAND_CMD_READ_1 0x00U +#define PNAND_CMD_READ_2 0x30U +#define PNAND_CMD_READ_ID 0x90U +#define PNAND_CMD_READ_STATUS 0x70U +#define PNAND_CMD_RESET 0xFFU + +const parallel_nand_geometry_t parallel_nand_mt29f1g08_geometry = { + PNAND_MT29F1G08_PAGE_MAIN, + PNAND_MT29F1G08_PAGE_OOB, + PNAND_MT29F1G08_PAGES_PER_BLOCK, + PNAND_MT29F1G08_BLOCKS, + PNAND_MT29F1G08_COLUMN_CYCLES, + PNAND_MT29F1G08_ROW_CYCLES, + 1000000UL +}; + +static int port_valid(const parallel_nand_port_t *port) +{ + return port != 0 && port->init != 0 && port->select != 0 && + port->deselect != 0 && port->write_command != 0 && + port->write_address != 0 && port->read_data != 0 && + port->is_ready != 0 && port->set_write_protect != 0 && + port->delay_us != 0; +} + +static int geometry_valid(const parallel_nand_geometry_t *g) +{ + return g != 0 && g->page_main_size != 0UL && + g->pages_per_block != 0UL && g->block_count != 0UL && + g->column_cycles != 0U && g->row_cycles != 0U && + g->ready_timeout_us != 0UL; +} + +unsigned long parallel_nand_page_count(const parallel_nand_t *nand) +{ + if (nand == 0) return 0UL; + return nand->geometry.pages_per_block * nand->geometry.block_count; +} + +static int wait_ready(parallel_nand_t *nand) +{ + unsigned long elapsed = 0UL; + while (!nand->port->is_ready()) { + if (elapsed >= nand->geometry.ready_timeout_us) + return PNAND_ERROR_TIMEOUT; + nand->port->delay_us(1UL); + elapsed++; + } + return PNAND_OK; +} + +int parallel_nand_reset(parallel_nand_t *nand) +{ + if (nand == 0 || !port_valid(nand->port)) return PNAND_ERROR_ARGUMENT; + nand->port->select(); + nand->port->write_command(PNAND_CMD_RESET); + nand->port->deselect(); + return wait_ready(nand); +} + +int parallel_nand_init(parallel_nand_t *nand, + const parallel_nand_port_t *port, + const parallel_nand_geometry_t *geometry) +{ + if (nand == 0 || !port_valid(port) || !geometry_valid(geometry)) + return PNAND_ERROR_ARGUMENT; + nand->port = port; + nand->geometry = *geometry; + if (port->init() != 0) return PNAND_ERROR_PORT; + port->deselect(); + /* Ядро пока только читает: аппаратно запрещаем PROGRAM/ERASE. */ + port->set_write_protect(1); + return parallel_nand_reset(nand); +} + +int parallel_nand_read_status(parallel_nand_t *nand, pnand_octet_t *status) +{ + if (nand == 0 || status == 0) return PNAND_ERROR_ARGUMENT; + nand->port->select(); + nand->port->write_command(PNAND_CMD_READ_STATUS); + *status = nand->port->read_data() & 0xFFU; + nand->port->deselect(); + return PNAND_OK; +} + +int parallel_nand_read_id(parallel_nand_t *nand, pnand_octet_t address, + pnand_octet_t *id, unsigned int count) +{ + unsigned int i; + if (nand == 0 || id == 0 || count == 0U) return PNAND_ERROR_ARGUMENT; + nand->port->select(); + nand->port->write_command(PNAND_CMD_READ_ID); + nand->port->write_address(address & 0xFFU); + for (i = 0U; i < count; i++) id[i] = nand->port->read_data() & 0xFFU; + nand->port->deselect(); + return PNAND_OK; +} + +static int begin_page_read(parallel_nand_t *nand, unsigned long page, + unsigned long column) +{ + unsigned int i; + unsigned long value; + unsigned long total = nand->geometry.page_main_size + + nand->geometry.page_oob_size; + if (page >= parallel_nand_page_count(nand) || column >= total) + return PNAND_ERROR_RANGE; + + nand->port->select(); + nand->port->write_command(PNAND_CMD_READ_1); + value = column; + for (i = 0U; i < nand->geometry.column_cycles; i++) { + nand->port->write_address((pnand_octet_t)(value & 0xFFUL)); + value >>= 8; + } + value = page; + for (i = 0U; i < nand->geometry.row_cycles; i++) { + nand->port->write_address((pnand_octet_t)(value & 0xFFUL)); + value >>= 8; + } + nand->port->write_command(PNAND_CMD_READ_2); + nand->port->deselect(); + return wait_ready(nand); +} + +int parallel_nand_read_page_slice(parallel_nand_t *nand, unsigned long page, + unsigned long column, pnand_octet_t *out, + unsigned long count) +{ + unsigned long i; + unsigned long total; + int result; + if (nand == 0 || out == 0 || count == 0UL) return PNAND_ERROR_ARGUMENT; + total = nand->geometry.page_main_size + nand->geometry.page_oob_size; + if (column >= total || count > total - column) return PNAND_ERROR_RANGE; + result = begin_page_read(nand, page, column); + if (result != PNAND_OK) return result; + nand->port->select(); + for (i = 0UL; i < count; i++) out[i] = nand->port->read_data() & 0xFFU; + nand->port->deselect(); + return PNAND_OK; +} + +int parallel_nand_read_page(parallel_nand_t *nand, unsigned long page, + pnand_octet_t *main_data, + unsigned long main_capacity, + pnand_octet_t *oob, + unsigned long oob_capacity) +{ + unsigned long i; + int result; + if (nand == 0 || main_data == 0 || oob == 0) + return PNAND_ERROR_ARGUMENT; + if (main_capacity < nand->geometry.page_main_size || + oob_capacity < nand->geometry.page_oob_size) + return PNAND_ERROR_BUFFER; + result = begin_page_read(nand, page, 0UL); + if (result != PNAND_OK) return result; + nand->port->select(); + for (i = 0UL; i < nand->geometry.page_main_size; i++) + main_data[i] = nand->port->read_data() & 0xFFU; + for (i = 0UL; i < nand->geometry.page_oob_size; i++) + oob[i] = nand->port->read_data() & 0xFFU; + nand->port->deselect(); + return PNAND_OK; +} + +int parallel_nand_is_bad_block(parallel_nand_t *nand, unsigned long block, + int *is_bad) +{ + unsigned long first_page; + pnand_octet_t marker; + int result; + if (nand == 0 || is_bad == 0) return PNAND_ERROR_ARGUMENT; + if (block >= nand->geometry.block_count) return PNAND_ERROR_RANGE; + first_page = block * nand->geometry.pages_per_block; + result = parallel_nand_read_page_slice(nand, first_page, + nand->geometry.page_main_size, &marker, 1UL); + if (result != PNAND_OK) return result; + if ((marker & 0xFFU) != 0xFFU) { + *is_bad = 1; + return PNAND_OK; + } + result = parallel_nand_read_page_slice(nand, first_page + 1UL, + nand->geometry.page_main_size, &marker, 1UL); + if (result != PNAND_OK) return result; + *is_bad = ((marker & 0xFFU) != 0xFFU); + return PNAND_OK; +} diff --git a/src/parallel_nand_gas.c b/src/parallel_nand_gas.c new file mode 100644 index 0000000..461374b --- /dev/null +++ b/src/parallel_nand_gas.c @@ -0,0 +1,151 @@ +#include "parallel_nand_gas.h" + +static unsigned int pack_pair(const pnand_octet_t *data, unsigned int index) +{ + return (unsigned int)((data[index * 2U] & 0xFFU) | + ((data[index * 2U + 1U] & 0xFFU) << 8)); +} + +parallel_nand_gas_result_t parallel_nand_gas_read( + parallel_nand_gas_t *gas, unsigned int address, unsigned int *value) +{ + unsigned int offset; + const parallel_nand_geometry_t *g; + if (gas == 0 || value == 0 || address < gas->base) + return PNAND_GAS_REJECTED; + g = &gas->nand->geometry; + offset = address - gas->base; + if (offset >= PNAND_GAS_REGION_REGS) return PNAND_GAS_NO_REG; + if (offset >= PNAND_GAS_DATA_OFFSET && + offset < PNAND_GAS_DATA_OFFSET + PNAND_GAS_DATA_REGS) { + if ((gas->status & PNAND_GAS_STATUS_PAGE_VALID) == 0U) + return PNAND_GAS_REJECTED; + *value = pack_pair(gas->main_data, + offset - PNAND_GAS_DATA_OFFSET); + return PNAND_GAS_OK; + } + if (offset >= PNAND_GAS_OOB_OFFSET && + offset < PNAND_GAS_OOB_OFFSET + PNAND_GAS_OOB_REGS) { + if ((gas->status & PNAND_GAS_STATUS_PAGE_VALID) == 0U) + return PNAND_GAS_REJECTED; + *value = pack_pair(gas->oob, + offset - PNAND_GAS_OOB_OFFSET); + return PNAND_GAS_OK; + } + switch (offset) { + case PNAND_GAS_REG_SIGNATURE: *value = 0x4E44U; break; /* ND */ + case PNAND_GAS_REG_VERSION: *value = 0x0001U; break; + case PNAND_GAS_REG_STATUS: *value = gas->status; break; + case PNAND_GAS_REG_ERROR: *value = (unsigned int)gas->error & 0xFFFFU; break; + case PNAND_GAS_REG_COMMAND: *value = PNAND_GAS_CMD_NONE; break; + case PNAND_GAS_REG_PAGE_LO: *value = (unsigned int)gas->selected_page & 0xFFFFU; break; + case PNAND_GAS_REG_PAGE_HI: *value = (unsigned int)(gas->selected_page >> 16) & 0xFFFFU; break; + case PNAND_GAS_REG_PAGE_MAIN: *value = (unsigned int)g->page_main_size; break; + case PNAND_GAS_REG_PAGE_OOB: *value = (unsigned int)g->page_oob_size; break; + case PNAND_GAS_REG_PAGES_PER_BLOCK: *value = (unsigned int)g->pages_per_block; break; + case PNAND_GAS_REG_BLOCKS: *value = (unsigned int)g->block_count; break; + case PNAND_GAS_REG_MANUFACTURER_ID: *value = (unsigned int)gas->id[0]; break; + case PNAND_GAS_REG_DEVICE_ID: *value = (unsigned int)gas->id[1]; break; + case PNAND_GAS_REG_BAD_BLOCK: + *value = (gas->status & PNAND_GAS_STATUS_BAD_BLOCK) != 0U; break; + case PNAND_GAS_REG_GENERATION_LO: *value = (unsigned int)gas->generation & 0xFFFFU; break; + case PNAND_GAS_REG_GENERATION_HI: *value = (unsigned int)(gas->generation >> 16) & 0xFFFFU; break; + default: return PNAND_GAS_NO_REG; + } + *value &= 0xFFFFU; + return PNAND_GAS_OK; +} + +static void finish(parallel_nand_gas_t *gas, int result) +{ + gas->error = result; + gas->status &= (0xFFFFU ^ PNAND_GAS_STATUS_BUSY); + gas->status |= PNAND_GAS_STATUS_READY; + if (result != PNAND_OK) gas->status |= PNAND_GAS_STATUS_ERROR; +} + +static parallel_nand_gas_result_t run_command(parallel_nand_gas_t *gas, + unsigned int command) +{ + int result = PNAND_OK; + int bad = 0; + gas->status = PNAND_GAS_STATUS_BUSY; + if (command == PNAND_GAS_CMD_RESET) { + result = parallel_nand_reset(gas->nand); + } else if (command == PNAND_GAS_CMD_READ_ID) { + result = parallel_nand_read_id(gas->nand, 0U, gas->id, 8U); + } else if (command == PNAND_GAS_CMD_READ_PAGE) { + unsigned long block; + if (gas->selected_page >= parallel_nand_page_count(gas->nand)) { + finish(gas, PNAND_ERROR_RANGE); + return PNAND_GAS_REJECTED; + } + result = parallel_nand_read_page(gas->nand, gas->selected_page, + gas->main_data, PNAND_MT29F1G08_PAGE_MAIN, + gas->oob, PNAND_MT29F1G08_PAGE_OOB); + if (result == PNAND_OK) { + gas->status |= PNAND_GAS_STATUS_PAGE_VALID; + gas->generation++; + block = gas->selected_page / + gas->nand->geometry.pages_per_block; + if (!gas->block_cache_valid || gas->cached_block != block) { + result = parallel_nand_is_bad_block(gas->nand, block, &bad); + if (result == PNAND_OK) { + gas->cached_block = block; + gas->cached_bad_block = bad; + gas->block_cache_valid = 1; + } + } else { + bad = gas->cached_bad_block; + } + if (result == PNAND_OK && bad) + gas->status |= PNAND_GAS_STATUS_BAD_BLOCK; + } + } else { + gas->status = PNAND_GAS_STATUS_READY; + return PNAND_GAS_REJECTED; + } + finish(gas, result); + return result == PNAND_OK ? PNAND_GAS_OK : PNAND_GAS_REJECTED; +} + +parallel_nand_gas_result_t parallel_nand_gas_write( + parallel_nand_gas_t *gas, unsigned int address, unsigned int value) +{ + unsigned int offset; + if (gas == 0 || address < gas->base) return PNAND_GAS_REJECTED; + offset = address - gas->base; + if (offset >= PNAND_GAS_REGION_REGS) return PNAND_GAS_NO_REG; + if ((gas->status & PNAND_GAS_STATUS_BUSY) != 0U) + return PNAND_GAS_REJECTED; + value &= 0xFFFFU; + switch (offset) { + case PNAND_GAS_REG_COMMAND: return run_command(gas, value); + case PNAND_GAS_REG_PAGE_LO: + gas->selected_page = (gas->selected_page & 0xFFFF0000UL) | value; + gas->status &= (0xFFFFU ^ PNAND_GAS_STATUS_PAGE_VALID); + return PNAND_GAS_OK; + case PNAND_GAS_REG_PAGE_HI: + gas->selected_page = (gas->selected_page & 0x0000FFFFUL) | + ((unsigned long)value << 16); + gas->status &= (0xFFFFU ^ PNAND_GAS_STATUS_PAGE_VALID); + return PNAND_GAS_OK; + default: return PNAND_GAS_READ_ONLY; + } +} + +void parallel_nand_gas_init(parallel_nand_gas_t *gas, + parallel_nand_t *nand, unsigned int base) +{ + unsigned int i; + gas->nand = nand; + gas->base = base; + gas->status = PNAND_GAS_STATUS_READY; + gas->error = PNAND_OK; + gas->selected_page = 0UL; + gas->generation = 0UL; + gas->cached_block = 0UL; + gas->cached_bad_block = 0; + gas->block_cache_valid = 0; + for (i = 0U; i < 8U; i++) gas->id[i] = 0U; +} diff --git a/src/parallel_nand_pcan_gas.c b/src/parallel_nand_pcan_gas.c new file mode 100644 index 0000000..9e7f6a8 --- /dev/null +++ b/src/parallel_nand_pcan_gas.c @@ -0,0 +1,43 @@ +#include "parallel_nand_pcan_gas.h" + +static pcan_gas_status_t convert(parallel_nand_gas_result_t result) +{ + switch (result) { + case PNAND_GAS_OK: return PCAN_GAS_OK; + case PNAND_GAS_NO_REG: return PCAN_GAS_NO_REG; + case PNAND_GAS_READ_ONLY: return PCAN_GAS_READ_ONLY; + default: return PCAN_GAS_REJECTED; + } +} + +static pcan_gas_status_t read_reg(const pcan_gas_region_t *region, + uint16_t offset, uint16_t *value) +{ + parallel_nand_gas_t *gas = (parallel_nand_gas_t *)region->user; + unsigned int portable_value; + parallel_nand_gas_result_t result = parallel_nand_gas_read( + gas, (unsigned int)region->base + offset, &portable_value); + if (result == PNAND_GAS_OK) *value = (uint16_t)portable_value; + return convert(result); +} + +static pcan_gas_status_t write_reg(const pcan_gas_region_t *region, + uint16_t offset, uint16_t value) +{ + parallel_nand_gas_t *gas = (parallel_nand_gas_t *)region->user; + return convert(parallel_nand_gas_write( + gas, (unsigned int)region->base + offset, value)); +} + +void parallel_nand_pcan_gas_region_init(pcan_gas_region_t *region, + parallel_nand_gas_t *gas) +{ + region->base = (uint16_t)gas->base; + region->count = PNAND_GAS_REGION_REGS; + region->storage = 0; + region->read = read_reg; + region->write = write_reg; + region->flags = 0U; + region->user = gas; + region->name = "parallel-nand"; +} diff --git a/tests/test_parallel_nand.c b/tests/test_parallel_nand.c new file mode 100644 index 0000000..572f129 --- /dev/null +++ b/tests/test_parallel_nand.c @@ -0,0 +1,97 @@ +#include +#include +#include +#include "parallel_nand.h" +#include "parallel_nand_gas.h" + +typedef struct { char kind; unsigned int value; } event_t; +static event_t events[64]; +static unsigned int event_count; +static pnand_octet_t input_data[32]; +static unsigned int input_count; +static unsigned int input_pos; + +static void record(char kind, unsigned int value) +{ + events[event_count].kind = kind; + events[event_count].value = value; + event_count++; +} +static int fake_init(void) { return 0; } +static void fake_select(void) { record('S', 0); } +static void fake_deselect(void) { record('D', 0); } +static void fake_cmd(pnand_octet_t v) { record('C', v); } +static void fake_addr(pnand_octet_t v) { record('A', v); } +static void fake_write(pnand_octet_t v) { record('W', v); } +static pnand_octet_t fake_read(void) +{ + return input_pos < input_count ? input_data[input_pos++] : 0xFFU; +} +static int fake_ready(void) { return 1; } +static void fake_wp(int enabled) { record('P', (unsigned int)enabled); } +static void fake_delay(unsigned long us) { (void)us; } + +static const parallel_nand_port_t fake_port = { + fake_init, fake_select, fake_deselect, fake_cmd, fake_addr, fake_write, + fake_read, fake_ready, fake_wp, fake_delay +}; + +static void clear_events(void) { event_count = 0U; } + +int main(void) +{ + parallel_nand_t nand; + pnand_octet_t id[2]; + pnand_octet_t bytes[2]; + parallel_nand_gas_t gas; + unsigned int reg; + + assert(parallel_nand_init(&nand, &fake_port, + ¶llel_nand_mt29f1g08_geometry) == PNAND_OK); + assert(events[0].kind == 'D'); + assert(events[1].kind == 'P' && events[1].value == 1U); + + clear_events(); + input_data[0] = 0x2CU; + input_data[1] = 0xF1U; + input_count = 2U; + input_pos = 0U; + assert(parallel_nand_read_id(&nand, 0U, id, 2U) == PNAND_OK); + assert(id[0] == 0x2CU && id[1] == 0xF1U); + assert(events[1].kind == 'C' && events[1].value == 0x90U); + assert(events[2].kind == 'A' && events[2].value == 0U); + + clear_events(); + input_data[0] = 0x12U; + input_data[1] = 0x34U; + input_count = 2U; + input_pos = 0U; + assert(parallel_nand_read_page_slice(&nand, 0x1234UL, 0x56UL, + bytes, 2UL) == PNAND_OK); + assert(bytes[0] == 0x12U && bytes[1] == 0x34U); + assert(events[1].kind == 'C' && events[1].value == 0x00U); + assert(events[2].kind == 'A' && events[2].value == 0x56U); + assert(events[3].kind == 'A' && events[3].value == 0x00U); + assert(events[4].kind == 'A' && events[4].value == 0x34U); + assert(events[5].kind == 'A' && events[5].value == 0x12U); + assert(events[6].kind == 'C' && events[6].value == 0x30U); + + parallel_nand_gas_init(&gas, &nand, PNAND_GAS_DEFAULT_BASE); + assert(parallel_nand_gas_read(&gas, 0xE000U, ®) == PNAND_GAS_OK); + assert(reg == 0x4E44U); + assert(parallel_nand_gas_write(&gas, 0xE005U, 0x1234U) == + PNAND_GAS_OK); + assert(parallel_nand_gas_write(&gas, 0xE006U, 0x0001U) == + PNAND_GAS_OK); + assert(gas.selected_page == 0x00011234UL); + gas.main_data[0] = 0x34U; + gas.main_data[1] = 0x12U; + gas.status |= PNAND_GAS_STATUS_PAGE_VALID; + assert(parallel_nand_gas_read(&gas, 0xE100U, ®) == PNAND_GAS_OK); + assert(reg == 0x1234U); + assert(parallel_nand_gas_write(&gas, 0xE000U, 0U) == + PNAND_GAS_READ_ONLY); + + puts("parallel_nand: OK"); + return 0; +} From daaca0d94b7bdb746bfc27e5ca8b31b585bed653 Mon Sep 17 00:00:00 2001 From: Andrey Kruchinkin Date: Fri, 11 Sep 2026 19:42:15 +0300 Subject: [PATCH 2/6] feat: add STM32F103RC support for HY27UF084G2M --- README.md | 11 +- include/parallel_nand.h | 14 ++ ports/stm32f103rc/README.md | 51 +++++++ .../parallel_nand_port_stm32f103rc.c | 142 ++++++++++++++++++ .../parallel_nand_port_stm32f103rc.h | 16 ++ src/parallel_nand.c | 10 ++ tests/test_parallel_nand.c | 20 +++ 7 files changed, 262 insertions(+), 2 deletions(-) create mode 100644 ports/stm32f103rc/README.md create mode 100644 ports/stm32f103rc/parallel_nand_port_stm32f103rc.c create mode 100644 ports/stm32f103rc/parallel_nand_port_stm32f103rc.h diff --git a/README.md b/README.md index ebc4a27..b2b585e 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,15 @@ # parallel-nand Переносимый C99-драйвер асинхронной parallel NAND x8 и окно управления через -общее адресное пространство GAS. Первый профиль — Micron `MT29F1G08ABADA`: -2048+64 байта, 64 страницы в блоке, 1024 блока. +общее адресное пространство GAS. Есть профили Micron `MT29F1G08ABADA` и +Hynix `HY27UF084G2M` (512 MiB, 2048+64 байта, 64 страницы в блоке, +4096 блоков, ID `AD DC 80 95`). Подготовлены аппаратные порты: - `TMS320F2812` — XINTF Zone 6; - `STM32F407VET6` — аппаратный FSMC NAND Bank 2; +- `STM32F103RCT6` — GPIO bit-bang, так как в LQFP64 FSMC недоступен; - `STM32G474CEU6` — GPIO bit-bang для корпуса UFQFPN-48. Ядро не зависит от HAL и транспорта GUI. STM32 HAL используется только в @@ -55,6 +57,11 @@ void memory_init(void) } ``` +Для Hynix на STM32F103RCT6 используйте +`parallel_nand_port_stm32f103rc()` и +`parallel_nand_hy27uf084g2m_geometry`; распиновка и ограничения приведены в +[`ports/stm32f103rc/README.md`](ports/stm32f103rc/README.md). + Диспетчер общего адресного пространства вызывает `parallel_nand_gas_read()`/`parallel_nand_gas_write()` для адресов `0xE000..0xE51F`. Для STM32 с `c/set-protocol/pcan_gas` дополнительно включить diff --git a/include/parallel_nand.h b/include/parallel_nand.h index 8773ede..7b6382d 100644 --- a/include/parallel_nand.h +++ b/include/parallel_nand.h @@ -16,6 +16,19 @@ extern "C" { #define PNAND_MT29F1G08_PAGE_TOTAL \ (PNAND_MT29F1G08_PAGE_MAIN + PNAND_MT29F1G08_PAGE_OOB) +#define PNAND_HY27UF084G2M_PAGE_MAIN 2048UL +#define PNAND_HY27UF084G2M_PAGE_OOB 64UL +#define PNAND_HY27UF084G2M_PAGES_PER_BLOCK 64UL +#define PNAND_HY27UF084G2M_BLOCKS 4096UL +#define PNAND_HY27UF084G2M_COLUMN_CYCLES 2U +#define PNAND_HY27UF084G2M_ROW_CYCLES 3U +#define PNAND_HY27UF084G2M_MANUFACTURER_ID 0xADU +#define PNAND_HY27UF084G2M_DEVICE_ID 0xDCU +#define PNAND_HY27UF084G2M_ID3 0x80U +#define PNAND_HY27UF084G2M_ID4 0x95U +#define PNAND_HY27UF084G2M_PAGE_TOTAL \ + (PNAND_HY27UF084G2M_PAGE_MAIN + PNAND_HY27UF084G2M_PAGE_OOB) + typedef enum { PNAND_OK = 0, PNAND_ERROR_ARGUMENT = -1, @@ -41,6 +54,7 @@ typedef struct { } parallel_nand_t; extern const parallel_nand_geometry_t parallel_nand_mt29f1g08_geometry; +extern const parallel_nand_geometry_t parallel_nand_hy27uf084g2m_geometry; int parallel_nand_init(parallel_nand_t *nand, const parallel_nand_port_t *port, diff --git a/ports/stm32f103rc/README.md b/ports/stm32f103rc/README.md new file mode 100644 index 0000000..aaba927 --- /dev/null +++ b/ports/stm32f103rc/README.md @@ -0,0 +1,51 @@ +# Порт STM32F103RCT6 для HY27UF084G2M + +У `STM32F103RCT6` корпус LQFP64. Согласно datasheet STM32F103xC/D/E, функция +FSMC в этом корпусе недоступна, поэтому порт выполняет асинхронные NAND-циклы +через GPIO. + +## Соединения + +| HY27UF084G2M | STM32F103RCT6 | LQFP64 | +|---|---|---:| +| `I/O0..I/O7` | `PB0..PB7` | 26, 27, 28, 55, 56, 57, 58, 59 | +| `CLE` | `PC0` | 8 | +| `ALE` | `PC1` | 9 | +| `WE#` | `PC2` | 10 | +| `RE#` | `PC3` | 11 | +| `CE#` | `PC4` | 24 | +| `WP#` | `PC5` | 25 | +| `R/B#` | `PC6` | 37 | + +Питание NAND — 3,3 В. На каждый вывод питания нужен 100 нФ, рядом с чипом — +1–4,7 мкФ. `R/B#` имеет открытый сток и требует внешней подтяжки 4,7–10 кОм +к 3,3 В. Не оставляйте `WP#` неподключённым. + +`PB2` также является `BOOT1`: NAND держит I/O в высокоимпедансном состоянии +до командного цикла, но штатную конфигурацию загрузки платы всё равно нужно +обеспечить подтяжкой. `PB3/PB4` заняты JTAG; порт отключает JTAG и сохраняет +SWD на `PA13/PA14`. + +## Подключение к проекту Cube/HAL + +Добавьте `parallel_nand_port_stm32f103rc.c`, путь к его заголовку и включите +GPIO HAL. Инициализация: + +```c +#include "parallel_nand.h" +#include "parallel_nand_port_stm32f103rc.h" + +static parallel_nand_t nand; + +int nand_init(void) +{ + return parallel_nand_init( + &nand, + parallel_nand_port_stm32f103rc(), + ¶llel_nand_hy27uf084g2m_geometry); +} +``` + +После успешной инициализации прочитайте четыре байта ID и ожидайте +`AD DC 80 95`. Ядро библиотеки работает только на чтение и удерживает `WP#` +в активном низком уровне; PROGRAM/ERASE пока намеренно не реализованы. diff --git a/ports/stm32f103rc/parallel_nand_port_stm32f103rc.c b/ports/stm32f103rc/parallel_nand_port_stm32f103rc.c new file mode 100644 index 0000000..1a4d8f4 --- /dev/null +++ b/ports/stm32f103rc/parallel_nand_port_stm32f103rc.c @@ -0,0 +1,142 @@ +#include "parallel_nand_port_stm32f103rc.h" +#include "stm32f1xx_hal.h" + +/* STM32F103RCT6 LQFP64: FSMC is unavailable, so use GPIO bit-bang. */ +#define DATA_MASK 0x00FFU +#define CLE_PIN GPIO_PIN_0 +#define ALE_PIN GPIO_PIN_1 +#define WE_PIN GPIO_PIN_2 +#define RE_PIN GPIO_PIN_3 +#define CE_PIN GPIO_PIN_4 +#define WP_PIN GPIO_PIN_5 +#define RB_PIN GPIO_PIN_6 + +static int data_is_output; + +static void data_input(void) +{ + if (data_is_output) { + GPIOB->CRL = 0x44444444UL; /* PB0..PB7: floating input. */ + data_is_output = 0; + } +} + +static void data_output(void) +{ + if (!data_is_output) { + GPIOB->CRL = 0x33333333UL; /* PB0..PB7: 50 MHz push-pull. */ + data_is_output = 1; + } +} + +static void short_delay(void) +{ + __NOP(); __NOP(); __NOP(); __NOP(); +} + +static int f103rc_init(void) +{ + GPIO_InitTypeDef io; + + __HAL_RCC_AFIO_CLK_ENABLE(); + __HAL_RCC_GPIOB_CLK_ENABLE(); + __HAL_RCC_GPIOC_CLK_ENABLE(); + + /* Latch inactive control levels while pins are still inputs. */ + GPIOC->BSRR = WE_PIN | RE_PIN | CE_PIN | + ((uint32_t)(CLE_PIN | ALE_PIN | WP_PIN) << 16); + + /* PB3/PB4 carry I/O3/I/O4. Keep SWD, release only the JTAG pins. */ + __HAL_AFIO_REMAP_SWJ_NOJTAG(); + + GPIOB->BSRR = DATA_MASK; + GPIOB->CRL = 0x33333333UL; + data_is_output = 1; + data_input(); + + io.Pin = CLE_PIN | ALE_PIN | WE_PIN | RE_PIN | CE_PIN | WP_PIN; + io.Mode = GPIO_MODE_OUTPUT_PP; + io.Pull = GPIO_NOPULL; + io.Speed = GPIO_SPEED_FREQ_HIGH; + HAL_GPIO_Init(GPIOC, &io); + + io.Pin = RB_PIN; + io.Mode = GPIO_MODE_INPUT; + io.Pull = GPIO_NOPULL; + HAL_GPIO_Init(GPIOC, &io); + return 0; +} + +static void f103rc_select(void) { GPIOC->BRR = CE_PIN; } +static void f103rc_deselect(void) { GPIOC->BSRR = CE_PIN; } + +static void write_cycle(pnand_octet_t value) +{ + data_output(); + GPIOB->BRR = DATA_MASK; + GPIOB->BSRR = (uint32_t)value & DATA_MASK; + short_delay(); + GPIOC->BRR = WE_PIN; + short_delay(); + GPIOC->BSRR = WE_PIN; + short_delay(); +} + +static void f103rc_write_command(pnand_octet_t value) +{ + GPIOC->BSRR = CLE_PIN; + GPIOC->BRR = ALE_PIN; + write_cycle(value); + GPIOC->BRR = CLE_PIN; +} + +static void f103rc_write_address(pnand_octet_t value) +{ + GPIOC->BSRR = ALE_PIN; + GPIOC->BRR = CLE_PIN; + write_cycle(value); + GPIOC->BRR = ALE_PIN; +} + +static void f103rc_write_data(pnand_octet_t value) +{ + GPIOC->BRR = CLE_PIN | ALE_PIN; + write_cycle(value); +} + +static pnand_octet_t f103rc_read_data(void) +{ + pnand_octet_t value; + data_input(); + GPIOC->BRR = CLE_PIN | ALE_PIN | RE_PIN; + short_delay(); + value = (pnand_octet_t)(GPIOB->IDR & DATA_MASK); + GPIOC->BSRR = RE_PIN; + short_delay(); + return value; +} + +static int f103rc_is_ready(void) { return (GPIOC->IDR & RB_PIN) != 0U; } + +static void f103rc_set_wp(int enabled) +{ + if (enabled) GPIOC->BRR = WP_PIN; + else GPIOC->BSRR = WP_PIN; +} + +static void f103rc_delay_us(unsigned long us) +{ + uint32_t cycles = (SystemCoreClock / 3000000UL) * (uint32_t)us; + while (cycles-- != 0U) __NOP(); +} + +static const parallel_nand_port_t f103rc_port = { + f103rc_init, f103rc_select, f103rc_deselect, + f103rc_write_command, f103rc_write_address, f103rc_write_data, + f103rc_read_data, f103rc_is_ready, f103rc_set_wp, f103rc_delay_us +}; + +const parallel_nand_port_t *parallel_nand_port_stm32f103rc(void) +{ + return &f103rc_port; +} diff --git a/ports/stm32f103rc/parallel_nand_port_stm32f103rc.h b/ports/stm32f103rc/parallel_nand_port_stm32f103rc.h new file mode 100644 index 0000000..7d76f27 --- /dev/null +++ b/ports/stm32f103rc/parallel_nand_port_stm32f103rc.h @@ -0,0 +1,16 @@ +#ifndef PARALLEL_NAND_PORT_STM32F103RC_H +#define PARALLEL_NAND_PORT_STM32F103RC_H + +#include "parallel_nand_port.h" + +#ifdef __cplusplus +extern "C" { +#endif + +const parallel_nand_port_t *parallel_nand_port_stm32f103rc(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/parallel_nand.c b/src/parallel_nand.c index 62158c4..bd182a9 100644 --- a/src/parallel_nand.c +++ b/src/parallel_nand.c @@ -16,6 +16,16 @@ const parallel_nand_geometry_t parallel_nand_mt29f1g08_geometry = { 1000000UL }; +const parallel_nand_geometry_t parallel_nand_hy27uf084g2m_geometry = { + PNAND_HY27UF084G2M_PAGE_MAIN, + PNAND_HY27UF084G2M_PAGE_OOB, + PNAND_HY27UF084G2M_PAGES_PER_BLOCK, + PNAND_HY27UF084G2M_BLOCKS, + PNAND_HY27UF084G2M_COLUMN_CYCLES, + PNAND_HY27UF084G2M_ROW_CYCLES, + 1000000UL +}; + static int port_valid(const parallel_nand_port_t *port) { return port != 0 && port->init != 0 && port->select != 0 && diff --git a/tests/test_parallel_nand.c b/tests/test_parallel_nand.c index 572f129..d2ae00a 100644 --- a/tests/test_parallel_nand.c +++ b/tests/test_parallel_nand.c @@ -76,6 +76,26 @@ int main(void) assert(events[5].kind == 'A' && events[5].value == 0x12U); assert(events[6].kind == 'C' && events[6].value == 0x30U); + assert(parallel_nand_init(&nand, &fake_port, + ¶llel_nand_hy27uf084g2m_geometry) == PNAND_OK); + assert(parallel_nand_page_count(&nand) == 262144UL); + assert(PNAND_HY27UF084G2M_MANUFACTURER_ID == 0xADU); + assert(PNAND_HY27UF084G2M_DEVICE_ID == 0xDCU); + + clear_events(); + input_data[0] = 0xABU; + input_count = 1U; + input_pos = 0U; + assert(parallel_nand_read_page_slice(&nand, 0x23456UL, 0x78UL, + bytes, 1UL) == PNAND_OK); + assert(bytes[0] == 0xABU); + assert(events[2].kind == 'A' && events[2].value == 0x78U); + assert(events[3].kind == 'A' && events[3].value == 0x00U); + assert(events[4].kind == 'A' && events[4].value == 0x56U); + assert(events[5].kind == 'A' && events[5].value == 0x34U); + assert(events[6].kind == 'A' && events[6].value == 0x02U); + assert(events[7].kind == 'C' && events[7].value == 0x30U); + parallel_nand_gas_init(&gas, &nand, PNAND_GAS_DEFAULT_BASE); assert(parallel_nand_gas_read(&gas, 0xE000U, ®) == PNAND_GAS_OK); assert(reg == 0x4E44U); From 3a1e8d469a0124d78401722710898398580d950f Mon Sep 17 00:00:00 2001 From: Andrey Kruchinkin Date: Fri, 11 Sep 2026 22:01:46 +0300 Subject: [PATCH 3/6] docs: make pinout readable in narrow views --- ports/stm32f103rc/README.md | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/ports/stm32f103rc/README.md b/ports/stm32f103rc/README.md index aaba927..14cb662 100644 --- a/ports/stm32f103rc/README.md +++ b/ports/stm32f103rc/README.md @@ -6,16 +6,26 @@ FSMC в этом корпусе недоступна, поэтому порт в ## Соединения -| HY27UF084G2M | STM32F103RCT6 | LQFP64 | -|---|---|---:| -| `I/O0..I/O7` | `PB0..PB7` | 26, 27, 28, 55, 56, 57, 58, 59 | -| `CLE` | `PC0` | 8 | -| `ALE` | `PC1` | 9 | -| `WE#` | `PC2` | 10 | -| `RE#` | `PC3` | 11 | -| `CE#` | `PC4` | 24 | -| `WP#` | `PC5` | 25 | -| `R/B#` | `PC6` | 37 | +Шина данных: + +- `I/O0 → PB0`, вывод MCU 26; +- `I/O1 → PB1`, вывод MCU 27; +- `I/O2 → PB2`, вывод MCU 28; +- `I/O3 → PB3`, вывод MCU 55; +- `I/O4 → PB4`, вывод MCU 56; +- `I/O5 → PB5`, вывод MCU 57; +- `I/O6 → PB6`, вывод MCU 58; +- `I/O7 → PB7`, вывод MCU 59. + +Управляющие сигналы: + +- `CLE → PC0`, вывод MCU 8; +- `ALE → PC1`, вывод MCU 9; +- `WE# → PC2`, вывод MCU 10; +- `RE# → PC3`, вывод MCU 11; +- `CE# → PC4`, вывод MCU 24; +- `WP# → PC5`, вывод MCU 25; +- `R/B# → PC6`, вывод MCU 37. Питание NAND — 3,3 В. На каждый вывод питания нужен 100 нФ, рядом с чипом — 1–4,7 мкФ. `R/B#` имеет открытый сток и требует внешней подтяжки 4,7–10 кОм From 11f16f618be9b5261cb1b348193c68c90d43d6d4 Mon Sep 17 00:00:00 2001 From: Andrey Kruchinkin Date: Fri, 11 Sep 2026 22:04:08 +0300 Subject: [PATCH 4/6] docs: restore compact pinout tables --- ports/stm32f103rc/README.md | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/ports/stm32f103rc/README.md b/ports/stm32f103rc/README.md index 14cb662..aaba927 100644 --- a/ports/stm32f103rc/README.md +++ b/ports/stm32f103rc/README.md @@ -6,26 +6,16 @@ FSMC в этом корпусе недоступна, поэтому порт в ## Соединения -Шина данных: - -- `I/O0 → PB0`, вывод MCU 26; -- `I/O1 → PB1`, вывод MCU 27; -- `I/O2 → PB2`, вывод MCU 28; -- `I/O3 → PB3`, вывод MCU 55; -- `I/O4 → PB4`, вывод MCU 56; -- `I/O5 → PB5`, вывод MCU 57; -- `I/O6 → PB6`, вывод MCU 58; -- `I/O7 → PB7`, вывод MCU 59. - -Управляющие сигналы: - -- `CLE → PC0`, вывод MCU 8; -- `ALE → PC1`, вывод MCU 9; -- `WE# → PC2`, вывод MCU 10; -- `RE# → PC3`, вывод MCU 11; -- `CE# → PC4`, вывод MCU 24; -- `WP# → PC5`, вывод MCU 25; -- `R/B# → PC6`, вывод MCU 37. +| HY27UF084G2M | STM32F103RCT6 | LQFP64 | +|---|---|---:| +| `I/O0..I/O7` | `PB0..PB7` | 26, 27, 28, 55, 56, 57, 58, 59 | +| `CLE` | `PC0` | 8 | +| `ALE` | `PC1` | 9 | +| `WE#` | `PC2` | 10 | +| `RE#` | `PC3` | 11 | +| `CE#` | `PC4` | 24 | +| `WP#` | `PC5` | 25 | +| `R/B#` | `PC6` | 37 | Питание NAND — 3,3 В. На каждый вывод питания нужен 100 нФ, рядом с чипом — 1–4,7 мкФ. `R/B#` имеет открытый сток и требует внешней подтяжки 4,7–10 кОм From c0c20f6dc24550d3a89dc0ec32caf9dfc5995263 Mon Sep 17 00:00:00 2001 From: Andrey Kruchinkin Date: Sat, 12 Sep 2026 00:24:40 +0300 Subject: [PATCH 5/6] feat: add STM32F103ZE FSMC NAND port --- README.md | 8 +- ports/stm32f103ze/README.md | 59 ++++++ .../parallel_nand_port_stm32f103ze.c | 183 ++++++++++++++++++ .../parallel_nand_port_stm32f103ze.h | 16 ++ 4 files changed, 263 insertions(+), 3 deletions(-) create mode 100644 ports/stm32f103ze/README.md create mode 100644 ports/stm32f103ze/parallel_nand_port_stm32f103ze.c create mode 100644 ports/stm32f103ze/parallel_nand_port_stm32f103ze.h diff --git a/README.md b/README.md index b2b585e..915931e 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Hynix `HY27UF084G2M` (512 MiB, 2048+64 байта, 64 страницы в бло - `TMS320F2812` — XINTF Zone 6; - `STM32F407VET6` — аппаратный FSMC NAND Bank 2; - `STM32F103RCT6` — GPIO bit-bang, так как в LQFP64 FSMC недоступен; +- `STM32F103ZET6` — аппаратный FSMC NAND Bank 3; - `STM32G474CEU6` — GPIO bit-bang для корпуса UFQFPN-48. Ядро не зависит от HAL и транспорта GUI. STM32 HAL используется только в @@ -57,10 +58,11 @@ void memory_init(void) } ``` -Для Hynix на STM32F103RCT6 используйте -`parallel_nand_port_stm32f103rc()` и +Для Hynix на STM32F103ZET6 используйте +`parallel_nand_port_stm32f103ze()` и `parallel_nand_hy27uf084g2m_geometry`; распиновка и ограничения приведены в -[`ports/stm32f103rc/README.md`](ports/stm32f103rc/README.md). +[`ports/stm32f103ze/README.md`](ports/stm32f103ze/README.md). Порт RCT6 +с GPIO bit-bang сохранён как отдельный вариант для корпуса LQFP64. Диспетчер общего адресного пространства вызывает `parallel_nand_gas_read()`/`parallel_nand_gas_write()` для адресов diff --git a/ports/stm32f103ze/README.md b/ports/stm32f103ze/README.md new file mode 100644 index 0000000..0be0c35 --- /dev/null +++ b/ports/stm32f103ze/README.md @@ -0,0 +1,59 @@ +# Порт STM32F103ZET6 для HY27UF084G2M + +Порт использует аппаратный контроллер `FSMC NAND Bank 3`, 8-битную шину и +окно памяти `0x70000000`. Сигналы `CE#`, `WE#`, `RE#`, `CLE` и `ALE` +формируются FSMC. Защита записи `WP#` управляется отдельным GPIO `PG8`. + +## Соединения + +| HY27UF084G2M | STM32F103ZET6 | LQFP144 | Функция FSMC | +|---|---|---:|---| +| `I/O0` | `PD14` | 85 | `D0` | +| `I/O1` | `PD15` | 86 | `D1` | +| `I/O2` | `PD0` | 114 | `D2` | +| `I/O3` | `PD1` | 115 | `D3` | +| `I/O4` | `PE7` | 58 | `D4` | +| `I/O5` | `PE8` | 59 | `D5` | +| `I/O6` | `PE9` | 60 | `D6` | +| `I/O7` | `PE10` | 63 | `D7` | +| `CLE` | `PD11` | 80 | `A16/CLE` | +| `ALE` | `PD12` | 81 | `A17/ALE` | +| `WE#` | `PD5` | 119 | `NWE` | +| `RE#` | `PD4` | 118 | `NOE` | +| `CE#` | `PD7` | 123 | `NCE2` | +| `R/B#` | `PD6` | 122 | вход GPIO (`NWAIT`) | +| `WP#` | `PG8` | 93 | выход GPIO | + +`R/B#` имеет открытый сток и требует внешней подтяжки 4,7–10 кОм к 3,3 В. +Питание NAND — 3,3 В; возле каждой пары питания нужен 100 нФ, рядом с +микросхемой — 1–4,7 мкФ. Все номера выводов MCU указаны для вида сверху. + +## Подключение к STM32Cube HAL + +В `stm32f1xx_hal_conf.h` должны быть включены `HAL_NAND_MODULE_ENABLED` и +`HAL_GPIO_MODULE_ENABLED`. В проект добавляются: + +- `parallel_nand_port_stm32f103ze.c`; +- `stm32f1xx_hal_nand.c`; +- `stm32f1xx_ll_fsmc.c`. + +Инициализация переносимой библиотеки: + +```c +#include "parallel_nand.h" +#include "parallel_nand_port_stm32f103ze.h" + +static parallel_nand_t nand; + +int nand_init(void) +{ + return parallel_nand_init( + &nand, + parallel_nand_port_stm32f103ze(), + ¶llel_nand_hy27uf084g2m_geometry); +} +``` + +Начальные тайминги намеренно консервативны. После запуска проверьте +осциллографом длительности `WE#`/`RE#` и время установки данных. Ожидаемый ID +HY27UF084G2M: `AD DC 80 95`. diff --git a/ports/stm32f103ze/parallel_nand_port_stm32f103ze.c b/ports/stm32f103ze/parallel_nand_port_stm32f103ze.c new file mode 100644 index 0000000..e1e6a3e --- /dev/null +++ b/ports/stm32f103ze/parallel_nand_port_stm32f103ze.c @@ -0,0 +1,183 @@ +#include "parallel_nand_port_stm32f103ze.h" + +#include "stm32f1xx_hal.h" +#include "stm32f1xx_hal_nand.h" + +/* STM32F103 names this block FSMC NAND Bank 3. Its HAL memory window is + * NAND_DEVICE1; each access makes FSMC assert PD7/FSMC_NCE2 automatically. */ +#define PNAND_F103ZE_DATA_ADDRESS 0x70000000UL +#define PNAND_F103ZE_COMMAND_AREA (1UL << 16U) +#define PNAND_F103ZE_ADDRESS_AREA (1UL << 17U) + +/* WP# is not generated by FSMC, so it is controlled as an ordinary GPIO. */ +#define PNAND_F103ZE_WP_PORT GPIOG +#define PNAND_F103ZE_WP_PIN GPIO_PIN_8 + +/* HAL owns the FSMC registers through this handle during initialization. */ +static NAND_HandleTypeDef nand_handle; + +/** Configure all FSMC signals that belong to GPIO port D. */ +static void configure_fsmc_gpio_d(void) +{ + GPIO_InitTypeDef pin = {0}; + + /* D0..D3, NOE, NWE, NCE2, CLE and ALE are driven by FSMC. On STM32F1 + * alternate-function output mode is selected without an AF number. */ + pin.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5 | + GPIO_PIN_7 | GPIO_PIN_11 | GPIO_PIN_12 | + GPIO_PIN_14 | GPIO_PIN_15; + pin.Mode = GPIO_MODE_AF_PP; + pin.Pull = GPIO_NOPULL; + pin.Speed = GPIO_SPEED_FREQ_HIGH; + HAL_GPIO_Init(GPIOD, &pin); + + /* R/B# is read directly. Its open-drain output needs an external + * 4.7..10 kOhm pull-up to 3.3 V. */ + pin.Pin = GPIO_PIN_6; + pin.Mode = GPIO_MODE_INPUT; + pin.Pull = GPIO_NOPULL; + HAL_GPIO_Init(GPIOD, &pin); +} + +/** Configure FSMC data bits D4..D7 on GPIO port E. */ +static void configure_fsmc_gpio_e(void) +{ + GPIO_InitTypeDef pin = {0}; + + pin.Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10; + pin.Mode = GPIO_MODE_AF_PP; + pin.Pull = GPIO_NOPULL; + pin.Speed = GPIO_SPEED_FREQ_HIGH; + HAL_GPIO_Init(GPIOE, &pin); +} + +/** Configure the explicit write-protect output and assert protection. */ +static void configure_write_protect(void) +{ + GPIO_InitTypeDef pin = {0}; + + /* Set the safe active-low level before changing the pad to an output. */ + HAL_GPIO_WritePin(PNAND_F103ZE_WP_PORT, PNAND_F103ZE_WP_PIN, + GPIO_PIN_RESET); + + pin.Pin = PNAND_F103ZE_WP_PIN; + pin.Mode = GPIO_MODE_OUTPUT_PP; + pin.Pull = GPIO_NOPULL; + pin.Speed = GPIO_SPEED_FREQ_LOW; + HAL_GPIO_Init(PNAND_F103ZE_WP_PORT, &pin); +} + +/** Initialize the STM32F1 HAL NAND layer with conservative 72 MHz timings. */ +static int f103ze_init(void) +{ + FSMC_NAND_PCC_TimingTypeDef common_timing = {0}; + FSMC_NAND_PCC_TimingTypeDef attribute_timing = {0}; + + __HAL_RCC_GPIOD_CLK_ENABLE(); + __HAL_RCC_GPIOE_CLK_ENABLE(); + __HAL_RCC_GPIOG_CLK_ENABLE(); + __HAL_RCC_FSMC_CLK_ENABLE(); + + configure_fsmc_gpio_d(); + configure_fsmc_gpio_e(); + configure_write_protect(); + + nand_handle.Instance = FSMC_NAND_DEVICE; + nand_handle.Init.NandBank = FSMC_NAND_BANK3; + nand_handle.Init.Waitfeature = FSMC_NAND_PCC_WAIT_FEATURE_DISABLE; + nand_handle.Init.MemoryDataWidth = FSMC_NAND_PCC_MEM_BUS_WIDTH_8; + nand_handle.Init.EccComputation = FSMC_NAND_ECC_DISABLE; + nand_handle.Init.ECCPageSize = FSMC_NAND_ECC_PAGE_SIZE_2048BYTE; + nand_handle.Init.TCLRSetupTime = 2U; + nand_handle.Init.TARSetupTime = 2U; + + /* One FSMC tick is about 13.9 ns at HCLK=72 MHz. These deliberately + * relaxed values are suitable for first bring-up and signal probing. */ + common_timing.SetupTime = 2U; + common_timing.WaitSetupTime = 5U; + common_timing.HoldSetupTime = 3U; + common_timing.HiZSetupTime = 2U; + attribute_timing = common_timing; + + return HAL_NAND_Init(&nand_handle, &common_timing, &attribute_timing) == + HAL_OK + ? 0 + : -1; +} + +static void f103ze_select(void) +{ + /* A memory-window access asserts NCE2; no separate software GPIO exists. */ +} + +static void f103ze_deselect(void) +{ + /* NCE2 is released automatically when the FSMC access finishes. */ +} + +static void f103ze_write_command(pnand_octet_t value) +{ + *(__IO uint8_t *)(PNAND_F103ZE_DATA_ADDRESS | + PNAND_F103ZE_COMMAND_AREA) = (uint8_t)value; + __DSB(); /* Complete the external bus write before the next NAND cycle. */ +} + +static void f103ze_write_address(pnand_octet_t value) +{ + *(__IO uint8_t *)(PNAND_F103ZE_DATA_ADDRESS | + PNAND_F103ZE_ADDRESS_AREA) = (uint8_t)value; + __DSB(); +} + +static void f103ze_write_data(pnand_octet_t value) +{ + *(__IO uint8_t *)PNAND_F103ZE_DATA_ADDRESS = (uint8_t)value; + __DSB(); +} + +static pnand_octet_t f103ze_read_data(void) +{ + uint8_t value = *(__IO uint8_t *)PNAND_F103ZE_DATA_ADDRESS; + __DSB(); + return (pnand_octet_t)value; +} + +static int f103ze_is_ready(void) +{ + return HAL_GPIO_ReadPin(GPIOD, GPIO_PIN_6) == GPIO_PIN_SET; +} + +static void f103ze_set_write_protect(int enabled) +{ + /* NAND WP# is active low: RESET protects, SET permits program/erase. */ + HAL_GPIO_WritePin(PNAND_F103ZE_WP_PORT, PNAND_F103ZE_WP_PIN, + enabled ? GPIO_PIN_RESET : GPIO_PIN_SET); +} + +static void f103ze_delay_us(unsigned long us) +{ + /* Approximate delay used only around reset/status polling. The factor + * accounts for loop instructions in addition to the NOP. */ + uint32_t cycles = (SystemCoreClock / 3000000UL) * (uint32_t)us; + while (cycles-- != 0U) { + __NOP(); + } +} + +static const parallel_nand_port_t f103ze_port = { + f103ze_init, + f103ze_select, + f103ze_deselect, + f103ze_write_command, + f103ze_write_address, + f103ze_write_data, + f103ze_read_data, + f103ze_is_ready, + f103ze_set_write_protect, + f103ze_delay_us +}; + +const parallel_nand_port_t *parallel_nand_port_stm32f103ze(void) +{ + return &f103ze_port; +} diff --git a/ports/stm32f103ze/parallel_nand_port_stm32f103ze.h b/ports/stm32f103ze/parallel_nand_port_stm32f103ze.h new file mode 100644 index 0000000..d5483a4 --- /dev/null +++ b/ports/stm32f103ze/parallel_nand_port_stm32f103ze.h @@ -0,0 +1,16 @@ +#ifndef PARALLEL_NAND_PORT_STM32F103ZE_H +#define PARALLEL_NAND_PORT_STM32F103ZE_H + +#include "parallel_nand_port.h" + +#ifdef __cplusplus +extern "C" { +#endif + +const parallel_nand_port_t *parallel_nand_port_stm32f103ze(void); + +#ifdef __cplusplus +} +#endif + +#endif From 67cca68fcc69c20abb9b13a32a995c8fa085c2ea Mon Sep 17 00:00:00 2001 From: Andrey Kruchinkin Date: Tue, 15 Sep 2026 12:25:15 +0300 Subject: [PATCH 6/6] feat: sync current parallel NAND implementation from templates --- README.md | 24 ++- docs/EMPTY_BOARD_PINOUT.md | 6 +- docs/MEMORY_GAS.md | 42 ++++- include/parallel_nand.h | 13 +- include/parallel_nand_gas.h | 13 +- include/parallel_nand_port.h | 2 + ports/stm32f103rc/README.md | 4 +- .../parallel_nand_port_stm32f103rc.c | 3 +- ports/stm32f103ze/README.md | 3 + .../parallel_nand_port_stm32f103ze.c | 3 +- ports/stm32f407ve/README.md | 5 + .../parallel_nand_port_stm32f407ve.c | 3 +- ports/stm32g474ce/README.md | 3 + .../parallel_nand_port_stm32g474ce.c | 3 +- ports/tms320f2812/README.md | 7 +- .../parallel_nand_port_tms320f2812.c | 3 +- src/parallel_nand.c | 107 +++++++++++- src/parallel_nand_gas.c | 124 +++++++++++++- tests/test_parallel_nand.c | 159 +++++++++++++++++- 19 files changed, 495 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 915931e..d257563 100644 --- a/README.md +++ b/README.md @@ -76,10 +76,28 @@ void memory_init(void) [docs/MEMORY_GAS.md](docs/MEMORY_GAS.md). Электрические соединения — в README соответствующего порта и [docs/EMPTY_BOARD_PINOUT.md](docs/EMPTY_BOARD_PINOUT.md). +## PROGRAM и ERASE + +- `parallel_nand_program_page()` записывает полную main-область и, при + необходимости, полную OOB-область страницы. +- `parallel_nand_erase_block()` стирает один физический блок. +- Перед изменением ядро проверяет заводской bad-block marker, на время операции + снимает `WP#`, ждёт `R/B#`, проверяет fail-бит статуса и снова включает + защиту при любом результате. +- Порт обязан объявить `write_supported = 1`; иначе возвращается + `PNAND_ERROR_WRITE_PROTECTED`. Это защищает платы, где `WP#` постоянно + соединён с GND. +- Через GAS разрушительные команды требуют записи `0xA55A` в `CONFIRM` + непосредственно перед `PROGRAM_PAGE` или `ERASE_BLOCK`. + +NAND можно программировать только из `1` в `0`; для повторной записи сначала +стирается весь блок. Не стирайте заводские bad-блоки и не используйте raw-запись +как файловую систему без ECC, wear leveling и защиты загрузочных блоков. + ## Текущие границы -- Реализовано безопасное чтение, RESET, READ ID, STATUS и bad-block marker. -- PROGRAM/ERASE намеренно отсутствуют; ядро включает аппаратную защиту `WP#`. +- Реализованы чтение, программирование страницы, стирание блока, RESET, + READ ID, STATUS и проверка bad-block marker. - ECC пока не исправляет данные. GUI получает физические main/OOB и должен помечать дамп как raw. - В полном дампе bad-блоки не пропускаются: сохраняется физический порядок. @@ -95,3 +113,5 @@ void memory_init(void) 5. Записать номер страницы в `0xE005/0xE006`, затем `3` в `0xE004`. 6. Дождаться `READY | PAGE_VALID` в `0xE002`. 7. Прочитать main из `0xE100..0xE4FF`, OOB из `0xE500..0xE51F`. +8. PROGRAM/ERASE проверять только на заведомо расходном блоке после сохранения + полного raw-дампа. diff --git a/docs/EMPTY_BOARD_PINOUT.md b/docs/EMPTY_BOARD_PINOUT.md index 5094b1f..e0f72de 100644 --- a/docs/EMPTY_BOARD_PINOUT.md +++ b/docs/EMPTY_BOARD_PINOUT.md @@ -30,7 +30,7 @@ TSOP-48 (суффикс `WP`). На второй фотографии NAND — | `CLE` | 16 | фиксация команды | | `ALE` | 17 | фиксация адреса | | `WE#` | 18 | строб команды/адреса от МК | -| `WP#` | 19 | для read-only 10 кОм на GND; для записи — GPIO и pull-down | +| `WP#` | 19 | GPIO МК и pull-down 10 кОм; только для чтения допустим GND через 10 кОм | | `I/O0` | 29 | двунаправленная шина, бит 0 | | `I/O1` | 30 | двунаправленная шина, бит 1 | | `I/O2` | 31 | двунаправленная шина, бит 2 | @@ -69,10 +69,12 @@ TSOP-48 (суффикс `WP`). На второй фотографии NAND — ## Первый запуск 1. Не устанавливать NAND и проверить 3,3 В, отсутствие КЗ и уровни `CE#/WE#/RE#`. -2. Установить NAND, оставить `WP#` в нуле и выполнить RESET `FFh`. +2. Установить NAND, проверить pull-down и уровень `WP# = 0`, выполнить RESET `FFh`. 3. Прочитать ID `90h`: ожидаемые первые байты для профиля — `2C F1`. 4. Считать одну страницу два раза и сравнить main/OOB побайтно. 5. Только после устойчивого чтения запускать полный дамп через GAS. +6. PROGRAM/ERASE проверять на расходном блоке после сохранения raw-дампа; + убедиться осциллографом, что `WP#` поднимается только на время операции. ## Источники для проверки footprint diff --git a/docs/MEMORY_GAS.md b/docs/MEMORY_GAS.md index b760002..a91be51 100644 --- a/docs/MEMORY_GAS.md +++ b/docs/MEMORY_GAS.md @@ -18,6 +18,8 @@ parallel_nand_gas ▼ общее ядро parallel_nand ├─ TMS320F2812: XINTF + ├─ STM32F103RC: GPIO + ├─ STM32F103ZE: FSMC ├─ STM32F407VE: FSMC └─ STM32G474CE: GPIO ``` @@ -77,7 +79,7 @@ ARM. | Адрес | Доступ | Название | Значение | |---:|:---:|---|---| | `E000` | R | `SIGNATURE` | `0x4E44` (`ND`) | -| `E001` | R | `VERSION` | версия окна, сейчас 1 | +| `E001` | R | `VERSION` | версия окна, сейчас 2 | | `E002` | R | `STATUS` | состояние операции | | `E003` | R | `ERROR` | код `PNAND_*`, 0 = нет ошибки | | `E004` | W | `COMMAND` | команда окна | @@ -92,8 +94,9 @@ ARM. | `E00D` | R | `BAD_BLOCK` | 1 для физически плохого блока | | `E00E` | R | `GENERATION_LO` | счётчик загруженных страниц, low | | `E00F` | R | `GENERATION_HI` | счётчик загруженных страниц, high | -| `E100..E4FF` | R | `PAGE_DATA` | 1024 регистра main, два байта LE | -| `E500..E51F` | R | `PAGE_OOB` | 32 регистра OOB, два байта LE | +| `E010` | W | `CONFIRM` | `0xA55A` непосредственно перед разрушительной командой | +| `E100..E4FF` | R/W | `PAGE_DATA` | 1024 регистра main, два байта LE | +| `E500..E51F` | R/W | `PAGE_OOB` | 32 регистра OOB, два байта LE | Между управляющими регистрами и окнами данных есть зарезервированные адреса. Чтение блока через них не выполняется: каждый запрос должен начинаться внутри @@ -107,6 +110,8 @@ ARM. | 1 | `0x0002` | `BUSY` | | 2 | `0x0004` | `PAGE_VALID` | | 3 | `0x0008` | `BAD_BLOCK` | +| 4 | `0x0010` | `WRITE_SUPPORTED`, порт способен управлять `WP#` | +| 5 | `0x0020` | `BUFFER_DIRTY`, буфер изменён после чтения/очистки | | 15 | `0x8000` | `ERROR`, подробность в `E003` | GUI читает окно данных только при `READY=1`, `PAGE_VALID=1`, `ERROR=0`. @@ -118,6 +123,9 @@ GUI читает окно данных только при `READY=1`, `PAGE_VALI | 1 | `RESET` | сброс NAND и ожидание Ready | | 2 | `READ_ID` | обновляет `E00B/E00C` | | 3 | `READ_PAGE` | загружает main/OOB выбранной страницы | +| 4 | `CLEAR_BUFFER` | заполняет main/OOB значением `0xFF` для подготовки записи | +| 5 | `PROGRAM_PAGE` | программирует выбранную страницу из буфера | +| 6 | `ERASE_BLOCK` | стирает блок, содержащий выбранную страницу | Запись номера страницы и запуск — две отдельные операции. Сначала GUI пишет `PAGE_LO/PAGE_HI`, затем записывает `READ_PAGE` в `COMMAND`. Нельзя отправлять @@ -172,6 +180,28 @@ GUI хранит номер последней полностью записан ## PROGRAM/ERASE -Запись и стирание не включены намеренно. Они требуют отдельного разрешения, -проверки ECC, защиты загрузочных блоков и двухфазного подтверждения через GAS. -До добавления этих функций `WP#` удерживается активным аппаратным портом. +Разрушительные команды доступны только если установлен флаг +`WRITE_SUPPORTED`. Ядро проверяет bad-block marker и отказывается изменять +заводской плохой блок. `WP#` снимается только внутри операции и включается +обратно после проверки status fail-бита, тайм-аута или другой ошибки. + +Для программирования очищенной страницы: + +1. Записать `PAGE_LO/PAGE_HI`. +2. Выполнить `CLEAR_BUFFER`. +3. Записать необходимые main/OOB-регистры. Незаписанные байты останутся + `0xFF`; OOB лучше не менять без определённой ECC-разметки. +4. Записать `0xA55A` в `CONFIRM`. +5. Следующей записью без промежуточных операций отправить `PROGRAM_PAGE`. +6. Проверить `READY`, отсутствие `ERROR` и изменение `GENERATION`. + +Для стирания записать любую страницу целевого блока в `PAGE_LO/PAGE_HI`, затем +`0xA55A` в `CONFIRM` и сразу `ERASE_BLOCK`. Изменение страницы, буфера или +другого управляющего регистра сбрасывает подтверждение. После успешного +стирания `PAGE_VALID` очищается. + +Повторное программирование не может превратить `0` обратно в `1`: для этого +требуется стирание всего блока. Перед первым тестом сохраните полный raw-дамп, +исключите загрузочные блоки и используйте заведомо расходный исправный блок. +Библиотека не реализует ECC, wear leveling, журналирование и восстановление +после пропадания питания. diff --git a/include/parallel_nand.h b/include/parallel_nand.h index 7b6382d..615ab30 100644 --- a/include/parallel_nand.h +++ b/include/parallel_nand.h @@ -35,7 +35,12 @@ typedef enum { PNAND_ERROR_PORT = -2, PNAND_ERROR_TIMEOUT = -3, PNAND_ERROR_RANGE = -4, - PNAND_ERROR_BUFFER = -5 + PNAND_ERROR_BUFFER = -5, + PNAND_ERROR_WRITE_PROTECTED = -6, + PNAND_ERROR_PROGRAM_FAILED = -7, + PNAND_ERROR_ERASE_FAILED = -8, + PNAND_ERROR_BAD_BLOCK = -9, + PNAND_ERROR_CONFIRMATION = -10 } parallel_nand_result_t; typedef struct { @@ -71,6 +76,12 @@ int parallel_nand_read_page(parallel_nand_t *nand, unsigned long page, int parallel_nand_read_page_slice(parallel_nand_t *nand, unsigned long page, unsigned long column, pnand_octet_t *out, unsigned long count); +int parallel_nand_program_page(parallel_nand_t *nand, unsigned long page, + const pnand_octet_t *main_data, + unsigned long main_count, + const pnand_octet_t *oob, + unsigned long oob_count); +int parallel_nand_erase_block(parallel_nand_t *nand, unsigned long block); int parallel_nand_is_bad_block(parallel_nand_t *nand, unsigned long block, int *is_bad); unsigned long parallel_nand_page_count(const parallel_nand_t *nand); diff --git a/include/parallel_nand_gas.h b/include/parallel_nand_gas.h index 50238cc..261f0e9 100644 --- a/include/parallel_nand_gas.h +++ b/include/parallel_nand_gas.h @@ -30,20 +30,28 @@ enum { PNAND_GAS_REG_DEVICE_ID = 0x000C, PNAND_GAS_REG_BAD_BLOCK = 0x000D, PNAND_GAS_REG_GENERATION_LO = 0x000E, - PNAND_GAS_REG_GENERATION_HI = 0x000F + PNAND_GAS_REG_GENERATION_HI = 0x000F, + PNAND_GAS_REG_CONFIRM = 0x0010 }; enum { PNAND_GAS_CMD_NONE = 0, PNAND_GAS_CMD_RESET = 1, PNAND_GAS_CMD_READ_ID = 2, - PNAND_GAS_CMD_READ_PAGE = 3 + PNAND_GAS_CMD_READ_PAGE = 3, + PNAND_GAS_CMD_CLEAR_BUFFER = 4, + PNAND_GAS_CMD_PROGRAM_PAGE = 5, + PNAND_GAS_CMD_ERASE_BLOCK = 6 }; +#define PNAND_GAS_CONFIRM_MAGIC 0xA55AU + #define PNAND_GAS_STATUS_READY 0x0001U #define PNAND_GAS_STATUS_BUSY 0x0002U #define PNAND_GAS_STATUS_PAGE_VALID 0x0004U #define PNAND_GAS_STATUS_BAD_BLOCK 0x0008U +#define PNAND_GAS_STATUS_WRITE_SUPPORTED 0x0010U +#define PNAND_GAS_STATUS_BUFFER_DIRTY 0x0020U #define PNAND_GAS_STATUS_ERROR 0x8000U typedef struct { @@ -56,6 +64,7 @@ typedef struct { unsigned long cached_block; int cached_bad_block; int block_cache_valid; + int destructive_armed; pnand_octet_t id[8]; pnand_octet_t main_data[PNAND_MT29F1G08_PAGE_MAIN]; pnand_octet_t oob[PNAND_MT29F1G08_PAGE_OOB]; diff --git a/include/parallel_nand_port.h b/include/parallel_nand_port.h index a9e73c1..8bf93e3 100644 --- a/include/parallel_nand_port.h +++ b/include/parallel_nand_port.h @@ -19,6 +19,8 @@ typedef struct { int (*is_ready)(void); void (*set_write_protect)(int enabled); void (*delay_us)(unsigned long us); + /* Nonzero only when set_write_protect(0) physically releases WP#. */ + int write_supported; } parallel_nand_port_t; #ifdef __cplusplus diff --git a/ports/stm32f103rc/README.md b/ports/stm32f103rc/README.md index aaba927..21ce9b6 100644 --- a/ports/stm32f103rc/README.md +++ b/ports/stm32f103rc/README.md @@ -47,5 +47,5 @@ int nand_init(void) ``` После успешной инициализации прочитайте четыре байта ID и ожидайте -`AD DC 80 95`. Ядро библиотеки работает только на чтение и удерживает `WP#` -в активном низком уровне; PROGRAM/ERASE пока намеренно не реализованы. +`AD DC 80 95`. Порт поддерживает PROGRAM/ERASE: ядро держит `WP#` в активном +низком уровне между операциями и поднимает его только на время изменения NAND. diff --git a/ports/stm32f103rc/parallel_nand_port_stm32f103rc.c b/ports/stm32f103rc/parallel_nand_port_stm32f103rc.c index 1a4d8f4..cfa5f39 100644 --- a/ports/stm32f103rc/parallel_nand_port_stm32f103rc.c +++ b/ports/stm32f103rc/parallel_nand_port_stm32f103rc.c @@ -133,7 +133,8 @@ static void f103rc_delay_us(unsigned long us) static const parallel_nand_port_t f103rc_port = { f103rc_init, f103rc_select, f103rc_deselect, f103rc_write_command, f103rc_write_address, f103rc_write_data, - f103rc_read_data, f103rc_is_ready, f103rc_set_wp, f103rc_delay_us + f103rc_read_data, f103rc_is_ready, f103rc_set_wp, f103rc_delay_us, + 1 }; const parallel_nand_port_t *parallel_nand_port_stm32f103rc(void) diff --git a/ports/stm32f103ze/README.md b/ports/stm32f103ze/README.md index 0be0c35..115e7cd 100644 --- a/ports/stm32f103ze/README.md +++ b/ports/stm32f103ze/README.md @@ -57,3 +57,6 @@ int nand_init(void) Начальные тайминги намеренно консервативны. После запуска проверьте осциллографом длительности `WE#`/`RE#` и время установки данных. Ожидаемый ID HY27UF084G2M: `AD DC 80 95`. + +Порт поддерживает PROGRAM/ERASE. `WP#` на `PG8` остаётся активным между +операциями и снимается ядром только на время программирования или стирания. diff --git a/ports/stm32f103ze/parallel_nand_port_stm32f103ze.c b/ports/stm32f103ze/parallel_nand_port_stm32f103ze.c index e1e6a3e..83134ef 100644 --- a/ports/stm32f103ze/parallel_nand_port_stm32f103ze.c +++ b/ports/stm32f103ze/parallel_nand_port_stm32f103ze.c @@ -174,7 +174,8 @@ static const parallel_nand_port_t f103ze_port = { f103ze_read_data, f103ze_is_ready, f103ze_set_write_protect, - f103ze_delay_us + f103ze_delay_us, + 1 }; const parallel_nand_port_t *parallel_nand_port_stm32f103ze(void) diff --git a/ports/stm32f407ve/README.md b/ports/stm32f407ve/README.md index 0decb57..0ec2c78 100644 --- a/ports/stm32f407ve/README.md +++ b/ports/stm32f407ve/README.md @@ -24,3 +24,8 @@ `stm32f4xx_ll_fsmc.c`, включить FSMC и не создавать второй `NAND_HandleTypeDef` для Bank 2. Начальные тайминги консервативны; финальные значения подтвердить осциллографом. + +В базовой схеме `WP#` аппаратно зафиксирован в активном уровне, поэтому порт +объявляет `write_supported = 0`, а PROGRAM/ERASE возвращают +`PNAND_ERROR_WRITE_PROTECTED`. Для записи назначьте отдельный GPIO с внешним +pull-down, реализуйте управление `WP#` и включите признак поддержки порта. diff --git a/ports/stm32f407ve/parallel_nand_port_stm32f407ve.c b/ports/stm32f407ve/parallel_nand_port_stm32f407ve.c index 50290c2..d2fab45 100644 --- a/ports/stm32f407ve/parallel_nand_port_stm32f407ve.c +++ b/ports/stm32f407ve/parallel_nand_port_stm32f407ve.c @@ -84,7 +84,8 @@ static void f407_delay_us(unsigned long us) static const parallel_nand_port_t f407_port = { f407_init, f407_select, f407_deselect, f407_write_command, f407_write_address, f407_write_data, - f407_read_data, f407_is_ready, f407_set_wp, f407_delay_us + f407_read_data, f407_is_ready, f407_set_wp, f407_delay_us, + 0 }; const parallel_nand_port_t *parallel_nand_port_stm32f407ve(void) diff --git a/ports/stm32g474ce/README.md b/ports/stm32g474ce/README.md index 24e07e6..8a938ad 100644 --- a/ports/stm32g474ce/README.md +++ b/ports/stm32g474ce/README.md @@ -19,3 +19,6 @@ HAL только для инициализации, а циклы NAND выпо Выбранные `PB3/PB4` нельзя одновременно использовать для trace/JTAG; SWD на `PA13/PA14` остаётся доступен. Если эти пины нужны проекту, измените таблицу и операции шины: текущая оптимизация рассчитана на непрерывные `PB0..PB7`. + +Порт поддерживает PROGRAM/ERASE через управляемый `WP#` на `PA5`. Внешний +pull-down сохраняет защиту до инициализации GPIO и при сбросе МК. diff --git a/ports/stm32g474ce/parallel_nand_port_stm32g474ce.c b/ports/stm32g474ce/parallel_nand_port_stm32g474ce.c index 3f18b64..b9819b6 100644 --- a/ports/stm32g474ce/parallel_nand_port_stm32g474ce.c +++ b/ports/stm32g474ce/parallel_nand_port_stm32g474ce.c @@ -125,7 +125,8 @@ static void g474_delay_us(unsigned long us) static const parallel_nand_port_t g474_port = { g474_init, g474_select, g474_deselect, g474_write_command, g474_write_address, g474_write_data, - g474_read_data, g474_is_ready, g474_set_wp, g474_delay_us + g474_read_data, g474_is_ready, g474_set_wp, g474_delay_us, + 1 }; const parallel_nand_port_t *parallel_nand_port_stm32g474ce(void) diff --git a/ports/tms320f2812/README.md b/ports/tms320f2812/README.md index 7a33632..db1dfd1 100644 --- a/ports/tms320f2812/README.md +++ b/ports/tms320f2812/README.md @@ -13,9 +13,10 @@ Адреса C28x задаются в 16-битных словах: data `0x100000`, command `0x100001`, address `0x100002`. Используется только младший байт XINTF. -`WP#` в базовом варианте аппаратный: для режима только чтения соединить с GND -через 10 кОм; если позже понадобится PROGRAM/ERASE, заменить на GPIO и реализовать -`tms_set_wp()`. +`WP#` в базовом варианте аппаратный и соединён с GND через 10 кОм. Поэтому +порт объявляет `write_supported = 0`, а PROGRAM/ERASE возвращают +`PNAND_ERROR_WRITE_PROTECTED`. Для записи нужно перенести `WP#` на GPIO, +реализовать `tms_set_wp()` и установить признак поддержки в структуре порта. Задержка в порте приблизительная. После выбора частоты CPU проверить импульсы осциллографом и при необходимости изменить `PNAND_TMS_CPU_MHZ`. diff --git a/ports/tms320f2812/parallel_nand_port_tms320f2812.c b/ports/tms320f2812/parallel_nand_port_tms320f2812.c index b1dda6d..d82f325 100644 --- a/ports/tms320f2812/parallel_nand_port_tms320f2812.c +++ b/ports/tms320f2812/parallel_nand_port_tms320f2812.c @@ -56,7 +56,8 @@ static void tms_set_wp(int enabled) static const parallel_nand_port_t tms_port = { tms_init, tms_select, tms_deselect, tms_write_command, tms_write_address, tms_write_data, - tms_read_data, tms_is_ready, tms_set_wp, tms_delay_us + tms_read_data, tms_is_ready, tms_set_wp, tms_delay_us, + 0 }; const parallel_nand_port_t *parallel_nand_port_tms320f2812(void) diff --git a/src/parallel_nand.c b/src/parallel_nand.c index bd182a9..90d28b1 100644 --- a/src/parallel_nand.c +++ b/src/parallel_nand.c @@ -4,8 +4,15 @@ #define PNAND_CMD_READ_2 0x30U #define PNAND_CMD_READ_ID 0x90U #define PNAND_CMD_READ_STATUS 0x70U +#define PNAND_CMD_PROGRAM_1 0x80U +#define PNAND_CMD_PROGRAM_2 0x10U +#define PNAND_CMD_ERASE_1 0x60U +#define PNAND_CMD_ERASE_2 0xD0U #define PNAND_CMD_RESET 0xFFU +#define PNAND_STATUS_FAIL 0x01U +#define PNAND_STATUS_WP 0x80U + const parallel_nand_geometry_t parallel_nand_mt29f1g08_geometry = { PNAND_MT29F1G08_PAGE_MAIN, PNAND_MT29F1G08_PAGE_OOB, @@ -67,6 +74,8 @@ int parallel_nand_reset(parallel_nand_t *nand) nand->port->select(); nand->port->write_command(PNAND_CMD_RESET); nand->port->deselect(); + /* R/B# may not fall until tWB after the command cycle. */ + nand->port->delay_us(1UL); return wait_ready(nand); } @@ -80,7 +89,7 @@ int parallel_nand_init(parallel_nand_t *nand, nand->geometry = *geometry; if (port->init() != 0) return PNAND_ERROR_PORT; port->deselect(); - /* Ядро пока только читает: аппаратно запрещаем PROGRAM/ERASE. */ + /* WP# stays asserted except for the shortest possible mutation window. */ port->set_write_protect(1); return parallel_nand_reset(nand); } @@ -132,6 +141,7 @@ static int begin_page_read(parallel_nand_t *nand, unsigned long page, } nand->port->write_command(PNAND_CMD_READ_2); nand->port->deselect(); + nand->port->delay_us(1UL); /* Cover tWB before polling R/B#. */ return wait_ready(nand); } @@ -177,6 +187,101 @@ int parallel_nand_read_page(parallel_nand_t *nand, unsigned long page, return PNAND_OK; } +static void write_address_cycles(parallel_nand_t *nand, + unsigned long value, + unsigned int count) +{ + unsigned int i; + for (i = 0U; i < count; i++) { + nand->port->write_address((pnand_octet_t)(value & 0xFFUL)); + value >>= 8; + } +} + +static int mutation_supported(const parallel_nand_t *nand) +{ + return nand != 0 && port_valid(nand->port) && + nand->port->write_data != 0 && + nand->port->write_supported != 0; +} + +static int finish_mutation(parallel_nand_t *nand, int failure_result) +{ + pnand_octet_t status = 0U; + int result = wait_ready(nand); + if (result == PNAND_OK) + result = parallel_nand_read_status(nand, &status); + nand->port->set_write_protect(1); + if (result != PNAND_OK) return result; + if ((status & PNAND_STATUS_WP) == 0U) + return PNAND_ERROR_WRITE_PROTECTED; + return (status & PNAND_STATUS_FAIL) != 0U ? failure_result : PNAND_OK; +} + +int parallel_nand_program_page(parallel_nand_t *nand, unsigned long page, + const pnand_octet_t *main_data, + unsigned long main_count, + const pnand_octet_t *oob, + unsigned long oob_count) +{ + unsigned long i; + unsigned long block; + int bad = 0; + int result; + if (nand == 0 || main_data == 0) return PNAND_ERROR_ARGUMENT; + if (!mutation_supported(nand)) return PNAND_ERROR_WRITE_PROTECTED; + if (page >= parallel_nand_page_count(nand)) return PNAND_ERROR_RANGE; + if (main_count != nand->geometry.page_main_size) + return PNAND_ERROR_BUFFER; + if ((oob == 0 && oob_count != 0UL) || + (oob != 0 && oob_count != nand->geometry.page_oob_size)) + return PNAND_ERROR_BUFFER; + + block = page / nand->geometry.pages_per_block; + result = parallel_nand_is_bad_block(nand, block, &bad); + if (result != PNAND_OK) return result; + if (bad) return PNAND_ERROR_BAD_BLOCK; + + nand->port->set_write_protect(0); + nand->port->delay_us(1UL); + nand->port->select(); + nand->port->write_command(PNAND_CMD_PROGRAM_1); + write_address_cycles(nand, 0UL, nand->geometry.column_cycles); + write_address_cycles(nand, page, nand->geometry.row_cycles); + for (i = 0UL; i < main_count; i++) + nand->port->write_data(main_data[i] & 0xFFU); + for (i = 0UL; i < oob_count; i++) + nand->port->write_data(oob[i] & 0xFFU); + nand->port->write_command(PNAND_CMD_PROGRAM_2); + nand->port->deselect(); + nand->port->delay_us(1UL); /* Cover tWB before polling R/B#. */ + return finish_mutation(nand, PNAND_ERROR_PROGRAM_FAILED); +} + +int parallel_nand_erase_block(parallel_nand_t *nand, unsigned long block) +{ + unsigned long first_page; + int bad = 0; + int result; + if (nand == 0) return PNAND_ERROR_ARGUMENT; + if (!mutation_supported(nand)) return PNAND_ERROR_WRITE_PROTECTED; + if (block >= nand->geometry.block_count) return PNAND_ERROR_RANGE; + result = parallel_nand_is_bad_block(nand, block, &bad); + if (result != PNAND_OK) return result; + if (bad) return PNAND_ERROR_BAD_BLOCK; + + first_page = block * nand->geometry.pages_per_block; + nand->port->set_write_protect(0); + nand->port->delay_us(1UL); + nand->port->select(); + nand->port->write_command(PNAND_CMD_ERASE_1); + write_address_cycles(nand, first_page, nand->geometry.row_cycles); + nand->port->write_command(PNAND_CMD_ERASE_2); + nand->port->deselect(); + nand->port->delay_us(1UL); /* Cover tWB before polling R/B#. */ + return finish_mutation(nand, PNAND_ERROR_ERASE_FAILED); +} + int parallel_nand_is_bad_block(parallel_nand_t *nand, unsigned long block, int *is_bad) { diff --git a/src/parallel_nand_gas.c b/src/parallel_nand_gas.c index 461374b..996d24d 100644 --- a/src/parallel_nand_gas.c +++ b/src/parallel_nand_gas.c @@ -6,6 +6,20 @@ static unsigned int pack_pair(const pnand_octet_t *data, unsigned int index) ((data[index * 2U + 1U] & 0xFFU) << 8)); } +static void unpack_pair(pnand_octet_t *data, unsigned int index, + unsigned int value) +{ + data[index * 2U] = (pnand_octet_t)(value & 0xFFU); + data[index * 2U + 1U] = (pnand_octet_t)((value >> 8) & 0xFFU); +} + +static unsigned int persistent_status(const parallel_nand_gas_t *gas) +{ + return gas->nand->port->write_supported != 0 + ? PNAND_GAS_STATUS_WRITE_SUPPORTED + : 0U; +} + parallel_nand_gas_result_t parallel_nand_gas_read( parallel_nand_gas_t *gas, unsigned int address, unsigned int *value) { @@ -13,11 +27,16 @@ parallel_nand_gas_result_t parallel_nand_gas_read( const parallel_nand_geometry_t *g; if (gas == 0 || value == 0 || address < gas->base) return PNAND_GAS_REJECTED; + /* Confirmation authorizes exactly the immediately following write. */ + gas->destructive_armed = 0; g = &gas->nand->geometry; offset = address - gas->base; if (offset >= PNAND_GAS_REGION_REGS) return PNAND_GAS_NO_REG; if (offset >= PNAND_GAS_DATA_OFFSET && offset < PNAND_GAS_DATA_OFFSET + PNAND_GAS_DATA_REGS) { + if ((unsigned long)(offset - PNAND_GAS_DATA_OFFSET) * 2UL >= + g->page_main_size) + return PNAND_GAS_NO_REG; if ((gas->status & PNAND_GAS_STATUS_PAGE_VALID) == 0U) return PNAND_GAS_REJECTED; *value = pack_pair(gas->main_data, @@ -26,6 +45,9 @@ parallel_nand_gas_result_t parallel_nand_gas_read( } if (offset >= PNAND_GAS_OOB_OFFSET && offset < PNAND_GAS_OOB_OFFSET + PNAND_GAS_OOB_REGS) { + if ((unsigned long)(offset - PNAND_GAS_OOB_OFFSET) * 2UL >= + g->page_oob_size) + return PNAND_GAS_NO_REG; if ((gas->status & PNAND_GAS_STATUS_PAGE_VALID) == 0U) return PNAND_GAS_REJECTED; *value = pack_pair(gas->oob, @@ -34,7 +56,7 @@ parallel_nand_gas_result_t parallel_nand_gas_read( } switch (offset) { case PNAND_GAS_REG_SIGNATURE: *value = 0x4E44U; break; /* ND */ - case PNAND_GAS_REG_VERSION: *value = 0x0001U; break; + case PNAND_GAS_REG_VERSION: *value = 0x0002U; break; case PNAND_GAS_REG_STATUS: *value = gas->status; break; case PNAND_GAS_REG_ERROR: *value = (unsigned int)gas->error & 0xFFFFU; break; case PNAND_GAS_REG_COMMAND: *value = PNAND_GAS_CMD_NONE; break; @@ -50,6 +72,7 @@ parallel_nand_gas_result_t parallel_nand_gas_read( *value = (gas->status & PNAND_GAS_STATUS_BAD_BLOCK) != 0U; break; case PNAND_GAS_REG_GENERATION_LO: *value = (unsigned int)gas->generation & 0xFFFFU; break; case PNAND_GAS_REG_GENERATION_HI: *value = (unsigned int)(gas->generation >> 16) & 0xFFFFU; break; + case PNAND_GAS_REG_CONFIRM: *value = 0U; break; default: return PNAND_GAS_NO_REG; } *value &= 0xFFFFU; @@ -60,7 +83,7 @@ static void finish(parallel_nand_gas_t *gas, int result) { gas->error = result; gas->status &= (0xFFFFU ^ PNAND_GAS_STATUS_BUSY); - gas->status |= PNAND_GAS_STATUS_READY; + gas->status |= PNAND_GAS_STATUS_READY | persistent_status(gas); if (result != PNAND_OK) gas->status |= PNAND_GAS_STATUS_ERROR; } @@ -69,7 +92,11 @@ static parallel_nand_gas_result_t run_command(parallel_nand_gas_t *gas, { int result = PNAND_OK; int bad = 0; - gas->status = PNAND_GAS_STATUS_BUSY; + int armed = gas->destructive_armed; + unsigned int i; + unsigned int previous_status = gas->status; + gas->destructive_armed = 0; + gas->status = PNAND_GAS_STATUS_BUSY | persistent_status(gas); if (command == PNAND_GAS_CMD_RESET) { result = parallel_nand_reset(gas->nand); } else if (command == PNAND_GAS_CMD_READ_ID) { @@ -101,8 +128,59 @@ static parallel_nand_gas_result_t run_command(parallel_nand_gas_t *gas, if (result == PNAND_OK && bad) gas->status |= PNAND_GAS_STATUS_BAD_BLOCK; } + } else if (command == PNAND_GAS_CMD_CLEAR_BUFFER) { + for (i = 0U; i < PNAND_MT29F1G08_PAGE_MAIN; i++) + gas->main_data[i] = 0xFFU; + for (i = 0U; i < PNAND_MT29F1G08_PAGE_OOB; i++) + gas->oob[i] = 0xFFU; + gas->status |= PNAND_GAS_STATUS_PAGE_VALID | + PNAND_GAS_STATUS_BUFFER_DIRTY; + gas->generation++; + } else if (command == PNAND_GAS_CMD_PROGRAM_PAGE) { + if (!armed || + (previous_status & PNAND_GAS_STATUS_PAGE_VALID) == 0U) { + gas->status |= previous_status & + (PNAND_GAS_STATUS_PAGE_VALID | + PNAND_GAS_STATUS_BUFFER_DIRTY | + PNAND_GAS_STATUS_BAD_BLOCK); + finish(gas, PNAND_ERROR_CONFIRMATION); + return PNAND_GAS_REJECTED; + } + result = parallel_nand_program_page( + gas->nand, gas->selected_page, + gas->main_data, gas->nand->geometry.page_main_size, + gas->oob, gas->nand->geometry.page_oob_size); + gas->status |= PNAND_GAS_STATUS_PAGE_VALID; + if (result == PNAND_OK) { + gas->generation++; + gas->block_cache_valid = 0; + } else { + gas->status |= PNAND_GAS_STATUS_BUFFER_DIRTY; + } + } else if (command == PNAND_GAS_CMD_ERASE_BLOCK) { + unsigned long block; + if (!armed || + gas->selected_page >= parallel_nand_page_count(gas->nand)) { + finish(gas, !armed ? PNAND_ERROR_CONFIRMATION : + PNAND_ERROR_RANGE); + return PNAND_GAS_REJECTED; + } + block = gas->selected_page / gas->nand->geometry.pages_per_block; + result = parallel_nand_erase_block(gas->nand, block); + if (result == PNAND_OK) { + gas->status &= (0xFFFFU ^ (PNAND_GAS_STATUS_PAGE_VALID | + PNAND_GAS_STATUS_BUFFER_DIRTY | + PNAND_GAS_STATUS_BAD_BLOCK)); + gas->generation++; + gas->block_cache_valid = 0; + } else { + gas->status |= previous_status & + (PNAND_GAS_STATUS_PAGE_VALID | + PNAND_GAS_STATUS_BUFFER_DIRTY | + PNAND_GAS_STATUS_BAD_BLOCK); + } } else { - gas->status = PNAND_GAS_STATUS_READY; + gas->status = PNAND_GAS_STATUS_READY | persistent_status(gas); return PNAND_GAS_REJECTED; } finish(gas, result); @@ -119,18 +197,47 @@ parallel_nand_gas_result_t parallel_nand_gas_write( if ((gas->status & PNAND_GAS_STATUS_BUSY) != 0U) return PNAND_GAS_REJECTED; value &= 0xFFFFU; + if (offset >= PNAND_GAS_DATA_OFFSET && + offset < PNAND_GAS_DATA_OFFSET + PNAND_GAS_DATA_REGS) { + unsigned int index = offset - PNAND_GAS_DATA_OFFSET; + if ((gas->status & PNAND_GAS_STATUS_PAGE_VALID) == 0U || + (unsigned long)index * 2UL >= gas->nand->geometry.page_main_size) + return PNAND_GAS_REJECTED; + gas->destructive_armed = 0; + unpack_pair(gas->main_data, index, value); + gas->status |= PNAND_GAS_STATUS_BUFFER_DIRTY; + return PNAND_GAS_OK; + } + if (offset >= PNAND_GAS_OOB_OFFSET && + offset < PNAND_GAS_OOB_OFFSET + PNAND_GAS_OOB_REGS) { + unsigned int index = offset - PNAND_GAS_OOB_OFFSET; + if ((gas->status & PNAND_GAS_STATUS_PAGE_VALID) == 0U || + (unsigned long)index * 2UL >= gas->nand->geometry.page_oob_size) + return PNAND_GAS_REJECTED; + gas->destructive_armed = 0; + unpack_pair(gas->oob, index, value); + gas->status |= PNAND_GAS_STATUS_BUFFER_DIRTY; + return PNAND_GAS_OK; + } switch (offset) { case PNAND_GAS_REG_COMMAND: return run_command(gas, value); + case PNAND_GAS_REG_CONFIRM: + gas->destructive_armed = value == PNAND_GAS_CONFIRM_MAGIC; + return gas->destructive_armed ? PNAND_GAS_OK : PNAND_GAS_REJECTED; case PNAND_GAS_REG_PAGE_LO: + gas->destructive_armed = 0; gas->selected_page = (gas->selected_page & 0xFFFF0000UL) | value; gas->status &= (0xFFFFU ^ PNAND_GAS_STATUS_PAGE_VALID); return PNAND_GAS_OK; case PNAND_GAS_REG_PAGE_HI: + gas->destructive_armed = 0; gas->selected_page = (gas->selected_page & 0x0000FFFFUL) | ((unsigned long)value << 16); gas->status &= (0xFFFFU ^ PNAND_GAS_STATUS_PAGE_VALID); return PNAND_GAS_OK; - default: return PNAND_GAS_READ_ONLY; + default: + gas->destructive_armed = 0; + return PNAND_GAS_READ_ONLY; } } @@ -140,12 +247,17 @@ void parallel_nand_gas_init(parallel_nand_gas_t *gas, unsigned int i; gas->nand = nand; gas->base = base; - gas->status = PNAND_GAS_STATUS_READY; + gas->status = PNAND_GAS_STATUS_READY | persistent_status(gas); gas->error = PNAND_OK; gas->selected_page = 0UL; gas->generation = 0UL; gas->cached_block = 0UL; gas->cached_bad_block = 0; gas->block_cache_valid = 0; + gas->destructive_armed = 0; for (i = 0U; i < 8U; i++) gas->id[i] = 0U; + for (i = 0U; i < PNAND_MT29F1G08_PAGE_MAIN; i++) + gas->main_data[i] = 0xFFU; + for (i = 0U; i < PNAND_MT29F1G08_PAGE_OOB; i++) + gas->oob[i] = 0xFFU; } diff --git a/tests/test_parallel_nand.c b/tests/test_parallel_nand.c index d2ae00a..82dc797 100644 --- a/tests/test_parallel_nand.c +++ b/tests/test_parallel_nand.c @@ -5,7 +5,7 @@ #include "parallel_nand_gas.h" typedef struct { char kind; unsigned int value; } event_t; -static event_t events[64]; +static event_t events[256]; static unsigned int event_count; static pnand_octet_t input_data[32]; static unsigned int input_count; @@ -33,17 +33,35 @@ static void fake_delay(unsigned long us) { (void)us; } static const parallel_nand_port_t fake_port = { fake_init, fake_select, fake_deselect, fake_cmd, fake_addr, fake_write, - fake_read, fake_ready, fake_wp, fake_delay + fake_read, fake_ready, fake_wp, fake_delay, 1 }; static void clear_events(void) { event_count = 0U; } +static unsigned int find_event(unsigned int start, char kind, + unsigned int value) +{ + unsigned int i; + for (i = start; i < event_count; i++) { + if (events[i].kind == kind && events[i].value == value) return i; + } + return event_count; +} + int main(void) { parallel_nand_t nand; pnand_octet_t id[2]; pnand_octet_t bytes[2]; + pnand_octet_t main_data[2] = {0xAAU, 0x55U}; + pnand_octet_t oob[2] = {0xFFU, 0xFEU}; parallel_nand_gas_t gas; + parallel_nand_geometry_t small_geometry = { + 2UL, 2UL, 2UL, 4UL, 2U, 2U, 100UL + }; + parallel_nand_port_t read_only_port; + parallel_nand_t read_only_nand; + unsigned int pos; unsigned int reg; assert(parallel_nand_init(&nand, &fake_port, @@ -96,9 +114,96 @@ int main(void) assert(events[6].kind == 'A' && events[6].value == 0x02U); assert(events[7].kind == 'C' && events[7].value == 0x30U); + assert(parallel_nand_init(&nand, &fake_port, &small_geometry) == + PNAND_OK); + clear_events(); + input_data[0] = 0xFFU; + input_data[1] = 0xFFU; + input_data[2] = 0x80U; + input_count = 3U; + input_pos = 0U; + assert(parallel_nand_program_page(&nand, 3UL, main_data, 2UL, + oob, 2UL) == PNAND_OK); + pos = find_event(0U, 'P', 0U); + assert(pos < event_count); + pos = find_event(pos + 1U, 'C', 0x80U); + assert(pos < event_count); + pos = find_event(pos + 1U, 'W', 0xAAU); + assert(pos < event_count); + pos = find_event(pos + 1U, 'W', 0x55U); + assert(pos < event_count); + pos = find_event(pos + 1U, 'W', 0xFFU); + assert(pos < event_count); + pos = find_event(pos + 1U, 'W', 0xFEU); + assert(pos < event_count); + pos = find_event(pos + 1U, 'C', 0x10U); + assert(pos < event_count); + pos = find_event(pos + 1U, 'C', 0x70U); + assert(pos < event_count); + assert(events[event_count - 1U].kind == 'P' && + events[event_count - 1U].value == 1U); + + clear_events(); + input_data[0] = 0xFFU; + input_data[1] = 0xFFU; + input_data[2] = 0x80U; + input_count = 3U; + input_pos = 0U; + assert(parallel_nand_erase_block(&nand, 2UL) == PNAND_OK); + pos = find_event(0U, 'P', 0U); + pos = find_event(pos + 1U, 'C', 0x60U); + assert(pos < event_count); + pos = find_event(pos + 1U, 'A', 0x04U); + assert(pos < event_count); + pos = find_event(pos + 1U, 'C', 0xD0U); + assert(pos < event_count); + assert(events[event_count - 1U].kind == 'P' && + events[event_count - 1U].value == 1U); + + clear_events(); + input_data[0] = 0xFFU; + input_data[1] = 0xFFU; + input_data[2] = 0x81U; + input_count = 3U; + input_pos = 0U; + assert(parallel_nand_program_page(&nand, 0UL, main_data, 2UL, + 0, 0UL) == + PNAND_ERROR_PROGRAM_FAILED); + assert(events[event_count - 1U].kind == 'P' && + events[event_count - 1U].value == 1U); + + clear_events(); + input_data[0] = 0xFFU; + input_data[1] = 0xFFU; + input_data[2] = 0x00U; + input_count = 3U; + input_pos = 0U; + assert(parallel_nand_program_page(&nand, 0UL, main_data, 2UL, + 0, 0UL) == + PNAND_ERROR_WRITE_PROTECTED); + assert(events[event_count - 1U].kind == 'P' && + events[event_count - 1U].value == 1U); + + clear_events(); + input_data[0] = 0x00U; + input_count = 1U; + input_pos = 0U; + assert(parallel_nand_erase_block(&nand, 0UL) == + PNAND_ERROR_BAD_BLOCK); + assert(find_event(0U, 'P', 0U) == event_count); + + read_only_port = fake_port; + read_only_port.write_supported = 0; + assert(parallel_nand_init(&read_only_nand, &read_only_port, + &small_geometry) == PNAND_OK); + assert(parallel_nand_erase_block(&read_only_nand, 0UL) == + PNAND_ERROR_WRITE_PROTECTED); + parallel_nand_gas_init(&gas, &nand, PNAND_GAS_DEFAULT_BASE); assert(parallel_nand_gas_read(&gas, 0xE000U, ®) == PNAND_GAS_OK); assert(reg == 0x4E44U); + assert(parallel_nand_gas_read(&gas, 0xE001U, ®) == PNAND_GAS_OK); + assert(reg == 0x0002U); assert(parallel_nand_gas_write(&gas, 0xE005U, 0x1234U) == PNAND_GAS_OK); assert(parallel_nand_gas_write(&gas, 0xE006U, 0x0001U) == @@ -112,6 +217,56 @@ int main(void) assert(parallel_nand_gas_write(&gas, 0xE000U, 0U) == PNAND_GAS_READ_ONLY); + assert((gas.status & PNAND_GAS_STATUS_WRITE_SUPPORTED) != 0U); + assert(parallel_nand_gas_write(&gas, 0xE005U, 0U) == PNAND_GAS_OK); + assert(parallel_nand_gas_write(&gas, 0xE006U, 0U) == PNAND_GAS_OK); + assert(parallel_nand_gas_write(&gas, 0xE004U, + PNAND_GAS_CMD_CLEAR_BUFFER) == + PNAND_GAS_OK); + assert(parallel_nand_gas_write(&gas, 0xE100U, 0x1234U) == + PNAND_GAS_OK); + assert(gas.main_data[0] == 0x34U && gas.main_data[1] == 0x12U); + assert(parallel_nand_gas_write(&gas, 0xE004U, + PNAND_GAS_CMD_PROGRAM_PAGE) == + PNAND_GAS_REJECTED); + assert(gas.error == PNAND_ERROR_CONFIRMATION); + + assert(parallel_nand_gas_write(&gas, 0xE010U, + PNAND_GAS_CONFIRM_MAGIC) == + PNAND_GAS_OK); + assert(parallel_nand_gas_read(&gas, 0xE002U, ®) == PNAND_GAS_OK); + assert(parallel_nand_gas_write(&gas, 0xE004U, + PNAND_GAS_CMD_PROGRAM_PAGE) == + PNAND_GAS_REJECTED); + assert(gas.error == PNAND_ERROR_CONFIRMATION); + + input_data[0] = 0xFFU; + input_data[1] = 0xFFU; + input_data[2] = 0x80U; + input_count = 3U; + input_pos = 0U; + assert(parallel_nand_gas_write(&gas, 0xE010U, + PNAND_GAS_CONFIRM_MAGIC) == + PNAND_GAS_OK); + assert(parallel_nand_gas_write(&gas, 0xE004U, + PNAND_GAS_CMD_PROGRAM_PAGE) == + PNAND_GAS_OK); + assert((gas.status & PNAND_GAS_STATUS_BUFFER_DIRTY) == 0U); + + assert(parallel_nand_gas_write(&gas, 0xE005U, 2U) == PNAND_GAS_OK); + input_data[0] = 0xFFU; + input_data[1] = 0xFFU; + input_data[2] = 0x80U; + input_count = 3U; + input_pos = 0U; + assert(parallel_nand_gas_write(&gas, 0xE010U, + PNAND_GAS_CONFIRM_MAGIC) == + PNAND_GAS_OK); + assert(parallel_nand_gas_write(&gas, 0xE004U, + PNAND_GAS_CMD_ERASE_BLOCK) == + PNAND_GAS_OK); + assert((gas.status & PNAND_GAS_STATUS_PAGE_VALID) == 0U); + puts("parallel_nand: OK"); return 0; }