Files
templates/c/parallel-nand/src/parallel_nand_gas.c

264 lines
11 KiB
C

#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));
}
static void unpack_pair(pnand_octet_t *data, unsigned int index,
unsigned int value)
{
data[index * 2U] = (pnand_octet_t)(value & 0xFFU);
data[index * 2U + 1U] = (pnand_octet_t)((value >> 8) & 0xFFU);
}
static unsigned int persistent_status(const parallel_nand_gas_t *gas)
{
return gas->nand->port->write_supported != 0
? PNAND_GAS_STATUS_WRITE_SUPPORTED
: 0U;
}
parallel_nand_gas_result_t parallel_nand_gas_read(
parallel_nand_gas_t *gas, unsigned int address, unsigned int *value)
{
unsigned int offset;
const parallel_nand_geometry_t *g;
if (gas == 0 || value == 0 || address < gas->base)
return PNAND_GAS_REJECTED;
/* Confirmation authorizes exactly the immediately following write. */
gas->destructive_armed = 0;
g = &gas->nand->geometry;
offset = address - gas->base;
if (offset >= PNAND_GAS_REGION_REGS) return PNAND_GAS_NO_REG;
if (offset >= PNAND_GAS_DATA_OFFSET &&
offset < PNAND_GAS_DATA_OFFSET + PNAND_GAS_DATA_REGS) {
if ((unsigned long)(offset - PNAND_GAS_DATA_OFFSET) * 2UL >=
g->page_main_size)
return PNAND_GAS_NO_REG;
if ((gas->status & PNAND_GAS_STATUS_PAGE_VALID) == 0U)
return PNAND_GAS_REJECTED;
*value = pack_pair(gas->main_data,
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 ((unsigned long)(offset - PNAND_GAS_OOB_OFFSET) * 2UL >=
g->page_oob_size)
return PNAND_GAS_NO_REG;
if ((gas->status & PNAND_GAS_STATUS_PAGE_VALID) == 0U)
return PNAND_GAS_REJECTED;
*value = pack_pair(gas->oob,
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 = 0x0002U; break;
case PNAND_GAS_REG_STATUS: *value = gas->status; break;
case PNAND_GAS_REG_ERROR: *value = (unsigned int)gas->error & 0xFFFFU; break;
case PNAND_GAS_REG_COMMAND: *value = PNAND_GAS_CMD_NONE; break;
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;
case PNAND_GAS_REG_CONFIRM: *value = 0U; 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 | persistent_status(gas);
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;
int armed = gas->destructive_armed;
unsigned int i;
unsigned int previous_status = gas->status;
gas->destructive_armed = 0;
gas->status = PNAND_GAS_STATUS_BUSY | persistent_status(gas);
if (command == PNAND_GAS_CMD_RESET) {
result = parallel_nand_reset(gas->nand);
} else if (command == PNAND_GAS_CMD_READ_ID) {
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 if (command == PNAND_GAS_CMD_CLEAR_BUFFER) {
for (i = 0U; i < PNAND_MT29F1G08_PAGE_MAIN; i++)
gas->main_data[i] = 0xFFU;
for (i = 0U; i < PNAND_MT29F1G08_PAGE_OOB; i++)
gas->oob[i] = 0xFFU;
gas->status |= PNAND_GAS_STATUS_PAGE_VALID |
PNAND_GAS_STATUS_BUFFER_DIRTY;
gas->generation++;
} else if (command == PNAND_GAS_CMD_PROGRAM_PAGE) {
if (!armed ||
(previous_status & PNAND_GAS_STATUS_PAGE_VALID) == 0U) {
gas->status |= previous_status &
(PNAND_GAS_STATUS_PAGE_VALID |
PNAND_GAS_STATUS_BUFFER_DIRTY |
PNAND_GAS_STATUS_BAD_BLOCK);
finish(gas, PNAND_ERROR_CONFIRMATION);
return PNAND_GAS_REJECTED;
}
result = parallel_nand_program_page(
gas->nand, gas->selected_page,
gas->main_data, gas->nand->geometry.page_main_size,
gas->oob, gas->nand->geometry.page_oob_size);
gas->status |= PNAND_GAS_STATUS_PAGE_VALID;
if (result == PNAND_OK) {
gas->generation++;
gas->block_cache_valid = 0;
} else {
gas->status |= PNAND_GAS_STATUS_BUFFER_DIRTY;
}
} else if (command == PNAND_GAS_CMD_ERASE_BLOCK) {
unsigned long block;
if (!armed ||
gas->selected_page >= parallel_nand_page_count(gas->nand)) {
finish(gas, !armed ? PNAND_ERROR_CONFIRMATION :
PNAND_ERROR_RANGE);
return PNAND_GAS_REJECTED;
}
block = gas->selected_page / gas->nand->geometry.pages_per_block;
result = parallel_nand_erase_block(gas->nand, block);
if (result == PNAND_OK) {
gas->status &= (0xFFFFU ^ (PNAND_GAS_STATUS_PAGE_VALID |
PNAND_GAS_STATUS_BUFFER_DIRTY |
PNAND_GAS_STATUS_BAD_BLOCK));
gas->generation++;
gas->block_cache_valid = 0;
} else {
gas->status |= previous_status &
(PNAND_GAS_STATUS_PAGE_VALID |
PNAND_GAS_STATUS_BUFFER_DIRTY |
PNAND_GAS_STATUS_BAD_BLOCK);
}
} else {
gas->status = PNAND_GAS_STATUS_READY | persistent_status(gas);
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;
if (offset >= PNAND_GAS_DATA_OFFSET &&
offset < PNAND_GAS_DATA_OFFSET + PNAND_GAS_DATA_REGS) {
unsigned int index = offset - PNAND_GAS_DATA_OFFSET;
if ((gas->status & PNAND_GAS_STATUS_PAGE_VALID) == 0U ||
(unsigned long)index * 2UL >= gas->nand->geometry.page_main_size)
return PNAND_GAS_REJECTED;
gas->destructive_armed = 0;
unpack_pair(gas->main_data, index, value);
gas->status |= PNAND_GAS_STATUS_BUFFER_DIRTY;
return PNAND_GAS_OK;
}
if (offset >= PNAND_GAS_OOB_OFFSET &&
offset < PNAND_GAS_OOB_OFFSET + PNAND_GAS_OOB_REGS) {
unsigned int index = offset - PNAND_GAS_OOB_OFFSET;
if ((gas->status & PNAND_GAS_STATUS_PAGE_VALID) == 0U ||
(unsigned long)index * 2UL >= gas->nand->geometry.page_oob_size)
return PNAND_GAS_REJECTED;
gas->destructive_armed = 0;
unpack_pair(gas->oob, index, value);
gas->status |= PNAND_GAS_STATUS_BUFFER_DIRTY;
return PNAND_GAS_OK;
}
switch (offset) {
case PNAND_GAS_REG_COMMAND: return run_command(gas, value);
case PNAND_GAS_REG_CONFIRM:
gas->destructive_armed = value == PNAND_GAS_CONFIRM_MAGIC;
return gas->destructive_armed ? PNAND_GAS_OK : PNAND_GAS_REJECTED;
case PNAND_GAS_REG_PAGE_LO:
gas->destructive_armed = 0;
gas->selected_page = (gas->selected_page & 0xFFFF0000UL) | value;
gas->status &= (0xFFFFU ^ PNAND_GAS_STATUS_PAGE_VALID);
return PNAND_GAS_OK;
case PNAND_GAS_REG_PAGE_HI:
gas->destructive_armed = 0;
gas->selected_page = (gas->selected_page & 0x0000FFFFUL) |
((unsigned long)value << 16);
gas->status &= (0xFFFFU ^ PNAND_GAS_STATUS_PAGE_VALID);
return PNAND_GAS_OK;
default:
gas->destructive_armed = 0;
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 | persistent_status(gas);
gas->error = PNAND_OK;
gas->selected_page = 0UL;
gas->generation = 0UL;
gas->cached_block = 0UL;
gas->cached_bad_block = 0;
gas->block_cache_valid = 0;
gas->destructive_armed = 0;
for (i = 0U; i < 8U; i++) gas->id[i] = 0U;
for (i = 0U; i < PNAND_MT29F1G08_PAGE_MAIN; i++)
gas->main_data[i] = 0xFFU;
for (i = 0U; i < PNAND_MT29F1G08_PAGE_OOB; i++)
gas->oob[i] = 0xFFU;
}