307 lines
11 KiB
C
307 lines
11 KiB
C
#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_PROGRAM_1 0x80U
|
|
#define PNAND_CMD_PROGRAM_2 0x10U
|
|
#define PNAND_CMD_ERASE_1 0x60U
|
|
#define PNAND_CMD_ERASE_2 0xD0U
|
|
#define PNAND_CMD_RESET 0xFFU
|
|
|
|
#define PNAND_STATUS_FAIL 0x01U
|
|
#define PNAND_STATUS_WP 0x80U
|
|
|
|
const parallel_nand_geometry_t parallel_nand_mt29f1g08_geometry = {
|
|
PNAND_MT29F1G08_PAGE_MAIN,
|
|
PNAND_MT29F1G08_PAGE_OOB,
|
|
PNAND_MT29F1G08_PAGES_PER_BLOCK,
|
|
PNAND_MT29F1G08_BLOCKS,
|
|
PNAND_MT29F1G08_COLUMN_CYCLES,
|
|
PNAND_MT29F1G08_ROW_CYCLES,
|
|
1000000UL
|
|
};
|
|
|
|
const parallel_nand_geometry_t parallel_nand_hy27uf084g2m_geometry = {
|
|
PNAND_HY27UF084G2M_PAGE_MAIN,
|
|
PNAND_HY27UF084G2M_PAGE_OOB,
|
|
PNAND_HY27UF084G2M_PAGES_PER_BLOCK,
|
|
PNAND_HY27UF084G2M_BLOCKS,
|
|
PNAND_HY27UF084G2M_COLUMN_CYCLES,
|
|
PNAND_HY27UF084G2M_ROW_CYCLES,
|
|
1000000UL
|
|
};
|
|
|
|
static int port_valid(const parallel_nand_port_t *port)
|
|
{
|
|
return port != 0 && port->init != 0 && port->select != 0 &&
|
|
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();
|
|
/* R/B# may not fall until tWB after the command cycle. */
|
|
nand->port->delay_us(1UL);
|
|
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();
|
|
/* WP# stays asserted except for the shortest possible mutation window. */
|
|
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();
|
|
nand->port->delay_us(1UL); /* Cover tWB before polling R/B#. */
|
|
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;
|
|
}
|
|
|
|
static void write_address_cycles(parallel_nand_t *nand,
|
|
unsigned long value,
|
|
unsigned int count)
|
|
{
|
|
unsigned int i;
|
|
for (i = 0U; i < count; i++) {
|
|
nand->port->write_address((pnand_octet_t)(value & 0xFFUL));
|
|
value >>= 8;
|
|
}
|
|
}
|
|
|
|
static int mutation_supported(const parallel_nand_t *nand)
|
|
{
|
|
return nand != 0 && port_valid(nand->port) &&
|
|
nand->port->write_data != 0 &&
|
|
nand->port->write_supported != 0;
|
|
}
|
|
|
|
static int finish_mutation(parallel_nand_t *nand, int failure_result)
|
|
{
|
|
pnand_octet_t status = 0U;
|
|
int result = wait_ready(nand);
|
|
if (result == PNAND_OK)
|
|
result = parallel_nand_read_status(nand, &status);
|
|
nand->port->set_write_protect(1);
|
|
if (result != PNAND_OK) return result;
|
|
if ((status & PNAND_STATUS_WP) == 0U)
|
|
return PNAND_ERROR_WRITE_PROTECTED;
|
|
return (status & PNAND_STATUS_FAIL) != 0U ? failure_result : PNAND_OK;
|
|
}
|
|
|
|
int parallel_nand_program_page(parallel_nand_t *nand, unsigned long page,
|
|
const pnand_octet_t *main_data,
|
|
unsigned long main_count,
|
|
const pnand_octet_t *oob,
|
|
unsigned long oob_count)
|
|
{
|
|
unsigned long i;
|
|
unsigned long block;
|
|
int bad = 0;
|
|
int result;
|
|
if (nand == 0 || main_data == 0) return PNAND_ERROR_ARGUMENT;
|
|
if (!mutation_supported(nand)) return PNAND_ERROR_WRITE_PROTECTED;
|
|
if (page >= parallel_nand_page_count(nand)) return PNAND_ERROR_RANGE;
|
|
if (main_count != nand->geometry.page_main_size)
|
|
return PNAND_ERROR_BUFFER;
|
|
if ((oob == 0 && oob_count != 0UL) ||
|
|
(oob != 0 && oob_count != nand->geometry.page_oob_size))
|
|
return PNAND_ERROR_BUFFER;
|
|
|
|
block = page / nand->geometry.pages_per_block;
|
|
result = parallel_nand_is_bad_block(nand, block, &bad);
|
|
if (result != PNAND_OK) return result;
|
|
if (bad) return PNAND_ERROR_BAD_BLOCK;
|
|
|
|
nand->port->set_write_protect(0);
|
|
nand->port->delay_us(1UL);
|
|
nand->port->select();
|
|
nand->port->write_command(PNAND_CMD_PROGRAM_1);
|
|
write_address_cycles(nand, 0UL, nand->geometry.column_cycles);
|
|
write_address_cycles(nand, page, nand->geometry.row_cycles);
|
|
for (i = 0UL; i < main_count; i++)
|
|
nand->port->write_data(main_data[i] & 0xFFU);
|
|
for (i = 0UL; i < oob_count; i++)
|
|
nand->port->write_data(oob[i] & 0xFFU);
|
|
nand->port->write_command(PNAND_CMD_PROGRAM_2);
|
|
nand->port->deselect();
|
|
nand->port->delay_us(1UL); /* Cover tWB before polling R/B#. */
|
|
return finish_mutation(nand, PNAND_ERROR_PROGRAM_FAILED);
|
|
}
|
|
|
|
int parallel_nand_erase_block(parallel_nand_t *nand, unsigned long block)
|
|
{
|
|
unsigned long first_page;
|
|
int bad = 0;
|
|
int result;
|
|
if (nand == 0) return PNAND_ERROR_ARGUMENT;
|
|
if (!mutation_supported(nand)) return PNAND_ERROR_WRITE_PROTECTED;
|
|
if (block >= nand->geometry.block_count) return PNAND_ERROR_RANGE;
|
|
result = parallel_nand_is_bad_block(nand, block, &bad);
|
|
if (result != PNAND_OK) return result;
|
|
if (bad) return PNAND_ERROR_BAD_BLOCK;
|
|
|
|
first_page = block * nand->geometry.pages_per_block;
|
|
nand->port->set_write_protect(0);
|
|
nand->port->delay_us(1UL);
|
|
nand->port->select();
|
|
nand->port->write_command(PNAND_CMD_ERASE_1);
|
|
write_address_cycles(nand, first_page, nand->geometry.row_cycles);
|
|
nand->port->write_command(PNAND_CMD_ERASE_2);
|
|
nand->port->deselect();
|
|
nand->port->delay_us(1UL); /* Cover tWB before polling R/B#. */
|
|
return finish_mutation(nand, PNAND_ERROR_ERASE_FAILED);
|
|
}
|
|
|
|
int parallel_nand_is_bad_block(parallel_nand_t *nand, unsigned long block,
|
|
int *is_bad)
|
|
{
|
|
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;
|
|
}
|