7f725bcd5c
The NAND Flash environment consists of several distinct components: - NAND framework (drivers harness for NAND controllers and NAND chips) - NAND simulator (NANDsim) - NAND file system (NAND FS) - Companion tools and utilities - Documentation (manual pages) This work is still experimental. Please use with caution. Obtained from: Semihalf Supported by: FreeBSD Foundation, Juniper Networks
1321 lines
29 KiB
C
1321 lines
29 KiB
C
/*-
|
|
* Copyright (C) 2009-2012 Semihalf
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
/* Generic NAND driver */
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/module.h>
|
|
#include <sys/rman.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/time.h>
|
|
#include <sys/malloc.h>
|
|
|
|
#include <dev/nand/nand.h>
|
|
#include <dev/nand/nandbus.h>
|
|
#include "nfc_if.h"
|
|
#include "nand_if.h"
|
|
#include "nandbus_if.h"
|
|
|
|
|
|
static int onfi_nand_probe(device_t dev);
|
|
static int large_nand_probe(device_t dev);
|
|
static int small_nand_probe(device_t dev);
|
|
static int generic_nand_attach(device_t dev);
|
|
static int generic_nand_detach(device_t dev);
|
|
|
|
static int generic_erase_block(device_t, uint32_t);
|
|
static int generic_erase_block_intlv(device_t, uint32_t);
|
|
static int generic_read_page (device_t, uint32_t, void *, uint32_t, uint32_t);
|
|
static int generic_read_oob(device_t, uint32_t, void *, uint32_t, uint32_t);
|
|
static int generic_program_page(device_t, uint32_t, void *, uint32_t, uint32_t);
|
|
static int generic_program_page_intlv(device_t, uint32_t, void *, uint32_t,
|
|
uint32_t);
|
|
static int generic_program_oob(device_t, uint32_t, void *, uint32_t, uint32_t);
|
|
static int generic_is_blk_bad(device_t, uint32_t, uint8_t *);
|
|
static int generic_get_ecc(device_t, void *, void *, int *);
|
|
static int generic_correct_ecc(device_t, void *, void *, void *);
|
|
|
|
static int small_read_page(device_t, uint32_t, void *, uint32_t, uint32_t);
|
|
static int small_read_oob(device_t, uint32_t, void *, uint32_t, uint32_t);
|
|
static int small_program_page(device_t, uint32_t, void *, uint32_t, uint32_t);
|
|
static int small_program_oob(device_t, uint32_t, void *, uint32_t, uint32_t);
|
|
|
|
static int onfi_is_blk_bad(device_t, uint32_t, uint8_t *);
|
|
static int onfi_read_parameter(struct nand_chip *, struct onfi_params *);
|
|
|
|
static int nand_send_address(device_t, int32_t, int32_t, int8_t);
|
|
|
|
static device_method_t onand_methods[] = {
|
|
/* Device interface */
|
|
DEVMETHOD(device_probe, onfi_nand_probe),
|
|
DEVMETHOD(device_attach, generic_nand_attach),
|
|
DEVMETHOD(device_detach, generic_nand_detach),
|
|
|
|
DEVMETHOD(nand_read_page, generic_read_page),
|
|
DEVMETHOD(nand_program_page, generic_program_page),
|
|
DEVMETHOD(nand_program_page_intlv, generic_program_page_intlv),
|
|
DEVMETHOD(nand_read_oob, generic_read_oob),
|
|
DEVMETHOD(nand_program_oob, generic_program_oob),
|
|
DEVMETHOD(nand_erase_block, generic_erase_block),
|
|
DEVMETHOD(nand_erase_block_intlv, generic_erase_block_intlv),
|
|
|
|
DEVMETHOD(nand_is_blk_bad, onfi_is_blk_bad),
|
|
DEVMETHOD(nand_get_ecc, generic_get_ecc),
|
|
DEVMETHOD(nand_correct_ecc, generic_correct_ecc),
|
|
{ 0, 0 }
|
|
};
|
|
|
|
static device_method_t lnand_methods[] = {
|
|
/* Device interface */
|
|
DEVMETHOD(device_probe, large_nand_probe),
|
|
DEVMETHOD(device_attach, generic_nand_attach),
|
|
DEVMETHOD(device_detach, generic_nand_detach),
|
|
|
|
DEVMETHOD(nand_read_page, generic_read_page),
|
|
DEVMETHOD(nand_program_page, generic_program_page),
|
|
DEVMETHOD(nand_read_oob, generic_read_oob),
|
|
DEVMETHOD(nand_program_oob, generic_program_oob),
|
|
DEVMETHOD(nand_erase_block, generic_erase_block),
|
|
|
|
DEVMETHOD(nand_is_blk_bad, generic_is_blk_bad),
|
|
DEVMETHOD(nand_get_ecc, generic_get_ecc),
|
|
DEVMETHOD(nand_correct_ecc, generic_correct_ecc),
|
|
{ 0, 0 }
|
|
};
|
|
|
|
static device_method_t snand_methods[] = {
|
|
/* Device interface */
|
|
DEVMETHOD(device_probe, small_nand_probe),
|
|
DEVMETHOD(device_attach, generic_nand_attach),
|
|
DEVMETHOD(device_detach, generic_nand_detach),
|
|
|
|
DEVMETHOD(nand_read_page, small_read_page),
|
|
DEVMETHOD(nand_program_page, small_program_page),
|
|
DEVMETHOD(nand_read_oob, small_read_oob),
|
|
DEVMETHOD(nand_program_oob, small_program_oob),
|
|
DEVMETHOD(nand_erase_block, generic_erase_block),
|
|
|
|
DEVMETHOD(nand_is_blk_bad, generic_is_blk_bad),
|
|
DEVMETHOD(nand_get_ecc, generic_get_ecc),
|
|
DEVMETHOD(nand_correct_ecc, generic_correct_ecc),
|
|
{ 0, 0 }
|
|
};
|
|
|
|
devclass_t onand_devclass;
|
|
devclass_t lnand_devclass;
|
|
devclass_t snand_devclass;
|
|
|
|
driver_t onand_driver = {
|
|
"onand",
|
|
onand_methods,
|
|
sizeof(struct nand_chip)
|
|
};
|
|
|
|
driver_t lnand_driver = {
|
|
"lnand",
|
|
lnand_methods,
|
|
sizeof(struct nand_chip)
|
|
};
|
|
|
|
driver_t snand_driver = {
|
|
"snand",
|
|
snand_methods,
|
|
sizeof(struct nand_chip)
|
|
};
|
|
|
|
DRIVER_MODULE(onand, nandbus, onand_driver, onand_devclass, 0, 0);
|
|
DRIVER_MODULE(lnand, nandbus, lnand_driver, lnand_devclass, 0, 0);
|
|
DRIVER_MODULE(snand, nandbus, snand_driver, snand_devclass, 0, 0);
|
|
|
|
static int
|
|
onfi_nand_probe(device_t dev)
|
|
{
|
|
struct nandbus_ivar *ivar;
|
|
|
|
ivar = device_get_ivars(dev);
|
|
if (ivar && ivar->is_onfi) {
|
|
device_set_desc(dev, "ONFI compliant NAND");
|
|
return (BUS_PROBE_DEFAULT);
|
|
}
|
|
|
|
return (ENODEV);
|
|
}
|
|
|
|
static int
|
|
large_nand_probe(device_t dev)
|
|
{
|
|
struct nandbus_ivar *ivar;
|
|
|
|
ivar = device_get_ivars(dev);
|
|
if (ivar && !ivar->is_onfi && ivar->params->page_size >= 512) {
|
|
device_set_desc(dev, ivar->params->name);
|
|
return (BUS_PROBE_DEFAULT);
|
|
}
|
|
|
|
return (ENODEV);
|
|
}
|
|
|
|
static int
|
|
small_nand_probe(device_t dev)
|
|
{
|
|
struct nandbus_ivar *ivar;
|
|
|
|
ivar = device_get_ivars(dev);
|
|
if (ivar && !ivar->is_onfi && ivar->params->page_size == 512) {
|
|
device_set_desc(dev, ivar->params->name);
|
|
return (BUS_PROBE_DEFAULT);
|
|
}
|
|
|
|
return (ENODEV);
|
|
}
|
|
|
|
static int
|
|
generic_nand_attach(device_t dev)
|
|
{
|
|
struct nand_chip *chip;
|
|
struct nandbus_ivar *ivar;
|
|
struct onfi_params *onfi_params;
|
|
device_t nandbus, nfc;
|
|
int err;
|
|
|
|
chip = device_get_softc(dev);
|
|
chip->dev = dev;
|
|
|
|
ivar = device_get_ivars(dev);
|
|
chip->id.man_id = ivar->man_id;
|
|
chip->id.dev_id = ivar->dev_id;
|
|
chip->num = ivar->cs;
|
|
|
|
/* TODO remove when HW ECC supported */
|
|
nandbus = device_get_parent(dev);
|
|
nfc = device_get_parent(nandbus);
|
|
|
|
chip->nand = device_get_softc(nfc);
|
|
|
|
if (ivar->is_onfi) {
|
|
onfi_params = malloc(sizeof(struct onfi_params),
|
|
M_NAND, M_WAITOK | M_ZERO);
|
|
if (onfi_params == NULL)
|
|
return (ENXIO);
|
|
|
|
if (onfi_read_parameter(chip, onfi_params)) {
|
|
nand_debug(NDBG_GEN,"Could not read parameter page!\n");
|
|
free(onfi_params, M_NAND);
|
|
return (ENXIO);
|
|
}
|
|
|
|
nand_onfi_set_params(chip, onfi_params);
|
|
/* Set proper column and row cycles */
|
|
ivar->cols = (onfi_params->address_cycles >> 4) & 0xf;
|
|
ivar->rows = onfi_params->address_cycles & 0xf;
|
|
free(onfi_params, M_NAND);
|
|
|
|
} else {
|
|
|
|
nand_set_params(chip, ivar->params);
|
|
}
|
|
|
|
err = nand_init_stat(chip);
|
|
if (err) {
|
|
generic_nand_detach(dev);
|
|
return (err);
|
|
}
|
|
|
|
err = nand_init_bbt(chip);
|
|
if (err) {
|
|
generic_nand_detach(dev);
|
|
return (err);
|
|
}
|
|
|
|
err = nand_make_dev(chip);
|
|
if (err) {
|
|
generic_nand_detach(dev);
|
|
return (err);
|
|
}
|
|
|
|
err = create_geom_disk(chip);
|
|
if (err) {
|
|
generic_nand_detach(dev);
|
|
return (err);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
generic_nand_detach(device_t dev)
|
|
{
|
|
struct nand_chip *chip;
|
|
|
|
chip = device_get_softc(dev);
|
|
|
|
nand_destroy_bbt(chip);
|
|
destroy_geom_disk(chip);
|
|
nand_destroy_dev(chip);
|
|
nand_destroy_stat(chip);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
can_write(device_t nandbus)
|
|
{
|
|
uint8_t status;
|
|
|
|
if (NANDBUS_WAIT_READY(nandbus, &status))
|
|
return (0);
|
|
|
|
if (!(status & NAND_STATUS_WP)) {
|
|
nand_debug(NDBG_GEN,"Chip is write-protected");
|
|
return (0);
|
|
}
|
|
|
|
return (1);
|
|
}
|
|
|
|
static int
|
|
check_fail(device_t nandbus)
|
|
{
|
|
uint8_t status;
|
|
|
|
NANDBUS_WAIT_READY(nandbus, &status);
|
|
if (status & NAND_STATUS_FAIL) {
|
|
nand_debug(NDBG_GEN,"Status failed %x", status);
|
|
return (ENXIO);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
onfi_read_parameter(struct nand_chip *chip, struct onfi_params *params)
|
|
{
|
|
device_t nandbus;
|
|
|
|
nand_debug(NDBG_GEN,"read parameter");
|
|
|
|
nandbus = device_get_parent(chip->dev);
|
|
|
|
NANDBUS_SELECT_CS(nandbus, chip->num);
|
|
|
|
if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_READ_PARAMETER))
|
|
return (ENXIO);
|
|
|
|
if (nand_send_address(chip->dev, -1, -1, PAGE_PARAMETER_DEF))
|
|
return (ENXIO);
|
|
|
|
if (NANDBUS_START_COMMAND(nandbus))
|
|
return (ENXIO);
|
|
|
|
NANDBUS_READ_BUFFER(nandbus, params, sizeof(struct onfi_params));
|
|
|
|
/* TODO */
|
|
/* Check for signature */
|
|
/* Check CRC */
|
|
/* Use redundant page if necessary */
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
send_read_page(device_t nand, uint8_t start_command, uint8_t end_command,
|
|
uint32_t row, uint32_t column)
|
|
{
|
|
device_t nandbus = device_get_parent(nand);
|
|
|
|
if (NANDBUS_SEND_COMMAND(nandbus, start_command))
|
|
return (ENXIO);
|
|
|
|
if (nand_send_address(nand, row, column, -1))
|
|
return (ENXIO);
|
|
|
|
if (NANDBUS_SEND_COMMAND(nandbus, end_command))
|
|
return (ENXIO);
|
|
|
|
if (NANDBUS_START_COMMAND(nandbus))
|
|
return (ENXIO);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
generic_read_page(device_t nand, uint32_t page, void *buf, uint32_t len,
|
|
uint32_t offset)
|
|
{
|
|
struct nand_chip *chip;
|
|
struct page_stat *pg_stat;
|
|
device_t nandbus;
|
|
uint32_t row;
|
|
|
|
nand_debug(NDBG_GEN,"%p raw read page %x[%x] at %x", nand, page, len, offset);
|
|
chip = device_get_softc(nand);
|
|
nandbus = device_get_parent(nand);
|
|
|
|
if (nand_check_page_boundary(chip, page))
|
|
return (ENXIO);
|
|
|
|
page_to_row(&chip->chip_geom, page, &row);
|
|
|
|
if (send_read_page(nand, NAND_CMD_READ, NAND_CMD_READ_END, row,
|
|
offset))
|
|
return (ENXIO);
|
|
|
|
DELAY(chip->t_r);
|
|
|
|
NANDBUS_READ_BUFFER(nandbus, buf, len);
|
|
|
|
if (check_fail(nandbus))
|
|
return (ENXIO);
|
|
|
|
pg_stat = &(chip->pg_stat[page]);
|
|
pg_stat->page_raw_read++;
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
generic_read_oob(device_t nand, uint32_t page, void* buf, uint32_t len,
|
|
uint32_t offset)
|
|
{
|
|
struct nand_chip *chip;
|
|
device_t nandbus;
|
|
uint32_t row;
|
|
|
|
nand_debug(NDBG_GEN,"%p raw read oob %x[%x] at %x", nand, page, len, offset);
|
|
chip = device_get_softc(nand);
|
|
nandbus = device_get_parent(nand);
|
|
|
|
if (nand_check_page_boundary(chip, page)) {
|
|
nand_debug(NDBG_GEN,"page boundary check failed: %08x\n", page);
|
|
return (ENXIO);
|
|
}
|
|
|
|
page_to_row(&chip->chip_geom, page, &row);
|
|
|
|
offset += chip->chip_geom.page_size;
|
|
|
|
if (send_read_page(nand, NAND_CMD_READ, NAND_CMD_READ_END, row,
|
|
offset))
|
|
return (ENXIO);
|
|
|
|
DELAY(chip->t_r);
|
|
|
|
NANDBUS_READ_BUFFER(nandbus, buf, len);
|
|
|
|
if (check_fail(nandbus))
|
|
return (ENXIO);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
send_start_program_page(device_t nand, uint32_t row, uint32_t column)
|
|
{
|
|
device_t nandbus = device_get_parent(nand);
|
|
|
|
if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_PROG))
|
|
return (ENXIO);
|
|
|
|
if (nand_send_address(nand, row, column, -1))
|
|
return (ENXIO);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
send_end_program_page(device_t nandbus, uint8_t end_command)
|
|
{
|
|
|
|
if (NANDBUS_SEND_COMMAND(nandbus, end_command))
|
|
return (ENXIO);
|
|
|
|
if (NANDBUS_START_COMMAND(nandbus))
|
|
return (ENXIO);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
generic_program_page(device_t nand, uint32_t page, void *buf, uint32_t len,
|
|
uint32_t offset)
|
|
{
|
|
struct nand_chip *chip;
|
|
struct page_stat *pg_stat;
|
|
device_t nandbus;
|
|
uint32_t row;
|
|
|
|
nand_debug(NDBG_GEN,"%p raw prog page %x[%x] at %x", nand, page, len,
|
|
offset);
|
|
chip = device_get_softc(nand);
|
|
nandbus = device_get_parent(nand);
|
|
|
|
if (nand_check_page_boundary(chip, page))
|
|
return (ENXIO);
|
|
|
|
page_to_row(&chip->chip_geom, page, &row);
|
|
|
|
if (!can_write(nandbus))
|
|
return (ENXIO);
|
|
|
|
if (send_start_program_page(nand, row, offset))
|
|
return (ENXIO);
|
|
|
|
NANDBUS_WRITE_BUFFER(nandbus, buf, len);
|
|
|
|
if (send_end_program_page(nandbus, NAND_CMD_PROG_END))
|
|
return (ENXIO);
|
|
|
|
DELAY(chip->t_prog);
|
|
|
|
if (check_fail(nandbus))
|
|
return (ENXIO);
|
|
|
|
pg_stat = &(chip->pg_stat[page]);
|
|
pg_stat->page_raw_written++;
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
generic_program_page_intlv(device_t nand, uint32_t page, void *buf,
|
|
uint32_t len, uint32_t offset)
|
|
{
|
|
struct nand_chip *chip;
|
|
struct page_stat *pg_stat;
|
|
device_t nandbus;
|
|
uint32_t row;
|
|
|
|
nand_debug(NDBG_GEN,"%p raw prog page %x[%x] at %x", nand, page, len, offset);
|
|
chip = device_get_softc(nand);
|
|
nandbus = device_get_parent(nand);
|
|
|
|
if (nand_check_page_boundary(chip, page))
|
|
return (ENXIO);
|
|
|
|
page_to_row(&chip->chip_geom, page, &row);
|
|
|
|
if (!can_write(nandbus))
|
|
return (ENXIO);
|
|
|
|
if (send_start_program_page(nand, row, offset))
|
|
return (ENXIO);
|
|
|
|
NANDBUS_WRITE_BUFFER(nandbus, buf, len);
|
|
|
|
if (send_end_program_page(nandbus, NAND_CMD_PROG_INTLV))
|
|
return (ENXIO);
|
|
|
|
DELAY(chip->t_prog);
|
|
|
|
if (check_fail(nandbus))
|
|
return (ENXIO);
|
|
|
|
pg_stat = &(chip->pg_stat[page]);
|
|
pg_stat->page_raw_written++;
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
generic_program_oob(device_t nand, uint32_t page, void* buf, uint32_t len,
|
|
uint32_t offset)
|
|
{
|
|
struct nand_chip *chip;
|
|
device_t nandbus;
|
|
uint32_t row;
|
|
|
|
nand_debug(NDBG_GEN,"%p raw prog oob %x[%x] at %x", nand, page, len,
|
|
offset);
|
|
chip = device_get_softc(nand);
|
|
nandbus = device_get_parent(nand);
|
|
|
|
if (nand_check_page_boundary(chip, page))
|
|
return (ENXIO);
|
|
|
|
page_to_row(&chip->chip_geom, page, &row);
|
|
offset += chip->chip_geom.page_size;
|
|
|
|
if (!can_write(nandbus))
|
|
return (ENXIO);
|
|
|
|
if (send_start_program_page(nand, row, offset))
|
|
return (ENXIO);
|
|
|
|
NANDBUS_WRITE_BUFFER(nandbus, buf, len);
|
|
|
|
if (send_end_program_page(nandbus, NAND_CMD_PROG_END))
|
|
return (ENXIO);
|
|
|
|
DELAY(chip->t_prog);
|
|
|
|
if (check_fail(nandbus))
|
|
return (ENXIO);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
send_erase_block(device_t nand, uint32_t row, uint8_t second_command)
|
|
{
|
|
device_t nandbus = device_get_parent(nand);
|
|
|
|
if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_ERASE))
|
|
return (ENXIO);
|
|
|
|
if (nand_send_address(nand, row, -1, -1))
|
|
return (ENXIO);
|
|
|
|
if (NANDBUS_SEND_COMMAND(nandbus, second_command))
|
|
return (ENXIO);
|
|
|
|
if (NANDBUS_START_COMMAND(nandbus))
|
|
return (ENXIO);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
generic_erase_block(device_t nand, uint32_t block)
|
|
{
|
|
struct block_stat *blk_stat;
|
|
struct nand_chip *chip;
|
|
device_t nandbus;
|
|
int row;
|
|
|
|
nand_debug(NDBG_GEN,"%p erase block %x", nand, block);
|
|
nandbus = device_get_parent(nand);
|
|
chip = device_get_softc(nand);
|
|
|
|
if (block >= (chip->chip_geom.blks_per_lun * chip->chip_geom.luns))
|
|
return (ENXIO);
|
|
|
|
row = (block << chip->chip_geom.blk_shift) &
|
|
chip->chip_geom.blk_mask;
|
|
|
|
nand_debug(NDBG_GEN,"%p erase block row %x", nand, row);
|
|
|
|
if (!can_write(nandbus))
|
|
return (ENXIO);
|
|
|
|
send_erase_block(nand, row, NAND_CMD_ERASE_END);
|
|
|
|
DELAY(chip->t_bers);
|
|
|
|
if (check_fail(nandbus))
|
|
return (ENXIO);
|
|
|
|
blk_stat = &(chip->blk_stat[block]);
|
|
blk_stat->block_erased++;
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
generic_erase_block_intlv(device_t nand, uint32_t block)
|
|
{
|
|
struct block_stat *blk_stat;
|
|
struct nand_chip *chip;
|
|
device_t nandbus;
|
|
int row;
|
|
|
|
nand_debug(NDBG_GEN,"%p erase block %x", nand, block);
|
|
nandbus = device_get_parent(nand);
|
|
chip = device_get_softc(nand);
|
|
|
|
if (block >= (chip->chip_geom.blks_per_lun * chip->chip_geom.luns))
|
|
return (ENXIO);
|
|
|
|
row = (block << chip->chip_geom.blk_shift) &
|
|
chip->chip_geom.blk_mask;
|
|
|
|
if (!can_write(nandbus))
|
|
return (ENXIO);
|
|
|
|
send_erase_block(nand, row, NAND_CMD_ERASE_INTLV);
|
|
|
|
DELAY(chip->t_bers);
|
|
|
|
if (check_fail(nandbus))
|
|
return (ENXIO);
|
|
|
|
blk_stat = &(chip->blk_stat[block]);
|
|
blk_stat->block_erased++;
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
static int
|
|
onfi_is_blk_bad(device_t device, uint32_t block_number, uint8_t *bad)
|
|
{
|
|
struct nand_chip *chip;
|
|
int page_number, i, j, err;
|
|
uint8_t *oob;
|
|
|
|
chip = device_get_softc(device);
|
|
|
|
oob = malloc(chip->chip_geom.oob_size, M_NAND, M_WAITOK);
|
|
if (!oob) {
|
|
device_printf(device, "%s: cannot allocate oob\n", __func__);
|
|
return (ENOMEM);
|
|
}
|
|
|
|
page_number = block_number * chip->chip_geom.pgs_per_blk;
|
|
*bad = 0;
|
|
/* Check OOB of first and last page */
|
|
for (i = 0; i < 2; i++, page_number+= chip->chip_geom.pgs_per_blk - 1) {
|
|
err = generic_read_oob(device, page_number, oob,
|
|
chip->chip_geom.oob_size, 0);
|
|
if (err) {
|
|
device_printf(device, "%s: cannot allocate oob\n",
|
|
__func__);
|
|
free(oob, M_NAND);
|
|
return (ENOMEM);
|
|
}
|
|
|
|
for (j = 0; j < chip->chip_geom.oob_size; j++) {
|
|
if (!oob[j]) {
|
|
*bad = 1;
|
|
free(oob, M_NAND);
|
|
return (0);
|
|
}
|
|
}
|
|
}
|
|
|
|
free(oob, M_NAND);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
send_small_read_page(device_t nand, uint8_t start_command,
|
|
uint32_t row, uint32_t column)
|
|
{
|
|
device_t nandbus = device_get_parent(nand);
|
|
|
|
if (NANDBUS_SEND_COMMAND(nandbus, start_command))
|
|
return (ENXIO);
|
|
|
|
if (nand_send_address(nand, row, column, -1))
|
|
return (ENXIO);
|
|
|
|
if (NANDBUS_START_COMMAND(nandbus))
|
|
return (ENXIO);
|
|
|
|
return (0);
|
|
}
|
|
|
|
|
|
static int
|
|
small_read_page(device_t nand, uint32_t page, void *buf, uint32_t len,
|
|
uint32_t offset)
|
|
{
|
|
struct nand_chip *chip;
|
|
struct page_stat *pg_stat;
|
|
device_t nandbus;
|
|
uint32_t row;
|
|
|
|
nand_debug(NDBG_GEN,"%p small read page %x[%x] at %x", nand, page, len, offset);
|
|
chip = device_get_softc(nand);
|
|
nandbus = device_get_parent(nand);
|
|
|
|
if (nand_check_page_boundary(chip, page))
|
|
return (ENXIO);
|
|
|
|
page_to_row(&chip->chip_geom, page, &row);
|
|
|
|
if (offset < 256) {
|
|
if (send_small_read_page(nand, NAND_CMD_SMALLA, row, offset))
|
|
return (ENXIO);
|
|
} else {
|
|
offset -= 256;
|
|
if (send_small_read_page(nandbus, NAND_CMD_SMALLB, row, offset))
|
|
return (ENXIO);
|
|
}
|
|
|
|
DELAY(chip->t_r);
|
|
|
|
NANDBUS_READ_BUFFER(nandbus, buf, len);
|
|
|
|
if (check_fail(nandbus))
|
|
return (ENXIO);
|
|
|
|
pg_stat = &(chip->pg_stat[page]);
|
|
pg_stat->page_raw_read++;
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
small_read_oob(device_t nand, uint32_t page, void *buf, uint32_t len,
|
|
uint32_t offset)
|
|
{
|
|
struct nand_chip *chip;
|
|
struct page_stat *pg_stat;
|
|
device_t nandbus;
|
|
uint32_t row;
|
|
|
|
nand_debug(NDBG_GEN,"%p small read oob %x[%x] at %x", nand, page, len, offset);
|
|
chip = device_get_softc(nand);
|
|
nandbus = device_get_parent(nand);
|
|
|
|
if (nand_check_page_boundary(chip, page))
|
|
return (ENXIO);
|
|
|
|
page_to_row(&chip->chip_geom, page, &row);
|
|
|
|
if (send_small_read_page(nand, NAND_CMD_SMALLOOB, row, 0))
|
|
return (ENXIO);
|
|
|
|
DELAY(chip->t_r);
|
|
|
|
NANDBUS_READ_BUFFER(nandbus, buf, len);
|
|
|
|
if (check_fail(nandbus))
|
|
return (ENXIO);
|
|
|
|
pg_stat = &(chip->pg_stat[page]);
|
|
pg_stat->page_raw_read++;
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
small_program_page(device_t nand, uint32_t page, void* buf, uint32_t len,
|
|
uint32_t offset)
|
|
{
|
|
struct nand_chip *chip;
|
|
device_t nandbus;
|
|
uint32_t row;
|
|
|
|
nand_debug(NDBG_GEN,"%p small prog page %x[%x] at %x", nand, page, len, offset);
|
|
chip = device_get_softc(nand);
|
|
nandbus = device_get_parent(nand);
|
|
|
|
if (nand_check_page_boundary(chip, page))
|
|
return (ENXIO);
|
|
|
|
page_to_row(&chip->chip_geom, page, &row);
|
|
|
|
if (!can_write(nandbus))
|
|
return (ENXIO);
|
|
|
|
if (offset < 256) {
|
|
if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_SMALLA))
|
|
return (ENXIO);
|
|
} else {
|
|
if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_SMALLB))
|
|
return (ENXIO);
|
|
}
|
|
|
|
if (send_start_program_page(nand, row, offset))
|
|
return (ENXIO);
|
|
|
|
NANDBUS_WRITE_BUFFER(nandbus, buf, len);
|
|
|
|
if (send_end_program_page(nandbus, NAND_CMD_PROG_END))
|
|
return (ENXIO);
|
|
|
|
DELAY(chip->t_prog);
|
|
|
|
if (check_fail(nandbus))
|
|
return (ENXIO);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
small_program_oob(device_t nand, uint32_t page, void* buf, uint32_t len,
|
|
uint32_t offset)
|
|
{
|
|
struct nand_chip *chip;
|
|
device_t nandbus;
|
|
uint32_t row;
|
|
|
|
nand_debug(NDBG_GEN,"%p small prog oob %x[%x] at %x", nand, page, len, offset);
|
|
chip = device_get_softc(nand);
|
|
nandbus = device_get_parent(nand);
|
|
|
|
if (nand_check_page_boundary(chip, page))
|
|
return (ENXIO);
|
|
|
|
page_to_row(&chip->chip_geom, page, &row);
|
|
|
|
if (!can_write(nandbus))
|
|
return (ENXIO);
|
|
|
|
if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_SMALLOOB))
|
|
return (ENXIO);
|
|
|
|
if (send_start_program_page(nand, row, offset))
|
|
return (ENXIO);
|
|
|
|
NANDBUS_WRITE_BUFFER(nandbus, buf, len);
|
|
|
|
if (send_end_program_page(nandbus, NAND_CMD_PROG_END))
|
|
return (ENXIO);
|
|
|
|
DELAY(chip->t_prog);
|
|
|
|
if (check_fail(nandbus))
|
|
return (ENXIO);
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
nand_send_address(device_t nand, int32_t row, int32_t col, int8_t id)
|
|
{
|
|
struct nandbus_ivar *ivar;
|
|
device_t nandbus;
|
|
uint8_t addr;
|
|
int err = 0;
|
|
int i;
|
|
|
|
nandbus = device_get_parent(nand);
|
|
ivar = device_get_ivars(nand);
|
|
|
|
if (id != -1) {
|
|
nand_debug(NDBG_GEN,"send_address: send id %02x", id);
|
|
err = NANDBUS_SEND_ADDRESS(nandbus, id);
|
|
}
|
|
|
|
if (!err && col != -1) {
|
|
for (i = 0; i < ivar->cols; i++, col >>= 8) {
|
|
addr = (uint8_t)(col & 0xff);
|
|
nand_debug(NDBG_GEN,"send_address: send address column "
|
|
"%02x", addr);
|
|
err = NANDBUS_SEND_ADDRESS(nandbus, addr);
|
|
if (err)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!err && row != -1) {
|
|
for (i = 0; i < ivar->rows; i++, row >>= 8) {
|
|
addr = (uint8_t)(row & 0xff);
|
|
nand_debug(NDBG_GEN,"send_address: send address row "
|
|
"%02x", addr);
|
|
err = NANDBUS_SEND_ADDRESS(nandbus, addr);
|
|
if (err)
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (err);
|
|
}
|
|
|
|
static int
|
|
generic_is_blk_bad(device_t dev, uint32_t block, uint8_t *bad)
|
|
{
|
|
struct nand_chip *chip;
|
|
int page_number, err, i;
|
|
uint8_t *oob;
|
|
|
|
chip = device_get_softc(dev);
|
|
|
|
oob = malloc(chip->chip_geom.oob_size, M_NAND, M_WAITOK);
|
|
if (!oob) {
|
|
device_printf(dev, "%s: cannot allocate OOB\n", __func__);
|
|
return (ENOMEM);
|
|
}
|
|
|
|
page_number = block * chip->chip_geom.pgs_per_blk;
|
|
*bad = 0;
|
|
|
|
/* Check OOB of first and second page */
|
|
for (i = 0; i < 2; i++) {
|
|
err = NAND_READ_OOB(dev, page_number + i, oob,
|
|
chip->chip_geom.oob_size, 0);
|
|
if (err) {
|
|
device_printf(dev, "%s: cannot allocate OOB\n",
|
|
__func__);
|
|
free(oob, M_NAND);
|
|
return (ENOMEM);
|
|
}
|
|
|
|
if (!oob[0]) {
|
|
*bad = 1;
|
|
free(oob, M_NAND);
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
free(oob, M_NAND);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
generic_get_ecc(device_t dev, void *buf, void *ecc, int *needwrite)
|
|
{
|
|
struct nand_chip *chip = device_get_softc(dev);
|
|
struct chip_geom *cg = &chip->chip_geom;
|
|
|
|
return (NANDBUS_GET_ECC(device_get_parent(dev), buf, cg->page_size,
|
|
ecc, needwrite));
|
|
}
|
|
|
|
static int
|
|
generic_correct_ecc(device_t dev, void *buf, void *readecc, void *calcecc)
|
|
{
|
|
struct nand_chip *chip = device_get_softc(dev);
|
|
struct chip_geom *cg = &chip->chip_geom;
|
|
|
|
return (NANDBUS_CORRECT_ECC(device_get_parent(dev), buf,
|
|
cg->page_size, readecc, calcecc));
|
|
}
|
|
|
|
|
|
#if 0
|
|
int
|
|
nand_chng_read_col(device_t nand, uint32_t col, void *buf, size_t len)
|
|
{
|
|
struct nand_chip *chip;
|
|
device_t nandbus;
|
|
|
|
chip = device_get_softc(nand);
|
|
nandbus = device_get_parent(nand);
|
|
|
|
if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_CHNG_READ_COL))
|
|
return (ENXIO);
|
|
|
|
if (NANDBUS_SEND_ADDRESS(nandbus, -1, col, -1))
|
|
return (ENXIO);
|
|
|
|
if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_CHNG_READ_COL_END))
|
|
return (ENXIO);
|
|
|
|
if (NANDBUS_START_COMMAND(nandbus))
|
|
return (ENXIO);
|
|
|
|
if (buf != NULL && len > 0)
|
|
NANDBUS_READ_BUFFER(nandbus, buf, len);
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
nand_chng_write_col(device_t dev, uint32_t col, void *buf,
|
|
size_t len)
|
|
{
|
|
struct nand_chip *chip;
|
|
device_t nandbus;
|
|
|
|
chip = device_get_softc(dev);
|
|
nandbus = device_get_parent(dev);
|
|
|
|
if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_CHNG_WRITE_COL))
|
|
return (ENXIO);
|
|
|
|
if (NANDBUS_SEND_ADDRESS(nandbus, -1, col, -1))
|
|
return (ENXIO);
|
|
|
|
if (buf != NULL && len > 0)
|
|
NANDBUS_WRITE_BUFFER(nandbus, buf, len);
|
|
|
|
if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_CHNG_READ_COL_END))
|
|
return (ENXIO);
|
|
|
|
if (NANDBUS_START_COMMAND(nandbus))
|
|
return (ENXIO);
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
nand_copyback_read(device_t dev, uint32_t page, uint32_t col,
|
|
void *buf, size_t len)
|
|
{
|
|
struct nand_chip *chip;
|
|
struct page_stat *pg_stat;
|
|
device_t nandbus;
|
|
uint32_t row;
|
|
|
|
nand_debug(NDBG_GEN," raw read page %x[%x] at %x", page, col, len);
|
|
chip = device_get_softc(dev);
|
|
nandbus = device_get_parent(dev);
|
|
|
|
if (nand_check_page_boundary(chip, page))
|
|
return (ENXIO);
|
|
|
|
page_to_row(&chip->chip_geom, page, &row);
|
|
|
|
if (send_read_page(nand, NAND_CMD_READ, NAND_CMD_READ_CPBK, row, 0))
|
|
return (ENXIO);
|
|
|
|
DELAY(chip->t_r);
|
|
if (check_fail(nandbus))
|
|
return (ENXIO);
|
|
|
|
if (buf != NULL && len > 0)
|
|
NANDBUS_READ_BUFFER(nandbus, buf, len);
|
|
|
|
pg_stat = &(chip->pg_stat[page]);
|
|
pg_stat->page_raw_read++;
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
nand_copyback_prog(device_t dev, uint32_t page, uint32_t col,
|
|
void *buf, size_t len)
|
|
{
|
|
struct nand_chip *chip;
|
|
struct page_stat *pg_stat;
|
|
device_t nandbus;
|
|
uint32_t row;
|
|
|
|
nand_debug(NDBG_GEN,"copyback prog page %x[%x]", page, len);
|
|
chip = device_get_softc(dev);
|
|
nandbus = device_get_parent(dev);
|
|
|
|
if (nand_check_page_boundary(chip, page))
|
|
return (ENXIO);
|
|
|
|
page_to_row(&chip->chip_geom, page, &row);
|
|
|
|
if (!can_write(nandbus))
|
|
return (ENXIO);
|
|
|
|
if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_CHNG_WRITE_COL))
|
|
return (ENXIO);
|
|
|
|
if (NANDBUS_SEND_ADDRESS(nandbus, row, col, -1))
|
|
return (ENXIO);
|
|
|
|
if (buf != NULL && len > 0)
|
|
NANDBUS_WRITE_BUFFER(nandbus, buf, len);
|
|
|
|
if (send_end_program_page(nandbus, NAND_CMD_PROG_END))
|
|
return (ENXIO);
|
|
|
|
DELAY(chip->t_prog);
|
|
|
|
if (check_fail(nandbus))
|
|
return (ENXIO);
|
|
|
|
pg_stat = &(chip->pg_stat[page]);
|
|
pg_stat->page_raw_written++;
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
nand_copyback_prog_intlv(device_t dev, uint32_t page)
|
|
{
|
|
struct nand_chip *chip;
|
|
struct page_stat *pg_stat;
|
|
device_t nandbus;
|
|
uint32_t row;
|
|
|
|
nand_debug(NDBG_GEN,"cache prog page %x", page);
|
|
chip = device_get_softc(dev);
|
|
nandbus = device_get_parent(dev);
|
|
|
|
if (nand_check_page_boundary(chip, page))
|
|
return (ENXIO);
|
|
|
|
page_to_row(&chip->chip_geom, page, &row);
|
|
|
|
if (!can_write(nandbus))
|
|
return (ENXIO);
|
|
|
|
if (send_start_program_page(nand, row, 0))
|
|
return (ENXIO);
|
|
|
|
if (send_end_program_page(nandbus, NAND_CMD_PROG_INTLV))
|
|
return (ENXIO);
|
|
|
|
DELAY(chip->t_prog);
|
|
|
|
if (check_fail(nandbus))
|
|
return (ENXIO);
|
|
|
|
pg_stat = &(chip->pg_stat[page]);
|
|
pg_stat->page_raw_written++;
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
nand_prog_cache(device_t dev, uint32_t page, uint32_t col,
|
|
void *buf, size_t len, uint8_t end)
|
|
{
|
|
struct nand_chip *chip;
|
|
struct page_stat *pg_stat;
|
|
device_t nandbus;
|
|
uint32_t row;
|
|
uint8_t command;
|
|
|
|
nand_debug(NDBG_GEN,"cache prog page %x[%x]", page, len);
|
|
chip = device_get_softc(dev);
|
|
nandbus = device_get_parent(dev);
|
|
|
|
if (nand_check_page_boundary(chip, page))
|
|
return (ENXIO);
|
|
|
|
page_to_row(&chip->chip_geom, page, &row);
|
|
|
|
if (!can_write(nandbus))
|
|
return (ENXIO);
|
|
|
|
if (send_start_program_page(dev, row, 0))
|
|
return (ENXIO);
|
|
|
|
NANDBUS_WRITE_BUFFER(nandbus, buf, len);
|
|
|
|
if (end)
|
|
command = NAND_CMD_PROG_END;
|
|
else
|
|
command = NAND_CMD_PROG_CACHE;
|
|
|
|
if (send_end_program_page(nandbus, command))
|
|
return (ENXIO);
|
|
|
|
DELAY(chip->t_prog);
|
|
|
|
if (check_fail(nandbus))
|
|
return (ENXIO);
|
|
|
|
pg_stat = &(chip->pg_stat[page]);
|
|
pg_stat->page_raw_written++;
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
nand_read_cache(device_t dev, uint32_t page, uint32_t col,
|
|
void *buf, size_t len, uint8_t end)
|
|
{
|
|
struct nand_chip *chip;
|
|
struct page_stat *pg_stat;
|
|
device_t nandbus;
|
|
uint32_t row;
|
|
uint8_t command;
|
|
|
|
nand_debug(NDBG_GEN,"cache read page %x[%x] ", page, len);
|
|
chip = device_get_softc(dev);
|
|
nandbus = device_get_parent(dev);
|
|
|
|
if (nand_check_page_boundary(chip, page))
|
|
return (ENXIO);
|
|
|
|
page_to_row(&chip->chip_geom, page, &row);
|
|
|
|
if (page != -1) {
|
|
if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_READ))
|
|
return (ENXIO);
|
|
|
|
if (NANDBUS_SEND_ADDRESS(nandbus, row, col, -1))
|
|
return (ENXIO);
|
|
}
|
|
|
|
if (end)
|
|
command = NAND_CMD_READ_CACHE_END;
|
|
else
|
|
command = NAND_CMD_READ_CACHE;
|
|
|
|
if (NANDBUS_SEND_COMMAND(nandbus, command))
|
|
return (ENXIO);
|
|
|
|
if (NANDBUS_START_COMMAND(nandbus))
|
|
return (ENXIO);
|
|
|
|
DELAY(chip->t_r);
|
|
if (check_fail(nandbus))
|
|
return (ENXIO);
|
|
|
|
if (buf != NULL && len > 0)
|
|
NANDBUS_READ_BUFFER(nandbus, buf, len);
|
|
|
|
pg_stat = &(chip->pg_stat[page]);
|
|
pg_stat->page_raw_read++;
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
nand_get_feature(device_t dev, uint8_t feat, void *buf)
|
|
{
|
|
struct nand_chip *chip;
|
|
device_t nandbus;
|
|
|
|
nand_debug(NDBG_GEN,"nand get feature");
|
|
|
|
chip = device_get_softc(dev);
|
|
nandbus = device_get_parent(dev);
|
|
|
|
if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_GET_FEATURE))
|
|
return (ENXIO);
|
|
|
|
if (NANDBUS_SEND_ADDRESS(nandbus, -1, -1, feat))
|
|
return (ENXIO);
|
|
|
|
if (NANDBUS_START_COMMAND(nandbus))
|
|
return (ENXIO);
|
|
|
|
DELAY(chip->t_r);
|
|
NANDBUS_READ_BUFFER(nandbus, buf, 4);
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
nand_set_feature(device_t dev, uint8_t feat, void *buf)
|
|
{
|
|
struct nand_chip *chip;
|
|
device_t nandbus;
|
|
|
|
nand_debug(NDBG_GEN,"nand set feature");
|
|
|
|
chip = device_get_softc(dev);
|
|
nandbus = device_get_parent(dev);
|
|
|
|
if (NANDBUS_SEND_COMMAND(nandbus, NAND_CMD_SET_FEATURE))
|
|
return (ENXIO);
|
|
|
|
if (NANDBUS_SEND_ADDRESS(nandbus, -1, -1, feat))
|
|
return (ENXIO);
|
|
|
|
NANDBUS_WRITE_BUFFER(nandbus, buf, 4);
|
|
|
|
if (NANDBUS_START_COMMAND(nandbus))
|
|
return (ENXIO);
|
|
|
|
return (0);
|
|
}
|
|
#endif
|