/* 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.
 */
/*
 * LXVPD support
 *
 */


#include <skiboot.h>
#include <device.h>
#include <vpd.h>
#include <pci.h>
#include <pci-cfg.h>

#include "lxvpd.h"

struct lxvpd_slot_info {
	uint8_t    switch_id;
	uint8_t    vswitch_id;
	uint8_t    dev_id;
	struct pci_slot_info	ps;
};

/*
 * XXX TODO: Add 1006 maps to add function loc codes and loc code maps
 * (ie. -Tn part of the location code) 
 */
struct lxvpd_slot_info_data {
	uint8_t			num_slots;
	struct lxvpd_slot_info	info[];
};

static bool lxvpd_supported_slot(struct phb *phb, struct pci_device *pd)
{
	/* PCI/PCI-X we only support top level PHB with NULL "pd" */
	if (phb->phb_type < phb_type_pcie_v1)
		return pd == NULL;

	/* Now we have PCI Express, we should never have a NULL "pd" */
	if (!pd)
		return false;

	/* We support the root complex at the top level */
	if (pd->dev_type == PCIE_TYPE_ROOT_PORT && !pd->parent)
		return true;

	/* We support an upstream switch port below the root complex */
	if (pd->dev_type == PCIE_TYPE_SWITCH_UPPORT &&
	    pd->parent && pd->parent->dev_type == PCIE_TYPE_ROOT_PORT &&
	    !pd->parent->parent)
		return true;

	/* We support a downstream switch port below an upstream port
	 * below the root complex
	 */
	if (pd->dev_type == PCIE_TYPE_SWITCH_DNPORT &&
	    pd->parent && pd->parent->dev_type == PCIE_TYPE_SWITCH_UPPORT &&
	    pd->parent->parent &&
	    pd->parent->parent->dev_type == PCIE_TYPE_ROOT_PORT &&
	    !pd->parent->parent->parent)
		return true;

	/* Anything else, bail */
	return false;
}

void lxvpd_get_slot_info(struct phb *phb, struct pci_device * pd)
{
	struct lxvpd_slot_info_data *sdata = phb->platform_data;
	bool is_phb = (pd && pd->parent) ? false : true;
	bool entry_found = false;
	uint8_t idx;

	/* Check if we have slot info */
	if (!sdata)
		return;

	prlog(PR_TRACE, "LXVPD: Get Slot Info PHB%d pd=%x\n", phb->opal_id,
	    pd ? pd->bdfn : 0);

	/*
	 * This code only handles PHBs and PCIe switches at the top level.
	 *
	 * We do not handle any other switch nor any other type of PCI/PCI-X
	 * bridge.
	 */
	if (!lxvpd_supported_slot(phb, pd)) {
		prlog(PR_TRACE, "LXVPD: Unsupported slot\n");
		return;
	}

	/* Iterate the slot map */
	for (idx = 0; idx <= sdata->num_slots; idx++) {
		struct lxvpd_slot_info *info = &sdata->info[idx];
		uint8_t pd_dev = (pd->bdfn >> 3) & 0x1f;

		/* Match PHB with switch_id == 0 */
		if (is_phb && info->switch_id == 0) {
			entry_found = true;
			break;
		}

		/* Match switch port with switch_id != 0 */
		if (!is_phb && info->switch_id !=0 && info->dev_id == pd_dev) {
			entry_found = true;
			break;
		}
	}

	if (entry_found) {
		pd->slot_info = &sdata->info[idx].ps;
		prlog(PR_TRACE, "PCI: PCIE Slot Info: \n"
		      "       Label       %s\n"
		      "       Pluggable   0x%x\n"
		      "       Power Ctl   0x%x\n"
		      "       Wired Lanes 0x%x\n"
		      "       Bus Clock   0x%x\n"
		      "       Connector   0x%x\n"
		      "       Slot Index  %d\n",
		      pd->slot_info->label,
		      pd->slot_info->pluggable?1:0,
		      pd->slot_info->power_ctl?1:0,
		      pd->slot_info->wired_lanes,
		      pd->slot_info->bus_clock,
		      pd->slot_info->connector_type,
		      pd->slot_info->slot_index);
	} else {
		prlog(PR_TRACE, "PCI: PCIE Slot Info Not Found\n");
	}
}

static struct lxvpd_slot_info *lxvpd_alloc_slot_info(struct phb *phb, int count)
{
	struct lxvpd_slot_info_data *data;

	data = zalloc(sizeof(struct lxvpd_slot_info_data) *
		      count * sizeof(struct lxvpd_slot_info));
	assert(data);
	data->num_slots = count;
	phb->platform_data = data;

	return data->info;
}

