/*
 * This file is part of the coreboot project.
 *
 * Copyright (C) 2014 Imagination Technologies
 *
 * 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; version 2 of
 * the License.
 *
 * 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.
 */

#include <arch/io.h>
#include <stdint.h>
#include <soc/clocks.h>
#include <assert.h>
#include <boardid.h>

#define PADS_FUNCTION_SELECT0_ADDR	(0xB8101C00 + 0xC0)

#define GPIO_BIT_EN_ADDR(bank)		(0xB8101C00 + 0x200 + (0x24 * (bank)))
#define PAD_DRIVE_STRENGTH_ADDR(bank)	(0xB8101C00 + 0x120 + (0x4 * (bank)))
#define MAX_NO_MFIOS				89
#define PAD_DRIVE_STRENGTH_LENGTH		2
#define PAD_DRIVE_STRENGTH_MASK			0x3

typedef enum {
	DRIVE_STRENGTH_2mA      = 0,
	DRIVE_STRENGTH_4mA      = 1,
	DRIVE_STRENGTH_8mA      = 2,
	DRIVE_STRENGTH_12mA     = 3
} drive_strength;

/* MFIO definitions for UART1 */
#define UART1_RXD_MFIO			59
#define UART1_TXD_MFIO			60

/* MFIO definitions for SPIM */
#define SPIM1_D0_TXD_MFIO		5
#define SPIM1_D1_RXD_MFIO		4
#define SPIM1_MCLK_MFIO			3
#define SPIM1_D2_MFIO			6
#define SPIM1_D3_MFIO			7
#define SPIM1_CS0_MFIO			0

/* MFIO definitions for I2C */
#define I2C_DATA_MFIO(i)		(28 + (2*(i)))
#define I2C_CLK_MFIO(i)			(29 + (2*(i)))
#define I2C_DATA_FUNCTION_OFFSET(i)	(20 + (2*(i)))
#define I2C_CLK_FUNCTION_OFFSET(i)	(21 + (2*(i)))
#define I2C_DATA_FUNCTION_MASK		0x1
#define I2C_CLK_FUNCTION_MASK		0x1

static void pad_drive_strength(u32 pad, drive_strength strength)
{
	u32 reg, drive_strength_shift;

	assert(pad <= MAX_NO_MFIOS);
	assert(!(strength & ~(PAD_DRIVE_STRENGTH_MASK)));

	/* Set drive strength value */
	drive_strength_shift = (pad % 16) * PAD_DRIVE_STRENGTH_LENGTH;
	reg = read32(PAD_DRIVE_STRENGTH_ADDR(pad / 16));
	reg &= ~(PAD_DRIVE_STRENGTH_MASK << drive_strength_shift);
	reg |= strength << drive_strength_shift;
	write32(PAD_DRIVE_STRENGTH_ADDR(pad / 16), reg);
}

static void uart1_mfio_setup(void)
{
	u32 reg, mfio_mask;

	/*
	 * Disable GPIO for UART1 MFIOs
	 * All UART MFIOs have MFIO/16 = 3, therefore we use GPIO pad 3
	 * This is the only function (0) of these MFIOs and therfore there
	 * is no need to set up a function number in the corresponding
	 * function select register.
	 */
	reg = read32(GPIO_BIT_EN_ADDR(3));
	mfio_mask =  1 << (UART1_RXD_MFIO % 16);
	mfio_mask |= 1 << (UART1_TXD_MFIO % 16);
	/* Clear relevant bits */
	reg &= ~mfio_mask;
	/*
	 * Set corresponding bits in the upper half word
	 * in order to be able to modify the chosen pins
	 */
	reg |= mfio_mask << 16;
	write32(GPIO_BIT_EN_ADDR(3), reg);
}

