/*
 * Maintainer :
 *
 * (C) Copyright 2009, 2010
 * Allied Telesis <www.alliedtelesis.com>
 * Marvell Semiconductor <www.marvell.com>
 * Written-by: Prafulla Wadaskar <prafulla@marvell.com>
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA
 */

#include <common.h>
#include <command.h>
#include <netdev.h>
#include <miiphy.h>
#include <asm/io.h>
#include <asm/arch/discovery.h>
#include <asm/arch/gpio.h>
#include <asm/arch/mpp.h>
#include <idprom.h>

#include "sbx81cfc.h"
#include "fpga/cfcFpgaLoader.h"
#include "../SBx8100/mpi.h"
#ifdef CONFIG_USB_STORAGE
#include <usb.h>
#endif

DECLARE_GLOBAL_DATA_PTR;

struct led {
	u8 *reg;
	u8 value;
	u8 mask;
};

struct led amber_solid = {
	SBX81CFC_FPGA_BASE+0x6c,
	0x2,
	0x3
};

struct led green_solid = {
	SBX81CFC_FPGA_BASE+0x6c,
	0x3,
	0x3
};

struct led amber_flash = {
	SBX81CFC_FPGA_BASE+0x6c,
	0x0,
	0x3
};

struct led green_flash = {
	SBX81CFC_FPGA_BASE+0x6c,
	0x1,
	0x3
};

static void status_led_set(struct led *led)
{
	u8 val;
	val = readb(led->reg);
	val &= ~led->mask;
	val |= led->value;
	writeb(val,led->reg);
}

int board_init(void)
{
	struct discowin_nonremap_registers *win8_13_nonremapregs =
		(struct discowin_nonremap_registers *)(DISCO_CPU_WIN_BASE + 0x900);

	/* Power-down unused MV78100 subsystems. The required
	 * subsystems are:
	 *
	 *  GE0		b1
	 *  GE1		b2
	 *  PEX0.0	b5
	 *  PEX1.0	b9
	 *  USB0	b17
	 *  Device	b23
	 */
	writel( ((1 << 1) | (1 << 2)
		 | (1 << 5) | (1 << 9)
		 | (1 << 17) | (1 << 23)),
		DISCO_CPU_REG_BASE + 0x1c );


	/* CPU Address Map */

	/* Window 13: NOR FLASH #0 address space (0xfc000000, 64MiB, BootCS) */
	writel(DISCOCPU_WIN_CTRL_DATA(1024 * 1024 * 64, DISCOCPU_TARGET_MEMORY,
		DISCOCPU_ATTR_BOOTCS, DISCOCPU_WIN_ENABLE), &win8_13_nonremapregs[13 - 8].ctrl);
	writel(0xfc000000, &win8_13_nonremapregs[13 - 8].base);

	/* Window 10: NOR FLASH #1 address space (0xf8000000, 64MiB, CS[0]) */
	writel(DISCOCPU_WIN_CTRL_DATA(1024 * 1024 * 64, DISCOCPU_TARGET_MEMORY,
		DISCOCPU_ATTR_DRAM_CS0, DISCOCPU_WIN_ENABLE), &win8_13_nonremapregs[10 - 8].ctrl);
	writel(0xf8000000, &win8_13_nonremapregs[10 - 8].base);

	/* Window 9: FPGA / NVSRAM address space (0xf2000000, 1MiB, CS[3]) */
	writel(DISCOCPU_WIN_CTRL_DATA(1024 * 1024 * 1, DISCOCPU_TARGET_MEMORY,
		DISCOCPU_ATTR_DRAM_CS3, DISCOCPU_WIN_ENABLE), &win8_13_nonremapregs[9 - 8].ctrl);
	writel(0xf2000000, &win8_13_nonremapregs[9 - 8].base);


	/* Device Bus */

	/* BOOTCS: NOR FLASH #0, 16bit */
	writel(0x403e07cf, DISCO_DEV_BUS_BASE + 0x00);
	writel(0x000f0f0f, DISCO_DEV_BUS_BASE + 0x04);

	/* CS[0]: NOR FLASH #1, 16bit */
	writel(0x403e07cf, DISCO_DEV_BUS_BASE + 0x08);
	writel(0x000f0f0f, DISCO_DEV_BUS_BASE + 0x0c);

	/* CS[1]: unused */
	writel(0x00000000, DISCO_DEV_BUS_BASE + 0x10);

	/* CS[2]: unused */
	writel(0x00000000, DISCO_DEV_BUS_BASE + 0x18);

	/* CS[3]: FPGA / NVSRAM - 8bit */
	writel(0x003e07cf, DISCO_DEV_BUS_BASE + 0x20);
	writel(0x000f0f0f, DISCO_DEV_BUS_BASE + 0x24);

	/* NAND Flash control - not tied to BOOTCS or CS[*] */
	writel(0x0007d900, DISCO_DEV_BUS_BASE + 0x70);


	/*
	 * default gpio configuration
	 * There are maximum 32 gpios controlled through 1 set of registers
	 */
	disco_config_gpio(SBX81CFC_OE_VAL,
			  SBX81CFC_OE);

	/* Multi-Purpose Pins Functionality configuration */
	u32 discompp_config[] = {
		MPP0_GE1_TXCLK,
		MPP1_GE1_TXCTL,
		MPP2_GE1_RXCTL,
		MPP3_GE1_RXCLK,
		MPP4_GE1_TXD0,
		MPP5_GE1_TXD1,
		MPP6_GE1_TXD2,
		MPP7_GE1_TXD3,
		MPP8_GE1_RXD0,
		MPP9_GE1_RXD1,
		MPP10_GE1_RXD2,
		MPP11_GE1_RXD3,
		MPP12_GPIO,
		MPP13_SYSRST_OUTn,
		MPP14_GPIO,
		MPP15_GPIO,
		MPP16_GPIO,
		MPP17_GPIO,
		MPP18_UA0_CTSn,
		MPP19_UA0_RTSn,
		MPP20_GPIO,
		MPP21_GPIO,
		MPP22_GPIO,
		MPP23_GPIO,
		MPP24_GPIO,
		MPP25_GPIO,
		MPP26_GPIO,
		MPP27_GPIO,
		MPP28_GPIO,
		MPP29_GPIO,
		MPP30_GPIO,
		MPP31_GPIO,
		MPP32_GPIO,
		MPP33_GPIO,
		MPP34_GPIO,
		MPP35_GPIO,
		MPP36_GPIO,
		MPP37_GPIO,
		MPP38_GPIO,
		MPP39_GPIO,
		/* Device Bus in 16bit mode, so MPP40-47 available for use */
		MPP48_GPIO,
		MPP49_GPIO,
		0
	};
	discovery_mpp_conf(discompp_config);

	/*
	 * arch number of board
	 */
	gd->bd->bi_arch_number = MACH_TYPE_SBX81CFC;

	/* adress of boot parameters */
	gd->bd->bi_boot_params = disco_sdram_bar(0) + 0x100;

	return 0;
}

