/*
 * Copyright (C) 2013 Lucas Stach <l.stach@pengutronix.de>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
 */

/**
 * @file
 * @brief Boot informations provided by the Tegra SoC and it's BootROM. All
 * accessor functions are a header only implementations, as they are meant to
 * be used by both the main CPU complex (ARMv7) and the AVP (ARMv4).
 */

#include <sizes.h>
#include <io.h>
#include <mach/iomap.h>

/* Bootinfotable */

#define NV_BIT_BCTSIZE		0x38	/* size of the BCT in IRAM */
#define NV_BIT_BCTPTR		0x3C	/* location of the BCT in IRAM */

/* ODM data */
#define BCT_ODMDATA_OFFSET	12	/* offset from the _end_ of the BCT */

#define T20_ODMDATA_RAMSIZE_SHIFT	28
#define T20_ODMDATA_RAMSIZE_MASK	(3 << T20_ODMDATA_RAMSIZE_SHIFT)
#define T20_ODMDATA_UARTTYPE_SHIFT	18
#define T20_ODMDATA_UARTTYPE_MASK	(3 << T20_ODMDATA_UARTTYPE_SHIFT)
#define T20_ODMDATA_UARTID_SHIFT	15
#define T20_ODMDATA_UARTID_MASK		(7 << T20_ODMDATA_UARTID_SHIFT)

static inline u32 tegra_get_odmdata(void)
{
	u32 bctsize, bctptr, odmdata;

	bctsize = cpu_readl(TEGRA_IRAM_BASE + NV_BIT_BCTSIZE);
	bctptr = cpu_readl(TEGRA_IRAM_BASE + NV_BIT_BCTPTR);

	odmdata = cpu_readl(bctptr + bctsize - BCT_ODMDATA_OFFSET);

	return odmdata;
}

/* chip ID */
#define APB_MISC_HIDREV			0x804
#define HIDREV_CHIPID_SHIFT		8
#define HIDREV_CHIPID_MASK		(0xff << HIDREV_CHIPID_SHIFT)

enum tegra_chiptype {
	TEGRA_UNK_REV = -1,
	TEGRA20 = 0,
};

static inline enum tegra_chiptype tegra_get_chiptype(void)
{
	u32 hidrev;

	hidrev = readl(TEGRA_APB_MISC_BASE + APB_MISC_HIDREV);

	switch ((hidrev & HIDREV_CHIPID_MASK) >> HIDREV_CHIPID_SHIFT) {
	case 0x20:
		return TEGRA20;
	default:
		return TEGRA_UNK_REV;
	}
}

static inline int tegra_get_num_cores(void)
{
	switch (tegra_get_chiptype()) {
	case TEGRA20:
		return 2;
		break;
	default:
		return 0;
		break;
	}
}

/* Runtime data */
static inline int tegra_cpu_is_maincomplex(void)
{
	u32 tag0;

	tag0 = readl(TEGRA_UP_TAG_BASE);

	return (tag0 & 0xff) == 0x55;
}

static inline uint32_t tegra20_get_ramsize(void)
{
	switch ((tegra_get_odmdata() & T20_ODMDATA_RAMSIZE_MASK) >>
		T20_ODMDATA_RAMSIZE_SHIFT) {
	case 1:
		return SZ_256M;
	default:
	case 2:
		return SZ_512M;
	case 3:
		return SZ_1G;
	}
}

static long uart_id_to_base[] = {
	TEGRA_UARTA_BASE,
	TEGRA_UARTB_BASE,
	TEGRA_UARTC_BASE,
	TEGRA_UARTD_BASE,
	TEGRA_UARTE_BASE,
};

static inline long tegra20_get_debuguart_base(void)
{
	u32 odmdata;
	int id;

	odmdata = tegra_get_odmdata();

	/*
	 * Get type, we accept both "2" and "3", as they both demark a UART,
	 * depending on the board type.
	 */
	if (!(((odmdata & T20_ODMDATA_UARTTYPE_MASK) >>
	      T20_ODMDATA_UARTTYPE_SHIFT) & 0x2))
		return 0;

	id = (odmdata & T20_ODMDATA_UARTID_MASK) >> T20_ODMDATA_UARTID_SHIFT;
	if (id > ARRAY_SIZE(uart_id_to_base))
		return 0;

	return uart_id_to_base[id];
}

#define CRC_OSC_CTRL			0x050
#define CRC_OSC_CTRL_OSC_FREQ_SHIFT	30
#define CRC_OSC_CTRL_OSC_FREQ_MASK	(0x3 << CRC_OSC_CTRL_OSC_FREQ_SHIFT)

static inline unsigned int tegra_get_osc_clock(void)
{
	u32 osc_ctrl = readl(TEGRA_CLK_RESET_BASE + CRC_OSC_CTRL);

	switch ((osc_ctrl & CRC_OSC_CTRL_OSC_FREQ_MASK) >>
		CRC_OSC_CTRL_OSC_FREQ_SHIFT) {
	case 0:
		return 13000000;
	case 1:
		return 19200000;
	case 2:
		return 12000000;
	case 3:
		return 26000000;
	default:
		return 0;
	}
}

/* reset vector for the main CPU complex */
void tegra_maincomplex_entry(void);
