/* Copyright 2013-2014 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 "spira.h"
#include <cpu.h>
#include <fsp.h>
#include <opal.h>
#include <ccan/str/str.h>
#include <ccan/array_size/array_size.h>
#include <device.h>
#include <p7ioc.h>
#include <vpd.h>
#include <inttypes.h>
#include <string.h>

#include "hdata.h"

static void io_add_common(struct dt_node *hn, const struct cechub_io_hub *hub)
{
	dt_add_property_cells(hn, "#address-cells", 2);
	dt_add_property_cells(hn, "#size-cells", 2);
	dt_add_property_cells(hn, "ibm,buid-ext", be32_to_cpu(hub->buid_ext));
	dt_add_property_cells(hn, "ibm,chip-id",
			      pcid_to_chip_id(be32_to_cpu(hub->proc_chip_id)));
	dt_add_property_cells(hn, "ibm,gx-index", be32_to_cpu(hub->gx_index));
	dt_add_property_cells(hn, "revision", be32_to_cpu(hub->ec_level));

	/* Instead of exposing the GX BARs as separate ranges as we *should*
	 * do in an ideal world, we just create a pass-through ranges and
	 * we use separate properties for the BARs.
	 *
	 * This is hackish but will do for now and avoids us having to
	 * do too complex ranges property parsing
	 */
	dt_add_property(hn, "ranges", NULL, 0);
	dt_add_property_u64(hn, "ibm,gx-bar-1", be64_to_cpu(hub->gx_ctrl_bar1));
	dt_add_property_u64(hn, "ibm,gx-bar-2", be64_to_cpu(hub->gx_ctrl_bar2));

	/* Add presence detect if valid */
	if (hub->flags & CECHUB_HUB_FLAG_FAB_BR0_PDT)
		dt_add_property_cells(hn, "ibm,br0-presence-detect",
				      hub->fab_br0_pdt);
	if (hub->flags & CECHUB_HUB_FLAG_FAB_BR1_PDT)
		dt_add_property_cells(hn, "ibm,br1-presence-detect",
				      hub->fab_br1_pdt);
}

static bool io_get_lx_info(const void *kwvpd, unsigned int kwvpd_sz,
			   int lx_idx, struct dt_node *hn)
{
	const void *lxr;
	char recname[5];
	uint32_t lxrbuf[2] = { 0, 0 };

	/* Find LXRn, where n is the index passed in*/
	strcpy(recname, "LXR0");
	recname[3] += lx_idx;
	lxr = vpd_find(kwvpd, kwvpd_sz, recname, "LX", NULL);
	if (!lxr) {
		/* Not found, try VINI */
		lxr = vpd_find(kwvpd, kwvpd_sz, "VINI",
			       "LX",  NULL);
		if (lxr)
			lx_idx = VPD_LOAD_LXRN_VINI;
	}
	if (!lxr) {
		prlog(PR_DEBUG, "CEC:     LXR%x not found !\n", lx_idx);
		return false;
	}

	memcpy(lxrbuf, lxr, sizeof(uint32_t)*2);

	prlog(PR_DEBUG, "CEC:     LXRn=%d LXR=%08x%08x\n", lx_idx, lxrbuf[0], lxrbuf[1]);
	prlog(PR_DEBUG, "CEC:     LX Info added to %llx\n", (long long)hn);

	/* Add the LX info */
	if (!dt_has_node_property(hn, "ibm,vpd-lx-info", NULL)) {
		dt_add_property_cells(hn, "ibm,vpd-lx-info",
				      lx_idx,
				      lxrbuf[0],
				      lxrbuf[1]);
	}

	return true;
}


static void io_get_loc_code(const void *sp_iohubs, struct dt_node *hn, const char *prop_name)
{
	const struct spira_fru_id *fru_id;
	unsigned int fru_id_sz;
	char loc_code[LOC_CODE_SIZE + 1];
	const char *slca_loc_code;

	/* Find SLCA Index */
	fru_id = HDIF_get_idata(sp_iohubs, CECHUB_FRU_ID_DATA, &fru_id_sz);
	if (fru_id) {
		memset(loc_code, 0, sizeof(loc_code));

		/* Find LOC Code from SLCA Index */
		slca_loc_code = slca_get_loc_code_index(be16_to_cpu(fru_id->slca_index));
		if (slca_loc_code) {
			strncpy(loc_code, slca_loc_code, LOC_CODE_SIZE);
			if (!dt_has_node_property(hn, prop_name, NULL)) {
				dt_add_property(hn, prop_name, loc_code,
						strlen(loc_code) + 1);
			}
			prlog(PR_DEBUG, "CEC:     %s: %s (SLCA rsrc 0x%x)\n",
			      prop_name, loc_code,
			      be16_to_cpu(fru_id->rsrc_id));
		} else {
			prlog(PR_DEBUG, "CEC:     SLCA Loc not found: "
			      "index %d\n", fru_id->slca_index);
		}
	} else {
		prlog(PR_DEBUG, "CEC:     Hub FRU ID not found...\n");
	}
}

