// SPDX-License-Identifier: GPL-2.0+
/*
 * Traverse Ten64 Family board
 * Copyright 2017-2018 NXP
 * Copyright 2019-2021 Traverse Technologies
 */
#include <common.h>
#include <display_options.h>
#include <dm/uclass.h>
#include <env.h>
#include <i2c.h>
#include <init.h>
#include <log.h>
#include <malloc.h>
#include <errno.h>
#include <misc.h>
#include <netdev.h>
#include <fsl_ifc.h>
#include <fsl_ddr.h>
#include <fsl_sec.h>
#include <asm/global_data.h>
#include <asm/io.h>
#include <fdt_support.h>
#include <linux/delay.h>
#include <linux/libfdt.h>
#include <fsl-mc/fsl_mc.h>
#include <env_internal.h>
#include <asm/arch-fsl-layerscape/soc.h>
#include <asm/arch/ppa.h>
#include <hwconfig.h>
#include <asm/arch/fsl_serdes.h>
#include <asm/arch/soc.h>
#include <asm/arch-fsl-layerscape/fsl_icid.h>

#include <fsl_immap.h>

#include "../common/ten64-controller.h"

#define I2C_RETIMER_ADDR		0x27

DECLARE_GLOBAL_DATA_PTR;

static int ten64_read_board_info(struct t64uc_board_info *);
static void ten64_set_macaddrs_from_board_info(struct t64uc_board_info *);
static void ten64_board_retimer_ds110df410_init(void);

enum {
	TEN64_BOARD_REV_A = 0xFF,
	TEN64_BOARD_REV_B = 0xFE,
	TEN64_BOARD_REV_C = 0xFD
};

#define RESV_MEM_IN_BANK(b)	(gd->arch.resv_ram >= base[b] && \
				 gd->arch.resv_ram < base[b] + size[b])

int board_early_init_f(void)
{
	fsl_lsch3_early_init_f();
	return 0;
}

static u32 ten64_get_board_rev(void)
{
	struct ccsr_gur *dcfg = (void *)CFG_SYS_FSL_GUTS_ADDR;
	u32 board_rev_in = in_le32(&dcfg->gpporcr1);
	return board_rev_in;
}

int checkboard(void)
{
	enum boot_src src = get_boot_src();
	char boardmodel[32];
	struct t64uc_board_info boardinfo;
	u32 board_rev = ten64_get_board_rev();

	switch (board_rev) {
	case TEN64_BOARD_REV_A:
		snprintf(boardmodel, 32, "1064-0201A (Alpha)");
		break;
	case TEN64_BOARD_REV_B:
		snprintf(boardmodel, 32, "1064-0201B (Beta)");
		break;
	case TEN64_BOARD_REV_C:
		snprintf(boardmodel, 32, "1064-0201C");
		break;
	default:
		snprintf(boardmodel, 32, "1064 Revision %X", (0xFF - board_rev));
		break;
	}

	printf("Board: %s, boot from ", boardmodel);
	if (src == BOOT_SOURCE_SD_MMC)
		puts("SD card\n");
	else if (src == BOOT_SOURCE_QSPI_NOR)
		puts("QSPI\n");
	else
		printf("Unknown boot source %d\n", src);

	puts("Controller: ");
	if (CONFIG_IS_ENABLED(TEN64_CONTROLLER)) {
		/* Driver not compatible with alpha/beta board MCU firmware */
		if (board_rev <= TEN64_BOARD_REV_C) {
			if (ten64_read_board_info(&boardinfo)) {
				puts("ERROR: unable to communicate\n");
			} else {
				printf("firmware %d.%d.%d\n",
				       boardinfo.fwversion_major,
				       boardinfo.fwversion_minor,
				       boardinfo.fwversion_patch);
				ten64_set_macaddrs_from_board_info(&boardinfo);
			}
		} else {
			puts("not supported on this board revision\n");
		}
	} else {
		puts("driver not enabled (no MAC addresses or other information will be read)\n");
	}

	return 0;
}

int board_init(void)
{
	init_final_memctl_regs();

	if (CONFIG_IS_ENABLED(FSL_CAAM))
		sec_init();

	return 0;
}

int fsl_initdram(void)
{
	gd->ram_size = tfa_get_dram_size();

	if (!gd->ram_size)
		gd->ram_size = fsl_ddr_sdram_size();

	return 0;
}

void detail_board_ddr_info(void)
{
	puts("\nDDR    ");
	print_size(gd->bd->bi_dram[0].size + gd->bd->bi_dram[1].size, "");
	print_ddr_info(0);
}

void board_quiesce_devices(void)
{
	if (IS_ENABLED(CONFIG_FSL_MC_ENET))
		fsl_mc_ldpaa_exit(gd->bd);
}