static int fpga_init(void)
{
	/* download FPGA image */
	printf("FPGA:  ");
	cfcFpgaLoader(FpgaLoad, fpgaLoadLength);
	printf("done\n");

	/* Enable VPP to NOR Flash (both BootCS and CS0) */
	SBX81CFC_FPGA_BASE[0x80] |= ((1 << 2) | (1 << 3));

	/* Enable USB power */
	SBX81CFC_FPGA_BASE[0x90] |= (1 << 0);

	/* Put SW1/SW2 into reset */
	SBX81CFC_FPGA_BASE[0x20] |= ((1 << 0) | (1 << 1));

	/* Put I2C_SW/LM81 into reset */
	SBX81CFC_FPGA_BASE[0x20] |= ((1 << 4) | (1 << 5));

	status_led_set(&amber_solid);

	return 0;
}

int dram_init(void)
{
	int i;

	for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
		gd->bd->bi_dram[i].start = disco_sdram_bar(i);
		gd->bd->bi_dram[i].size = disco_sdram_bs(i);
	}

	mpi_board_init();

	return 0;
}

#ifdef CONFIG_RESET_PHY_R
void reset_phy(void)
{
	u16 phyadr;
	u16 phydata;

	miiphy_read(CONFIG_ETHPRIME, DISCOVERY_PHY_ADR_REQUEST,
		    DISCOVERY_PHY_ADR_REQUEST, &phyadr);

	/* Put the 88E1111 into reset */
	SBX81CFC_FPGA_BASE[0x20] |= (1 << 3);

        /* Wait 11ms for 88E1111 to reset */
        udelay(11000);

	/* Bring the 88E1111 out of reset */
	SBX81CFC_FPGA_BASE[0x20] &= ~(1 << 3);

        /* Wait 5ms before accessing registers 88E1111 */
        udelay(5000);

	/* Add delay to Rx/Tx timing */
	miiphy_read(CONFIG_ETHPRIME, phyadr, 20, &phydata);
	phydata |= (1 << 7) | (1 << 1);
	miiphy_write(CONFIG_ETHPRIME, phyadr, 20, phydata);

	/* Perform S/W reset to effect Rx/Tx timing changes */
	miiphy_read(CONFIG_ETHPRIME, phyadr, 0, &phydata);
	phydata |= (1 << 15);
	miiphy_write(CONFIG_ETHPRIME, phyadr, 0, phydata);
}
#endif /* CONFIG_RESET_PHY_R */

static int get_slot_id(void)
{
	int slot = readb(&SBX81CFC_FPGA_BASE[0x04]);

	return slot;
}

static void setenv_slot_id(void)
{
	int slot;
	char buf[16];

	if ((slot = get_slot_id()) < 0) {
		fprintf(stderr, "Error: could not determine slot number\n");
		slot = 0;
	}

	sprintf(buf, "%d", slot);
	setenv("slot_id", buf);
}

