From 20850b26df2c72a1fc21cd8a4146a57ebe06c204 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 4 Sep 2026 12:22:21 +0300 Subject: [PATCH] =?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 --- README.md | 1 + c/parallel-nand/CMakeLists.txt | 26 +++ c/parallel-nand/README.md | 88 ++++++++ c/parallel-nand/docs/EMPTY_BOARD_PINOUT.md | 81 ++++++++ c/parallel-nand/docs/MEMORY_GAS.md | 177 ++++++++++++++++ c/parallel-nand/include/parallel_nand.h | 68 +++++++ c/parallel-nand/include/parallel_nand_gas.h | 84 ++++++++ .../include/parallel_nand_pcan_gas.h | 11 + c/parallel-nand/include/parallel_nand_port.h | 28 +++ c/parallel-nand/ports/stm32f407ve/README.md | 26 +++ .../parallel_nand_port_stm32f407ve.c | 93 +++++++++ .../parallel_nand_port_stm32f407ve.h | 8 + c/parallel-nand/ports/stm32g474ce/README.md | 21 ++ .../parallel_nand_port_stm32g474ce.c | 134 ++++++++++++ .../parallel_nand_port_stm32g474ce.h | 8 + c/parallel-nand/ports/tms320f2812/README.md | 21 ++ .../parallel_nand_port_tms320f2812.c | 65 ++++++ .../parallel_nand_port_tms320f2812.h | 8 + c/parallel-nand/src/parallel_nand.c | 191 ++++++++++++++++++ c/parallel-nand/src/parallel_nand_gas.c | 151 ++++++++++++++ c/parallel-nand/src/parallel_nand_pcan_gas.c | 43 ++++ c/parallel-nand/tests/test_parallel_nand.c | 97 +++++++++ 22 files changed, 1430 insertions(+) create mode 100644 c/parallel-nand/CMakeLists.txt create mode 100644 c/parallel-nand/README.md create mode 100644 c/parallel-nand/docs/EMPTY_BOARD_PINOUT.md create mode 100644 c/parallel-nand/docs/MEMORY_GAS.md create mode 100644 c/parallel-nand/include/parallel_nand.h create mode 100644 c/parallel-nand/include/parallel_nand_gas.h create mode 100644 c/parallel-nand/include/parallel_nand_pcan_gas.h create mode 100644 c/parallel-nand/include/parallel_nand_port.h create mode 100644 c/parallel-nand/ports/stm32f407ve/README.md create mode 100644 c/parallel-nand/ports/stm32f407ve/parallel_nand_port_stm32f407ve.c create mode 100644 c/parallel-nand/ports/stm32f407ve/parallel_nand_port_stm32f407ve.h create mode 100644 c/parallel-nand/ports/stm32g474ce/README.md create mode 100644 c/parallel-nand/ports/stm32g474ce/parallel_nand_port_stm32g474ce.c create mode 100644 c/parallel-nand/ports/stm32g474ce/parallel_nand_port_stm32g474ce.h create mode 100644 c/parallel-nand/ports/tms320f2812/README.md create mode 100644 c/parallel-nand/ports/tms320f2812/parallel_nand_port_tms320f2812.c create mode 100644 c/parallel-nand/ports/tms320f2812/parallel_nand_port_tms320f2812.h create mode 100644 c/parallel-nand/src/parallel_nand.c create mode 100644 c/parallel-nand/src/parallel_nand_gas.c create mode 100644 c/parallel-nand/src/parallel_nand_pcan_gas.c create mode 100644 c/parallel-nand/tests/test_parallel_nand.c diff --git a/README.md b/README.md index 9f548cc..c6268bd 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ templates/ | [`c/rs485-boot`](c/rs485-boot) | прошивка по RS-485 в формате SETGUI v1: потоковый parser, CRC32 и resume | C99 | UART TX/RX, DE, Flash — **порты STM32F103 и STM32G474VET в комплекте** | | [`c/rtc-service`](c/rtc-service) | RTC с резервированным backup-томом | `stdint.h` | доступ к RTC и backup-памяти — **порт K1921VK028 в комплекте** | | [`c/firmware-info`](c/firmware-info) | SemVer, build stamp и git build ID работающего образа | C99 | config-порты STM32F1/F4/G4 и К1921ВК028; упаковка для Modbus/SETGUI | +| [`c/parallel-nand`](c/parallel-nand) | parallel NAND x8: raw main/OOB, bad-block marker и банковое окно GAS для SETGUI | C99, опционально `set-protocol/pcan_gas` | **порты TMS320F2812, STM32F407VE и STM32G474CEU6 в комплекте** | ### Python diff --git a/c/parallel-nand/CMakeLists.txt b/c/parallel-nand/CMakeLists.txt new file mode 100644 index 0000000..0960b91 --- /dev/null +++ b/c/parallel-nand/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/c/parallel-nand/README.md b/c/parallel-nand/README.md new file mode 100644 index 0000000..ebc4a27 --- /dev/null +++ b/c/parallel-nand/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/c/parallel-nand/docs/EMPTY_BOARD_PINOUT.md b/c/parallel-nand/docs/EMPTY_BOARD_PINOUT.md new file mode 100644 index 0000000..5094b1f --- /dev/null +++ b/c/parallel-nand/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/c/parallel-nand/docs/MEMORY_GAS.md b/c/parallel-nand/docs/MEMORY_GAS.md new file mode 100644 index 0000000..b760002 --- /dev/null +++ b/c/parallel-nand/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/c/parallel-nand/include/parallel_nand.h b/c/parallel-nand/include/parallel_nand.h new file mode 100644 index 0000000..8773ede --- /dev/null +++ b/c/parallel-nand/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/c/parallel-nand/include/parallel_nand_gas.h b/c/parallel-nand/include/parallel_nand_gas.h new file mode 100644 index 0000000..50238cc --- /dev/null +++ b/c/parallel-nand/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/c/parallel-nand/include/parallel_nand_pcan_gas.h b/c/parallel-nand/include/parallel_nand_pcan_gas.h new file mode 100644 index 0000000..939cd71 --- /dev/null +++ b/c/parallel-nand/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/c/parallel-nand/include/parallel_nand_port.h b/c/parallel-nand/include/parallel_nand_port.h new file mode 100644 index 0000000..a9e73c1 --- /dev/null +++ b/c/parallel-nand/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/c/parallel-nand/ports/stm32f407ve/README.md b/c/parallel-nand/ports/stm32f407ve/README.md new file mode 100644 index 0000000..0decb57 --- /dev/null +++ b/c/parallel-nand/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/c/parallel-nand/ports/stm32f407ve/parallel_nand_port_stm32f407ve.c b/c/parallel-nand/ports/stm32f407ve/parallel_nand_port_stm32f407ve.c new file mode 100644 index 0000000..50290c2 --- /dev/null +++ b/c/parallel-nand/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/c/parallel-nand/ports/stm32f407ve/parallel_nand_port_stm32f407ve.h b/c/parallel-nand/ports/stm32f407ve/parallel_nand_port_stm32f407ve.h new file mode 100644 index 0000000..36c1e8d --- /dev/null +++ b/c/parallel-nand/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/c/parallel-nand/ports/stm32g474ce/README.md b/c/parallel-nand/ports/stm32g474ce/README.md new file mode 100644 index 0000000..24e07e6 --- /dev/null +++ b/c/parallel-nand/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/c/parallel-nand/ports/stm32g474ce/parallel_nand_port_stm32g474ce.c b/c/parallel-nand/ports/stm32g474ce/parallel_nand_port_stm32g474ce.c new file mode 100644 index 0000000..3f18b64 --- /dev/null +++ b/c/parallel-nand/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/c/parallel-nand/ports/stm32g474ce/parallel_nand_port_stm32g474ce.h b/c/parallel-nand/ports/stm32g474ce/parallel_nand_port_stm32g474ce.h new file mode 100644 index 0000000..409e52d --- /dev/null +++ b/c/parallel-nand/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/c/parallel-nand/ports/tms320f2812/README.md b/c/parallel-nand/ports/tms320f2812/README.md new file mode 100644 index 0000000..7a33632 --- /dev/null +++ b/c/parallel-nand/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/c/parallel-nand/ports/tms320f2812/parallel_nand_port_tms320f2812.c b/c/parallel-nand/ports/tms320f2812/parallel_nand_port_tms320f2812.c new file mode 100644 index 0000000..b1dda6d --- /dev/null +++ b/c/parallel-nand/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/c/parallel-nand/ports/tms320f2812/parallel_nand_port_tms320f2812.h b/c/parallel-nand/ports/tms320f2812/parallel_nand_port_tms320f2812.h new file mode 100644 index 0000000..ecddb38 --- /dev/null +++ b/c/parallel-nand/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/c/parallel-nand/src/parallel_nand.c b/c/parallel-nand/src/parallel_nand.c new file mode 100644 index 0000000..62158c4 --- /dev/null +++ b/c/parallel-nand/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/c/parallel-nand/src/parallel_nand_gas.c b/c/parallel-nand/src/parallel_nand_gas.c new file mode 100644 index 0000000..461374b --- /dev/null +++ b/c/parallel-nand/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/c/parallel-nand/src/parallel_nand_pcan_gas.c b/c/parallel-nand/src/parallel_nand_pcan_gas.c new file mode 100644 index 0000000..9e7f6a8 --- /dev/null +++ b/c/parallel-nand/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/c/parallel-nand/tests/test_parallel_nand.c b/c/parallel-nand/tests/test_parallel_nand.c new file mode 100644 index 0000000..572f129 --- /dev/null +++ b/c/parallel-nand/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; +}