/*	$NetBSD: omap2_nand.c,v 1.4 2011/07/01 20:30:21 dyoung Exp $	*/

/*-
 * Copyright (c) 2010 Department of Software Engineering,
 *		      University of Szeged, Hungary
 * Copyright (c) 2010 Adam Hoka <ahoka@NetBSD.org>
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by the Department of Software Engineering, University of Szeged, Hungary
 *
 * 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 ``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 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.
 */

/* Device driver for the NAND controller found in Marvell ARM SoCs.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: omap2_nand.c,v 1.4 2011/07/01 20:30:21 dyoung Exp $");

//#include "opt_omap.h"
#include "opt_flash.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/cdefs.h>
#include <sys/device.h>

#include <sys/bus.h>

#include <dev/marvell/marvellvar.h>
#include <dev/nand/nand.h>
#include <dev/nand/onfi.h>

static int	marvell_nand_match(struct device *, struct cfdata *, void *);
static void	marvell_nand_attach(struct device *, struct device *, void *);
static int	marvell_nand_detach(device_t, int);

void marvell_nand_command(device_t self, uint8_t command);
void marvell_nand_address(device_t self, uint8_t address);
void marvell_nand_busy(device_t self);
void marvell_nand_read_1(device_t self, uint8_t *data);
void marvell_nand_write_1(device_t self, uint8_t data);
void marvell_nand_read_2(device_t self, uint16_t *data);
void marvell_nand_write_2(device_t self, uint16_t data);
bool marvell_nand_isbusy(device_t self);
void marvell_nand_read_buf_1(device_t self, void *buf, size_t len);
void marvell_nand_read_buf_2(device_t self, void *buf, size_t len);
void marvell_nand_write_buf_1(device_t self, const void *buf, size_t len);
void marvell_nand_write_buf_2(device_t self, const void *buf, size_t len);

struct marvell_nand_softc {
	device_t sc_dev;
	device_t sc_nanddev;
//	struct gpmc_softc *sc_gpmcsc;

	struct nand_interface	sc_nand_if;

	bus_space_handle_t	sc_ioh;
	bus_space_tag_t		sc_iot;

	bus_size_t		sc_cmd_reg;
	bus_size_t		sc_addr_reg;
	bus_size_t		sc_data_reg;
};

CFATTACH_DECL_NEW(mvnand, sizeof(struct marvell_nand_softc),
    marvell_nand_match, marvell_nand_attach, marvell_nand_detach, NULL);

void
marvell_nand_command(device_t self, uint8_t command)
{
	struct marvell_nand_softc *sc = device_private(self);

	bus_space_write_1(sc->sc_iot, sc->sc_ioh, sc->sc_cmd_reg, command);
};

void
marvell_nand_address(device_t self, uint8_t address)
{
	struct marvell_nand_softc *sc = device_private(self);

	bus_space_write_1(sc->sc_iot, sc->sc_ioh, sc->sc_addr_reg, address);
};

bool
marvell_nand_isbusy(device_t self)
{
	struct marvell_nand_softc *sc = device_private(self);
	uint8_t status;

	DELAY(1);		/* just to be sure we are not early */

	bus_space_write_1(sc->sc_iot, sc->sc_ioh,
	    sc->sc_cmd_reg, ONFI_READ_STATUS);

	DELAY(1);

	status = bus_space_read_1(sc->sc_iot,
	    sc->sc_ioh, sc->sc_data_reg);

	return !(status & ONFI_STATUS_RDY);
};

static int
marvell_nand_match(struct device *parent, struct cfdata *match, void *aux)
{
//	struct marvell_attach_args * const mva = aux;
//	bus_space_tag_t	iot;
//	bus_space_handle_t ioh;

//	iot = mva->mva_iot;

	/* PROFIT??? */

	return 1;
}