static struct dt_node *io_add_p7ioc(const struct cechub_io_hub *hub,
				    const void *sp_iohubs)
{
	struct dt_node *hn;
	uint64_t reg[2];

	const void *kwvpd;
	unsigned int kwvpd_sz;

	prlog(PR_DEBUG, "    GX#%d BUID_Ext = 0x%x\n",
	      be32_to_cpu(hub->gx_index),
	      be32_to_cpu(hub->buid_ext));
	prlog(PR_DEBUG, "    GX BAR 0 = 0x%016"PRIx64"\n",
	      be64_to_cpu(hub->gx_ctrl_bar0));
	prlog(PR_DEBUG, "    GX BAR 1 = 0x%016"PRIx64"\n",
	      be64_to_cpu(hub->gx_ctrl_bar1));
	prlog(PR_DEBUG, "    GX BAR 2 = 0x%016"PRIx64"\n",
	      be64_to_cpu(hub->gx_ctrl_bar2));
	prlog(PR_DEBUG, "    GX BAR 3 = 0x%016"PRIx64"\n",
	      be64_to_cpu(hub->gx_ctrl_bar3));
	prlog(PR_DEBUG, "    GX BAR 4 = 0x%016"PRIx64"\n",
	      be64_to_cpu(hub->gx_ctrl_bar4));

	/* We only know about memory map 1 */
	if (be32_to_cpu(hub->mem_map_vers) != 1) {
		prerror("P7IOC: Unknown memory map %d\n", be32_to_cpu(hub->mem_map_vers));
		/* We try to continue anyway ... */
	}

	reg[0] = cleanup_addr(be64_to_cpu(hub->gx_ctrl_bar1));
	reg[1] = 0x2000000;

	hn = dt_new_addr(dt_root, "io-hub", reg[0]);
	if (!hn)
		return NULL;

	dt_add_property(hn, "reg", reg, sizeof(reg));
	dt_add_property_strings(hn, "compatible", "ibm,p7ioc", "ibm,ioda-hub");

	kwvpd = HDIF_get_idata(sp_iohubs, CECHUB_ASCII_KEYWORD_VPD, &kwvpd_sz);
	if (kwvpd && kwvpd != sp_iohubs) {
		/*
		 * XX We don't know how to properly find the LXRn
		 * record so for now we'll just try LXR0 and if not
		 * found, we try LXR1
		 */
		if (!io_get_lx_info(kwvpd, kwvpd_sz, 0, hn))
			io_get_lx_info(kwvpd, kwvpd_sz, 1, hn);
	} else {
		prlog(PR_DEBUG, "CEC:     P7IOC Keywords not found.\n");
	}

	io_get_loc_code(sp_iohubs, hn, "ibm,io-base-loc-code");

	return hn;
}

static struct dt_node *io_add_phb3(const struct cechub_io_hub *hub,
				   const struct HDIF_common_hdr *sp_iohubs,
				   unsigned int index, struct dt_node *xcom,
				   unsigned int pe_xscom,
				   unsigned int pci_xscom,
				   unsigned int spci_xscom)
{
	struct dt_node *pbcq;
	uint32_t reg[6];
	unsigned int hdif_vers;

	/* Get HDIF version */
	hdif_vers = be16_to_cpu(sp_iohubs->version);

	/* Create PBCQ node under xscom */
	pbcq = dt_new_addr(xcom, "pbcq", pe_xscom);
	if (!pbcq)
		return NULL;

	/* "reg" property contains in order the PE, PCI and SPCI XSCOM
	 * addresses
	 */
	reg[0] = pe_xscom;
	reg[1] = 0x20;
	reg[2] = pci_xscom;
	reg[3] = 0x05;
	reg[4] = spci_xscom;
	reg[5] = 0x15;
	dt_add_property(pbcq, "reg", reg, sizeof(reg));

	/* A couple more things ... */
	dt_add_property_strings(pbcq, "compatible", "ibm,power8-pbcq");
	dt_add_property_cells(pbcq, "ibm,phb-index", index);
	dt_add_property_cells(pbcq, "ibm,hub-id", be16_to_cpu(hub->hub_num));

	/* The loc code of the PHB itself is different from the base
	 * loc code of the slots (It's actually the DCM's loc code).
	 */
	io_get_loc_code(sp_iohubs, pbcq, "ibm,loc-code");

	/* We indicate that this is an IBM setup, which means that
	 * the presence detect A/B bits are meaningful. So far we
	 * don't know whether they make any sense on customer setups
	 * so we only set that when booting with HDAT
	 */
	dt_add_property(pbcq, "ibm,use-ab-detect", NULL, 0);

	/* HDAT spec has these in version 0x6A and later */
	if (hdif_vers >= 0x6a) {
		u64 eq0 = be64_to_cpu(hub->phb_lane_eq[index][0]);
		u64 eq1 = be64_to_cpu(hub->phb_lane_eq[index][1]);
		u64 eq2 = be64_to_cpu(hub->phb_lane_eq[index][2]);
		u64 eq3 = be64_to_cpu(hub->phb_lane_eq[index][3]);

		dt_add_property_u64s(pbcq, "ibm,lane-eq", eq0, eq1, eq2, eq3);
	}

	/* Currently we only create a PBCQ node, the actual PHB nodes
	 * will be added by sapphire later on.
	 */
	return pbcq;
}