void fdt_fixup_board_enet(void *fdt)
{
	int offset;

	offset = fdt_path_offset(fdt, "/fsl-mc");

	if (offset < 0)
		offset = fdt_path_offset(fdt, "/soc/fsl-mc");

	if (offset < 0) {
		printf("%s: ERROR: fsl-mc node not found in device tree (error %d)\n",
		       __func__, offset);
		return;
	}

	if (get_mc_boot_status() == 0 &&
	    (is_lazy_dpl_addr_valid() || get_dpl_apply_status() == 0))
		fdt_status_okay(fdt, offset);
	else
		fdt_status_fail(fdt, offset);
}

/* Called after SoC board_late_init in fsl-layerscape/soc.c */
int fsl_board_late_init(void)
{
	ten64_board_retimer_ds110df410_init();
	return 0;
}

int ft_board_setup(void *blob, struct bd_info *bd)
{
	int i;
	u16 mc_memory_bank = 0;

	u64 *base;
	u64 *size;
	u64 mc_memory_base = 0;
	u64 mc_memory_size = 0;
	u16 total_memory_banks;

	debug("%s blob=0x%p\n", __func__, blob);

	ft_cpu_setup(blob, bd);

	fdt_fixup_mc_ddr(&mc_memory_base, &mc_memory_size);

	if (mc_memory_base != 0)
		mc_memory_bank++;

	total_memory_banks = CONFIG_NR_DRAM_BANKS + mc_memory_bank;

	base = calloc(total_memory_banks, sizeof(u64));
	size = calloc(total_memory_banks, sizeof(u64));

	/* fixup DT for the two GPP DDR banks */
	for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
		base[i] = gd->bd->bi_dram[i].start;
		size[i] = gd->bd->bi_dram[i].size;
		/* reduce size if reserved memory is within this bank */
		if (CONFIG_IS_ENABLED(RESV_RAM) && RESV_MEM_IN_BANK(i))
			size[i] = gd->arch.resv_ram - base[i];
	}

	if (mc_memory_base != 0) {
		for (i = 0; i <= total_memory_banks; i++) {
			if (base[i] == 0 && size[i] == 0) {
				base[i] = mc_memory_base;
				size[i] = mc_memory_size;
				break;
			}
		}
	}

	fdt_fixup_memory_banks(blob, base, size, total_memory_banks);

	fdt_fsl_mc_fixup_iommu_map_entry(blob);

	if (CONFIG_IS_ENABLED(FSL_MC_ENET))
		fdt_fixup_board_enet(blob);

	fdt_fixup_icid(blob);

	return 0;
}

#define MACADDRBITS(a, b) (u8)(((a) >> (b)) & 0xFF)

/** Probe and return a udevice for the Ten64 board microcontroller.
 * Optionally, return the I2C bus the microcontroller resides on
 * @i2c_bus_out: return I2C bus device handle in this pointer
 */
static int ten64_get_micro_udevice(struct udevice **ucdev, struct udevice **i2c_bus_out)
{
	int ret;
	struct udevice *i2cbus;

	ret = uclass_get_device_by_seq(UCLASS_I2C, 0, &i2cbus);
	if (ret) {
		printf("%s: Could not get I2C UCLASS", __func__);
		return ret;
	}
	if (i2c_bus_out)
		*i2c_bus_out = i2cbus;

	ret = dm_i2c_probe(i2cbus, 0x7E, DM_I2C_CHIP_RD_ADDRESS, ucdev);
	if (ret) {
		printf("%s: Could not get microcontroller device\n", __func__);
		return ret;
	}
	return ret;
}

static int ten64_read_board_info(struct t64uc_board_info *boardinfo)
{
	struct udevice *ucdev;
	int ret;

	ret = ten64_get_micro_udevice(&ucdev, NULL);
	if (ret)
		return ret;

	ret = misc_call(ucdev, TEN64_CNTRL_GET_BOARD_INFO, NULL, 0, (void *)boardinfo, 0);
	if (ret)
		return ret;

	return 0;
}

static void ten64_set_macaddrs_from_board_info(struct t64uc_board_info *boardinfo)
{
	char ethaddr[18];
	char enetvar[10];
	u8 intfidx, this_dpmac_num;
	u64 macaddr = 0;
	/* We will copy the MAC address returned from the
	 * uC (48 bits) into the u64 macaddr
	 */
	u8 *macaddr_bytes = (u8 *)&macaddr + 2;

	/** MAC addresses are allocated in order of the physical port numbers,
	 * DPMAC7->10 is "eth0" through "eth3"
	 * DPMAC3->6 is "eth4" through "eth7"
	 * DPMAC2 and 1 are "eth8" and "eth9" respectively
	 */
	int allocation_order[10] = {7, 8, 9, 10, 3, 4, 5, 6, 2, 1};

	memcpy(macaddr_bytes, boardinfo->mac, 6);
	/* MAC address bytes from uC are in big endian,
	 * convert to CPU
	 */
	macaddr = __be64_to_cpu(macaddr);

	for (intfidx = 0; intfidx < 10; intfidx++) {
		snprintf(ethaddr, 18, "%02X:%02X:%02X:%02X:%02X:%02X",
			 MACADDRBITS(macaddr, 40),
			 MACADDRBITS(macaddr, 32),
			 MACADDRBITS(macaddr, 24),
			 MACADDRBITS(macaddr, 16),
			 MACADDRBITS(macaddr, 8),
			 MACADDRBITS(macaddr, 0));

		this_dpmac_num = allocation_order[intfidx];
		printf("DPMAC%d: %s\n", this_dpmac_num, ethaddr);
		snprintf(enetvar, 10,
			 (this_dpmac_num != 1) ? "eth%daddr" : "ethaddr",
			 this_dpmac_num - 1);
		macaddr++;

		if (!env_get(enetvar))
			env_set(enetvar, ethaddr);
	}
}