#ifdef CONFIG_MISC_INIT_R
int misc_init_r(void)
{
	/* TODO: check our list of "reserved" MAC addresses
	 */
	uchar mac[6] = {0x02, 0x00, 0xcd, 0x00, 0x00, 0x00};

	fpga_init();
#ifdef CONFIG_USB_STORAGE
	usb_init();
#endif
	setenv_slot_id();
#ifdef CONFIG_DIAG_IDPROM
	idprom_setmacaddr(1);
	idprom_setmachid();
#endif
	mac[5] = simple_strtoul(getenv("slot_id"), NULL, 10);
	eth_setenv_enetaddr("ethaddr", mac);
#ifdef MTDPARTS_DEFAULT
	setenv("mtdparts", MTDPARTS_DEFAULT);
#endif
#ifdef MTDPARTS_MTDOOPS
	setenv("mtdoops", MTDPARTS_MTDOOPS);
#endif
	status_led_set(&green_flash);

	return 0;
}
#endif

/*********************************************************************
 * ATL specific extensions
 *********************************************************************/

/* atl_get_offset_from_base()
 *
 * This function returns the amount the special blocks (env, epi3, bootcode)
 * have moved up if the the flash size is larger than 32MiB. If the flash size
 * is 32MiB then that is considered to be 'base' if that makes sense? If the
 * size is larger than 32MiB, then this function needs to return the offset
 * of the last 32MiB of flash.
 */

u32 atl_get_offset_from_base(void)
{
	/* SBx81CFC only support 128MiB, so offset of
	 * last 32MiB is 96MiB
	 */
	return ((128 - 32) * 1024 * 1024);
}

/* atl_get_env_addr()
 *
 * Get location of U-Boot environment. ENV code was hacked a little to make this work
 */

u32 atl_get_env_addr(void)
{
	return CONFIG_ENV_ADDR;
}

u8 sdcard_is_present(void)
{
	int present = !(SBX81CFC_FPGA_BASE[0x70] & (1 << 0));

	if (present)
		SBX81CFC_FPGA_BASE[0x71] |= (1 << 0) ;
	else
		SBX81CFC_FPGA_BASE[0x71] &= ~(1 << 0) ;

	return present;
}

static u8 sdcard_changed = 0;

u8 sdcard_has_changed(void)
{
	u8 ret = sdcard_changed;
	sdcard_changed = 0;
	return ret;
}

void sdcard_check_changed(void)
{
	static u8 old_present = 0;
	u8 present = sdcard_is_present();
	if (present != old_present) {
		old_present = present;
		sdcard_changed = 1;
	}
}

u8 sdcard_write_protected(void)
{
	return !!(SBX81CFC_FPGA_BASE[0x70] & (1 << 1));
}

void sdcard_select(u8 select)
{

	if (select) {
		/* Enable SPI chip select */
		SBX81CFC_FPGA_BASE[0x75] |= (1 << 0);
		/* Turn the led GREEN flashing */
		SBX81CFC_FPGA_BASE[0x71] |= ((1 << 0) | (1 << 1)) ;
	} else {
		SBX81CFC_FPGA_BASE[0x75] &= ~(1 << 0);
		/* Turn the led GREEN solid */
		SBX81CFC_FPGA_BASE[0x71] &= ~((1 << 1));
	}
}

void try_bootp_boot(void)
{
	char buf[32];

	cfc_dx160_setup();
	//start with eth1
	setenv("ethact", "egiga1");
	//only try for one retry cycle.
	setenv("netretry", "no");

	if (run_command("bootp", 0) >= 0) {
		sprintf(buf, "awbootm %s", getenv("fileaddr"));
		run_command(buf, 0);
	}
}

int backplane_access(int enable)
{
	if (enable) {
		/* Assert HW master */
		SBX81CFC_FPGA_BASE[0x01] = (1 << 1);
		/* Bring i2c muxes out of reset */
		SBX81CFC_FPGA_BASE[0x20] &= ~(1 << 4);
	} else {
		/* Relinquish HW master */
		SBX81CFC_FPGA_BASE[0x01] = (1 << 0);
		/* Put i2c muxes into reset */
		SBX81CFC_FPGA_BASE[0x20] |= (1 << 4);
	}

	/* Check the ACT bit in the Activity Status register */
	if (!!(SBX81CFC_FPGA_BASE[0x00] & (1 << 1)) == enable) {
		return 0;
	} else {
		return 1;
	}
}

static int do_backplane_access(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
	int enable = 0;

	if (argc != 2)
		return -1;

	if (strcmp("enable", argv[1]) == 0)
		enable = 1;
	else if (strcmp("disable", argv[1]) == 0)
		enable = 0;
	else
		return -1;

	printf("%s backplane access\n", enable ? "Enabling" : "Disabling");
	if (backplane_access(enable) == 0) {
		printf("Success!\n");
		return 0;
	} else {
		printf("Failed!\n");
		return -1;
	}
}

U_BOOT_CMD(
	backplane, 2, 1, do_backplane_access,
	"Enable/Disable access to the backplane",
	"(enable|disable)"
	);