static struct dt_node *add_pec_stack(const struct cechub_io_hub *hub,
				     struct dt_node *pbcq, int stack_index,
				     int phb_index, u8 active_phbs)
{
	struct dt_node *stack;
	u64 eq[8];
	u8 *gen4;
	int i;

	stack = dt_new_addr(pbcq, "stack", stack_index);
	assert(stack);

	dt_add_property_cells(stack, "reg", stack_index);
	dt_add_property_cells(stack, "ibm,phb-index", phb_index);
	dt_add_property_string(stack, "compatible", "ibm,power9-phb-stack");

	/* XXX: This should probably just return if the PHB is disabled
	 *      rather than adding the extra properties.
	 */

	if (active_phbs & (0x80 >> phb_index))
		dt_add_property_string(stack, "status", "okay");
	else
		dt_add_property_string(stack, "status", "disabled");

	for (i = 0; i < 4; i++) /* gen 3 eq settings */
		eq[i] = be64_to_cpu(hub->phb_lane_eq[phb_index][i]);
	for (i = 0; i < 4; i++) /* gen 4 eq settings */
		eq[i+4] = be64_to_cpu(hub->phb4_lane_eq[phb_index][i]);

	/* Lane-eq settings are packed 2 bytes per lane for 16 lanes
	 * On P9 DD2, 1 byte  per lane is  used in the hardware
	 */

	/* Repack 2 byte lane settings into 1 byte */
	gen4 = (u8 *)&eq[4];
	for (i = 0; i < 16; i++)
		gen4[i] = gen4[2*i];

	dt_add_property_u64s(stack, "ibm,lane-eq", eq[0], eq[1],
			     eq[2], eq[3], eq[4], eq[5]);
	return stack;
}

static struct dt_node *io_add_phb4(const struct cechub_io_hub *hub,
				   const struct HDIF_common_hdr *sp_iohubs,
				   struct dt_node *xcom,
				   unsigned int pec_index,
				   int stacks,
				   int phb_base)
{
	struct dt_node *pbcq;
	uint32_t reg[4];
	uint8_t active_phb_mask = hub->fab_br0_pdt;
	uint32_t pe_xscom  = 0x4010c00 + (pec_index * 0x0000400);
	uint32_t pci_xscom = 0xd010800 + (pec_index * 0x1000000);
	int i;

	/* Create PBCQ node under xscom */
	pbcq = dt_new_addr(xcom, "pbcq", pe_xscom);
	if (!pbcq)
		return NULL;

	/* "reg" property contains (in order) the PE and PCI XSCOM addresses */
	reg[0] = pe_xscom;
	reg[1] = 0x100;
	reg[2] = pci_xscom;
	reg[3] = 0x200;
	dt_add_property(pbcq, "reg", reg, sizeof(reg));

	/* The hubs themselves go under the stacks */
	dt_add_property_strings(pbcq, "compatible", "ibm,power9-pbcq");
	dt_add_property_cells(pbcq, "ibm,pec-index", pec_index);
	dt_add_property_cells(pbcq, "#address-cells", 1);
	dt_add_property_cells(pbcq, "#size-cells", 0);

	for (i = 0; i < stacks; i++)
		add_pec_stack(hub, pbcq, i, phb_base + i, active_phb_mask);

	dt_add_property_cells(pbcq, "ibm,hub-id", be16_to_cpu(hub->hub_num));

	/* The loc code of the PHB itself is different from the base
	 * loc code of the slots (It's actually the DCM's loc code).
	 */
	io_get_loc_code(sp_iohubs, pbcq, "ibm,loc-code");

	prlog(PR_INFO, "CEC: Added PHB4 PBCQ %d with %d stacks\n",
		pec_index, stacks);

	/* the actual PHB nodes created later on by skiboot */
	return pbcq;
}

