/* Copyright 2017 Supermicro Inc.
 * Copyright 2018 IBM Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <skiboot.h>
#include <device.h>
#include <console.h>
#include <chip.h>
#include <ipmi.h>
#include <psi.h>
#include <npu-regs.h>
#include <opal-internal.h>
#include <cpu.h>
#include <timebase.h>

#include "astbmc.h"

static bool p9dsu_riser_found = false;

static const struct slot_table_entry p9dsu1u_phb0_0_slot[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0,0),
		.name = "UIO Slot1",
		.power_limit = 75,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu1u_phb0_1_slot[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0,0),
		.name = "UIO Slot2",
		.power_limit = 75,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu1u_phb0_2_slot[] = {
	{
		.etype = st_builtin_dev,
		.location = ST_LOC_DEVFN(0,0),
		.name = "Onboard LAN",
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu1u_phb0_3_slot[] = {
	{
		.etype = st_builtin_dev,
		.location = ST_LOC_DEVFN(0,0),
		.name = "Onboard SAS",
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu1u_phb0_4_slot[] = {
	{
		.etype = st_builtin_dev,
		.location = ST_LOC_DEVFN(0,0),
		.name = "Onboard BMC",
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu1u_phb0_5_slot[] = {
	{
		.etype = st_builtin_dev,
		.location = ST_LOC_DEVFN(0,0),
		.name = "Onboard USB",
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu1u_phb8_0_slot[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0,0),
		.name = "WIO Slot1",
		.power_limit = 75,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu1u_phb8_1_slot[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0,0),
		.name = "WIO-R Slot",
		.power_limit = 75,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu1u_phb8_2_slot[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0,0),
		.name = "WIO Slot3",
		.power_limit = 75,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu1u_phb8_3_slot[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0,0),
		.name = "WIO Slot2",
		.power_limit = 75,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu1u_phb_table[] = {
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(0,0),
		.children = p9dsu1u_phb0_0_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(0,1),
		.children = p9dsu1u_phb0_1_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(0,2),
		.children = p9dsu1u_phb0_2_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(0,3),
		.children = p9dsu1u_phb0_3_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(0,4),
		.children = p9dsu1u_phb0_4_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(0,5),
		.children = p9dsu1u_phb0_5_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(8,0),
		.children = p9dsu1u_phb8_0_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(8,1),
		.children = p9dsu1u_phb8_1_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(8,2),
		.children = p9dsu1u_phb8_2_slot,
	},
		{
		.etype = st_phb,
		.location = ST_LOC_PHB(8,3),
		.children = p9dsu1u_phb8_3_slot,
	},
	{ .etype = st_end },
};


static const struct slot_table_entry p9dsu2u_phb0_0_slot[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0,0),
		.name = "UIO Slot1",
		.power_limit = 75,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2u_phb0_1_slot[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0,0),
		.name = "UIO Slot2",
		.power_limit = 75,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2u_phb0_2_slot[] = {
	{
		.etype = st_builtin_dev,
		.location = ST_LOC_DEVFN(0,0),
		.name = "Onboard LAN",
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2u_phb0_3_slot[] = {
	{
		.etype = st_builtin_dev,
		.location = ST_LOC_DEVFN(0,0),
		.name = "Onboard SAS",
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2u_phb0_4_slot[] = {
	{
		.etype = st_builtin_dev,
		.location = ST_LOC_DEVFN(0,0),
		.name = "Onboard BMC",
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2u_phb0_5_slot[] = {
	{
		.etype = st_builtin_dev,
		.location = ST_LOC_DEVFN(0,0),
		.name = "Onboard USB",
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2u_phb8_0_slot[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0,0),
		.name = "WIO Slot1",
		.power_limit = 75,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2u_phb8_1_slot[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0,0),
		.name = "WIO-R Slot",
		.power_limit = 75,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2u_phb8_2_slot[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0,0),
		.name = "WIO Slot3",
		.power_limit = 75,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2u_phb8_3_slot[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0,0),
		.name = "WIO Slot3",
		.power_limit = 75,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2u_phb8_4_slot[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0,0),
		.name = "WIO Slot2",
		.power_limit = 75,
	},
	{ .etype = st_end },
};


static const struct slot_table_entry p9dsu2u_phb_table[] = {
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(0,0),
		.children = p9dsu2u_phb0_0_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(0,1),
		.children = p9dsu2u_phb0_1_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(0,2),
		.children = p9dsu2u_phb0_2_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(0,3),
		.children = p9dsu2u_phb0_3_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(0,4),
		.children = p9dsu2u_phb0_4_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(0,5),
		.children = p9dsu2u_phb0_5_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(8,0),
		.children = p9dsu2u_phb8_0_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(8,1),
		.children = p9dsu2u_phb8_1_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(8,2),
		.children = p9dsu2u_phb8_2_slot,
	},
		{
		.etype = st_phb,
		.location = ST_LOC_PHB(8,3),
		.children = p9dsu2u_phb8_3_slot,
	},
			{
		.etype = st_phb,
		.location = ST_LOC_PHB(8,4),
		.children = p9dsu2u_phb8_4_slot,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2uess_uio_plx_down[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0x1,0),
		.name = "UIO Slot2",
		.power_limit = 75,
	},
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0x8,0),
		.name = "PLX switch",
		.power_limit = 75,
	},
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0x9,0),
		.name = "Onboard LAN",
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2uess_uio_plx_up[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0,0),
		.children = p9dsu2uess_uio_plx_down,
		.name = "PLX up",
		.power_limit = 75,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2uess_wio_plx_down[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0x1,0),
		.name = "WIO Slot1",
		.power_limit = 75,
	},
    	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0x8,0),
		.name = "PLX switch",
		.power_limit = 75,
	},
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0x9,0),
		.name = "WIO Slot2",
		.power_limit = 75,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2uess_wio_plx_up[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0,0),
		.children = p9dsu2uess_wio_plx_down,
		.name = "PLX up",
		.power_limit = 75,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2uess_phb0_0_slot[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0,0),
		.name = "UIO Slot1",
		.power_limit = 75,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2uess_phb0_1_slot[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0,0),
		.children = p9dsu2uess_uio_plx_up,
		.name = "PLX",
		.power_limit = 75,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2uess_phb0_2_slot[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0,0),
		.name = "UIO Slot3",
		.power_limit = 75,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2uess_phb0_3_slot[] = {
	{
		.etype = st_builtin_dev,
		.location = ST_LOC_DEVFN(0,0),
		.name = "Onboard SAS",
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2uess_phb0_4_slot[] = {
	{
		.etype = st_builtin_dev,
		.location = ST_LOC_DEVFN(0,0),
		.name = "Onboard BMC",
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2uess_phb0_5_slot[] = {
	{
		.etype = st_builtin_dev,
		.location = ST_LOC_DEVFN(0,0),
		.name = "Onboard USB",
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2uess_phb8_0_slot[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0,0),
		.name = "WIO Slot3",
		.power_limit = 75,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2uess_phb8_1_slot[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0,0),
		.name = "WIO-R Slot",
		.power_limit = 75,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2uess_phb8_2_slot[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0,0),
		.children = p9dsu2uess_wio_plx_up,
		.name = "PLX",
		.power_limit = 75,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2uess_phb8_3_slot[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0,0),
		.name = "WIO Slot4",
		.power_limit = 75,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2uess_phb8_4_slot[] = {
	{
		.etype = st_pluggable_slot,
		.location = ST_LOC_DEVFN(0,0),
		.name = "WIO Slot5",
		.power_limit = 75,
	},
	{ .etype = st_end },
};

static const struct slot_table_entry p9dsu2uess_phb_table[] = {
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(0,0),
		.children = p9dsu2uess_phb0_0_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(0,1),
		.children = p9dsu2uess_phb0_1_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(0,2),
		.children = p9dsu2uess_phb0_2_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(0,3),
		.children = p9dsu2uess_phb0_3_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(0,4),
		.children = p9dsu2uess_phb0_4_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(0,5),
		.children = p9dsu2uess_phb0_5_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(8,0),
		.children = p9dsu2uess_phb8_0_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(8,1),
		.children = p9dsu2uess_phb8_1_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(8,2),
		.children = p9dsu2uess_phb8_2_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(8,3),
		.children = p9dsu2uess_phb8_3_slot,
	},
	{
		.etype = st_phb,
		.location = ST_LOC_PHB(8,4),
		.children = p9dsu2uess_phb8_4_slot,
	},
	{ .etype = st_end },
};


/*
 * HACK: Hostboot doesn't export the correct data for the system VPD EEPROM
 *       for this system. So we need to work around it here.
 */
