// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2020 Stefan Roese <sr@denx.de>
 */

#include <dm.h>
#include <fdt_support.h>
#include <ram.h>
#include <asm/gpio.h>

#include <mach/octeon_ddr.h>
#include <mach/cvmx-qlm.h>
#include <mach/octeon_qlm.h>
#include <mach/octeon_fdt.h>
#include <mach/cvmx-helper.h>
#include <mach/cvmx-helper-cfg.h>
#include <mach/cvmx-helper-util.h>
#include <mach/cvmx-bgxx-defs.h>

#include "board_ddr.h"

#define MAX_MIX_ENV_VARS	4

#define EBB7304_DEF_DRAM_FREQ	800

static struct ddr_conf board_ddr_conf[] = {
	OCTEON_EBB7304_DDR_CONFIGURATION
};

static int no_phy[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };

struct ddr_conf *octeon_ddr_conf_table_get(int *count, int *def_ddr_freq)
{
	*count = ARRAY_SIZE(board_ddr_conf);
	*def_ddr_freq = EBB7304_DEF_DRAM_FREQ;

	return board_ddr_conf;
}

/*
 * parse_env_var:	Parse the environment variable ("bgx_for_mix%d") to
 *			extract the lmac it is set to.
 *
 *  index:		Index of environment variable to parse.
 *			environment variable.
 *  env_bgx:		Updated with the bgx of the lmac in the environment
 *			variable.
 *  env_lmac:		Updated with the index of lmac in the environment
 *			variable.
 *
 *  returns:		Zero on success, error otherwise.
 */
static int parse_env_var(int index, int *env_bgx, int *env_lmac)
{
	char env_var[20];
	ulong xipd_port;

	sprintf(env_var, "bgx_for_mix%d", index);
	xipd_port = env_get_ulong(env_var, 0, 0xffff);
	if (xipd_port != 0xffff) {
		int xiface;
		struct cvmx_xiface xi;
		struct cvmx_xport xp;

		/*
		 * The environemt variable is set to the xipd port. Convert the
		 * xipd port to numa node, bgx, and lmac.
		 */
		xiface = cvmx_helper_get_interface_num(xipd_port);
		xi = cvmx_helper_xiface_to_node_interface(xiface);
		xp = cvmx_helper_ipd_port_to_xport(xipd_port);
		*env_bgx = xi.interface;
		*env_lmac = cvmx_helper_get_interface_index_num(xp.port);
		return 0;
	}

	return -1;
}

/*
 * get_lmac_fdt_node:	Search the device tree for the node corresponding to
 *			a given bgx lmac.
 *
 *  fdt:		Pointer to flat device tree
 *  search_node:	Numa node of the lmac to search for.
 *  search_bgx:		Bgx of the lmac to search for.
 *  search_lmac:	Lmac index to search for.
 *  compat:		Compatible string to search for.

 *  returns:		The device tree node of the lmac if found,
 *			or -1 otherwise.
 */
static int get_lmac_fdt_node(const void *fdt, int search_node, int search_bgx, int search_lmac,
			     const char *compat)
{
	int node;
	const fdt32_t *reg;
	u64 addr;
	int fdt_node = -1;
	int fdt_bgx = -1;
	int fdt_lmac = -1;
	int len;
	int parent;

	/* Iterate through all bgx ports */
	node = -1;
	while ((node = fdt_node_offset_by_compatible((void *)fdt, node,
						     compat)) >= 0) {
		/* Get the node and bgx from the physical address */
		parent = fdt_parent_offset(fdt, node);
		reg = fdt_getprop(fdt, parent, "reg", &len);
		if (parent < 0 || !reg)
			continue;

		addr = fdt_translate_address((void *)fdt, parent, reg);
		fdt_node = (addr >> 36) & 0x7;
		fdt_bgx = (addr >> 24) & 0xf;

		/* Get the lmac index from the reg property */
		reg = fdt_getprop(fdt, node, "reg", &len);
		if (reg)
			fdt_lmac = *reg;

		/* Check for a match */
		if (search_node == fdt_node && search_bgx == fdt_bgx &&
		    search_lmac == fdt_lmac)
			return node;
	}

	return -1;
}