/* The retimer (DS110DF410) is one of the devices without
 * a RESET line, but a power switch is on the board
 * allowing it to be reset via uC command
 */
static int board_cycle_retimer(struct udevice **retim_dev)
{
	int ret;
	u8 loop;
	struct udevice *uc_dev;
	struct udevice *i2cbus;

	ret = ten64_get_micro_udevice(&uc_dev, &i2cbus);
	if (ret)
		return ret;

	ret = dm_i2c_probe(i2cbus, I2C_RETIMER_ADDR, 0, retim_dev);
	if (ret == 0) {
		puts("(retimer on, resetting...) ");

		ret = misc_call(uc_dev, TEN64_CNTRL_10G_OFF, NULL, 0, NULL, 0);
		mdelay(1000);
	}

	ret = misc_call(uc_dev, TEN64_CNTRL_10G_ON, NULL, 0, NULL, 0);

	// Wait for retimer to come back
	for (loop = 0; loop < 5; loop++) {
		ret = dm_i2c_probe(i2cbus, I2C_RETIMER_ADDR, 0, retim_dev);
		if (ret == 0)
			return 0;
		mdelay(500);
	}

	return -ENOSYS;
}

/* ten64_board_retimer_ds110df410_init() - Configure the 10G retimer
 * Adopted from the t102xqds board file
 */
static void ten64_board_retimer_ds110df410_init(void)
{
	u8 reg;
	int ret;
	struct udevice *retim_dev;
	u32 board_rev = ten64_get_board_rev();

	puts("Retimer: ");
	/* Retimer power cycle not implemented on early board
	 * revisions/controller firmwares
	 */
	if (CONFIG_IS_ENABLED(TEN64_CONTROLLER) &&
	    board_rev >= TEN64_BOARD_REV_C) {
		ret = board_cycle_retimer(&retim_dev);
		if (ret) {
			puts("Retimer power on failed\n");
			return;
		}
	}

	/* Access to Control/Shared register */
	reg = 0x0;

	ret = dm_i2c_write(retim_dev, 0xff, &reg, 1);
	if (ret) {
		printf("Error writing to retimer register (error %d)\n", ret);
		return;
	}

	/* Read device revision and ID */
	dm_i2c_read(retim_dev, 1, &reg, 1);
	if (reg == 0xF0)
		puts("DS110DF410 found\n");
	else
		printf("Unknown retimer 0x%xn\n", reg);

	/* Enable Broadcast */
	reg = 0x0c;
	dm_i2c_write(retim_dev, 0xff, &reg, 1);

	/* Perform a full reset (state, channel and clock)
	 * for all channels
	 * as the DS110DF410 does not have a RESET line
	 */
	dm_i2c_read(retim_dev, 0, &reg, 1);
	reg |= 0x7;
	dm_i2c_write(retim_dev, 0, &reg, 1);

	/* Set rate/subrate = 0 */
	reg = 0x6;
	dm_i2c_write(retim_dev, 0x2F, &reg, 1);

	/* Set data rate as 10.3125 Gbps */
	reg = 0x0;
	dm_i2c_write(retim_dev, 0x60, &reg, 1);
	reg = 0xb2;
	dm_i2c_write(retim_dev, 0x61, &reg, 1);
	reg = 0x90;
	dm_i2c_write(retim_dev, 0x62, &reg, 1);
	reg = 0xb3;
	dm_i2c_write(retim_dev, 0x63, &reg, 1);
	reg = 0xff;
	dm_i2c_write(retim_dev, 0x64, &reg, 1);

	/* Invert channel 2 (Lower SFP TX to CPU) due to the SFP being inverted */
	reg = 0x05;
	dm_i2c_write(retim_dev, 0xFF, &reg, 1);
	dm_i2c_read(retim_dev, 0x1F, &reg, 1);
	reg |= 0x80;
	dm_i2c_write(retim_dev, 0x1F, &reg, 1);

	puts("OK\n");
}