static void p9dsu_dt_fixups(void)
{
	struct dt_node *n = dt_find_by_path(dt_root,
		"/xscom@603fc00000000/i2cm@a2000/i2c-bus@0/eeprom@50");

	if (n) {
		dt_check_del_prop(n, "compatible");
		dt_add_property_string(n, "compatible", "atmel,24c256");

		dt_check_del_prop(n, "label");
		dt_add_property_string(n, "label", "system-vpd");
	}
}

static bool p9dsu_probe(void)
{
	if (!(dt_node_is_compatible(dt_root, "supermicro,p9dsu") ||
	      dt_node_is_compatible(dt_root, "supermicro,p9dsu1u") ||
	      dt_node_is_compatible(dt_root, "supermicro,p9dsu2u") ||
	      dt_node_is_compatible(dt_root, "supermicro,p9dsu2uess")))
		return false;

	p9dsu_riser_found = true;

	/* Lot of common early inits here */
	astbmc_early_init();

	/* Setup UART for use by OPAL (Linux hvc) */
	uart_set_console_policy(UART_CONSOLE_OPAL);

	p9dsu_dt_fixups();

	if (dt_node_is_compatible(dt_root, "supermicro,p9dsu1u")) {
		prlog(PR_INFO, "Detected p9dsu1u variant\n");
		slot_table_init(p9dsu1u_phb_table);
	} else if (dt_node_is_compatible(dt_root, "supermicro,p9dsu2u")) {
		prlog(PR_INFO, "Detected p9dsu2u variant\n");
		slot_table_init(p9dsu2u_phb_table);
	} else if (dt_node_is_compatible(dt_root, "supermicro,p9dsu2uess")) {
		prlog(PR_INFO, "Detected p9dsu2uess variant\n");
		slot_table_init(p9dsu2uess_phb_table);
	} else {
	/*
	 * else we need to ask the BMC what subtype we are, but we need IPMI
	 * which we don't get until astbmc_init(), so we delay setting up the
	 * slot table until later.
	 *
	 * This only applies if you're using a Hostboot that doesn't do this
	 * for us.
	 */
	p9dsu_riser_found = false;
	}

	return true;
}