static void lxvpd_parse_1004_map(struct phb *phb, const uint8_t *sm, uint8_t sz)
{
	const struct pci_slot_entry_1004 *entry = NULL;
	struct lxvpd_slot_info *slot_info, *info;
	uint8_t num_slots, slot, idx;

	num_slots = (sz / sizeof(struct pci_slot_entry_1004));
	slot_info = lxvpd_alloc_slot_info(phb, num_slots);

	/* Iterate through the entries in the keyword */
	entry = (const struct pci_slot_entry_1004 *)sm;
	for (slot = 0; slot < num_slots; slot++) {
		info = &slot_info[slot];

		/* Put slot info into pci device structure */
		info->switch_id = entry->pba >> 4;
		info->vswitch_id = entry->pba &0xf;
		info->dev_id = entry->sba;
		for (idx = 0; idx < 3; idx++)
			info->ps.label[idx] = entry->label[idx];
		info->ps.label[3] = 0;
		info->ps.pluggable = ((entry->p0.byte & 0x20) == 0);
		info->ps.power_ctl = ((entry->p0.power_ctl & 0x40) == 1);
		switch(entry->p1.wired_lanes) {
		case 1: info->ps.wired_lanes = PCI_SLOT_WIRED_LANES_PCIX_32; break;
		case 2: /* fall through */
		case 3: info->ps.wired_lanes = PCI_SLOT_WIRED_LANES_PCIX_64; break;
		case 4: info->ps.wired_lanes = PCI_SLOT_WIRED_LANES_PCIE_X1; break;
		case 5: info->ps.wired_lanes = PCI_SLOT_WIRED_LANES_PCIE_X4; break;
		case 6: info->ps.wired_lanes = PCI_SLOT_WIRED_LANES_PCIE_X8; break;
		case 7: info->ps.wired_lanes = PCI_SLOT_WIRED_LANES_PCIE_X16; break;
		default:
			info->ps.wired_lanes = PCI_SLOT_WIRED_LANES_UNKNOWN;
		}
		info->ps.wired_lanes = (entry->p1.wired_lanes - 3);
		info->ps.bus_clock = (entry->p2.bus_clock - 4);
		info->ps.connector_type = (entry->p2.connector_type - 5);
		if (entry->p3.byte < 0xC0)
			info->ps.card_desc = ((entry->p3.byte >> 6) - 4) ;
		else
			info->ps.card_desc = (entry->p3.byte >> 6);
		info->ps.card_mech = ((entry->p3.byte >> 4) & 0x3);
		info->ps.pwr_led_ctl = ((entry->p3.byte & 0xF) >> 2);
		info->ps.attn_led_ctl = (entry->p3.byte & 0x3);
		info->ps.slot_index = entry->slot_index;
		entry++;
	}
}

static void lxvpd_parse_1005_map(struct phb *phb, const uint8_t *sm, uint8_t sz)
{
	const struct pci_slot_entry_1005 *entry = NULL;
	struct lxvpd_slot_info *slot_info, *info;
	uint8_t num_slots, slot, idx;

	num_slots = (sz / sizeof(struct pci_slot_entry_1005));
	slot_info = lxvpd_alloc_slot_info(phb, num_slots);

	/* Iterate through the entries in the keyword */
	entry = (const struct pci_slot_entry_1005 *)sm;
	for (slot = 0; slot < num_slots; slot++) {
		info = &slot_info[slot];

		/* Put slot info into pci device structure */
		info->switch_id = entry->pba >> 4;
		info->vswitch_id = entry->pba &0xf;
		info->dev_id = entry->switch_device_id;
		for (idx = 0; idx < 8; idx++)
			info->ps.label[idx] = entry->label[idx];
		info->ps.label[8] = 0;
		info->ps.pluggable = (entry->p0.pluggable == 0);
		info->ps.power_ctl = entry->p0.power_ctl;
		info->ps.wired_lanes = entry->p1.wired_lanes;
		if (info->ps.wired_lanes > PCI_SLOT_WIRED_LANES_PCIE_X32)
			info->ps.wired_lanes = PCI_SLOT_WIRED_LANES_UNKNOWN;
		info->ps.bus_clock = entry->p2.bus_clock;
		info->ps.connector_type = entry->p2.connector_type;
		info->ps.card_desc = (entry->p3.byte >> 6);
		info->ps.card_mech = ((entry->p3.byte >> 4) & 0x3);
		info->ps.pwr_led_ctl = ((entry->p3.byte & 0xF) >> 2);
		info->ps.attn_led_ctl = (entry->p3.byte & 0x3);
		info->ps.slot_index = entry->slot_index;
		entry++;
	}
}

void lxvpd_process_slot_entries(struct phb *phb,
				struct dt_node *node,
				uint8_t chip_id,
				uint8_t index)
{
	const void *lxvpd;
	const uint8_t *pr_rec, *pr_end, *sm;
	size_t lxvpd_size, pr_size;
	const uint16_t *mf = NULL;
	char record[5] = "PR00";
	uint8_t mf_sz, sm_sz;
	bool found = false;

	record[2] += chip_id;
	record[3] += index;
	record[4] = 0;

	/* Get LX VPD pointer */
	lxvpd = dt_prop_get_def_size(node, "ibm,io-vpd", NULL, &lxvpd_size);
	if (!lxvpd) {
		printf("LXVPD: LX VPD not found for %s in %p\n",
		       record, phb->dt_node);
		return;
	}

	pr_rec = vpd_find_record(lxvpd, lxvpd_size, record, &pr_size);
	if (!pr_rec) {
		printf("LXVPD: %s record not found in LX VPD in %p\n",
		       record, phb->dt_node);
		return;
	}
	pr_end = pr_rec + pr_size;

	prlog(PR_TRACE, "LXVPD: %s record for PHB%d is %ld bytes\n",
	      record, phb->opal_id, pr_size);

	/* As long as there's still something in the PRxy record... */
	while(pr_rec < pr_end) {
		pr_size = pr_end - pr_rec;

		/* Find the next MF keyword */
		mf = vpd_find_keyword(pr_rec, pr_size, "MF", &mf_sz);
		/* And the corresponding SM */
		sm = vpd_find_keyword(pr_rec, pr_size, "SM", &sm_sz);
		if (!mf || !sm) {
			if (!found)
				printf("LXVPD: Slot Map keyword %s not found\n",
				       record);
			return;
		}
		prlog(PR_TRACE, "LXVPD: Found 0x%04x map...\n", *mf);

		switch (*mf) {
		case 0x1004:
			lxvpd_parse_1004_map(phb, sm + 1, sm_sz - 1);
			found = true;
			break;
		case 0x1005:
			lxvpd_parse_1005_map(phb, sm + 1, sm_sz - 1);
			found = true;
			break;
			/* Add support for 0x1006 maps ... */
		}
		pr_rec = sm + sm_sz;
	}
}