static struct dt_node *io_add_p8(const struct cechub_io_hub *hub,
				 const struct HDIF_common_hdr *sp_iohubs)
{
	struct dt_node *xscom;
	unsigned int i, chip_id;

	chip_id = pcid_to_chip_id(be32_to_cpu(hub->proc_chip_id));

	prlog(PR_INFO, "CEC:     HW CHIP=0x%x, HW TOPO=0x%04x\n", chip_id,
	      be16_to_cpu(hub->hw_topology));

	xscom = find_xscom_for_chip(chip_id);
	if (!xscom) {
		prerror("P8: Can't find XSCOM for chip %d\n", chip_id);
		return NULL;
	}

	/* Create PHBs, max 3 */
	for (i = 0; i < 3; i++) {
		if (hub->fab_br0_pdt & (0x80 >> i))
			/* XSCOM addresses are the same on Murano and Venice */
			io_add_phb3(hub, sp_iohubs, i, xscom,
				    0x02012000 + (i * 0x400),
				    0x09012000 + (i * 0x400),
				    0x09013c00 + (i * 0x40));
	}

	/* HACK: We return the XSCOM device for the VPD info */
	return xscom;
}

static struct dt_node *io_add_p9(const struct cechub_io_hub *hub,
				 const struct HDIF_common_hdr *sp_iohubs)
{
	struct dt_node *xscom;
	unsigned int chip_id;

	chip_id = pcid_to_chip_id(be32_to_cpu(hub->proc_chip_id));

	prlog(PR_INFO, "CEC:     HW CHIP=0x%x, HW TOPO=0x%04x\n", chip_id,
	      be16_to_cpu(hub->hw_topology));

	xscom = find_xscom_for_chip(chip_id);
	if (!xscom) {
		prerror("P9: Can't find XSCOM for chip %d\n", chip_id);
		return NULL;
	}

	prlog(PR_DEBUG, "IOHUB: PHB4 active bridge mask %x\n",
		(u32) hub->fab_br0_pdt);

	/* Create PBCQs */
	io_add_phb4(hub, sp_iohubs, xscom, 0, 1, 0);
	io_add_phb4(hub, sp_iohubs, xscom, 1, 2, 1);
	io_add_phb4(hub, sp_iohubs, xscom, 2, 3, 3);

	return xscom;
}


static void io_add_p8_cec_vpd(const struct HDIF_common_hdr *sp_iohubs)
{
	const struct HDIF_child_ptr *iokids;
	const void *iokid;	
	const void *kwvpd;
	unsigned int kwvpd_sz;

	/* P8 LXR0 kept in IO KID Keyword VPD */
	iokids = HDIF_child_arr(sp_iohubs, CECHUB_CHILD_IO_KIDS);
	if (!CHECK_SPPTR(iokids)) {
		prlog(PR_WARNING, "CEC:     No IOKID child array !\n");
		return;
	}
	if (!iokids->count) {
		prlog(PR_WARNING, "CEC:     IOKID count is 0 !\n");
		return;
	}
	if (be32_to_cpu(iokids->count) > 1) {
		prlog(PR_WARNING, "CEC:     WARNING ! More than 1 IO KID !!! (%d)\n",
		      iokids->count);
		/* Ignoring the additional ones */
	}

	iokid = HDIF_child(sp_iohubs, iokids, 0, "IO KID");
	if (!iokid) {
		prlog(PR_WARNING, "CEC:     No IO KID structure in child array !\n");
		return;
	}

	/* Grab base location code for slots */
	io_get_loc_code(iokid, dt_root, "ibm,io-base-loc-code");

	kwvpd = HDIF_get_idata(iokid, CECHUB_ASCII_KEYWORD_VPD, &kwvpd_sz);
	if (!kwvpd) {
		prlog(PR_WARNING, "CEC:     No VPD entry in IO KID !\n");
		return;
	}

	/* Grab LX load info */
	io_get_lx_info(kwvpd, kwvpd_sz, 0, dt_root);
}

/*
 * Assumptions:
 *
 * a) the IOSLOT index is the hub ID -CHECK
 *
 */

static struct dt_node *dt_slots;

static void add_i2c_link(struct dt_node *node, const char *link_name,
			u32 i2c_link)
{
	/* FIXME: Do something not shit */
	dt_add_property_cells(node, link_name, i2c_link);
}