static void p9dsu_riser_query_complete(struct ipmi_msg *m)
{
	u8 *riser_id = (u8*)m->user_data;
	lwsync();
	*riser_id = m->data[0];
	ipmi_free_msg(m);
}

static void p9dsu_init(void)
{
	u8 smc_riser_req[] = {0x03, 0x70, 0x01, 0x02};
	struct ipmi_msg *ipmi_msg;
	u8 riser_id = 0;
	const char *p9dsu_variant;
	int timeout_ms = 3000;

	astbmc_init();
	/*
	 * Now we have IPMI up and running we can ask the BMC for what p9dsu
	 * variant we are if Hostboot isn't the patched one that does this
	 * for us.
	 */
	if (!p9dsu_riser_found) {
		ipmi_msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE,
				      IPMI_CODE(IPMI_NETFN_APP, 0x52),
				      p9dsu_riser_query_complete,
				      &riser_id,
				      smc_riser_req, sizeof(smc_riser_req), 1);
		ipmi_queue_msg(ipmi_msg);
		while(riser_id==0 && timeout_ms > 0) {
			time_wait_ms(10);
			timeout_ms -= 10;
		}
		switch (riser_id) {
		case 0x9:
			p9dsu_variant = "supermicro,p9dsu1u";
			slot_table_init(p9dsu1u_phb_table);
			break;
		case 0x19:
			p9dsu_variant = "supermicro,p9dsu2u";
			slot_table_init(p9dsu2u_phb_table);
			break;
		case 0x1D:
			p9dsu_variant = "supermicro,p9dsu2uess";
			slot_table_init(p9dsu2uess_phb_table);
			break;
		default:
			prlog(PR_ERR, "Defaulting to p9dsu2uess\n");
			p9dsu_variant = "supermicro,p9dsu2uess";
			slot_table_init(p9dsu2uess_phb_table);
			break;
		}
		prlog(PR_INFO,"Detected %s variant via IPMI\n", p9dsu_variant);
		dt_check_del_prop(dt_root, "compatible");
		dt_add_property_strings(dt_root, "compatible", "ibm,powernv",
					"supermicro,p9dsu", p9dsu_variant);
	}
}

static const struct bmc_sw_config bmc_sw_smc = {
	.ipmi_oem_partial_add_esel   = IPMI_CODE(0x3a, 0xf0),
	.ipmi_oem_hiomap_cmd         = IPMI_CODE(0x3a, 0x5a),
};

/* Provided by Eric Chen (SMC) */
const struct bmc_hw_config p9dsu_bmc_hw = {
	.scu_revision_id = 0x04030303,
	.mcr_configuration = 0x11000756,
	.mcr_scu_mpll = 0x000071c1,
	.mcr_scu_strap = 0x00000000,
};

static const struct bmc_platform bmc_plat_ast2500_smc = {
	.name = "SMC",
	.hw = &p9dsu_bmc_hw,
	.sw = &bmc_sw_smc,
};

DECLARE_PLATFORM(p9dsu1u) = {
	.name			= "p9dsu",
	.probe			= p9dsu_probe,
	.init			= p9dsu_init,
	.start_preload_resource	= flash_start_preload_resource,
	.resource_loaded	= flash_resource_loaded,
	.bmc			= &bmc_plat_ast2500_smc,
	.pci_get_slot_info	= slot_table_get_slot_info,
	.cec_power_down         = astbmc_ipmi_power_down,
	.cec_reboot             = astbmc_ipmi_reboot,
	.elog_commit		= ipmi_elog_commit,
	.exit			= ipmi_wdt_final_reset,
	.terminate		= ipmi_terminate,
};