/*
 * get_mix_fdt_node:	Search the device tree for the node corresponding to
 *			a given mix.
 *
 *  fdt:		Pointer to flat device tree
 *  search_node:	Mix numa node to search for.
 *  search_index:	Mix index to search for.
 *
 *  returns:		The device tree node of the lmac if found,
 *			or -1 otherwise.
 */
static int get_mix_fdt_node(const void *fdt, int search_node, int search_index)
{
	int node;

	/* Iterate through all the mix fdt nodes */
	node = -1;
	while ((node = fdt_node_offset_by_compatible((void *)fdt, node,
						     "cavium,octeon-7890-mix")) >= 0) {
		int parent;
		int len;
		const char *name;
		int mix_numa_node;
		const fdt32_t *reg;
		int mix_index = -1;
		u64 addr;

		/* Get the numa node of the mix from the parent node name */
		parent = fdt_parent_offset(fdt, node);
		if (parent < 0 ||
		    ((name = fdt_get_name(fdt, parent, &len)) == NULL) ||
		    ((name = strchr(name, '@')) == NULL))
			continue;

		name++;
		mix_numa_node = simple_strtol(name, NULL, 0) ? 1 : 0;

		/* Get the mix index from the reg property */
		reg = fdt_getprop(fdt, node, "reg", &len);
		if (reg) {
			addr = fdt_translate_address((void *)fdt, parent, reg);
			mix_index = (addr >> 11) & 1;
		}

		/* Check for a match */
		if (mix_numa_node == search_node && mix_index == search_index)
			return node;
	}

	return -1;
}

/*
 * fdt_fix_mix:		Fix the mix nodes in the device tree. Only the mix nodes
 *			configured by the user will be preserved. All other mix
 *			nodes will be trimmed.
 *
 *  fdt:		Pointer to flat device tree
 *
 *  returns:		Zero on success, error otherwise.
 */
static int fdt_fix_mix(const void *fdt)
{
	int node;
	int next_node;
	int len;
	int i;

	/* Parse all the mix port environment variables */
	for (i = 0; i < MAX_MIX_ENV_VARS; i++) {
		int env_node = 0;
		int env_bgx = -1;
		int env_lmac = -1;
		int lmac_fdt_node = -1;
		int mix_fdt_node = -1;
		int lmac_phandle;
		char *compat;

		/* Get the lmac for this environment variable */
		if (parse_env_var(i, &env_bgx, &env_lmac))
			continue;

		/* Get the fdt node for this lmac and add a phandle to it */
		compat = "cavium,octeon-7890-bgx-port";
		lmac_fdt_node = get_lmac_fdt_node(fdt, env_node, env_bgx,
						  env_lmac, compat);
		if (lmac_fdt_node < 0) {
			/* Must check for the xcv compatible string too */
			compat = "cavium,octeon-7360-xcv";
			lmac_fdt_node = get_lmac_fdt_node(fdt, env_node,
							  env_bgx, env_lmac,
							  compat);
			if (lmac_fdt_node < 0) {
				printf("WARNING: Failed to get lmac fdt node for %d%d%d\n",
				       env_node, env_bgx, env_lmac);
				continue;
			}
		}

		lmac_phandle = fdt_alloc_phandle((void *)fdt);
		fdt_set_phandle((void *)fdt, lmac_fdt_node, lmac_phandle);

		/* Get the fdt mix node corresponding to this lmac */
		mix_fdt_node = get_mix_fdt_node(fdt, env_node, env_lmac);
		if (mix_fdt_node < 0)
			continue;

		/* Point the mix to the lmac */
		fdt_getprop(fdt, mix_fdt_node, "cavium,mac-handle", &len);
		fdt_setprop_inplace((void *)fdt, mix_fdt_node,
				    "cavium,mac-handle", &lmac_phandle, len);
	}

	/* Trim unused mix'es from the device tree */
	for (node = fdt_next_node(fdt, -1, NULL); node >= 0; node = next_node) {
		const char *compat;
		const fdt32_t *reg;

		next_node = fdt_next_node(fdt, node, NULL);

		compat = fdt_getprop(fdt, node, "compatible", &len);
		if (compat) {
			if (strcmp(compat, "cavium,octeon-7890-mix"))
				continue;

			reg = fdt_getprop(fdt, node, "cavium,mac-handle", &len);
			if (reg) {
				if (*reg == 0xffff)
					fdt_nop_node((void *)fdt, node);
			}
		}
	}

	return 0;
}

