/* 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 <device.h>
#include <fsp.h>
#include <pci.h>
#include <pci-cfg.h>
#include <chip.h>
#include <i2c.h>
#include <timebase.h>
#include <hostservices.h>

#include "ibm-fsp.h"
#include "lxvpd.h"

static struct dt_node *dt_create_i2c_master(struct dt_node *n, uint32_t eng_id)
{
	struct dt_node *i2cm;
	uint64_t freq;
	uint32_t clock;

	/* Each master registers set is of length 0x20 */
	i2cm = dt_new_addr(n, "i2cm", 0xa0000 + eng_id * 0x20);
	if (!i2cm)
		return NULL;

	dt_add_property_string(i2cm, "compatible",
			       "ibm,power8-i2cm");
	dt_add_property_cells(i2cm, "reg", 0xa0000 + eng_id * 0x20,
			      0x20);
	dt_add_property_cells(i2cm, "chip-engine#", eng_id);
	dt_add_property_cells(i2cm, "#address-cells", 1);
	dt_add_property_cells(i2cm, "#size-cells", 0);

	/* Derive the clock source frequency */
	freq = dt_prop_get_u64_def(n, "bus-frequency", 0);
	clock = (u32)(freq / 4);
	if (clock)
		dt_add_property_cells(i2cm, "clock-frequency", clock);
	else
		dt_add_property_cells(i2cm, "clock-frequency", 125000000);
	return i2cm;
}

static struct dt_node *dt_create_i2c_bus(struct dt_node *i2cm,
					 const char *port_name, uint32_t port_id)
{
	static struct dt_node *port;

	port = dt_new_addr(i2cm, "i2c-bus", port_id);
	if (!port)
		return NULL;

	dt_add_property_strings(port, "compatible", "ibm,power8-i2c-port",
				"ibm,opal-i2c");
	dt_add_property_string(port, "ibm,port-name", port_name);
	dt_add_property_cells(port, "reg", port_id);
	dt_add_property_cells(port, "bus-frequency", 400000);
	dt_add_property_cells(port, "#address-cells", 1);
	dt_add_property_cells(port, "#size-cells", 0);

	return port;
}

static struct dt_node *dt_create_i2c_device(struct dt_node *bus, uint8_t addr,
					    const char *name, const char *compat,
					    const char *label)
{
	struct dt_node *dev;

	dev = dt_new_addr(bus, name, addr);
	if (!dev)
		return NULL;

	dt_add_property_string(dev, "compatible", compat);
	dt_add_property_string(dev, "label", label);
	dt_add_property_cells(dev, "reg", addr);
	dt_add_property_string(dev, "status", "reserved");

	return dev;
}

static void firenze_dt_fixup_i2cm(void)
{
	struct dt_node *master, *bus, *dev;
	struct proc_chip *c;
	const uint32_t *p;
	char name[32];
	uint64_t lx;

	if (dt_find_compatible_node(dt_root, NULL, "ibm,power8-i2cm"))
		return;

	p = dt_prop_get_def(dt_root, "ibm,vpd-lx-info", NULL);
	if (!p)
		return;

	lx = ((uint64_t)p[1] << 32) | p[2];

	switch (lx) {
	case LX_VPD_2S4U_BACKPLANE:
	case LX_VPD_2S2U_BACKPLANE:
	case LX_VPD_SHARK_BACKPLANE: /* XXX confirm ? */
		/* i2c nodes on chip 0x10 */
		c = get_chip(0x10);
		if (c) {
			/* Engine 1 */
			master = dt_create_i2c_master(c->devnode, 1);
			assert(master);
			snprintf(name, sizeof(name), "p8_%08x_e%dp%d", c->id, 1, 0);
			bus = dt_create_i2c_bus(master, name, 0);
			assert(bus);
			dev = dt_create_i2c_device(bus, 0x39, "power-control",
						   "maxim,5961", "pcie-hotplug");
			assert(dev);
			dt_add_property_strings(dev, "target-list", "slot-C4",
						"slot-C5");

			dev = dt_create_i2c_device(bus, 0x3a, "power-control",
						   "maxim,5961", "pcie-hotplug");
			assert(dev);
			dt_add_property_strings(dev, "target-list", "slot-C2",
						"slot-C3");
		} else {
			prlog(PR_INFO, "PLAT: Chip not found for the id 0x10\n");
		}

	/* Fall through */
	case LX_VPD_1S4U_BACKPLANE:
	case LX_VPD_1S2U_BACKPLANE:
		/* i2c nodes on chip 0 */
		c = get_chip(0);
		if (!c) {
			prlog(PR_INFO, "PLAT: Chip not found for the id 0x0\n");
			break;
		}

		/* Engine 1*/
		master = dt_create_i2c_master(c->devnode, 1);
		assert(master);
		snprintf(name, sizeof(name), "p8_%08x_e%dp%d", c->id, 1, 0);
		bus = dt_create_i2c_bus(master, name, 0);
		assert(bus);
		dev = dt_create_i2c_device(bus, 0x32, "power-control",
					   "maxim,5961", "pcie-hotplug");
		assert(dev);
		dt_add_property_strings(dev, "target-list", "slot-C10", "slot-C11");

		dev = dt_create_i2c_device(bus, 0x35, "power-control",
					   "maxim,5961", "pcie-hotplug");
		assert(dev);
		dt_add_property_strings(dev, "target-list", "slot-C6", "slot-C7");

		dev = dt_create_i2c_device(bus, 0x36, "power-control",
					   "maxim,5961", "pcie-hotplug");
		assert(dev);
		dt_add_property_strings(dev, "target-list", "slot-C8", "slot-C9");

		dev = dt_create_i2c_device(bus, 0x39, "power-control", "maxim,5961",
					   "pcie-hotplug");
		assert(dev);
		dt_add_property_strings(dev, "target-list", "slot-C12");
		break;
	default:
		break;
	}
}

static bool firenze_probe(void)
{
	if (!dt_node_is_compatible(dt_root, "ibm,firenze"))
		return false;

	firenze_dt_fixup_i2cm();

	return true;
}

static uint32_t ibm_fsp_occ_timeout(void)
{
	/* Use a fixed 60s value for now */
	return 60;
}

static void firenze_init(void)
{
	/* We call hservices_init to relocate the hbrt image now, as the FSP
	 * may request an OCC load any time after ibm_fsp_init.
	 */
	hservices_init();

	ibm_fsp_init();
}

DECLARE_PLATFORM(firenze) = {
	.name			= "Firenze",
	.probe			= firenze_probe,
	.init			= firenze_init,
	.exit			= ibm_fsp_exit,
	.cec_power_down		= ibm_fsp_cec_power_down,
	.cec_reboot		= ibm_fsp_cec_reboot,
	.pci_setup_phb		= firenze_pci_setup_phb,
	.pci_get_slot_info	= firenze_pci_get_slot_info,
	.pci_probe_complete	= firenze_pci_send_inventory,
	.nvram_info		= fsp_nvram_info,
	.nvram_start_read	= fsp_nvram_start_read,
	.nvram_write		= fsp_nvram_write,
	.occ_timeout		= ibm_fsp_occ_timeout,
	.elog_commit		= elog_fsp_commit,
	.start_preload_resource	= fsp_start_preload_resource,
	.resource_loaded	= fsp_resource_loaded,
	.sensor_read		= ibm_fsp_sensor_read,
	.terminate		= ibm_fsp_terminate,
};