/*
 * the root of the slots node has #address-cells = 2, <hub-index, phb-index>
 * can we ditch hub-index?
 */


static const struct slot_map_details *find_slot_details(
		const struct HDIF_common_hdr *ioslot, int entry)
{
	const struct slot_map_details *details = NULL;
	const struct HDIF_array_hdr *arr;
	unsigned int i;

	arr = HDIF_get_iarray(ioslot, IOSLOT_IDATA_DETAILS, NULL);
	HDIF_iarray_for_each(arr, i, details)
		if (be16_to_cpu(details->entry) == entry)
			break;

	return details;
}

static void parse_slot_details(struct dt_node *slot,
		const struct slot_map_details *details)
{
	u32 slot_caps;

	/*
	 * generic slot options
	 */

	dt_add_property_cells(slot, "max-power",
		be16_to_cpu(details->max_power));

	if (details->perst_ctl_type == SLOT_PERST_PHB_OR_SW)
		dt_add_property(slot, "pci-perst", NULL, 0);
	else if (details->perst_ctl_type == SLOT_PERST_SW_GPIO)
		dt_add_property_cells(slot, "gpio-perst", details->perst_gpio);

	if (details->presence_det_type == SLOT_PRESENCE_PCI)
		dt_add_property(slot, "pci-presence-detect", NULL, 0);

	/*
	 * specific slot capabilities
	 */
	slot_caps = be32_to_cpu(details->slot_caps);

	if (slot_caps & SLOT_CAP_LSI)
		dt_add_property(slot, "lsi", NULL, 0);

	if (slot_caps & SLOT_CAP_CAPI) {
		/* XXX: should we be more specific here?
		 *
		 * Also we should double check that this slot
		 * is a root connected slot.
		 */
		dt_add_property(slot, "capi", NULL, 0);
	}

	if (slot_caps & SLOT_CAP_CCARD) {
		dt_add_property(slot, "cable-card", NULL, 0);

		if (details->presence_det_type == SLOT_PRESENCE_I2C)
			add_i2c_link(slot, "i2c-presence-detect",
				be32_to_cpu(details->i2c_cable_card));
	}

	if (slot_caps & SLOT_CAP_HOTPLUG) {
		dt_add_property(slot, "hotplug", NULL, 0);

		/*
		 * Power control should only exist when the slot is hotplug
		 * capable
		 */
		if (details->power_ctrl_type == SLOT_PWR_I2C)
			add_i2c_link(slot, "i2c-power-ctrl",
				be32_to_cpu(details->i2c_power_ctl));
	}

	/*
	 * NB: Additional NVLink specific info is added to this node
	 *     when the SMP Link structures are parsed later on.
	 */
	if (slot_caps & SLOT_CAP_NVLINK)
		dt_add_property(slot, "nvlink", NULL, 0);
}

struct dt_node *find_slot_entry_node(struct dt_node *root, u32 entry_id)
{
	struct dt_node *node;

	for (node = dt_first(root); node; node = dt_next(root, node)) {
		if (!dt_has_node_property(node, DT_PRIVATE "entry_id", NULL))
			continue;

		if (dt_prop_get_u32(node, DT_PRIVATE "entry_id") == entry_id)
			return node;
	}

	return NULL;
}

/*
 * The internal HDAT representation of the various types of slot is kinda
 * dumb, translate it into something more sensible
 */
enum slot_types {
	st_root,
	st_slot,
	st_rc_slot,
	st_sw_upstream,
	st_sw_downstream,
	st_builtin
};

static const char *st_name(enum slot_types type)
{
	switch(type) {
	case st_root:		return "root-complex";
	case st_slot:		return "pluggable";
	case st_rc_slot:	return "pluggable"; /* differentiate? */
	case st_sw_upstream:	return "switch-up";
	case st_sw_downstream:	return "switch-down";
	case st_builtin:	return "builtin";
	}

	return "(none)";
}

static enum slot_types xlate_type(uint8_t type, u32 features)
{
	bool is_slot = features & SLOT_FEAT_SLOT;

	switch (type) {
	case SLOT_TYPE_ROOT_COMPLEX:
		return is_slot ? st_rc_slot : st_root;
	case SLOT_TYPE_BUILTIN:
		return st_builtin;
	case SLOT_TYPE_SWITCH_UP:
		return st_sw_upstream;
	case SLOT_TYPE_SWITCH_DOWN:
		return is_slot ? st_slot : st_sw_downstream;
	}

	return -1; /* shouldn't happen */
}

static bool is_port(struct dt_node *n)
{
	//return dt_node_is_compatible(n, "compatible", "ibm,pcie-port");
	return dt_node_is_compatible(n, "ibm,pcie-port");
}

