#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; }