static void
marvell_nand_attach(device_t parent, device_t self, void *aux)
{
	struct marvell_nand_softc *sc = device_private(self);
	struct marvell_attach_args * const mva = aux;
	bus_size_t cs_offset;
	uint32_t val;

	sc->sc_dev = self;
	sc->sc_iot = mva->mva_iot;

	aprint_normal(": Marvell NAND Controller\n");
	aprint_naive("\n");

	if (bus_space_subregion(mva->mva_iot, mva->mva_ioh, mva->mva_offset,
	   mva->mva_size, &sc->sc_ioh)) {
		aprint_error_dev(self, "failed to subregion register space\n");
		return;
	}

	/* XXX verify this */
        sc->sc_cmd_reg = (1 << 0);
	sc->sc_addr_reg = (1 << 1);
	sc->sc_data_reg = 0;

	/*
	 * do the reset dance for NAND
	 */
	bus_space_write_1(sc->sc_iot, sc->sc_ioh,
	    sc->sc_cmd_reg, ONFI_RESET);

	marvell_nand_busy(self);

	nand_init_interface(&sc->sc_nand_if);

	sc->sc_nand_if.command = &marvell_nand_command;
	sc->sc_nand_if.address = &marvell_nand_address;
	sc->sc_nand_if.read_buf_1 = &marvell_nand_read_buf_1;
	sc->sc_nand_if.read_buf_2 = &marvell_nand_read_buf_2;
	sc->sc_nand_if.read_1 = &marvell_nand_read_1;
	sc->sc_nand_if.read_2 = &marvell_nand_read_2;
	sc->sc_nand_if.write_buf_1 = &marvell_nand_write_buf_1;
	sc->sc_nand_if.write_buf_2 = &marvell_nand_write_buf_2;
	sc->sc_nand_if.write_1 = &marvell_nand_write_1;
	sc->sc_nand_if.write_2 = &marvell_nand_write_2;
	sc->sc_nand_if.busy = &marvell_nand_busy;
	sc->sc_nand_if.ecc.necc_code_size = 3;
	sc->sc_nand_if.ecc.necc_block_size = 256;

	if (!pmf_device_register1(sc->sc_dev, NULL, NULL, NULL))
		aprint_error_dev(sc->sc_dev,
		    "couldn't establish power handler\n");

	sc->sc_nanddev = nand_attach_mi(&sc->sc_nand_if, sc->sc_dev);
}

static int
marvell_nand_detach(device_t device, int flags)
{
	struct marvell_nand_softc *sc = device_private(device);
	int ret = 0;

	bus_space_unmap(sc->sc_iot, sc->sc_ioh, GPMC_CS_SIZE);

	pmf_device_deregister(sc->sc_dev);

	if (sc->sc_nanddev != NULL)
		ret = config_detach(sc->sc_nanddev, flags);

	return ret;
}

void
marvell_nand_busy(device_t self)
{
	struct marvell_nand_softc *sc = device_private(self);

//	while (!(gpmc_register_read(sc->sc_gpmcsc, GPMC_STATUS) & WAIT0)) {
//		DELAY(1);
//	}
}

void
marvell_nand_read_1(device_t self, uint8_t *data)
{
	struct marvell_nand_softc *sc = device_private(self);

	*data = bus_space_read_1(sc->sc_iot, sc->sc_ioh, sc->sc_data_reg);
}

void
marvell_nand_write_1(device_t self, uint8_t data)
{
	struct marvell_nand_softc *sc = device_private(self);

	bus_space_write_1(sc->sc_iot, sc->sc_ioh, sc->sc_data_reg, data);
}

void
marvell_nand_read_2(device_t self, uint16_t *data)
{
	struct marvell_nand_softc *sc = device_private(self);

	*data = bus_space_read_2(sc->sc_iot, sc->sc_ioh, sc->sc_data_reg);
}

void
marvell_nand_write_2(device_t self, uint16_t data)
{
	struct marvell_nand_softc *sc = device_private(self);

	bus_space_write_2(sc->sc_iot, sc->sc_ioh, sc->sc_data_reg, data);
}

void
marvell_nand_read_buf_1(device_t self, void *buf, size_t len)
{
	struct marvell_nand_softc *sc = device_private(self);

	KASSERT(buf != NULL);
	KASSERT(len >= 1);

	bus_space_read_multi_1(sc->sc_iot, sc->sc_ioh,
	    sc->sc_data_reg, buf, len);
}

void
marvell_nand_read_buf_2(device_t self, void *buf, size_t len)
{
	struct marvell_nand_softc *sc = device_private(self);

	KASSERT(buf != NULL);
	KASSERT(len >= 2);
	KASSERT(!(len & 0x01));

	bus_space_read_multi_2(sc->sc_iot, sc->sc_ioh,
	    sc->sc_data_reg, buf, len / 2);
}

void
marvell_nand_write_buf_1(device_t self, const void *buf, size_t len)
{
	struct marvell_nand_softc *sc = device_private(self);

	KASSERT(buf != NULL);
	KASSERT(len >= 1);

	bus_space_write_multi_1(sc->sc_iot, sc->sc_ioh,
	    sc->sc_data_reg, buf, len);
}

void
marvell_nand_write_buf_2(device_t self, const void *buf, size_t len)
{
	struct marvell_nand_softc *sc = device_private(self);

	KASSERT(buf != NULL);
	KASSERT(len >= 2);
	KASSERT(!(len & 0x01));

	bus_space_write_multi_2(sc->sc_iot, sc->sc_ioh,
	    sc->sc_data_reg, buf, len / 2);
}
