diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/flash/nand/Makefile.am | 3 | ||||
-rw-r--r-- | src/flash/nand/at91sam9.c | 750 | ||||
-rw-r--r-- | src/flash/nand/driver.c | 2 |
3 files changed, 754 insertions, 1 deletions
diff --git a/src/flash/nand/Makefile.am b/src/flash/nand/Makefile.am index f3033b8..bb9998e 100644 --- a/src/flash/nand/Makefile.am +++ b/src/flash/nand/Makefile.am @@ -24,7 +24,8 @@ NAND_DRIVERS = \ s3c2410.c \ s3c2412.c \ s3c2440.c \ - s3c2443.c + s3c2443.c \ + at91sam9.c noinst_HEADERS = \ arm_io.h \ diff --git a/src/flash/nand/at91sam9.c b/src/flash/nand/at91sam9.c new file mode 100644 index 0000000..7cfd763 --- /dev/null +++ b/src/flash/nand/at91sam9.c @@ -0,0 +1,750 @@ +/* + * Copyright (C) 2009 by Dean Glazeski + * dnglaze@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <target/arm.h> +#include <helper/log.h> +#include "imp.h" +#include "arm_io.h" + +#define AT91C_PIOx_SODR (0x30) /**< Offset to PIO SODR. */ +#define AT91C_PIOx_CODR (0x34) /**< Offset to PIO CODR. */ +#define AT91C_PIOx_PDSR (0x3C) /**< Offset to PIO PDSR. */ +#define AT91C_ECCx_CR (0x00) /**< Offset to ECC CR. */ +#define AT91C_ECCx_SR (0x08) /**< Offset to ECC SR. */ +#define AT91C_ECCx_PR (0x0C) /**< Offset to ECC PR. */ +#define AT91C_ECCx_NPR (0x10) /**< Offset to ECC NPR. */ + +/** + * Representation of a pin on an AT91SAM9 chip. + */ +struct at91sam9_pin { + /** Target this pin is on. */ + struct target *target; + + /** Address of the PIO controller. */ + uint32_t pioc; + + /** Pin number. */ + uint32_t num; +}; + +/** + * Private data for the controller that is stored in the NAND device structure. + */ +struct at91sam9_nand { + /** Target the NAND is attached to. */ + struct target *target; + + /** Address of the ECC controller for NAND. */ + uint32_t ecc; + + /** Address data is written to. */ + uint32_t data; + + /** Address commands are written to. */ + uint32_t cmd; + + /** Address addresses are written to. */ + uint32_t addr; + + /** I/O structure for hosted reads/writes. */ + struct arm_nand_data io; + + /** Pin representing the ready/~busy line. */ + struct at91sam9_pin busy; + + /** Pin representing the chip enable. */ + struct at91sam9_pin ce; +}; + +/** + * Checks if the target is halted and prints an error message if it isn't. + * + * @param target Target to be checked. + * @param label String label for where function is called from. + * @return True if the target is halted. + */ +static int at91sam9_halted(struct target *target, const char *label) +{ + if (target->state == TARGET_HALTED) + return true; + + LOG_ERROR("Target must be halted to use NAND controller (%s)", label); + return false; +} + +/** + * Initialize the AT91SAM9 NAND controller. + * + * @param nand NAND device the controller is attached to. + * @return Success or failure of initialization. + */ +static int at91sam9_init(struct nand_device *nand) +{ + struct at91sam9_nand *info = nand->controller_priv; + struct target *target = info->target; + + if (!at91sam9_halted(target, "init")) { + return ERROR_NAND_OPERATION_FAILED; + } + + return ERROR_OK; +} + +/** + * Enable NAND device attached to a controller. + * + * @param info NAND controller information for controlling NAND device. + * @return Success or failure of the enabling. + */ +static int at91sam9_enable(struct at91sam9_nand *info) +{ + struct target *target = info->target; + + return target_write_u32(target, info->ce.pioc + AT91C_PIOx_CODR, 1 << info->ce.num); +} + +/** + * Disable NAND device attached to a controller. + * + * @param info NAND controller information for controlling NAND device. + * @return Success or failure of the disabling. + */ +static int at91sam9_disable(struct at91sam9_nand *info) +{ + struct target *target = info->target; + + return target_write_u32(target, info->ce.pioc + AT91C_PIOx_SODR, 1 << info->ce.num); +} + +/** + * Send a command to the NAND device. + * + * @param nand NAND device to write the command to. + * @param command Command to be written. + * @return Success or failure of writing the command. + */ +static int at91sam9_command(struct nand_device *nand, uint8_t command) +{ + struct at91sam9_nand *info = nand->controller_priv; + struct target *target = info->target; + + if (!at91sam9_halted(target, "command")) { + return ERROR_NAND_OPERATION_FAILED; + } + + at91sam9_enable(info); + + return target_write_u8(target, info->cmd, command); +} + +/** + * Reset the AT91SAM9 NAND controller. + * + * @param nand NAND device to be reset. + * @return Success or failure of reset. + */ +static int at91sam9_reset(struct nand_device *nand) +{ + struct at91sam9_nand *info = nand->controller_priv; + + if (!at91sam9_halted(info->target, "reset")) { + return ERROR_NAND_OPERATION_FAILED; + } + + return at91sam9_disable(info); +} + +/** + * Send an address to the NAND device attached to an AT91SAM9 NAND controller. + * + * @param nand NAND device to send the address to. + * @param address Address to be sent. + * @return Success or failure of sending the address. + */ +static int at91sam9_address(struct nand_device *nand, uint8_t address) +{ + struct at91sam9_nand *info = nand->controller_priv; + struct target *target = info->target; + + if (!at91sam9_halted(info->target, "address")) { + return ERROR_NAND_OPERATION_FAILED; + } + + return target_write_u8(target, info->addr, address); +} + +/** + * Read data directly from the NAND device attached to an AT91SAM9 NAND + * controller. + * + * @param nand NAND device to read from. + * @param data Pointer to where the data should be put. + * @return Success or failure of reading the data. + */ +static int at91sam9_read_data(struct nand_device *nand, void *data) +{ + struct at91sam9_nand *info = nand->controller_priv; + struct target *target = info->target; + + if (!at91sam9_halted(info->target, "read data")) { + return ERROR_NAND_OPERATION_FAILED; + } + + return target_read_u8(target, info->data, data); +} + +/** + * Write data directly to the NAND device attached to an AT91SAM9 NAND + * controller. + * + * @param nand NAND device to be written to. + * @param data Data to be written. + * @return Success or failure of the data write. + */ +static int at91sam9_write_data(struct nand_device *nand, uint16_t data) +{ + struct at91sam9_nand *info = nand->controller_priv; + struct target *target = info->target; + + if (!at91sam9_halted(target, "write data")) { + return ERROR_NAND_OPERATION_FAILED; + } + + return target_write_u8(target, info->data, data); +} + +/** + * Determine if the NAND device is ready by looking at the ready/~busy pin. + * + * @param nand NAND device to check. + * @param timeout Time in milliseconds to wait for NAND to be ready. + * @return True if the NAND is ready in the timeout period. + */ +static int at91sam9_nand_ready(struct nand_device *nand, int timeout) +{ + struct at91sam9_nand *info = nand->controller_priv; + struct target *target = info->target; + uint32_t status; + + if (!at91sam9_halted(target, "nand ready")) { + return 0; + } + + do { + target_read_u32(target, info->busy.pioc + AT91C_PIOx_PDSR, &status); + + if (status & (1 << info->busy.num)) { + return 1; + } + + alive_sleep(1); + } while (timeout-- > 0); + + return 0; +} + +/** + * Read a block of data from the NAND device attached to an AT91SAM9. This + * utilizes the ARM hosted NAND read function. + * + * @param nand NAND device to read from. + * @param data Pointer to where the read data should be placed. + * @param size Size of the data being read. + * @return Success or failure of the hosted read. + */ +static int at91sam9_read_block_data(struct nand_device *nand, uint8_t *data, int size) +{ + struct at91sam9_nand *info = nand->controller_priv; + struct arm_nand_data *io = &info->io; + int status; + + if (!at91sam9_halted(info->target, "read block")) { + return ERROR_NAND_OPERATION_FAILED; + } + + io->chunk_size = nand->page_size; + status = arm_nandread(io, data, size); + + return status; +} + +/** + * Write a block of data to a NAND device attached to an AT91SAM9. This uses + * the ARM hosted write function to write the data. + * + * @param nand NAND device to write to. + * @param data Data to be written to device. + * @param size Size of the data being written. + * @return Success or failure of the hosted write. + */ +static int at91sam9_write_block_data(struct nand_device *nand, uint8_t *data, int size) +{ + struct at91sam9_nand *info = nand->controller_priv; + struct arm_nand_data *io = &info->io; + int status; + + if (!at91sam9_halted(info->target, "write block")) { + return ERROR_NAND_OPERATION_FAILED; + } + + io->chunk_size = nand->page_size; + status = arm_nandwrite(io, data, size); + + return status; +} + +/** + * Initialize the ECC controller on the AT91SAM9. + * + * @param target Target to configure ECC on. + * @param info NAND controller information for where the ECC is. + * @return Success or failure of initialization. + */ +static int at91sam9_ecc_init(struct target *target, struct at91sam9_nand *info) +{ + if (!info->ecc) { + LOG_ERROR("ECC controller address must be set when not reading raw NAND data"); + return ERROR_NAND_OPERATION_FAILED; + } + + // reset ECC parity registers + return target_write_u32(target, info->ecc + AT91C_ECCx_CR, 1); +} + +/** + * Initialize an area for the OOB based on whether a user is requesting the OOB + * data. This determines the size of the OOB and allocates the space in case + * the user has not requested the OOB data. + * + * @param nand NAND device we are creating an OOB for. + * @param oob Pointer to the user supplied OOB area. + * @param size Size of the OOB. + * @return Pointer to an area to store OOB data. + */ +static uint8_t * at91sam9_oob_init(struct nand_device *nand, uint8_t *oob, uint32_t *size) +{ + if (!oob) { + // user doesn't want OOB, allocate it + if (nand->page_size == 512) { + *size = 16; + } else if (nand->page_size == 2048) { + *size = 64; + } + + oob = malloc(*size); + if (!oob) { + LOG_ERROR("Unable to allocate space for OOB"); + } + + memset(oob, 0xFF, *size); + } + + return oob; +} + +/** + * Reads a page from an AT91SAM9 NAND controller and verifies using 1-bit ECC + * controller on chip. This makes an attempt to correct any errors that are + * encountered while reading the page of data. + * + * @param nand NAND device to read from + * @param page Page to be read. + * @param data Pointer to where data should be read to. + * @param data_size Size of the data to be read. + * @param oob Pointer to where OOB data should be read to. + * @param oob_size Size of the OOB data to be read. + * @return Success or failure of reading the NAND page. + */ +static int at91sam9_read_page(struct nand_device *nand, uint32_t page, + uint8_t *data, uint32_t data_size, uint8_t *oob, uint32_t oob_size) +{ + int retval; + struct at91sam9_nand *info = nand->controller_priv; + struct target *target = info->target; + uint8_t *oob_data; + uint32_t status; + + retval = at91sam9_ecc_init(target, info); + if (ERROR_OK != retval) { + return retval; + } + + retval = nand_page_command(nand, page, NAND_CMD_READ0, !data); + if (ERROR_OK != retval) { + return retval; + } + + if (data) { + retval = nand_read_data_page(nand, data, data_size); + if (ERROR_OK != retval) { + return retval; + } + } + + oob_data = at91sam9_oob_init(nand, oob, &oob_size); + retval = nand_read_data_page(nand, oob_data, oob_size); + if (ERROR_OK == retval && data) { + target_read_u32(target, info->ecc + AT91C_ECCx_SR, &status); + if (status & 1) { + LOG_ERROR("Error detected!"); + if (status & 4) { + LOG_ERROR("Multiple errors encountered; unrecoverable!"); + } else { + // attempt recovery + uint32_t parity; + + target_read_u32(target, info->ecc + AT91C_ECCx_PR, &parity); + uint32_t word = (parity & 0x0000FFF0) >> 4; + uint32_t bit = parity & 0x0F; + + data[word] ^= (0x1) << bit; + LOG_INFO("Data word %d, bit %d corrected.", word, bit); + } + } + + if (status & 2) { + // we could write back correct ECC data + LOG_ERROR("Error in ECC bytes detected"); + } + } + + if (!oob) { + // if it wasn't asked for, free it + free(oob_data); + } + + return retval; +} + +/** + * Write a page of data including 1-bit ECC information to a NAND device + * attached to an AT91SAM9 controller. If there is OOB data to be written, + * this will ignore the computed ECC from the ECC controller. + * + * @param nand NAND device to write to. + * @param page Page to write. + * @param data Pointer to data being written. + * @param data_size Size of the data being written. + * @param oob Pointer to OOB data being written. + * @param oob_size Size of the OOB data. + * @return Success or failure of the page write. + */ +static int at91sam9_write_page(struct nand_device *nand, uint32_t page, + uint8_t *data, uint32_t data_size, uint8_t *oob, uint32_t oob_size) +{ + struct at91sam9_nand *info = nand->controller_priv; + struct target *target = info->target; + int retval; + uint8_t *oob_data = oob; + uint32_t parity, nparity; + + retval = at91sam9_ecc_init(target, info); + if (ERROR_OK != retval) { + return retval; + } + + retval = nand_page_command(nand, page, NAND_CMD_SEQIN, !data); + if (ERROR_OK != retval) { + return retval; + } + + if (data) { + retval = nand_write_data_page(nand, data, data_size); + if (ERROR_OK != retval) { + LOG_ERROR("Unable to write data to NAND device"); + return retval; + } + } + + oob_data = at91sam9_oob_init(nand, oob, &oob_size); + + if (!oob) { + // no OOB given, so read in the ECC parity from the ECC controller + target_read_u32(target, info->ecc + AT91C_ECCx_PR, &parity); + target_read_u32(target, info->ecc + AT91C_ECCx_NPR, &nparity); + + oob_data[0] = (uint8_t) parity; + oob_data[1] = (uint8_t) (parity >> 8); + oob_data[2] = (uint8_t) nparity; + oob_data[3] = (uint8_t) (nparity >> 8); + } + + retval = nand_write_data_page(nand, oob_data, oob_size); + + if (!oob) { + free(oob_data); + } + + if (ERROR_OK != retval) { + LOG_ERROR("Unable to write OOB data to NAND"); + return retval; + } + + retval = nand_write_finish(nand); + + return retval; +} + +/** + * Handle the initial NAND device command for AT91SAM9 controllers. This + * initializes much of the controller information struct to be ready for future + * reads and writes. + */ +NAND_DEVICE_COMMAND_HANDLER(at91sam9_nand_device_command) +{ + struct target *target = NULL; + unsigned long chip = 0, ecc = 0; + struct at91sam9_nand *info = NULL; + + LOG_DEBUG("AT91SAM9 NAND Device Command\n"); + + if (CMD_ARGC < 3 || CMD_ARGC > 4) { + LOG_ERROR("parameters: %s target chip_addr", CMD_ARGV[0]); + return ERROR_NAND_OPERATION_FAILED; + } + + target = get_target(CMD_ARGV[1]); + if (!target) { + LOG_ERROR("invalid target: %s", CMD_ARGV[1]); + return ERROR_NAND_OPERATION_FAILED; + } + + COMMAND_PARSE_NUMBER(ulong, CMD_ARGV[2], chip); + if (chip == 0) { + LOG_ERROR("invalid NAND chip address: %s", CMD_ARGV[2]); + return ERROR_NAND_OPERATION_FAILED; + } + + if (CMD_ARGC == 4) { + COMMAND_PARSE_NUMBER(ulong, CMD_ARGV[3], ecc); + if (ecc == 0) { + LOG_ERROR("invalid ECC controller address: %s", CMD_ARGV[3]); + return ERROR_NAND_OPERATION_FAILED; + } + } + + info = calloc(1, sizeof(*info)); + if (!info) { + LOG_ERROR("unable to allocate space for controller private data"); + return ERROR_NAND_OPERATION_FAILED; + } + + info->target = target; + info->data = chip; + info->cmd = chip | (1 << 22); + info->addr = chip | (1 << 21); + info->ecc = ecc; + + nand->controller_priv = info; + info->io.target = target; + info->io.data = info->data; + info->io.op = ARM_NAND_NONE; + + return ERROR_OK; +} + +/** + * Handle the AT91SAM9 CLE command for specifying the address line to use for + * writing commands to a NAND device. + */ +COMMAND_HANDLER(handle_at91sam9_cle_command) +{ + struct nand_device *nand = NULL; + struct at91sam9_nand *info = NULL; + unsigned num, address_line; + + if (CMD_ARGC != 2) { + command_print(CMD_CTX, "incorrect number of arguments for 'at91sam9 cle' command"); + return ERROR_OK; + } + + COMMAND_PARSE_NUMBER(uint, CMD_ARGV[0], num); + nand = get_nand_device_by_num(num); + if (!nand) { + command_print(CMD_CTX, "invalid nand device number: %s", CMD_ARGV[0]); + return ERROR_OK; + } + + info = nand->controller_priv; + + COMMAND_PARSE_NUMBER(uint, CMD_ARGV[1], address_line); + info->cmd = info->data | (1 << address_line); + + return ERROR_OK; +} + +/** + * Handle the AT91SAM9 ALE command for specifying the address line to use for + * writing addresses to the NAND device. + */ +COMMAND_HANDLER(handle_at91sam9_ale_command) +{ + struct nand_device *nand = NULL; + struct at91sam9_nand *info = NULL; + unsigned num, address_line; + + if (CMD_ARGC != 2) { + return ERROR_COMMAND_SYNTAX_ERROR; + } + + COMMAND_PARSE_NUMBER(uint, CMD_ARGV[0], num); + nand = get_nand_device_by_num(num); + if (!nand) { + command_print(CMD_CTX, "invalid nand device number: %s", CMD_ARGV[0]); + return ERROR_COMMAND_ARGUMENT_INVALID; + } + + info = nand->controller_priv; + + COMMAND_PARSE_NUMBER(uint, CMD_ARGV[1], address_line); + info->addr = info->data | (1 << address_line); + + return ERROR_OK; +} + +/** + * Handle the AT91SAM9 RDY/~BUSY command for specifying the pin that watches the + * RDY/~BUSY line from the NAND device. + */ +COMMAND_HANDLER(handle_at91sam9_rdy_busy_command) +{ + struct nand_device *nand = NULL; + struct at91sam9_nand *info = NULL; + unsigned num, base_pioc, pin_num; + + if (CMD_ARGC != 3) { + return ERROR_COMMAND_SYNTAX_ERROR; + } + + COMMAND_PARSE_NUMBER(uint, CMD_ARGV[0], num); + nand = get_nand_device_by_num(num); + if (!nand) { + command_print(CMD_CTX, "invalid nand device number: %s", CMD_ARGV[0]); + return ERROR_COMMAND_ARGUMENT_INVALID; + } + + info = nand->controller_priv; + + COMMAND_PARSE_NUMBER(uint, CMD_ARGV[1], base_pioc); + info->busy.pioc = base_pioc; + + COMMAND_PARSE_NUMBER(uint, CMD_ARGV[2], pin_num); + info->busy.num = pin_num; + + return ERROR_OK; +} + +/** + * Handle the AT91SAM9 CE command for specifying the pin that is used to enable + * or disable the NAND device. + */ +COMMAND_HANDLER(handle_at91sam9_ce_command) +{ + struct nand_device *nand = NULL; + struct at91sam9_nand *info = NULL; + unsigned num, base_pioc, pin_num; + + if (CMD_ARGC != 3) { + return ERROR_COMMAND_SYNTAX_ERROR; + } + + COMMAND_PARSE_NUMBER(uint, CMD_ARGV[0], num); + nand = get_nand_device_by_num(num); + if (!nand) { + command_print(CMD_CTX, "invalid nand device number: %s", CMD_ARGV[0]); + return ERROR_COMMAND_ARGUMENT_INVALID; + } + + info = nand->controller_priv; + + COMMAND_PARSE_NUMBER(uint, CMD_ARGV[1], base_pioc); + info->ce.pioc = base_pioc; + + COMMAND_PARSE_NUMBER(uint, CMD_ARGV[2], pin_num); + info->ce.num = pin_num; + + return ERROR_OK; +} + +static const struct command_registration at91sam9_sub_command_handlers[] = { + { + .name = "cle", + .handler = handle_at91sam9_cle_command, + .mode = COMMAND_CONFIG, + .help = "set command latch enable address line (default is 22)", + .usage = "<device_id> <address_line>", + }, + { + .name = "ale", + .handler = handle_at91sam9_ale_command, + .mode = COMMAND_CONFIG, + .help = "set address latch enable address line (default is 21)", + .usage = "<device_id> <address_line>", + }, + { + .name = "rdy_busy", + .handler = handle_at91sam9_rdy_busy_command, + .mode = COMMAND_CONFIG, + .help = "set the input pin connected to RDY/~BUSY signal (no default)", + .usage = "<device_id> <base_pioc> <pin_num>", + }, + { + .name = "ce", + .handler = handle_at91sam9_ce_command, + .mode = COMMAND_CONFIG, + .help = "set the output pin connected to chip enable signal (no default)", + .usage = "<device_id> <base_pioc> <pin_num>", + }, + COMMAND_REGISTRATION_DONE +}; + +static const struct command_registration at91sam9_command_handler[] = { + { + .name = "at91sam9", + .mode = COMMAND_ANY, + .help = "AT91SAM9 NAND flash controller commands", + .chain = at91sam9_sub_command_handlers, + }, + COMMAND_REGISTRATION_DONE +}; + +/** + * Structure representing the AT91SAM9 NAND controller. + */ +struct nand_flash_controller at91sam9_nand_controller = { + .name = "at91sam9", + .nand_device_command = at91sam9_nand_device_command, + .commands = at91sam9_command_handler, + .init = at91sam9_init, + .command = at91sam9_command, + .reset = at91sam9_reset, + .address = at91sam9_address, + .read_data = at91sam9_read_data, + .write_data = at91sam9_write_data, + .nand_ready = at91sam9_nand_ready, + .read_block_data = at91sam9_read_block_data, + .write_block_data = at91sam9_write_block_data, + .read_page = at91sam9_read_page, + .write_page = at91sam9_write_page, +}; diff --git a/src/flash/nand/driver.c b/src/flash/nand/driver.c index 1ccc4f4..0e174b2 100644 --- a/src/flash/nand/driver.c +++ b/src/flash/nand/driver.c @@ -37,6 +37,7 @@ extern struct nand_flash_controller s3c2412_nand_controller; extern struct nand_flash_controller s3c2440_nand_controller; extern struct nand_flash_controller s3c2443_nand_controller; extern struct nand_flash_controller imx31_nand_flash_controller; +extern struct nand_flash_controller at91sam9_nand_controller; /* extern struct nand_flash_controller boundary_scan_nand_controller; */ @@ -51,6 +52,7 @@ static struct nand_flash_controller *nand_flash_controllers[] = &s3c2440_nand_controller, &s3c2443_nand_controller, &imx31_nand_flash_controller, + &at91sam9_nand_controller, /* &boundary_scan_nand_controller, */ NULL }; |