// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2013 Steffen Trumtrar <s.trumtrar@pengutronix.de>

#include <common.h>
#include <io.h>
#include <asm/barebox-arm.h>
#include <asm/barebox-arm-head.h>
#include <mach/init.h>
#include <mach/zynq7000-regs.h>
#include <serial/cadence.h>

#define DCI_DONE	(1 << 13)
#define PLL_ARM_LOCK	(1 << 0)
#define PLL_DDR_LOCK	(1 << 1)
#define PLL_IO_LOCK	(1 << 2)

extern char __dtb_zynq_zed_start[];

static void avnet_zedboard_ps7_init(void)
{
	/*
	 * Read OCM mapping configuration, if only the upper 64 KByte are
	 * mapped to the high address, it's very likely that we just got control
	 * from the BootROM. If the mapping is changed something other than the
	 * BootROM was running before us. Skip PS7 init to avoid cutting the
	 * branch we are sitting on in that case.
	 */
	if ((readl(0xf8000910) & 0xf) != 0x8)
		return;

	/* open sesame */
	writel(0x0000DF0D, ZYNQ_SLCR_UNLOCK);

	/* turn on LD9 */
	writel(0x00000200, 0xF800071C);
	writel(0x00000080, 0xE000A204);
	writel(0x00000080, 0xE000A000);

	/* ps7_clock_init_data */
	writel(0x1F000200, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_ARM_CLK_CTRL);
	writel(0x00F00701, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_DCI_CLK_CTRL);
	writel(0x00002803, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_UART_CLK_CTRL);
	writel(0x00000A03, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_DBG_CLK_CTRL);
	writel(0x00000501, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_PCAP_CLK_CTRL);
	writel(0x00000000, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_TOPSW_CLK_CTRL);
	writel(0x00100A00, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_FPGA0_CLK_CTRL);
	writel(0x00100700, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_FPGA1_CLK_CTRL);
	writel(0x00101400, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_FPGA2_CLK_CTRL);
	writel(0x00101400, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_FPGA3_CLK_CTRL);
	/* 6:2:1 mode */
	writel(0x00000001, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_CLK_621_TRUE);
	writel(0x01FC044D, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_APER_CLK_CTRL);

	/* configure the PLLs */
	/* ARM PLL */
	writel(0x00028008, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_ARM_PLL_CTRL);
	writel(0x000FA220, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_ARM_PLL_CFG);
	writel(0x00028010, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_ARM_PLL_CTRL);
	writel(0x00028011, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_ARM_PLL_CTRL);
	writel(0x00028010, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_ARM_PLL_CTRL);

	while (!(readl(ZYNQ_CLOCK_CTRL_BASE + ZYNQ_PLL_STATUS) & PLL_ARM_LOCK))
		;
	writel(0x00028000, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_ARM_PLL_CTRL);

	/* DDR PLL */
	/* set to bypass mode */
	writel(0x0001A018, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_DDR_PLL_CTRL);
	/* assert reset */
	writel(0x0001A019, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_DDR_PLL_CTRL);
	/* set feedback divs */
	writel(0x00020019, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_DDR_PLL_CTRL);
	writel(0x0012C220, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_DDR_PLL_CFG);
	/* set ddr2xclk and ddr3xclk: 3,2 */
	writel(0x0C200003, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_DDR_CLK_CTRL);
	/* deassert reset */
	writel(0x00020018, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_DDR_PLL_CTRL);
	/* wait pll lock */
	while (!(readl(ZYNQ_CLOCK_CTRL_BASE + ZYNQ_PLL_STATUS) & PLL_DDR_LOCK))
		;
	/* remove bypass mode */
	writel(0x00020008, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_DDR_PLL_CTRL);

	/* IO PLL */
	writel(0x0001E008, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_IO_PLL_CTRL);
	writel(0x001452C0, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_IO_PLL_CFG);
	writel(0x0001E010, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_IO_PLL_CTRL);
	writel(0x0001E011, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_IO_PLL_CTRL);
	writel(0x0001E010, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_IO_PLL_CTRL);

	while (!(readl(ZYNQ_CLOCK_CTRL_BASE + ZYNQ_PLL_STATUS) & PLL_IO_LOCK))
		;
	writel(0x0001E000, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_IO_PLL_CTRL);

	/*
	 * INP_TYPE[1:2] = {off, vref, diff, lvcmos}
	 * DCI_UPDATE[3], TERM_EN[4]
	 * DCI_TYPE[5:6] = {off, drive, res, term}
	 * IBUF_DISABLE_MODE[7] = {ibuf, ibuf_disable}
	 * TERM_DISABLE_MODE[8] = {always, dynamic}
	 * OUTPUT_EN[9:10] = {ibuf, res, res, obuf}
	 * PULLUP_EN[11]
	 */
	writel(0x00000600, ZYNQ_DDRIOB_ADDR0);
	writel(0x00000600, ZYNQ_DDRIOB_ADDR1);
	writel(0x00000672, ZYNQ_DDRIOB_DATA0);
	writel(0x00000672, ZYNQ_DDRIOB_DATA1);
	writel(0x00000674, ZYNQ_DDRIOB_DIFF0);
	writel(0x00000674, ZYNQ_DDRIOB_DIFF1);
	writel(0x00000600, ZYNQ_DDRIOB_CLOCK);
	/*
	 * Drive_P[0:6], Drive_N[7:13]
	 * Slew_P[14:18], Slew_N[19:23]
	 * GTL[24:26], RTerm[27:31]
	 */
	writel(0x00D6861C, ZYNQ_DDRIOB_DRIVE_SLEW_ADDR);
	writel(0x00F9861C, ZYNQ_DDRIOB_DRIVE_SLEW_DATA);
	writel(0x00F9861C, ZYNQ_DDRIOB_DRIVE_SLEW_DIFF);
	writel(0x00D6861C, ZYNQ_DDRIOB_DRIVE_SLEW_CLOCK);
	/*
	 * VREF_INT_EN[0]
	 * VREF_SEL[1:4] = {0001=0.6V, 0100=0.75V, 1000=0.9V}
	 * VREF_EXT_EN[5:6] = {dis/dis, dis/en, en/dis, en/en}
	 * RES[7:8], REFIO_EN[9]
	 */
	/* FIXME: Xilinx sets this to internal, but Zedboard should support
	   external VRef, too */
	writel(0x00000E09, ZYNQ_DDRIOB_DDR_CTRL);
	/*
	 * RESET[0], ENABLE[1]
	 * NREF_OPT1[6:7], NREF_OPT2[8:10], NREF_OPT4[11:13]
	 * PREF_OPT1[14:15], PREF_OPT2[17:19], UPDATE_CONTROL[20]
	 */
	writel(0x00000021, ZYNQ_DDRIOB_DCI_CTRL);
	writel(0x00000020, ZYNQ_DDRIOB_DCI_CTRL);
	writel(0x00100823, ZYNQ_DDRIOB_DCI_CTRL);

	while (!(readl(ZYNQ_DDRIOB_DCI_STATUS) & DCI_DONE))
		;

	writel(0x0E00E07F, 0xF8007000);

	/* ps7_ddr_init_data */
	writel(0x00000080, 0XF8006000);
	writel(0x00081081, 0XF8006004);
	writel(0x03C0780F, 0XF8006008);
	writel(0x02001001, 0XF800600C);
	writel(0x00014001, 0XF8006010);
	writel(0x0004159B, 0XF8006014);
	writel(0x452460D2, 0XF8006018);
	writel(0x720238E5, 0XF800601C);
	writel(0x272872D0, 0XF8006020);
	writel(0x0000003C, 0XF8006024);
	writel(0x00002007, 0XF8006028);
	writel(0x00000008, 0XF800602C);
	writel(0x00040930, 0XF8006030);
	writel(0x00010694, 0XF8006034);
	writel(0x00000000, 0XF8006038);
	writel(0x00000777, 0XF800603C);
	writel(0xFFF00000, 0XF8006040);
	writel(0x0FF66666, 0XF8006044);
	writel(0x0003C248, 0XF8006048);
	writel(0x77010800, 0XF8006050);
	writel(0x00000101, 0XF8006058);
	writel(0x00005003, 0XF800605C);
	writel(0x0000003E, 0XF8006060);
	writel(0x00020000, 0XF8006064);
	writel(0x00284141, 0XF8006068);
	writel(0x00001610, 0XF800606C);
	writel(0x00008000, 0XF80060A0);
	writel(0x10200802, 0XF80060A4);
	writel(0x0690CB73, 0XF80060A8);
	writel(0x000001FE, 0XF80060AC);
	writel(0x1CFFFFFF, 0XF80060B0);
	writel(0x00000200, 0XF80060B4);
	writel(0x00200066, 0XF80060B8);
	writel(0x00000000, 0XF80060BC);
	writel(0x00000000, 0XF80060C4);
	writel(0x00000000, 0XF80060C8);
	writel(0x00000000, 0XF80060DC);
	writel(0x00000000, 0XF80060F0);
	writel(0x00000008, 0XF80060F4);
	writel(0x00000000, 0XF8006114);
	writel(0x40000001, 0XF8006118);
	writel(0x40000001, 0XF800611C);
	writel(0x40000001, 0XF8006120);
	writel(0x40000001, 0XF8006124);
	writel(0x00033C03, 0XF800612C);
	writel(0x00034003, 0XF8006130);
	writel(0x0002F400, 0XF8006134);
	writel(0x00030400, 0XF8006138);
	writel(0x00000035, 0XF8006140);
	writel(0x00000035, 0XF8006144);
	writel(0x00000035, 0XF8006148);
	writel(0x00000035, 0XF800614C);
	writel(0x00000083, 0XF8006154);
	writel(0x00000083, 0XF8006158);
	writel(0x0000007F, 0XF800615C);
	writel(0x00000078, 0XF8006160);
	writel(0x00000124, 0XF8006168);
	writel(0x00000125, 0XF800616C);
	writel(0x00000112, 0XF8006170);
	writel(0x00000116, 0XF8006174);
	writel(0x000000C3, 0XF800617C);
	writel(0x000000C3, 0XF8006180);
	writel(0x000000BF, 0XF8006184);
	writel(0x000000B8, 0XF8006188);
	writel(0x10040080, 0XF8006190);
	writel(0x0001FC82, 0XF8006194);
	writel(0x00000000, 0XF8006204);
	writel(0x000803FF, 0XF8006208);
	writel(0x000803FF, 0XF800620C);
	writel(0x000803FF, 0XF8006210);
	writel(0x000803FF, 0XF8006214);
	writel(0x000003FF, 0XF8006218);
	writel(0x000003FF, 0XF800621C);
	writel(0x000003FF, 0XF8006220);
	writel(0x000003FF, 0XF8006224);
	writel(0x00000000, 0XF80062A8);
	writel(0x00000000, 0XF80062AC);
	writel(0x00005125, 0XF80062B0);
	writel(0x000012A8, 0XF80062B4);
	writel(0x00000081, 0XF8006000);

	/* poor mans pinctrl */
	writel(0x000002E0, ZYNQ_MIO_BASE + 0xC0);
	writel(0x000002E1, ZYNQ_MIO_BASE + 0xC4);
	/* UART1 pinmux */
	writel(0x000002E1, ZYNQ_MIO_BASE + 0xC8);
	writel(0x000002E0, ZYNQ_MIO_BASE + 0xCC);
	/* QSPI pinmux */
	writel(0x00001602, ZYNQ_MIO_BASE + 0x04);
	writel(0x00000702, ZYNQ_MIO_BASE + 0x08);
	writel(0x00000702, ZYNQ_MIO_BASE + 0x0c);
	writel(0x00000702, ZYNQ_MIO_BASE + 0x10);
	writel(0x00000702, ZYNQ_MIO_BASE + 0x14);
	writel(0x00000702, ZYNQ_MIO_BASE + 0x18);
	writel(0x00000602, ZYNQ_MIO_BASE + 0x20);

	/* poor mans clkctrl */
	writel(0x00001403, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_UART_CLK_CTRL);
	writel(0x00000101, ZYNQ_CLOCK_CTRL_BASE + ZYNQ_LQSPI_CLK_CTRL);

	/* GEM0 */
	writel(0x00000001, 0xf8000138);
	writel(0x00100801, 0xf8000140);
	writel(0x00000302, 0xf8000740);
	writel(0x00000302, 0xf8000744);
	writel(0x00000302, 0xf8000748);
	writel(0x00000302, 0xf800074C);
	writel(0x00000302, 0xf8000750);
	writel(0x00000302, 0xf8000754);
	writel(0x00001303, 0xf8000758);
	writel(0x00001303, 0xf800075C);
	writel(0x00001303, 0xf8000760);
	writel(0x00001303, 0xf8000764);
	writel(0x00001303, 0xf8000768);
	writel(0x00001303, 0xf800076C);
	writel(0x00000280, 0xf80007D0);
	writel(0x00000280, 0xf80007D4);

	writel(0x00000001, 0xf8000B00);

	/* FPGA Clock Control */
	writel(0x00101400, 0xf8000170);
	writel(0x00101400, 0xf8000180);
	writel(0x00101400, 0xf8000190);
	writel(0x00101400, 0xf80001a0);

	/* PCAP Clock Control */
	writel(0x00000501, 0xf8000168);

	/* lock up. secure, secure */
	writel(0x0000767B, ZYNQ_SLCR_LOCK);
}