static void kill_fdt_phy(void *fdt, int offset, void *arg)
{
	int len, phy_offset;
	const fdt32_t *php;
	u32 phandle;

	php = fdt_getprop(fdt, offset, "phy-handle", &len);
	if (php && len == sizeof(*php)) {
		phandle = fdt32_to_cpu(*php);
		fdt_nop_property(fdt, offset, "phy-handle");
		phy_offset = fdt_node_offset_by_phandle(fdt, phandle);
		if (phy_offset > 0)
			fdt_nop_node(fdt, phy_offset);
	}
}

void __fixup_xcv(void)
{
	unsigned long bgx = env_get_ulong("bgx_for_rgmii", 10,
					  (unsigned long)-1);
	char fdt_key[16];
	int i;

	debug("%s: BGX %d\n", __func__, (int)bgx);

	for (i = 0; i < 3; i++) {
		snprintf(fdt_key, sizeof(fdt_key),
			 bgx == i ? "%d,xcv" : "%d,not-xcv", i);
		debug("%s: trimming bgx %lu with key %s\n",
		      __func__, bgx, fdt_key);

		octeon_fdt_patch_rename((void *)gd->fdt_blob, fdt_key,
					"cavium,xcv-trim", true, NULL, NULL);
	}
}

/* QLM0 - QLM6 */
void __fixup_fdt(void)
{
	int qlm;
	int speed = 0;

	for (qlm = 0; qlm < 7; qlm++) {
		enum cvmx_qlm_mode mode;
		char fdt_key[16];
		const char *type_str = "none";

		mode = cvmx_qlm_get_mode(qlm);
		switch (mode) {
		case CVMX_QLM_MODE_SGMII:
		case CVMX_QLM_MODE_RGMII_SGMII:
		case CVMX_QLM_MODE_RGMII_SGMII_1X1:
			type_str = "sgmii";
			break;
		case CVMX_QLM_MODE_XAUI:
		case CVMX_QLM_MODE_RGMII_XAUI:
			speed = (cvmx_qlm_get_gbaud_mhz(qlm) * 8 / 10) * 4;
			if (speed == 10000)
				type_str = "xaui";
			else
				type_str = "dxaui";
			break;
		case CVMX_QLM_MODE_RXAUI:
		case CVMX_QLM_MODE_RGMII_RXAUI:
			type_str = "rxaui";
			break;
		case CVMX_QLM_MODE_XLAUI:
		case CVMX_QLM_MODE_RGMII_XLAUI:
			type_str = "xlaui";
			break;
		case CVMX_QLM_MODE_XFI:
		case CVMX_QLM_MODE_RGMII_XFI:
		case CVMX_QLM_MODE_RGMII_XFI_1X1:
			type_str = "10gbase-r";
			break;
		case CVMX_QLM_MODE_10G_KR:
		case CVMX_QLM_MODE_RGMII_10G_KR:
			type_str = "10G_KR";
			break;
		case CVMX_QLM_MODE_40G_KR4:
		case CVMX_QLM_MODE_RGMII_40G_KR4:
			type_str = "40G_KR4";
			break;
		case CVMX_QLM_MODE_SATA_2X1:
			type_str = "sata";
			break;
		case CVMX_QLM_MODE_SGMII_2X1:
		case CVMX_QLM_MODE_XFI_1X2:
		case CVMX_QLM_MODE_10G_KR_1X2:
		case CVMX_QLM_MODE_RXAUI_1X2:
		case CVMX_QLM_MODE_MIXED: // special for DLM5 & DLM6
		{
			cvmx_bgxx_cmrx_config_t cmr_config;
			cvmx_bgxx_spux_br_pmd_control_t pmd_control;
			int mux = cvmx_qlm_mux_interface(2);

			if (mux == 2) { // only dlm6
				cmr_config.u64 = csr_rd(CVMX_BGXX_CMRX_CONFIG(2, 2));
				pmd_control.u64 =
					csr_rd(CVMX_BGXX_SPUX_BR_PMD_CONTROL(2, 2));
			} else {
				if (qlm == 5) {
					cmr_config.u64 =
						csr_rd(CVMX_BGXX_CMRX_CONFIG(0, 2));
					pmd_control.u64 =
						csr_rd(CVMX_BGXX_SPUX_BR_PMD_CONTROL(0, 2));
				} else {
					cmr_config.u64 =
						csr_rd(CVMX_BGXX_CMRX_CONFIG(2, 2));
					pmd_control.u64 =
						csr_rd(CVMX_BGXX_SPUX_BR_PMD_CONTROL(2, 2));
				}
			}
			switch (cmr_config.s.lmac_type) {
			case 0:
				type_str = "sgmii";
				break;
			case 1:
				type_str = "xaui";
				break;
			case 2:
				type_str = "rxaui";
				break;
			case 3:
				if (pmd_control.s.train_en)
					type_str = "10G_KR";
				else
					type_str = "10gbase-r";
				break;
			case 4:
				if (pmd_control.s.train_en)
					type_str = "40G_KR4";
				else
					type_str = "xlaui";
				break;
			default:
				type_str = "none";
				break;
			}
			break;
		}
		default:
			type_str = "none";
			break;
		}
		sprintf(fdt_key, "%d,%s", qlm, type_str);
		debug("Patching qlm %d for %s for mode %d%s\n", qlm, fdt_key, mode,
		      no_phy[qlm] ? ", removing PHY" : "");
		octeon_fdt_patch_rename((void *)gd->fdt_blob, fdt_key, NULL, true,
					no_phy[qlm] ? kill_fdt_phy : NULL, NULL);
	}
}