/* this only works inside parse_one_ioslot() */
#define SM_LOG(level, fmt, ...) \
	prlog(level, "SLOTMAP: %x:%d:%d " \
		fmt, /* user input */ \
		chip_id, entry->phb_index, eid, \
		##__VA_ARGS__ /* user args */)

#define SM_ERR(fmt, ...) SM_LOG(PR_ERR, fmt, ##__VA_ARGS__)
#define SM_DBG(fmt, ...) SM_LOG(PR_DEBUG, fmt, ##__VA_ARGS__)

static void parse_one_slot(const struct slot_map_entry *entry,
		const struct slot_map_details *details, int chip_id)
{
	struct dt_node *node, *parent = NULL;
	u16 eid, pid, vid, did;
	u32 flags;
	int type;

	flags = be32_to_cpu(entry->features);
	type = xlate_type(entry->type, flags);

	eid = be16_to_cpu(entry->entry_id);
	pid = be16_to_cpu(entry->parent_id);

	SM_DBG("%s - eid = %d, pid = %d, name = %8s\n",
		st_name(type), eid, pid,
		strnlen(entry->name, 8) ? entry->name : "");

	/* empty slot, ignore it */
	if (eid == 0x0 && pid == 0x0)
		return;

	if (type != st_root && type != st_rc_slot) {
		parent = find_slot_entry_node(dt_slots, pid);
		if (!parent) {
			SM_ERR("Unable to find node for parent slot (id = %d)\n",
				pid);
			return;
		}
	}

	switch (type) {
	case st_root:
	case st_rc_slot:
		node = dt_new_2addr(dt_slots, "root-complex",
						chip_id, entry->phb_index);
		if (!node) {
			SM_ERR("Couldn't add DT node\n");
			return;
		}
		dt_add_property_cells(node, "reg", chip_id, entry->phb_index);
		dt_add_property_cells(node, "#address-cells", 2);
		dt_add_property_cells(node, "#size-cells", 0);
		dt_add_property_strings(node, "compatible",
				"ibm,pcie-port", "ibm,pcie-root-port");
		dt_add_property_cells(node, "ibm,chip-id", chip_id);
		parent = node;

		/*
		 * The representation of slots attached directly to the
		 * root complex is a bit wierd. If this is just a root
		 * complex then stop here, otherwise fall through to create
		 * the slot node.
		 */
		if (type == st_root)
			break;

		/* fallthrough*/
	case st_sw_upstream:
	case st_builtin:
	case st_slot:
		if (!is_port(parent)) {
			SM_ERR("%s connected to %s (%d), should be %s or %s!\n",
				st_name(type), parent->name, pid,
				st_name(st_root), st_name(st_sw_downstream));
			return;
		}

		vid = (be32_to_cpu(entry->vendor_id) & 0xffff);
		did = (be32_to_cpu(entry->device_id) & 0xffff);

		prlog(PR_DEBUG, "Found %s slot with %x:%x\n",
			st_name(type), vid, did);

		/* The VID:DID is only meaningful for builtins and switches */
		if (type == st_sw_upstream && vid && did) {
			node = dt_new_2addr(parent, st_name(type), vid, did);
			dt_add_property_cells(node, "reg", vid, did);
		} else {
			/*
			 * If we get no vdid then create a "wildcard" slot
			 * that matches any device
			 */
			node = dt_new(parent, st_name(type));
		}

		if (type == st_sw_upstream) {
			dt_add_property_cells(node, "#address-cells", 1);
			dt_add_property_cells(node, "#size-cells", 0);
			dt_add_property_cells(node, "upstream-port",
					entry->up_port);
		}
		break;

	case st_sw_downstream: /* slot connected to switch output */
		node = dt_new_addr(parent, "down-port", entry->down_port);
		dt_add_property_strings(node, "compatible",
				"ibm,pcie-port");
		dt_add_property_cells(node, "reg", entry->down_port);

		break;

	default:
		SM_ERR("Unknown slot map type %x\n", entry->type);
		return;
	}

	/*
	 * Now add any generic slot map properties.
	 */

	/* private since we don't want hdat stuff leaking */
	dt_add_property_cells(node, DT_PRIVATE "entry_id", eid);

	if (entry->mrw_slot_id)
		dt_add_property_cells(node, "mrw-slot-id",
				be16_to_cpu(entry->mrw_slot_id));

	if (entry->lane_mask)
		dt_add_property_cells(node, "lane-mask",
				be16_to_cpu(entry->lane_mask));

	/* what is the difference between this and the lane reverse? */
	if (entry->lane_reverse)
		dt_add_property_cells(node, "lanes-reversed",
				be16_to_cpu(entry->lane_reverse));

	if (strnlen(entry->name, sizeof(entry->name))) {
		/*
		 * HACK: On some platforms (witherspoon) the slot label is
		 * applied to the device rather than the pcie downstream port
		 * that has the slot under it. Hack around this by moving the
		 * slot label up if the parent port doesn't have one.
		 */
		if (dt_node_is_compatible(node->parent, "ibm,pcie-port") &&
		    !dt_find_property(node->parent, "ibm,slot-label")) {
			dt_add_property_nstr(node->parent, "ibm,slot-label",
					entry->name, sizeof(entry->name));
		}

		dt_add_property_nstr(node, "ibm,slot-label",
				entry->name, sizeof(entry->name));
	}

	if (entry->type == st_slot || entry->type == st_rc_slot)
		dt_add_property(node, "ibm,pluggable", NULL, 0);

	if (details)
		parse_slot_details(node, details);
}

/*
 * Under the IOHUB structure we have and idata array describing
 * the PHBs under each chip. The IOHUB structure also has a child
 * array called IOSLOT which describes slot map. The i`th element
 * of the IOSLOT array corresponds to the slot map of the i`th
 * element of the iohubs idata array.
 *
 * Probably.
 *
 * Furthermore, arrayarrayarrayarrayarray.
 */

static struct dt_node *get_slot_node(void)
{
	struct dt_node *slots = dt_find_by_name(dt_root, "ibm,pcie-slots");

	if (!slots) {
		slots = dt_new(dt_root, "ibm,pcie-slots");
		dt_add_property_cells(slots, "#address-cells", 2);
		dt_add_property_cells(slots, "#size-cells", 0);
	}

	return slots;
}

static void io_parse_slots(const struct HDIF_common_hdr *sp_iohubs, int hub_id)
{
	const struct HDIF_child_ptr *ioslot_arr;
	const struct HDIF_array_hdr *entry_arr;
	const struct HDIF_common_hdr *ioslot;
	const struct slot_map_entry *entry;
	unsigned int i, count;

	if (sp_iohubs->child_count <= CECHUB_CHILD_IOSLOTS)
		return;

	ioslot_arr = HDIF_child_arr(sp_iohubs, CECHUB_CHILD_IOSLOTS);
	if (!ioslot_arr)
		return;

	count = be32_to_cpu(ioslot_arr->count); /* should only be 1 */
	if (!count)
		return;

	dt_slots = get_slot_node();

	prlog(PR_DEBUG, "CEC: Found slot map for IOHUB %d\n", hub_id);
	if (count > 1)
		prerror("CEC: Multiple IOSLOTs found for IO HUB %d\n", hub_id);

	ioslot = HDIF_child(sp_iohubs, ioslot_arr, 0, "IOSLOT");
	if (!ioslot)
		return;

	entry_arr = HDIF_get_iarray(ioslot, IOSLOT_IDATA_SLOTMAP, NULL);
	HDIF_iarray_for_each(entry_arr, i, entry) {
		const struct slot_map_details *details;

		details = find_slot_details(ioslot,
				be16_to_cpu(entry->entry_id));
		parse_one_slot(entry, details, hub_id);
	}
}

static void io_parse_fru(const void *sp_iohubs)
{
	unsigned int i;
	int count;

	count = HDIF_get_iarray_size(sp_iohubs, CECHUB_FRU_IO_HUBS);
	if (count < 1) {
		prerror("CEC: IO FRU with no chips !\n");
		return;
	}

	prlog(PR_INFO, "CEC:   %d chips in FRU\n", count);

	/* Iterate IO hub array */
	for (i = 0; i < count; i++) {
		const struct cechub_io_hub *hub;
		unsigned int size, hub_id;
		struct dt_node *hn;
		uint32_t chip_id;

		hub = HDIF_get_iarray_item(sp_iohubs, CECHUB_FRU_IO_HUBS,
					   i, &size);
		if (!hub || size < CECHUB_IOHUB_MIN_SIZE) {
			prerror("CEC:     IO-HUB Chip %d bad idata\n", i);
			continue;
		}

		switch (hub->flags & CECHUB_HUB_FLAG_STATE_MASK) {
		case CECHUB_HUB_FLAG_STATE_OK:
			prlog(PR_DEBUG, "CEC:   IO Hub Chip #%d OK\n", i);
			break;
		case CECHUB_HUB_FLAG_STATE_FAILURES:
			prlog(PR_WARNING, "CEC:   IO Hub Chip #%d OK"
			      " with failures\n", i);
			break;
		case CECHUB_HUB_FLAG_STATE_NOT_INST:
			prlog(PR_DEBUG, "CEC:   IO Hub Chip #%d"
			      " Not installed\n", i);
			continue;
		case CECHUB_HUB_FLAG_STATE_UNUSABLE:
			prlog(PR_DEBUG, "CEC:   IO Hub Chip #%d Unusable\n", i);
			continue;
		}

		hub_id = be16_to_cpu(hub->iohub_id);

		/* GX BAR assignment */
		prlog(PR_DEBUG, "CEC:   PChip: %d HUB ID: %04x [EC=0x%x]"
		      " Hub#=%d)\n",
		      be32_to_cpu(hub->proc_chip_id), hub_id,
		      be32_to_cpu(hub->ec_level), be16_to_cpu(hub->hub_num));

		switch(hub_id) {
		case CECHUB_HUB_P7IOC:
			prlog(PR_INFO, "CEC:     P7IOC !\n");
			hn = io_add_p7ioc(hub, sp_iohubs);
			io_add_common(hn, hub);
			break;
		case CECHUB_HUB_MURANO:
		case CECHUB_HUB_MURANO_SEGU:
			prlog(PR_INFO, "CEC:     Murano !\n");
			io_add_p8(hub, sp_iohubs);
			break;
		case CECHUB_HUB_VENICE_WYATT:
			prlog(PR_INFO, "CEC:     Venice !\n");
			io_add_p8(hub, sp_iohubs);
			break;
		case CECHUB_HUB_NIMBUS_SFORAZ:
		case CECHUB_HUB_NIMBUS_MONZA:
		case CECHUB_HUB_NIMBUS_LAGRANGE:
			prlog(PR_INFO, "CEC:     Nimbus !\n");
			io_add_p9(hub, sp_iohubs);
			break;
		case CECHUB_HUB_CUMULUS_DUOMO:
			prlog(PR_INFO, "CEC:     Cumulus !\n");
			io_add_p9(hub, sp_iohubs);
			break;
		default:
			prlog(PR_ERR, "CEC:     Hub ID 0x%04x unsupported !\n",
			      hub_id);
		}

		chip_id = pcid_to_chip_id(be32_to_cpu(hub->proc_chip_id));

		/* parse the slot map if we have one */
		io_parse_slots(sp_iohubs, chip_id);
	}

	if (proc_gen == proc_gen_p8 || proc_gen == proc_gen_p9)
		io_add_p8_cec_vpd(sp_iohubs);
}

void io_parse(void)
{
	const struct HDIF_common_hdr *sp_iohubs;
	unsigned int i, size;

	/* Look for IO Hubs */
	if (!get_hdif(&spira.ntuples.cec_iohub_fru, "IO HUB")) {
		prerror("CEC: Cannot locate IO Hub FRU data !\n");
		return;
	}

	/*
	 * Note about LXRn numbering ...
	 *
	 * I can't completely make sense of what that is supposed to be, so
	 * for now, what we do is look for the first one we can find and
	 * increment it for each chip. Works for the machines I have here
	 */

	for_each_ntuple_idx(&spira.ntuples.cec_iohub_fru, sp_iohubs, i,
			    CECHUB_FRU_HDIF_SIG) {
		const struct cechub_hub_fru_id *fru_id_data;
		unsigned int type;
		static const char *typestr[] = {
			"Reservation",
			"Card",
			"CPU Card",
			"Backplane",
			"Backplane Extension"
		};
		fru_id_data = HDIF_get_idata(sp_iohubs, CECHUB_FRU_ID_DATA_AREA,
					     &size);
		if (!fru_id_data || size < sizeof(struct cechub_hub_fru_id)) {
			prerror("CEC: IO-HUB FRU %d, bad ID data\n", i);
			continue;
		}
		type = be32_to_cpu(fru_id_data->card_type);

		prlog(PR_INFO, "CEC: HUB FRU %d is %s\n",
		      i, type > 4 ? "Unknown" : typestr[type]);

		/*
		 * We currently only handle the backplane (Juno) and
		 * processor FRU (P8 machines)
		 */
		if (type != CECHUB_FRU_TYPE_CEC_BKPLANE &&
		    type != CECHUB_FRU_TYPE_CPU_CARD) {
			prerror("CEC:   Unsupported type\n");
			continue;
		}

		/* We don't support Hubs connected to pass-through ports */
		if (fru_id_data->flags & (CECHUB_FRU_FLAG_HEADLESS |
					  CECHUB_FRU_FLAG_PASSTHROUGH)) {
			prerror("CEC:   Headless or Passthrough unsupported\n");
			continue;
		}

		/* Ok, we have a reasonable candidate */
		io_parse_fru(sp_iohubs);
	}
}