static void avnet_zedboard_pbl_console_init(void)
{
	relocate_to_current_adr();
	setup_c();
	barrier();

	cadence_uart_init((void *)ZYNQ_UART1_BASE_ADDR);
	pbl_set_putc(cadence_uart_putc, (void *)ZYNQ_UART1_BASE_ADDR);

	pr_debug("\nAvnet ZedBoard PBL\n");
}

ENTRY_FUNCTION(start_avnet_zedboard, r0, r1, r2)
{

	void *fdt = __dtb_zynq_zed_start + get_runtime_offset();

	/* MIO_07 in GPIO Mode 3.3V VIO, can be uncomented because it is the default value */
	writel(0x0000DF0D, ZYNQ_SLCR_UNLOCK);
	writel(0x00000600, 0xF800071C );
	writel(0x0000767B, ZYNQ_SLCR_LOCK);

	/* turns on the LED MIO_07 */
	writel((1<<7), 0xe000a204 );    // Direction
	writel((1<<7), 0xe000a208 );    // Output enable
	writel((1<<7), 0xe000a040 );    // DATA Register

	arm_cpu_lowlevel_init();
	zynq_cpu_lowlevel_init();

	avnet_zedboard_ps7_init();

	if (IS_ENABLED(CONFIG_PBL_CONSOLE))
		avnet_zedboard_pbl_console_init();

	barebox_arm_entry(0, SZ_512M, fdt);
}
