/*-
 * 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: soc2013/dpl/head/sys/dev/nand/nand_generic.c 235866 2012-05-17 10:11:18Z gber $");

#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
