feat: sync current parallel NAND implementation from templates
This commit is contained in:
24
README.md
24
README.md
@@ -76,10 +76,28 @@ void memory_init(void)
|
|||||||
[docs/MEMORY_GAS.md](docs/MEMORY_GAS.md). Электрические соединения — в README
|
[docs/MEMORY_GAS.md](docs/MEMORY_GAS.md). Электрические соединения — в README
|
||||||
соответствующего порта и [docs/EMPTY_BOARD_PINOUT.md](docs/EMPTY_BOARD_PINOUT.md).
|
соответствующего порта и [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.
|
- Реализованы чтение, программирование страницы, стирание блока, RESET,
|
||||||
- PROGRAM/ERASE намеренно отсутствуют; ядро включает аппаратную защиту `WP#`.
|
READ ID, STATUS и проверка bad-block marker.
|
||||||
- ECC пока не исправляет данные. GUI получает физические main/OOB и должен
|
- ECC пока не исправляет данные. GUI получает физические main/OOB и должен
|
||||||
помечать дамп как raw.
|
помечать дамп как raw.
|
||||||
- В полном дампе bad-блоки не пропускаются: сохраняется физический порядок.
|
- В полном дампе bad-блоки не пропускаются: сохраняется физический порядок.
|
||||||
@@ -95,3 +113,5 @@ void memory_init(void)
|
|||||||
5. Записать номер страницы в `0xE005/0xE006`, затем `3` в `0xE004`.
|
5. Записать номер страницы в `0xE005/0xE006`, затем `3` в `0xE004`.
|
||||||
6. Дождаться `READY | PAGE_VALID` в `0xE002`.
|
6. Дождаться `READY | PAGE_VALID` в `0xE002`.
|
||||||
7. Прочитать main из `0xE100..0xE4FF`, OOB из `0xE500..0xE51F`.
|
7. Прочитать main из `0xE100..0xE4FF`, OOB из `0xE500..0xE51F`.
|
||||||
|
8. PROGRAM/ERASE проверять только на заведомо расходном блоке после сохранения
|
||||||
|
полного raw-дампа.
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ TSOP-48 (суффикс `WP`). На второй фотографии NAND —
|
|||||||
| `CLE` | 16 | фиксация команды |
|
| `CLE` | 16 | фиксация команды |
|
||||||
| `ALE` | 17 | фиксация адреса |
|
| `ALE` | 17 | фиксация адреса |
|
||||||
| `WE#` | 18 | строб команды/адреса от МК |
|
| `WE#` | 18 | строб команды/адреса от МК |
|
||||||
| `WP#` | 19 | для read-only 10 кОм на GND; для записи — GPIO и pull-down |
|
| `WP#` | 19 | GPIO МК и pull-down 10 кОм; только для чтения допустим GND через 10 кОм |
|
||||||
| `I/O0` | 29 | двунаправленная шина, бит 0 |
|
| `I/O0` | 29 | двунаправленная шина, бит 0 |
|
||||||
| `I/O1` | 30 | двунаправленная шина, бит 1 |
|
| `I/O1` | 30 | двунаправленная шина, бит 1 |
|
||||||
| `I/O2` | 31 | двунаправленная шина, бит 2 |
|
| `I/O2` | 31 | двунаправленная шина, бит 2 |
|
||||||
@@ -69,10 +69,12 @@ TSOP-48 (суффикс `WP`). На второй фотографии NAND —
|
|||||||
## Первый запуск
|
## Первый запуск
|
||||||
|
|
||||||
1. Не устанавливать NAND и проверить 3,3 В, отсутствие КЗ и уровни `CE#/WE#/RE#`.
|
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`.
|
3. Прочитать ID `90h`: ожидаемые первые байты для профиля — `2C F1`.
|
||||||
4. Считать одну страницу два раза и сравнить main/OOB побайтно.
|
4. Считать одну страницу два раза и сравнить main/OOB побайтно.
|
||||||
5. Только после устойчивого чтения запускать полный дамп через GAS.
|
5. Только после устойчивого чтения запускать полный дамп через GAS.
|
||||||
|
6. PROGRAM/ERASE проверять на расходном блоке после сохранения raw-дампа;
|
||||||
|
убедиться осциллографом, что `WP#` поднимается только на время операции.
|
||||||
|
|
||||||
## Источники для проверки footprint
|
## Источники для проверки footprint
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ parallel_nand_gas
|
|||||||
▼
|
▼
|
||||||
общее ядро parallel_nand
|
общее ядро parallel_nand
|
||||||
├─ TMS320F2812: XINTF
|
├─ TMS320F2812: XINTF
|
||||||
|
├─ STM32F103RC: GPIO
|
||||||
|
├─ STM32F103ZE: FSMC
|
||||||
├─ STM32F407VE: FSMC
|
├─ STM32F407VE: FSMC
|
||||||
└─ STM32G474CE: GPIO
|
└─ STM32G474CE: GPIO
|
||||||
```
|
```
|
||||||
@@ -77,7 +79,7 @@ ARM.
|
|||||||
| Адрес | Доступ | Название | Значение |
|
| Адрес | Доступ | Название | Значение |
|
||||||
|---:|:---:|---|---|
|
|---:|:---:|---|---|
|
||||||
| `E000` | R | `SIGNATURE` | `0x4E44` (`ND`) |
|
| `E000` | R | `SIGNATURE` | `0x4E44` (`ND`) |
|
||||||
| `E001` | R | `VERSION` | версия окна, сейчас 1 |
|
| `E001` | R | `VERSION` | версия окна, сейчас 2 |
|
||||||
| `E002` | R | `STATUS` | состояние операции |
|
| `E002` | R | `STATUS` | состояние операции |
|
||||||
| `E003` | R | `ERROR` | код `PNAND_*`, 0 = нет ошибки |
|
| `E003` | R | `ERROR` | код `PNAND_*`, 0 = нет ошибки |
|
||||||
| `E004` | W | `COMMAND` | команда окна |
|
| `E004` | W | `COMMAND` | команда окна |
|
||||||
@@ -92,8 +94,9 @@ ARM.
|
|||||||
| `E00D` | R | `BAD_BLOCK` | 1 для физически плохого блока |
|
| `E00D` | R | `BAD_BLOCK` | 1 для физически плохого блока |
|
||||||
| `E00E` | R | `GENERATION_LO` | счётчик загруженных страниц, low |
|
| `E00E` | R | `GENERATION_LO` | счётчик загруженных страниц, low |
|
||||||
| `E00F` | R | `GENERATION_HI` | счётчик загруженных страниц, high |
|
| `E00F` | R | `GENERATION_HI` | счётчик загруженных страниц, high |
|
||||||
| `E100..E4FF` | R | `PAGE_DATA` | 1024 регистра main, два байта LE |
|
| `E010` | W | `CONFIRM` | `0xA55A` непосредственно перед разрушительной командой |
|
||||||
| `E500..E51F` | R | `PAGE_OOB` | 32 регистра OOB, два байта LE |
|
| `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` |
|
| 1 | `0x0002` | `BUSY` |
|
||||||
| 2 | `0x0004` | `PAGE_VALID` |
|
| 2 | `0x0004` | `PAGE_VALID` |
|
||||||
| 3 | `0x0008` | `BAD_BLOCK` |
|
| 3 | `0x0008` | `BAD_BLOCK` |
|
||||||
|
| 4 | `0x0010` | `WRITE_SUPPORTED`, порт способен управлять `WP#` |
|
||||||
|
| 5 | `0x0020` | `BUFFER_DIRTY`, буфер изменён после чтения/очистки |
|
||||||
| 15 | `0x8000` | `ERROR`, подробность в `E003` |
|
| 15 | `0x8000` | `ERROR`, подробность в `E003` |
|
||||||
|
|
||||||
GUI читает окно данных только при `READY=1`, `PAGE_VALID=1`, `ERROR=0`.
|
GUI читает окно данных только при `READY=1`, `PAGE_VALID=1`, `ERROR=0`.
|
||||||
@@ -118,6 +123,9 @@ GUI читает окно данных только при `READY=1`, `PAGE_VALI
|
|||||||
| 1 | `RESET` | сброс NAND и ожидание Ready |
|
| 1 | `RESET` | сброс NAND и ожидание Ready |
|
||||||
| 2 | `READ_ID` | обновляет `E00B/E00C` |
|
| 2 | `READ_ID` | обновляет `E00B/E00C` |
|
||||||
| 3 | `READ_PAGE` | загружает main/OOB выбранной страницы |
|
| 3 | `READ_PAGE` | загружает main/OOB выбранной страницы |
|
||||||
|
| 4 | `CLEAR_BUFFER` | заполняет main/OOB значением `0xFF` для подготовки записи |
|
||||||
|
| 5 | `PROGRAM_PAGE` | программирует выбранную страницу из буфера |
|
||||||
|
| 6 | `ERASE_BLOCK` | стирает блок, содержащий выбранную страницу |
|
||||||
|
|
||||||
Запись номера страницы и запуск — две отдельные операции. Сначала GUI пишет
|
Запись номера страницы и запуск — две отдельные операции. Сначала GUI пишет
|
||||||
`PAGE_LO/PAGE_HI`, затем записывает `READ_PAGE` в `COMMAND`. Нельзя отправлять
|
`PAGE_LO/PAGE_HI`, затем записывает `READ_PAGE` в `COMMAND`. Нельзя отправлять
|
||||||
@@ -172,6 +180,28 @@ GUI хранит номер последней полностью записан
|
|||||||
|
|
||||||
## PROGRAM/ERASE
|
## PROGRAM/ERASE
|
||||||
|
|
||||||
Запись и стирание не включены намеренно. Они требуют отдельного разрешения,
|
Разрушительные команды доступны только если установлен флаг
|
||||||
проверки ECC, защиты загрузочных блоков и двухфазного подтверждения через GAS.
|
`WRITE_SUPPORTED`. Ядро проверяет bad-block marker и отказывается изменять
|
||||||
До добавления этих функций `WP#` удерживается активным аппаратным портом.
|
заводской плохой блок. `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, журналирование и восстановление
|
||||||
|
после пропадания питания.
|
||||||
|
|||||||
@@ -35,7 +35,12 @@ typedef enum {
|
|||||||
PNAND_ERROR_PORT = -2,
|
PNAND_ERROR_PORT = -2,
|
||||||
PNAND_ERROR_TIMEOUT = -3,
|
PNAND_ERROR_TIMEOUT = -3,
|
||||||
PNAND_ERROR_RANGE = -4,
|
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;
|
} parallel_nand_result_t;
|
||||||
|
|
||||||
typedef struct {
|
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,
|
int parallel_nand_read_page_slice(parallel_nand_t *nand, unsigned long page,
|
||||||
unsigned long column, pnand_octet_t *out,
|
unsigned long column, pnand_octet_t *out,
|
||||||
unsigned long count);
|
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 parallel_nand_is_bad_block(parallel_nand_t *nand, unsigned long block,
|
||||||
int *is_bad);
|
int *is_bad);
|
||||||
unsigned long parallel_nand_page_count(const parallel_nand_t *nand);
|
unsigned long parallel_nand_page_count(const parallel_nand_t *nand);
|
||||||
|
|||||||
@@ -30,20 +30,28 @@ enum {
|
|||||||
PNAND_GAS_REG_DEVICE_ID = 0x000C,
|
PNAND_GAS_REG_DEVICE_ID = 0x000C,
|
||||||
PNAND_GAS_REG_BAD_BLOCK = 0x000D,
|
PNAND_GAS_REG_BAD_BLOCK = 0x000D,
|
||||||
PNAND_GAS_REG_GENERATION_LO = 0x000E,
|
PNAND_GAS_REG_GENERATION_LO = 0x000E,
|
||||||
PNAND_GAS_REG_GENERATION_HI = 0x000F
|
PNAND_GAS_REG_GENERATION_HI = 0x000F,
|
||||||
|
PNAND_GAS_REG_CONFIRM = 0x0010
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
PNAND_GAS_CMD_NONE = 0,
|
PNAND_GAS_CMD_NONE = 0,
|
||||||
PNAND_GAS_CMD_RESET = 1,
|
PNAND_GAS_CMD_RESET = 1,
|
||||||
PNAND_GAS_CMD_READ_ID = 2,
|
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_READY 0x0001U
|
||||||
#define PNAND_GAS_STATUS_BUSY 0x0002U
|
#define PNAND_GAS_STATUS_BUSY 0x0002U
|
||||||
#define PNAND_GAS_STATUS_PAGE_VALID 0x0004U
|
#define PNAND_GAS_STATUS_PAGE_VALID 0x0004U
|
||||||
#define PNAND_GAS_STATUS_BAD_BLOCK 0x0008U
|
#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
|
#define PNAND_GAS_STATUS_ERROR 0x8000U
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -56,6 +64,7 @@ typedef struct {
|
|||||||
unsigned long cached_block;
|
unsigned long cached_block;
|
||||||
int cached_bad_block;
|
int cached_bad_block;
|
||||||
int block_cache_valid;
|
int block_cache_valid;
|
||||||
|
int destructive_armed;
|
||||||
pnand_octet_t id[8];
|
pnand_octet_t id[8];
|
||||||
pnand_octet_t main_data[PNAND_MT29F1G08_PAGE_MAIN];
|
pnand_octet_t main_data[PNAND_MT29F1G08_PAGE_MAIN];
|
||||||
pnand_octet_t oob[PNAND_MT29F1G08_PAGE_OOB];
|
pnand_octet_t oob[PNAND_MT29F1G08_PAGE_OOB];
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ typedef struct {
|
|||||||
int (*is_ready)(void);
|
int (*is_ready)(void);
|
||||||
void (*set_write_protect)(int enabled);
|
void (*set_write_protect)(int enabled);
|
||||||
void (*delay_us)(unsigned long us);
|
void (*delay_us)(unsigned long us);
|
||||||
|
/* Nonzero only when set_write_protect(0) physically releases WP#. */
|
||||||
|
int write_supported;
|
||||||
} parallel_nand_port_t;
|
} parallel_nand_port_t;
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|||||||
@@ -47,5 +47,5 @@ int nand_init(void)
|
|||||||
```
|
```
|
||||||
|
|
||||||
После успешной инициализации прочитайте четыре байта ID и ожидайте
|
После успешной инициализации прочитайте четыре байта ID и ожидайте
|
||||||
`AD DC 80 95`. Ядро библиотеки работает только на чтение и удерживает `WP#`
|
`AD DC 80 95`. Порт поддерживает PROGRAM/ERASE: ядро держит `WP#` в активном
|
||||||
в активном низком уровне; PROGRAM/ERASE пока намеренно не реализованы.
|
низком уровне между операциями и поднимает его только на время изменения NAND.
|
||||||
|
|||||||
@@ -133,7 +133,8 @@ static void f103rc_delay_us(unsigned long us)
|
|||||||
static const parallel_nand_port_t f103rc_port = {
|
static const parallel_nand_port_t f103rc_port = {
|
||||||
f103rc_init, f103rc_select, f103rc_deselect,
|
f103rc_init, f103rc_select, f103rc_deselect,
|
||||||
f103rc_write_command, f103rc_write_address, f103rc_write_data,
|
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)
|
const parallel_nand_port_t *parallel_nand_port_stm32f103rc(void)
|
||||||
|
|||||||
@@ -57,3 +57,6 @@ int nand_init(void)
|
|||||||
Начальные тайминги намеренно консервативны. После запуска проверьте
|
Начальные тайминги намеренно консервативны. После запуска проверьте
|
||||||
осциллографом длительности `WE#`/`RE#` и время установки данных. Ожидаемый ID
|
осциллографом длительности `WE#`/`RE#` и время установки данных. Ожидаемый ID
|
||||||
HY27UF084G2M: `AD DC 80 95`.
|
HY27UF084G2M: `AD DC 80 95`.
|
||||||
|
|
||||||
|
Порт поддерживает PROGRAM/ERASE. `WP#` на `PG8` остаётся активным между
|
||||||
|
операциями и снимается ядром только на время программирования или стирания.
|
||||||
|
|||||||
@@ -174,7 +174,8 @@ static const parallel_nand_port_t f103ze_port = {
|
|||||||
f103ze_read_data,
|
f103ze_read_data,
|
||||||
f103ze_is_ready,
|
f103ze_is_ready,
|
||||||
f103ze_set_write_protect,
|
f103ze_set_write_protect,
|
||||||
f103ze_delay_us
|
f103ze_delay_us,
|
||||||
|
1
|
||||||
};
|
};
|
||||||
|
|
||||||
const parallel_nand_port_t *parallel_nand_port_stm32f103ze(void)
|
const parallel_nand_port_t *parallel_nand_port_stm32f103ze(void)
|
||||||
|
|||||||
@@ -24,3 +24,8 @@
|
|||||||
`stm32f4xx_ll_fsmc.c`, включить FSMC и не создавать второй
|
`stm32f4xx_ll_fsmc.c`, включить FSMC и не создавать второй
|
||||||
`NAND_HandleTypeDef` для Bank 2. Начальные тайминги
|
`NAND_HandleTypeDef` для Bank 2. Начальные тайминги
|
||||||
консервативны; финальные значения подтвердить осциллографом.
|
консервативны; финальные значения подтвердить осциллографом.
|
||||||
|
|
||||||
|
В базовой схеме `WP#` аппаратно зафиксирован в активном уровне, поэтому порт
|
||||||
|
объявляет `write_supported = 0`, а PROGRAM/ERASE возвращают
|
||||||
|
`PNAND_ERROR_WRITE_PROTECTED`. Для записи назначьте отдельный GPIO с внешним
|
||||||
|
pull-down, реализуйте управление `WP#` и включите признак поддержки порта.
|
||||||
|
|||||||
@@ -84,7 +84,8 @@ static void f407_delay_us(unsigned long us)
|
|||||||
static const parallel_nand_port_t f407_port = {
|
static const parallel_nand_port_t f407_port = {
|
||||||
f407_init, f407_select, f407_deselect,
|
f407_init, f407_select, f407_deselect,
|
||||||
f407_write_command, f407_write_address, f407_write_data,
|
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)
|
const parallel_nand_port_t *parallel_nand_port_stm32f407ve(void)
|
||||||
|
|||||||
@@ -19,3 +19,6 @@ HAL только для инициализации, а циклы NAND выпо
|
|||||||
Выбранные `PB3/PB4` нельзя одновременно использовать для trace/JTAG; SWD на
|
Выбранные `PB3/PB4` нельзя одновременно использовать для trace/JTAG; SWD на
|
||||||
`PA13/PA14` остаётся доступен. Если эти пины нужны проекту, измените таблицу и
|
`PA13/PA14` остаётся доступен. Если эти пины нужны проекту, измените таблицу и
|
||||||
операции шины: текущая оптимизация рассчитана на непрерывные `PB0..PB7`.
|
операции шины: текущая оптимизация рассчитана на непрерывные `PB0..PB7`.
|
||||||
|
|
||||||
|
Порт поддерживает PROGRAM/ERASE через управляемый `WP#` на `PA5`. Внешний
|
||||||
|
pull-down сохраняет защиту до инициализации GPIO и при сбросе МК.
|
||||||
|
|||||||
@@ -125,7 +125,8 @@ static void g474_delay_us(unsigned long us)
|
|||||||
static const parallel_nand_port_t g474_port = {
|
static const parallel_nand_port_t g474_port = {
|
||||||
g474_init, g474_select, g474_deselect,
|
g474_init, g474_select, g474_deselect,
|
||||||
g474_write_command, g474_write_address, g474_write_data,
|
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)
|
const parallel_nand_port_t *parallel_nand_port_stm32g474ce(void)
|
||||||
|
|||||||
@@ -13,9 +13,10 @@
|
|||||||
Адреса C28x задаются в 16-битных словах: data `0x100000`, command
|
Адреса C28x задаются в 16-битных словах: data `0x100000`, command
|
||||||
`0x100001`, address `0x100002`. Используется только младший байт XINTF.
|
`0x100001`, address `0x100002`. Используется только младший байт XINTF.
|
||||||
|
|
||||||
`WP#` в базовом варианте аппаратный: для режима только чтения соединить с GND
|
`WP#` в базовом варианте аппаратный и соединён с GND через 10 кОм. Поэтому
|
||||||
через 10 кОм; если позже понадобится PROGRAM/ERASE, заменить на GPIO и реализовать
|
порт объявляет `write_supported = 0`, а PROGRAM/ERASE возвращают
|
||||||
`tms_set_wp()`.
|
`PNAND_ERROR_WRITE_PROTECTED`. Для записи нужно перенести `WP#` на GPIO,
|
||||||
|
реализовать `tms_set_wp()` и установить признак поддержки в структуре порта.
|
||||||
|
|
||||||
Задержка в порте приблизительная. После выбора частоты CPU проверить импульсы
|
Задержка в порте приблизительная. После выбора частоты CPU проверить импульсы
|
||||||
осциллографом и при необходимости изменить `PNAND_TMS_CPU_MHZ`.
|
осциллографом и при необходимости изменить `PNAND_TMS_CPU_MHZ`.
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ static void tms_set_wp(int enabled)
|
|||||||
static const parallel_nand_port_t tms_port = {
|
static const parallel_nand_port_t tms_port = {
|
||||||
tms_init, tms_select, tms_deselect,
|
tms_init, tms_select, tms_deselect,
|
||||||
tms_write_command, tms_write_address, tms_write_data,
|
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)
|
const parallel_nand_port_t *parallel_nand_port_tms320f2812(void)
|
||||||
|
|||||||
@@ -4,8 +4,15 @@
|
|||||||
#define PNAND_CMD_READ_2 0x30U
|
#define PNAND_CMD_READ_2 0x30U
|
||||||
#define PNAND_CMD_READ_ID 0x90U
|
#define PNAND_CMD_READ_ID 0x90U
|
||||||
#define PNAND_CMD_READ_STATUS 0x70U
|
#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_CMD_RESET 0xFFU
|
||||||
|
|
||||||
|
#define PNAND_STATUS_FAIL 0x01U
|
||||||
|
#define PNAND_STATUS_WP 0x80U
|
||||||
|
|
||||||
const parallel_nand_geometry_t parallel_nand_mt29f1g08_geometry = {
|
const parallel_nand_geometry_t parallel_nand_mt29f1g08_geometry = {
|
||||||
PNAND_MT29F1G08_PAGE_MAIN,
|
PNAND_MT29F1G08_PAGE_MAIN,
|
||||||
PNAND_MT29F1G08_PAGE_OOB,
|
PNAND_MT29F1G08_PAGE_OOB,
|
||||||
@@ -67,6 +74,8 @@ int parallel_nand_reset(parallel_nand_t *nand)
|
|||||||
nand->port->select();
|
nand->port->select();
|
||||||
nand->port->write_command(PNAND_CMD_RESET);
|
nand->port->write_command(PNAND_CMD_RESET);
|
||||||
nand->port->deselect();
|
nand->port->deselect();
|
||||||
|
/* R/B# may not fall until tWB after the command cycle. */
|
||||||
|
nand->port->delay_us(1UL);
|
||||||
return wait_ready(nand);
|
return wait_ready(nand);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +89,7 @@ int parallel_nand_init(parallel_nand_t *nand,
|
|||||||
nand->geometry = *geometry;
|
nand->geometry = *geometry;
|
||||||
if (port->init() != 0) return PNAND_ERROR_PORT;
|
if (port->init() != 0) return PNAND_ERROR_PORT;
|
||||||
port->deselect();
|
port->deselect();
|
||||||
/* Ядро пока только читает: аппаратно запрещаем PROGRAM/ERASE. */
|
/* WP# stays asserted except for the shortest possible mutation window. */
|
||||||
port->set_write_protect(1);
|
port->set_write_protect(1);
|
||||||
return parallel_nand_reset(nand);
|
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->write_command(PNAND_CMD_READ_2);
|
||||||
nand->port->deselect();
|
nand->port->deselect();
|
||||||
|
nand->port->delay_us(1UL); /* Cover tWB before polling R/B#. */
|
||||||
return wait_ready(nand);
|
return wait_ready(nand);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,6 +187,101 @@ int parallel_nand_read_page(parallel_nand_t *nand, unsigned long page,
|
|||||||
return PNAND_OK;
|
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 parallel_nand_is_bad_block(parallel_nand_t *nand, unsigned long block,
|
||||||
int *is_bad)
|
int *is_bad)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,6 +6,20 @@ static unsigned int pack_pair(const pnand_octet_t *data, unsigned int index)
|
|||||||
((data[index * 2U + 1U] & 0xFFU) << 8));
|
((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_result_t parallel_nand_gas_read(
|
||||||
parallel_nand_gas_t *gas, unsigned int address, unsigned int *value)
|
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;
|
const parallel_nand_geometry_t *g;
|
||||||
if (gas == 0 || value == 0 || address < gas->base)
|
if (gas == 0 || value == 0 || address < gas->base)
|
||||||
return PNAND_GAS_REJECTED;
|
return PNAND_GAS_REJECTED;
|
||||||
|
/* Confirmation authorizes exactly the immediately following write. */
|
||||||
|
gas->destructive_armed = 0;
|
||||||
g = &gas->nand->geometry;
|
g = &gas->nand->geometry;
|
||||||
offset = address - gas->base;
|
offset = address - gas->base;
|
||||||
if (offset >= PNAND_GAS_REGION_REGS) return PNAND_GAS_NO_REG;
|
if (offset >= PNAND_GAS_REGION_REGS) return PNAND_GAS_NO_REG;
|
||||||
if (offset >= PNAND_GAS_DATA_OFFSET &&
|
if (offset >= PNAND_GAS_DATA_OFFSET &&
|
||||||
offset < PNAND_GAS_DATA_OFFSET + PNAND_GAS_DATA_REGS) {
|
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)
|
if ((gas->status & PNAND_GAS_STATUS_PAGE_VALID) == 0U)
|
||||||
return PNAND_GAS_REJECTED;
|
return PNAND_GAS_REJECTED;
|
||||||
*value = pack_pair(gas->main_data,
|
*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 &&
|
if (offset >= PNAND_GAS_OOB_OFFSET &&
|
||||||
offset < PNAND_GAS_OOB_OFFSET + PNAND_GAS_OOB_REGS) {
|
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)
|
if ((gas->status & PNAND_GAS_STATUS_PAGE_VALID) == 0U)
|
||||||
return PNAND_GAS_REJECTED;
|
return PNAND_GAS_REJECTED;
|
||||||
*value = pack_pair(gas->oob,
|
*value = pack_pair(gas->oob,
|
||||||
@@ -34,7 +56,7 @@ parallel_nand_gas_result_t parallel_nand_gas_read(
|
|||||||
}
|
}
|
||||||
switch (offset) {
|
switch (offset) {
|
||||||
case PNAND_GAS_REG_SIGNATURE: *value = 0x4E44U; break; /* ND */
|
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_STATUS: *value = gas->status; break;
|
||||||
case PNAND_GAS_REG_ERROR: *value = (unsigned int)gas->error & 0xFFFFU; 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_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;
|
*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_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_GENERATION_HI: *value = (unsigned int)(gas->generation >> 16) & 0xFFFFU; break;
|
||||||
|
case PNAND_GAS_REG_CONFIRM: *value = 0U; break;
|
||||||
default: return PNAND_GAS_NO_REG;
|
default: return PNAND_GAS_NO_REG;
|
||||||
}
|
}
|
||||||
*value &= 0xFFFFU;
|
*value &= 0xFFFFU;
|
||||||
@@ -60,7 +83,7 @@ static void finish(parallel_nand_gas_t *gas, int result)
|
|||||||
{
|
{
|
||||||
gas->error = result;
|
gas->error = result;
|
||||||
gas->status &= (0xFFFFU ^ PNAND_GAS_STATUS_BUSY);
|
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;
|
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 result = PNAND_OK;
|
||||||
int bad = 0;
|
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) {
|
if (command == PNAND_GAS_CMD_RESET) {
|
||||||
result = parallel_nand_reset(gas->nand);
|
result = parallel_nand_reset(gas->nand);
|
||||||
} else if (command == PNAND_GAS_CMD_READ_ID) {
|
} 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)
|
if (result == PNAND_OK && bad)
|
||||||
gas->status |= PNAND_GAS_STATUS_BAD_BLOCK;
|
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 {
|
} else {
|
||||||
gas->status = PNAND_GAS_STATUS_READY;
|
gas->status = PNAND_GAS_STATUS_READY | persistent_status(gas);
|
||||||
return PNAND_GAS_REJECTED;
|
return PNAND_GAS_REJECTED;
|
||||||
}
|
}
|
||||||
finish(gas, result);
|
finish(gas, result);
|
||||||
@@ -119,18 +197,47 @@ parallel_nand_gas_result_t parallel_nand_gas_write(
|
|||||||
if ((gas->status & PNAND_GAS_STATUS_BUSY) != 0U)
|
if ((gas->status & PNAND_GAS_STATUS_BUSY) != 0U)
|
||||||
return PNAND_GAS_REJECTED;
|
return PNAND_GAS_REJECTED;
|
||||||
value &= 0xFFFFU;
|
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) {
|
switch (offset) {
|
||||||
case PNAND_GAS_REG_COMMAND: return run_command(gas, value);
|
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:
|
case PNAND_GAS_REG_PAGE_LO:
|
||||||
|
gas->destructive_armed = 0;
|
||||||
gas->selected_page = (gas->selected_page & 0xFFFF0000UL) | value;
|
gas->selected_page = (gas->selected_page & 0xFFFF0000UL) | value;
|
||||||
gas->status &= (0xFFFFU ^ PNAND_GAS_STATUS_PAGE_VALID);
|
gas->status &= (0xFFFFU ^ PNAND_GAS_STATUS_PAGE_VALID);
|
||||||
return PNAND_GAS_OK;
|
return PNAND_GAS_OK;
|
||||||
case PNAND_GAS_REG_PAGE_HI:
|
case PNAND_GAS_REG_PAGE_HI:
|
||||||
|
gas->destructive_armed = 0;
|
||||||
gas->selected_page = (gas->selected_page & 0x0000FFFFUL) |
|
gas->selected_page = (gas->selected_page & 0x0000FFFFUL) |
|
||||||
((unsigned long)value << 16);
|
((unsigned long)value << 16);
|
||||||
gas->status &= (0xFFFFU ^ PNAND_GAS_STATUS_PAGE_VALID);
|
gas->status &= (0xFFFFU ^ PNAND_GAS_STATUS_PAGE_VALID);
|
||||||
return PNAND_GAS_OK;
|
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;
|
unsigned int i;
|
||||||
gas->nand = nand;
|
gas->nand = nand;
|
||||||
gas->base = base;
|
gas->base = base;
|
||||||
gas->status = PNAND_GAS_STATUS_READY;
|
gas->status = PNAND_GAS_STATUS_READY | persistent_status(gas);
|
||||||
gas->error = PNAND_OK;
|
gas->error = PNAND_OK;
|
||||||
gas->selected_page = 0UL;
|
gas->selected_page = 0UL;
|
||||||
gas->generation = 0UL;
|
gas->generation = 0UL;
|
||||||
gas->cached_block = 0UL;
|
gas->cached_block = 0UL;
|
||||||
gas->cached_bad_block = 0;
|
gas->cached_bad_block = 0;
|
||||||
gas->block_cache_valid = 0;
|
gas->block_cache_valid = 0;
|
||||||
|
gas->destructive_armed = 0;
|
||||||
for (i = 0U; i < 8U; i++) gas->id[i] = 0U;
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
#include "parallel_nand_gas.h"
|
#include "parallel_nand_gas.h"
|
||||||
|
|
||||||
typedef struct { char kind; unsigned int value; } event_t;
|
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 unsigned int event_count;
|
||||||
static pnand_octet_t input_data[32];
|
static pnand_octet_t input_data[32];
|
||||||
static unsigned int input_count;
|
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 = {
|
static const parallel_nand_port_t fake_port = {
|
||||||
fake_init, fake_select, fake_deselect, fake_cmd, fake_addr, fake_write,
|
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 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)
|
int main(void)
|
||||||
{
|
{
|
||||||
parallel_nand_t nand;
|
parallel_nand_t nand;
|
||||||
pnand_octet_t id[2];
|
pnand_octet_t id[2];
|
||||||
pnand_octet_t bytes[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_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;
|
unsigned int reg;
|
||||||
|
|
||||||
assert(parallel_nand_init(&nand, &fake_port,
|
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[6].kind == 'A' && events[6].value == 0x02U);
|
||||||
assert(events[7].kind == 'C' && events[7].value == 0x30U);
|
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);
|
parallel_nand_gas_init(&gas, &nand, PNAND_GAS_DEFAULT_BASE);
|
||||||
assert(parallel_nand_gas_read(&gas, 0xE000U, ®) == PNAND_GAS_OK);
|
assert(parallel_nand_gas_read(&gas, 0xE000U, ®) == PNAND_GAS_OK);
|
||||||
assert(reg == 0x4E44U);
|
assert(reg == 0x4E44U);
|
||||||
|
assert(parallel_nand_gas_read(&gas, 0xE001U, ®) == PNAND_GAS_OK);
|
||||||
|
assert(reg == 0x0002U);
|
||||||
assert(parallel_nand_gas_write(&gas, 0xE005U, 0x1234U) ==
|
assert(parallel_nand_gas_write(&gas, 0xE005U, 0x1234U) ==
|
||||||
PNAND_GAS_OK);
|
PNAND_GAS_OK);
|
||||||
assert(parallel_nand_gas_write(&gas, 0xE006U, 0x0001U) ==
|
assert(parallel_nand_gas_write(&gas, 0xE006U, 0x0001U) ==
|
||||||
@@ -112,6 +217,56 @@ int main(void)
|
|||||||
assert(parallel_nand_gas_write(&gas, 0xE000U, 0U) ==
|
assert(parallel_nand_gas_write(&gas, 0xE000U, 0U) ==
|
||||||
PNAND_GAS_READ_ONLY);
|
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");
|
puts("parallel_nand: OK");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user