int board_fix_fdt(void)
{
	__fixup_fdt();
	__fixup_xcv();

	/* Fix the mix ports */
	fdt_fix_mix(gd->fdt_blob);

	return 0;
}

/*
 * Here is the description of the parameters that are passed to QLM
 * configuration:
 *
 *	param0 : The QLM to configure
 *	param1 : Speed to configure the QLM at
 *	param2 : Mode the QLM to configure
 *	param3 : 1 = RC, 0 = EP
 *	param4 : 0 = GEN1, 1 = GEN2, 2 = GEN3
 *	param5 : ref clock select, 0 = 100Mhz, 1 = 125MHz, 2 = 156MHz
 *	param6 : ref clock input to use:
 *		 0 - external reference (QLMx_REF_CLK)
 *		 1 = common clock 0 (QLMC_REF_CLK0)
 *		 2 = common_clock 1 (QLMC_REF_CLK1)
 */
static void board_configure_qlms(void)
{
	int speed[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
	int mode[8] = { -1, -1, -1, -1, -1, -1, -1, -1 };
	int pcie_rc[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
	int pcie_gen[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
	int ref_clock_sel[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
	int ref_clock_input[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
	struct gpio_desc desc;
	int rbgx, rqlm;
	char env_var[16];
	int qlm;
	int ret;

	/* RGMII PHY reset GPIO */
	ret = dm_gpio_lookup_name("gpio-controllerA27", &desc);
	if (ret)
		debug("gpio ret=%d\n", ret);
	ret = dm_gpio_request(&desc, "rgmii_phy_reset");
	if (ret)
		debug("gpio_request ret=%d\n", ret);
	ret = dm_gpio_set_dir_flags(&desc, GPIOD_IS_OUT);
	if (ret)
		debug("gpio dir ret=%d\n", ret);

	/* Put RGMII PHY in reset */
	dm_gpio_set_value(&desc, 0);

	octeon_init_qlm(0);

	rbgx = env_get_ulong("bgx_for_rgmii", 10, (unsigned long)-1);
	switch (rbgx) {
	case 0:
		rqlm = 2;
		break;
	case 1:
		rqlm = 3;
		break;
	case 2:
		rqlm = 5;
		break;
	default:
		rqlm = -1;
		break;
	}

	for (qlm = 0; qlm < 7; qlm++) {
		const char *mode_str;
		char spd_env[16];

		mode[qlm] = CVMX_QLM_MODE_DISABLED;
		sprintf(env_var, "qlm%d_mode", qlm);
		mode_str = env_get(env_var);
		if (!mode_str)
			continue;

		if (qlm == 4 && mode[4] != -1 &&
		    mode[4] != CVMX_QLM_MODE_SATA_2X1) {
			printf("Error: DLM 4 can only be configured for SATA\n");
			continue;
		}

		if (strstr(mode_str, ",no_phy"))
			no_phy[qlm] = 1;

		if (!strncmp(mode_str, "sgmii", 5)) {
			bool rgmii = false;

			speed[qlm] = 1250;
			if (rqlm == qlm && qlm < 5) {
				mode[qlm] = CVMX_QLM_MODE_RGMII_SGMII;
				rgmii = true;
			} else if (qlm == 6 || qlm == 5) {
				if (rqlm == qlm && qlm == 5) {
					mode[qlm] = CVMX_QLM_MODE_RGMII_SGMII_1X1;
					rgmii = true;
				} else if (rqlm == 5 && qlm == 6 &&
					   mode[5] != CVMX_QLM_MODE_RGMII_SGMII_1X1) {
					mode[qlm] = CVMX_QLM_MODE_RGMII_SGMII_2X1;
					rgmii = true;
				} else {
					mode[qlm] = CVMX_QLM_MODE_SGMII_2X1;
				}
			} else {
				mode[qlm] = CVMX_QLM_MODE_SGMII;
			}
			ref_clock_sel[qlm] = 2;

			if (qlm == 5 || qlm == 6)
				ref_clock_input[qlm] = 2; // use QLMC_REF_CLK1

			if (no_phy[qlm]) {
				int i;
				int start = 0, stop = 2;

				rbgx = 0;
				switch (qlm) {
				case 3:
					rbgx = 1;
				case 2:
					for (i = 0; i < 4; i++) {
						printf("Ignoring PHY for interface: %d, port: %d\n",
						       rbgx, i);
						cvmx_helper_set_port_force_link_up(rbgx, i, true);
					}
					break;
				case 6:
					start = 2;
					stop = 4;
				case 5:
					for (i = start; i < stop; i++) {
						printf("Ignoring PHY for interface: %d, port: %d\n",
						       2, i);
						cvmx_helper_set_port_force_link_up(2, i, true);
					}
					break;
				default:
					printf("SGMII not supported for QLM/DLM %d\n",
					       qlm);
					break;
				}
			}
			printf("QLM %d: SGMII%s\n",
			       qlm, rgmii ? ", RGMII" : "");
		} else if (!strncmp(mode_str, "xaui", 4)) {
			speed[qlm] = 3125;
			mode[qlm] = CVMX_QLM_MODE_XAUI;
			ref_clock_sel[qlm] = 2;
			if (qlm == 5 || qlm == 6)
				ref_clock_input[qlm] = 2; // use QLMC_REF_CLK1
			printf("QLM %d: XAUI\n", qlm);
		} else if (!strncmp(mode_str, "dxaui", 5)) {
			speed[qlm] = 6250;
			mode[qlm] = CVMX_QLM_MODE_XAUI;
			ref_clock_sel[qlm] = 2;
			if (qlm == 5 || qlm == 6)
				ref_clock_input[qlm] = 2; // use QLMC_REF_CLK1
			printf("QLM %d: DXAUI\n", qlm);
		} else if (!strncmp(mode_str, "rxaui", 5)) {
			bool rgmii = false;

			speed[qlm] = 6250;
			if (qlm == 5 || qlm == 6) {
				if (rqlm == qlm && qlm == 5) {
					mode[qlm] = CVMX_QLM_MODE_RGMII_RXAUI;
					rgmii = true;
				} else {
					mode[qlm] = CVMX_QLM_MODE_RXAUI_1X2;
				}
			} else {
				mode[qlm] = CVMX_QLM_MODE_RXAUI;
			}
			ref_clock_sel[qlm] = 2;
			if (qlm == 5 || qlm == 6)
				ref_clock_input[qlm] = 2; // use QLMC_REF_CLK1
			printf("QLM %d: RXAUI%s\n",
			       qlm, rgmii ? ", rgmii" : "");
		} else if (!strncmp(mode_str, "xlaui", 5)) {
			speed[qlm] = 103125;
			mode[qlm] = CVMX_QLM_MODE_XLAUI;
			ref_clock_sel[qlm] = 2;
			if (qlm == 5 || qlm == 6)
				ref_clock_input[qlm] = 2; // use QLMC_REF_CLK1
			sprintf(spd_env, "qlm%d_speed", qlm);
			if (env_get(spd_env)) {
				int spd = env_get_ulong(spd_env, 0, 8);

				if (spd)
					speed[qlm] = spd;
				else
					speed[qlm] = 103125;
			}
			printf("QLM %d: XLAUI\n", qlm);
		} else if (!strncmp(mode_str, "10gbase-r", 3)) {
			bool rgmii = false;

			speed[qlm] = 103125;
			if (rqlm == qlm) {
				mode[qlm] = CVMX_QLM_MODE_RGMII_XFI;
				rgmii = true;
			} else if (qlm == 5 || qlm == 6) {
				mode[qlm] = CVMX_QLM_MODE_XFI_1X2;
			} else {
				mode[qlm] = CVMX_QLM_MODE_XFI;
			}
			ref_clock_sel[qlm] = 2;
			if (qlm == 5 || qlm == 6)
				ref_clock_input[qlm] = 2; // use QLMC_REF_CLK1
			printf("QLM %d: XFI%s\n", qlm, rgmii ? ", RGMII" : "");
		} else if (!strncmp(mode_str, "10G_KR", 6)) {
			speed[qlm] = 103125;
			if (rqlm == qlm && qlm == 5)
				mode[qlm] = CVMX_QLM_MODE_RGMII_10G_KR;
			else if (qlm == 5 || qlm == 6)
				mode[qlm] = CVMX_QLM_MODE_10G_KR_1X2;
			else
				mode[qlm] = CVMX_QLM_MODE_10G_KR;
			ref_clock_sel[qlm] = 2;
			if (qlm == 5 || qlm == 6)
				ref_clock_input[qlm] = 2; // use QLMC_REF_CLK1
			printf("QLM %d: 10G_KR\n", qlm);
		} else if (!strncmp(mode_str, "40G_KR4", 7)) {
			speed[qlm] = 103125;
			mode[qlm] = CVMX_QLM_MODE_40G_KR4;
			ref_clock_sel[qlm] = 2;
			if (qlm == 5 || qlm == 6)
				ref_clock_input[qlm] = 2; // use QLMC_REF_CLK1
			printf("QLM %d: 40G_KR4\n", qlm);
		} else if (!strcmp(mode_str, "pcie")) {
			char *pmode;
			int lanes = 0;

			sprintf(env_var, "pcie%d_mode", qlm);
			pmode = env_get(env_var);
			if (pmode && !strcmp(pmode, "ep"))
				pcie_rc[qlm] = 0;
			else
				pcie_rc[qlm] = 1;
			sprintf(env_var, "pcie%d_gen", qlm);
			pcie_gen[qlm] = env_get_ulong(env_var, 0, 3);
			sprintf(env_var, "pcie%d_lanes", qlm);
			lanes = env_get_ulong(env_var, 0, 8);
			if (lanes == 8) {
				mode[qlm] = CVMX_QLM_MODE_PCIE_1X8;
			} else if (qlm == 5 || qlm == 6) {
				if (lanes != 2) {
					printf("QLM%d: Invalid lanes selected, defaulting to 2 lanes\n",
					       qlm);
				}
				mode[qlm] = CVMX_QLM_MODE_PCIE_1X2;
				ref_clock_input[qlm] = 1; // use QLMC_REF_CLK0
			} else {
				mode[qlm] = CVMX_QLM_MODE_PCIE;
			}
			ref_clock_sel[qlm] = 0;
			printf("QLM %d: PCIe gen%d %s, x%d lanes\n",
			       qlm, pcie_gen[qlm] + 1,
			       pcie_rc[qlm] ? "root complex" : "endpoint",
			       lanes);
		} else if (!strcmp(mode_str, "sata")) {
			mode[qlm] = CVMX_QLM_MODE_SATA_2X1;
			ref_clock_sel[qlm] = 0;
			ref_clock_input[qlm] = 1;
			sprintf(spd_env, "qlm%d_speed", qlm);
			if (env_get(spd_env)) {
				int spd = env_get_ulong(spd_env, 0, 8);

				if (spd == 1500 || spd == 3000 || spd == 3000)
					speed[qlm] = spd;
				else
					speed[qlm] = 6000;
			} else {
				speed[qlm] = 6000;
			}
		} else {
			printf("QLM %d: disabled\n", qlm);
		}
	}

	for (qlm = 0; qlm < 7; qlm++) {
		int rc;

		if (mode[qlm] == -1)
			continue;

		debug("Configuring qlm%d with speed(%d), mode(%d), RC(%d), Gen(%d), REF_CLK(%d), CLK_SOURCE(%d)\n",
		      qlm, speed[qlm], mode[qlm], pcie_rc[qlm],
		      pcie_gen[qlm] + 1,
		      ref_clock_sel[qlm], ref_clock_input[qlm]);
		rc = octeon_configure_qlm(qlm, speed[qlm], mode[qlm],
					  pcie_rc[qlm], pcie_gen[qlm],
					  ref_clock_sel[qlm],
					  ref_clock_input[qlm]);

		if (speed[qlm] == 6250) {
			if (mode[qlm] == CVMX_QLM_MODE_RXAUI) {
				octeon_qlm_tune_v3(0, qlm, speed[qlm], 0x12,
						   0xa0, -1, -1);
			} else {
				octeon_qlm_tune_v3(0, qlm, speed[qlm], 0xa,
						   0xa0, -1, -1);
			}
		} else if (speed[qlm] == 103125) {
			octeon_qlm_tune_v3(0, qlm, speed[qlm], 0xd, 0xd0,
					   -1, -1);
		}

		if (qlm == 4 && rc != 0)
			/*
			 * There is a bug with SATA with 73xx.  Until it's
			 * fixed we need to strip it from the device tree.
			 */
			octeon_fdt_patch_rename((void *)gd->fdt_blob, "4,none",
						NULL, true, NULL, NULL);
	}

	dm_gpio_set_value(&desc, 0); /* Put RGMII PHY in reset */
	mdelay(10);
	dm_gpio_set_value(&desc, 1); /* Take RGMII PHY out of reset */
}

int board_late_init(void)
{
	board_configure_qlms();

	return 0;
}