static void spim1_mfio_setup(void)
{
	u32 reg, mfio_mask;
	/*
	 * Disable GPIO for SPIM1 MFIOs
	 * All SPFI1 MFIOs have MFIO/16 = 0, therefore we use GPIO pad 0
	 * This is the only function (0) of these MFIOs and therfore there
	 * is no need to set up a function number in the corresponding
	 * function select register.
	 */
	reg = read32(GPIO_BIT_EN_ADDR(0));

	/* Disable GPIO for SPIM1 MFIOs */
	mfio_mask = 1 << (SPIM1_D0_TXD_MFIO % 16);
	mfio_mask |= 1 << (SPIM1_D1_RXD_MFIO % 16);
	mfio_mask |= 1 << (SPIM1_MCLK_MFIO % 16);
	mfio_mask |= 1 << (SPIM1_D2_MFIO % 16);
	mfio_mask |= 1 << (SPIM1_D3_MFIO % 16);
	mfio_mask |= 1 << (SPIM1_CS0_MFIO % 16);

	/* Clear relevant bits */
	reg &= ~mfio_mask;
	/*
	 * Set corresponding bits in the upper half word
	 * in order to be able to modify the chosen pins
	 */
	reg |= mfio_mask << 16;
	write32(GPIO_BIT_EN_ADDR(0), reg);

	/* Set drive strength to maximum for these MFIOs */
	pad_drive_strength(SPIM1_CS0_MFIO, DRIVE_STRENGTH_12mA);
	pad_drive_strength(SPIM1_D1_RXD_MFIO, DRIVE_STRENGTH_12mA);
	pad_drive_strength(SPIM1_D0_TXD_MFIO, DRIVE_STRENGTH_12mA);
	pad_drive_strength(SPIM1_D2_MFIO, DRIVE_STRENGTH_12mA);
	pad_drive_strength(SPIM1_D3_MFIO, DRIVE_STRENGTH_12mA);
	pad_drive_strength(SPIM1_MCLK_MFIO, DRIVE_STRENGTH_12mA);
}

static void i2c_mfio_setup(int interface)
{
	u32 reg, mfio_mask;

	assert(interface < 4);
	/*
	 * Disable GPIO for I2C MFIOs
	 */
	reg = read32(GPIO_BIT_EN_ADDR(I2C_DATA_MFIO(interface) / 16));
	mfio_mask =  1 << (I2C_DATA_MFIO(interface) % 16);
	mfio_mask |= 1 << (I2C_CLK_MFIO(interface) % 16);
	/* Clear relevant bits */
	reg &= ~mfio_mask;
	/*
	 * Set corresponding bits in the upper half word
	 * in order to be able to modify the chosen pins
	 */
	reg |= mfio_mask << 16;
	write32(GPIO_BIT_EN_ADDR(I2C_DATA_MFIO(interface) / 16), reg);

	/* for I2C0 and I2C1:
	 * Set bits to 0 (clear) which is the primary function
	 * for these MFIOs; those bits will all be set to 1 by
	 * default.
	 * There is no need to do that for I2C2 and I2C3
	 */
	if (interface > 1)
		return;
	reg = read32(PADS_FUNCTION_SELECT0_ADDR);
	reg &= ~(I2C_DATA_FUNCTION_MASK <<
		I2C_DATA_FUNCTION_OFFSET(interface));
	reg &= ~(I2C_CLK_FUNCTION_MASK <<
		I2C_CLK_FUNCTION_OFFSET(interface));
	write32(PADS_FUNCTION_SELECT0_ADDR, reg);
}

static void bootblock_mainboard_init(void)
{
	int ret;

	/* System PLL divided by 2 -> 400 MHz */
	/* The same frequency will be the input frequency for the SPFI block */
	system_clk_setup(1);

	/* MIPS CPU dividers: division by 1 -> 546 MHz
	 * This is set up as we cannot make any assumption about
	 * the values set or not by the boot ROM code */
	mips_clk_setup(0, 0);

	/* Setup system PLL at 800 MHz */
	ret = sys_pll_setup(2, 1);
	if (ret != CLOCKS_OK)
		return;
	/* Setup MIPS PLL at 546 MHz */
	ret = mips_pll_setup(2, 1, 1, 21);
	if (ret != CLOCKS_OK)
		return;

	/* Setup SPIM1 MFIOs */
	spim1_mfio_setup();
	/* Setup UART1 clock and MFIOs
	 * System PLL divided by 7 divided by 62 -> 1.8433 Mhz
	 */
	uart1_clk_setup(6, 61);
	uart1_mfio_setup();
}


static int init_extra_hardware(void)
{
	const struct board_hw *hardware;

	/* Obtain information about current board */
	hardware = board_get_hw();
	if (!hardware) {
		printk(BIOS_ERR, "%s: Invalid hardware information.\n",
			__func__);
		return -1;
	}

	/* Setup USB clock
	 * System clock divided by 8 -> 50 MHz
	 */
	if (usb_clk_setup(7, 2, 7) != CLOCKS_OK) {
		printk(BIOS_ERR, "%s: Failed to set up USB clock.\n",
			__func__);
		return -1;
	}

	/* Setup I2C clocks and MFIOs
	 * System PLL divided by 4 divided by 3 -> 33.33 MHz
	 */
	i2c_clk_setup(3, 2, hardware->i2c_interface);
	i2c_mfio_setup(hardware->i2c_interface);

	/* Ethernet clocks setup: ENET as clock source */
	eth_clk_setup(0, 7);
	/* ROM clock setup: system clock divided by 2 -> 200 MHz */
	/* Hash accelerator is driven from the ROM clock */
	rom_clk_setup(1);

	return 0;
}