/* Copyright 2016-2019 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.
 */
#define pr_fmt(fmt)  "IMC: " fmt
#include <skiboot.h>
#include <xscom.h>
#include <imc.h>
#include <chip.h>
#include <libxz/xz.h>
#include <device.h>
#include <p9_stop_api.H>

/*
 * IMC trace scom values
 */
#define IMC_TRACE_SAMPLESEL_VAL	1	/* select cpmc2 */
#define IMC_TRACE_CPMCLOAD_VAL	0xfa	/*
					 * Value to be loaded into cpmc2
					 * at sampling start
					 */
#define IMC_TRACE_CPMC2SEL_VAL	2	/* Event: CPM_32MHZ_CYC */
#define IMC_TRACE_BUFF_SIZE	0	/*
					 * b’000’- 4K entries * 64 per
					 * entry = 256K buffersize
					 */
/*
 * Nest IMC PMU names along with their bit values as represented in the
 * imc_chip_avl_vector(in struct imc_chip_cb, look at include/imc.h).
 * nest_pmus[] is an array containing all the possible nest IMC PMU node names.
 */
static char const *nest_pmus[] = {
	"powerbus0",
	"mcs0",
	"mcs1",
	"mcs2",
	"mcs3",
	"mcs4",
	"mcs5",
	"mcs6",
	"mcs7",
	"mba0",
	"mba1",
	"mba2",
	"mba3",
	"mba4",
	"mba5",
	"mba6",
	"mba7",
	"cen0",
	"cen1",
	"cen2",
	"cen3",
	"cen4",
	"cen5",
	"cen6",
	"cen7",
	"xlink0",
	"xlink1",
	"xlink2",
	"mcd0",
	"mcd1",
	"phb0",
	"phb1",
	"phb2",
	"phb3",
	"phb4",
	"phb5",
	"nx",
	"capp0",
	"capp1",
	"vas",
	"int",
	"alink0",
	"alink1",
	"alink2",
	"alink3",
	"nvlink0",
	"nvlink1",
	"nvlink2",
	"nvlink3",
	"nvlink4",
	"nvlink5",
	/* reserved bits : 51 - 63 */
};

/*
 * Due to Nest HW/OCC restriction, microcode will not support individual unit
 * events for these nest units mcs0, mcs1 ... mcs7 in the accumulation mode.
 * And events to monitor each mcs units individually will be supported only
 * in the debug mode (which will be supported by microcode in the future).
 * These will be advertised only when OPAL provides interface for the it.
 */
static char const *debug_mode_units[] = {
	"mcs0",
	"mcs1",
	"mcs2",
	"mcs3",
	"mcs4",
	"mcs5",
	"mcs6",
	"mcs7",
};

/*
 * Combined unit node events are counted when any of the individual
 * unit is enabled in the availability vector. That is,
 * ex, mcs01 unit node should be enabled only when mcs0 or mcs1 enabled.
 * mcs23 unit node should be enabled only when mcs2 or mcs3 is enabled
 */
static struct combined_units_node cu_node[] = {
	{ .name = "mcs01", .unit1 = PPC_BIT(1), .unit2 = PPC_BIT(2) },
	{ .name = "mcs23", .unit1 = PPC_BIT(3), .unit2 = PPC_BIT(4) },
	{ .name = "mcs45", .unit1 = PPC_BIT(5), .unit2 = PPC_BIT(6) },
	{ .name = "mcs67", .unit1 = PPC_BIT(7), .unit2 = PPC_BIT(8) },
};

static char *compress_buf;
static size_t compress_buf_size;
const char **prop_to_fix(struct dt_node *node);
static const char *props_to_fix[] = {"events", NULL};

static bool is_nest_mem_initialized(struct imc_chip_cb *ptr)
{
	/*
	 * Non zero value in "Status" field indicate memory initialized.
	 */
	if (!ptr->imc_chip_run_status)
		return false;

	return true;
}

/*
 * A Quad contains 4 cores in Power 9, and there are 4 addresses for
 * the Core Hardware Trace Macro (CHTM) attached to each core.
 * So, for core index 0 to core index 3, we have a sequential range of
 * SCOM port addresses in the arrays below, each for Hardware Trace Macro (HTM)
 * mode and PDBAR.
 */
static unsigned int pdbar_scom_index[] = {
	0x1001220B,
	0x1001230B,
	0x1001260B,
	0x1001270B
};
static unsigned int htm_scom_index[] = {
	0x10012200,
	0x10012300,
	0x10012600,
	0x10012700
};

static struct imc_chip_cb *get_imc_cb(uint32_t chip_id)
{
	struct proc_chip *chip = get_chip(chip_id);
	struct imc_chip_cb *cb;

	if (!chip->homer_base)
		return NULL; /* The No Homers Club */

	cb = (struct imc_chip_cb *)(chip->homer_base + P9_CB_STRUCT_OFFSET);
	if (!is_nest_mem_initialized(cb))
		return NULL;

	return cb;
}

static int pause_microcode_at_boot(void)
{
	struct proc_chip *chip;
	struct imc_chip_cb *cb;

	for_each_chip(chip) {
		cb = get_imc_cb(chip->id);
		if (cb)
			cb->imc_chip_command =  cpu_to_be64(NEST_IMC_DISABLE);
		else
			return -1; /* ucode is not init-ed */
	}

	return 0;
}

/*
 * Function return list of properties names for the fixup
 */
const char **prop_to_fix(struct dt_node *node)
{
	if (dt_node_is_compatible(node, "ibm,imc-counters"))
		return props_to_fix;

	return NULL;
}

/* Helper to get the IMC device type for a device node */
static int get_imc_device_type(struct dt_node *node)
{
	const struct dt_property *type;
	u32 val=0;

	if (!node)
		return -1;

	type = dt_find_property(node, "type");
	if (!type)
		return -1;

	val = dt_prop_get_u32(node, "type");
	switch (val){
	case IMC_COUNTER_CHIP:
		return IMC_COUNTER_CHIP;
	case IMC_COUNTER_CORE:
		return IMC_COUNTER_CORE;
	case IMC_COUNTER_THREAD:
		return IMC_COUNTER_THREAD;
	case IMC_COUNTER_TRACE:
		return IMC_COUNTER_TRACE;
	default:
		break;
	}

	/* Unknown/Unsupported IMC device type */
	return -1;
}

static bool is_nest_node(struct dt_node *node)
{
	if (get_imc_device_type(node) == IMC_COUNTER_CHIP)
		return true;

	return false;
}

static bool is_imc_device_type_supported(struct dt_node *node)
{
	u32 val = get_imc_device_type(node);
	struct proc_chip *chip = get_chip(this_cpu()->chip_id);
	uint64_t pvr;

	if ((val == IMC_COUNTER_CHIP) || (val == IMC_COUNTER_CORE) ||
						(val == IMC_COUNTER_THREAD))
		return true;

	if (val == IMC_COUNTER_TRACE) {
		pvr = mfspr(SPR_PVR);
		/*
		 * Trace mode is supported in Nimbus DD2.2
		 * and later versions.
		 */
		if ((chip->type == PROC_CHIP_P9_NIMBUS) &&
			(PVR_VERS_MAJ(pvr) == 2) && (PVR_VERS_MIN(pvr) >= 2))
			return true;
	}
	return false;
}

/*
 * Helper to check for the imc device type in the incoming device tree.
 * Remove unsupported device node.
 */
static void check_imc_device_type(struct dt_node *dev)
{
	struct dt_node *node;

	dt_for_each_compatible(dev, node, "ibm,imc-counters") {
		if (!is_imc_device_type_supported(node)) {
			/*
			 * ah nice, found a device type which I didnt know.
			 * Remove it and also mark node as NULL, since dt_next
			 * will try to fetch info for "prev" which is removed
			 * by dt_free.
			 */
			dt_free(node);
			node = NULL;
		}
	}

	return;
}

static void imc_dt_exports_prop_add(struct dt_node *dev)
{
	struct dt_node *node;
	struct proc_chip *chip;
	const struct dt_property *type;
	uint32_t offset = 0, size = 0;
	uint64_t baddr;
	char namebuf[32];


	dt_for_each_compatible(dev, node, "ibm,imc-counters") {
		type = dt_find_property(node, "type");
		if (type && is_nest_node(node)) {
			offset = dt_prop_get_u32(node, "offset");
			size = dt_prop_get_u32(node, "size");
		}
	}

	/*
	 * Enable only if we have valid values.
	 */
	if (!size && !offset)
		return;

	node = dt_find_by_name(opal_node, "exports");
	if (!node)
		return;

	for_each_chip(chip) {
		snprintf(namebuf, sizeof(namebuf), "imc_nest_chip_%x", chip->id);
		baddr = chip->homer_base;
		baddr += offset;
		dt_add_property_u64s(node, namebuf, baddr, size);
	}
}

/*
 * Remove the PMU device nodes from the incoming new subtree, if they are not
 * available in the hardware. The availability is described by the
 * control block's imc_chip_avl_vector.
 * Each bit represents a device unit. If the device is available, then
 * the bit is set else its unset.
 */
static void disable_unavailable_units(struct dt_node *dev)
{
	uint64_t avl_vec;
	struct imc_chip_cb *cb;
	struct dt_node *target;
	int i;
	bool disable_all_nests = false;
	struct proc_chip *chip;

	/*
	 * Check the state of ucode in all the chip.
	 * Disable the nest unit if ucode is not initialized
	 * in any of the chip.
	 */
	for_each_chip(chip) {
		cb = get_imc_cb(chip->id);
		if (!cb) {
			/*
			 * At least currently, if one chip isn't functioning,
			 * none of the IMC Nest units will be functional.
			 * So while you may *think* this should be per chip,
			 * it isn't.
			 */
			disable_all_nests = true;
			break;
		}
	}

	/* Add a property to "exports" node in opal_node */
	imc_dt_exports_prop_add(dev);

	/* Fetch the IMC control block structure */
	cb = get_imc_cb(this_cpu()->chip_id);
	if (cb && !disable_all_nests)
		avl_vec = be64_to_cpu(cb->imc_chip_avl_vector);
	else {
		avl_vec = 0; /* Remove only nest imc device nodes */

		/* Incase of mambo, just fake it */
		if (proc_chip_quirks & QUIRK_MAMBO_CALLOUTS)
			avl_vec = (0xffULL) << 56;
	}

	for (i = 0; i < ARRAY_SIZE(nest_pmus); i++) {
		if (!(PPC_BITMASK(i, i) & avl_vec)) {
			/* Check if the device node exists */
			target = dt_find_by_name(dev, nest_pmus[i]);
			if (!target)
				continue;
			/* Remove the device node */
			dt_free(target);
		}
	}

	/*
	 * Loop to detect debug mode units and remove them
	 * since the microcode does not support debug mode function yet.
	 */
	for (i = 0; i < ARRAY_SIZE(debug_mode_units); i++) {
		target = dt_find_by_name(dev, debug_mode_units[i]);
		if (!target)
			continue;
		/* Remove the device node */
		dt_free(target);
	}

	/*
	 * Based on availability unit vector from control block,
	 * check and enable combined unit nodes in the device tree.
	 */
	for (i = 0; i < MAX_NEST_COMBINED_UNITS ; i++ ) {
		if (!(cu_node[i].unit1 & avl_vec) &&
				!(cu_node[i].unit2 & avl_vec)) {
			target = dt_find_by_name(dev, cu_node[i].name);
			if (!target)
				continue;

			/* Remove the device node */
			dt_free(target);
		}
	}

	return;
}

/*
 * Function to queue the loading of imc catalog data
 * from the IMC pnor partition.
 */
void imc_catalog_preload(void)
{
	uint32_t pvr = (mfspr(SPR_PVR) & ~(0xf0ff));
	int ret = OPAL_SUCCESS;
	compress_buf_size = MAX_COMPRESSED_IMC_DTB_SIZE;

	if (proc_chip_quirks & QUIRK_MAMBO_CALLOUTS)
		return;

	/* Enable only for power 9 */
	if (proc_gen != proc_gen_p9)
		return;

	compress_buf = malloc(MAX_COMPRESSED_IMC_DTB_SIZE);
	if (!compress_buf) {
		prerror("Memory allocation for catalog failed\n");
		return;
	}

	ret = start_preload_resource(RESOURCE_ID_IMA_CATALOG,
					pvr, compress_buf, &compress_buf_size);
	if (ret != OPAL_SUCCESS) {
		prerror("Failed to load IMA_CATALOG: %d\n", ret);
		free(compress_buf);
		compress_buf = NULL;
	}

	return;
}

static void imc_dt_update_nest_node(struct dt_node *dev)
{
	struct proc_chip *chip;
	uint64_t *base_addr = NULL;
	uint32_t *chipids = NULL;
	int i=0, nr_chip = nr_chips();
	struct dt_node *node;
	const struct dt_property *type;

	/* Add the base_addr and chip-id properties for the nest node */
	base_addr = malloc(sizeof(uint64_t) * nr_chip);
	chipids = malloc(sizeof(uint32_t) * nr_chip);
	for_each_chip(chip) {
		base_addr[i] = chip->homer_base;
		chipids[i] = chip->id;
		i++;
	}

	dt_for_each_compatible(dev, node, "ibm,imc-counters") {
		type = dt_find_property(node, "type");
		if (type && is_nest_node(node)) {
			dt_add_property(node, "base-addr", base_addr, (i * sizeof(u64)));
			dt_add_property(node, "chip-id", chipids, (i * sizeof(u32)));
		}
	}
}

static struct xz_decompress *imc_xz;

void imc_decompress_catalog(void)
{
	void *decompress_buf = NULL;
	uint32_t pvr = (mfspr(SPR_PVR) & ~(0xf0ff));
	int ret;

	/* Check we succeeded in starting the preload */
	if (compress_buf == NULL)
		return;

	ret = wait_for_resource_loaded(RESOURCE_ID_IMA_CATALOG, pvr);
	if (ret != OPAL_SUCCESS) {
		prerror("IMC Catalog load failed\n");
		return;
	}

	/*
	 * Memory for decompression.
	 */
	decompress_buf = malloc(MAX_DECOMPRESSED_IMC_DTB_SIZE);
	if (!decompress_buf) {
		prerror("No memory for decompress_buf \n");
		return;
	}

	/*
	 * Decompress the compressed buffer
	 */
	imc_xz = malloc(sizeof(struct xz_decompress));
	if (!imc_xz) {
		prerror("No memory to decompress IMC catalog\n");
		free(decompress_buf);
		return;
	}

	imc_xz->dst = decompress_buf;
	imc_xz->src = compress_buf;
	imc_xz->dst_size = MAX_DECOMPRESSED_IMC_DTB_SIZE;
	imc_xz->src_size = compress_buf_size;
	xz_start_decompress(imc_xz);
}

/*
 * Load the IMC pnor partition and find the appropriate sub-partition
 * based on the platform's PVR.
 * Decompress the sub-partition and link the imc device tree to the
 * existing device tree.
 */
void imc_init(void)
{
	struct dt_node *dev;
	int err_flag = -1;

	if (proc_chip_quirks & QUIRK_MAMBO_CALLOUTS) {
		dev = dt_find_compatible_node(dt_root, NULL,
					"ibm,opal-in-memory-counters");
		if (!dev)
			return;

		goto imc_mambo;
	}

	/* Enable only for power 9 */
	if (proc_gen != proc_gen_p9)
		return;

	if (!imc_xz)
		return;

	wait_xz_decompress(imc_xz);
	if (imc_xz->status != OPAL_SUCCESS)
		goto err;

	/*
	 * Flow of the data from PNOR to main device tree:
	 *
	 * PNOR -> compressed local buffer (compress_buf)
	 * compressed local buffer -> decompressed local buf (decompress_buf)
	 * decompress local buffer -> main device tree
	 * free compressed local buffer
	 */


	/* Create a device tree entry for imc counters */
	dev = dt_new_root("imc-counters");
	if (!dev)
		goto err;

	/*
	 * Attach the new decompress_buf to the imc-counters node.
	 * dt_expand_node() does sanity checks for fdt_header, piggyback
	 */
	if (dt_expand_node(dev, imc_xz->dst, 0) < 0) {
		dt_free(dev);
		goto err;
	}

imc_mambo:
	/* Check and remove unsupported imc device types */
	check_imc_device_type(dev);

	/*
	 * Check and remove unsupported nest unit nodes by the microcode,
	 * from the incoming device tree.
	 */
	disable_unavailable_units(dev);

	/* Fix the phandle in the incoming device tree */
	dt_adjust_subtree_phandle(dev, prop_to_fix);

	/* Update the base_addr and chip-id for nest nodes */
	imc_dt_update_nest_node(dev);

	if (proc_chip_quirks & QUIRK_MAMBO_CALLOUTS)
		return;

	/*
	 * IMC nest counters has both in-band (ucode access) and out of band
	 * access to it. Since not all nest counter configurations are supported
	 * by ucode, out of band tools are used to characterize other
	 * configuration.
	 *
	 * If the ucode not paused and OS does not have IMC driver support,
	 * then out to band tools will race with ucode and end up getting
	 * undesirable values. Hence pause the ucode if it is already running.
	 */
	if (pause_microcode_at_boot())
		goto err;

	/*
	 * If the dt_attach_root() fails, "imc-counters" node will not be
	 * seen in the device-tree and hence OS should not make any
	 * OPAL_IMC_* calls.
	 */
	if (!dt_attach_root(dt_root, dev)) {
		dt_free(dev);
		goto err;
	}

	err_flag = OPAL_SUCCESS;

err:
	if (err_flag != OPAL_SUCCESS)
		prerror("IMC Devices not added\n");

	free(compress_buf);
	free(imc_xz->dst);
	free(imc_xz);
}

static int stop_api_init(struct proc_chip *chip, int phys_core_id,
			uint32_t scoms,  uint64_t data,
			const ScomOperation_t operation,
			const ScomSection_t section,
			const char *type)
{
	int ret;

	prlog(PR_DEBUG, "Configuring stopapi for IMC\n");
	ret = p9_stop_save_scom((void *)chip->homer_base, scoms,
				data, operation, section);
	if (ret) {
		prerror("IMC %s stopapi ret = %d, scoms = %x (core id = %x)\n",\
				type, ret, scoms, phys_core_id);
		if (ret != STOP_SAVE_SCOM_ENTRY_UPDATE_FAILED)
			wakeup_engine_state = WAKEUP_ENGINE_FAILED;
		else
			prerror("SCOM entries are full\n");
		return OPAL_HARDWARE;
	}

	return ret;
}

/*
 * opal_imc_counters_init : This call initialize the IMC engine.
 *
 * For Nest IMC, this is no-op and returns OPAL_SUCCESS at this point.
 * For Core IMC, this initializes core IMC Engine, by initializing
 * these scoms "PDBAR", "HTM_MODE" and the "EVENT_MASK" in a given cpu.
 */
static int64_t opal_imc_counters_init(uint32_t type, uint64_t addr, uint64_t cpu_pir)
{
	struct cpu_thread *c = find_cpu_by_pir(cpu_pir);
	int port_id, phys_core_id;
	int ret;
	uint32_t scoms;
	uint64_t trace_scom_val = TRACE_IMC_SCOM(IMC_TRACE_SAMPLESEL_VAL,
						 IMC_TRACE_CPMCLOAD_VAL, 0,
						 IMC_TRACE_CPMC2SEL_VAL,
						 IMC_TRACE_BUFF_SIZE);
	switch (type) {
	case OPAL_IMC_COUNTERS_NEST:
		return OPAL_SUCCESS;
	case OPAL_IMC_COUNTERS_CORE:
		if (!c)
			return OPAL_PARAMETER;

		/*
		 * Core IMC hardware mandates setting of htm_mode and
		 * pdbar in specific scom ports. port_id are in
		 * pdbar_scom_index[] and htm_scom_index[].
		 */
		phys_core_id = cpu_get_core_index(c);
		port_id = phys_core_id % 4;

		if (proc_chip_quirks & QUIRK_MAMBO_CALLOUTS)
			return OPAL_SUCCESS;

		/*
		 * Core IMC hardware mandate initing of three scoms
		 * to enbale or disable of the Core IMC engine.
		 *
		 * PDBAR: Scom contains the real address to store per-core
		 *        counter data in memory along with other bits.
		 *
		 * EventMask: Scom contain bits to denote event to multiplex
		 *            at different MSR[HV PR] values, along with bits for
		 *            sampling duration.
		 *
		 * HTM Scom: scom to enable counter data movement to memory.
		 */


		 if (xscom_write(c->chip_id,
				XSCOM_ADDR_P9_EP(phys_core_id,
						pdbar_scom_index[port_id]),
				(u64)(CORE_IMC_PDBAR_MASK & addr))) {
			prerror("error in xscom_write for pdbar\n");
			return OPAL_HARDWARE;
		}

		if (has_deep_states) {
			if (wakeup_engine_state == WAKEUP_ENGINE_PRESENT) {
				struct proc_chip *chip = get_chip(c->chip_id);

				scoms = XSCOM_ADDR_P9_EP(phys_core_id,
						pdbar_scom_index[port_id]);
				ret = stop_api_init(chip, phys_core_id, scoms,
						(u64)(CORE_IMC_PDBAR_MASK & addr),
						P9_STOP_SCOM_REPLACE,
						P9_STOP_SECTION_EQ_SCOM,
						"pdbar");
				if (ret)
					return ret;
				scoms = XSCOM_ADDR_P9_EC(phys_core_id,
						CORE_IMC_EVENT_MASK_ADDR);
				ret = stop_api_init(chip, phys_core_id, scoms,
						(u64)CORE_IMC_EVENT_MASK,
						P9_STOP_SCOM_REPLACE,
						P9_STOP_SECTION_CORE_SCOM,
						"event_mask");
				if (ret)
					return ret;
			} else {
				prerror("IMC: Wakeup engine not present!");
				return OPAL_HARDWARE;
			}
		}

		if (xscom_write(c->chip_id,
				XSCOM_ADDR_P9_EC(phys_core_id,
					 CORE_IMC_EVENT_MASK_ADDR),
				(u64)CORE_IMC_EVENT_MASK)) {
			prerror("error in xscom_write for event mask\n");
			return OPAL_HARDWARE;
		}

		if (xscom_write(c->chip_id,
				XSCOM_ADDR_P9_EP(phys_core_id,
						htm_scom_index[port_id]),
				(u64)CORE_IMC_HTM_MODE_DISABLE)) {
			prerror("error in xscom_write for htm mode\n");
			return OPAL_HARDWARE;
		}
		return OPAL_SUCCESS;
	case OPAL_IMC_COUNTERS_TRACE:
		if (!c)
			return OPAL_PARAMETER;

		phys_core_id = cpu_get_core_index(c);
		port_id = phys_core_id % 4;

		if (proc_chip_quirks & QUIRK_MAMBO_CALLOUTS)
			return OPAL_SUCCESS;

		if (has_deep_states) {
			if (wakeup_engine_state == WAKEUP_ENGINE_PRESENT) {
				struct proc_chip *chip = get_chip(c->chip_id);

				scoms = XSCOM_ADDR_P9_EC(phys_core_id,
							 TRACE_IMC_ADDR);
				ret = stop_api_init(chip, phys_core_id, scoms,
						    trace_scom_val,
						    P9_STOP_SCOM_REPLACE,
						    P9_STOP_SECTION_CORE_SCOM,
						    "trace_imc");
				if (ret)
					return ret;
			} else {
				prerror("IMC-trace:Wakeup engine not present!");
				return OPAL_HARDWARE;
			}
		}
		if (xscom_write(c->chip_id,
			XSCOM_ADDR_P9_EP(phys_core_id, htm_scom_index[port_id]),
					(u64)CORE_IMC_HTM_MODE_DISABLE)) {
				prerror("IMC-trace: error in xscom_write for htm mode\n");
				return OPAL_HARDWARE;
		}
		if (xscom_write(c->chip_id,
			XSCOM_ADDR_P9_EC(phys_core_id,
					TRACE_IMC_ADDR), trace_scom_val)) {
			prerror("IMC-trace: error in xscom_write for trace mode\n");
			return OPAL_HARDWARE;
		}
		return OPAL_SUCCESS;

	}

	return OPAL_SUCCESS;
}
opal_call(OPAL_IMC_COUNTERS_INIT, opal_imc_counters_init, 3);

/* opal_imc_counters_control_start: This call starts the nest/core imc engine. */
static int64_t opal_imc_counters_start(uint32_t type, uint64_t cpu_pir)
{
	u64 op;
	struct cpu_thread *c = find_cpu_by_pir(cpu_pir);
	struct imc_chip_cb *cb;
	int port_id, phys_core_id;

	if (!c)
		return OPAL_PARAMETER;

	switch (type) {
	case OPAL_IMC_COUNTERS_NEST:
		/* Fetch the IMC control block structure */
		cb = get_imc_cb(c->chip_id);
		if (!cb)
			return OPAL_HARDWARE;

		/* Set the run command */
		op = NEST_IMC_ENABLE;

		if (proc_chip_quirks & QUIRK_MAMBO_CALLOUTS)
			return OPAL_SUCCESS;

		/* Write the command to the control block now */
		cb->imc_chip_command = cpu_to_be64(op);

		return OPAL_SUCCESS;
	case OPAL_IMC_COUNTERS_CORE:
	case OPAL_IMC_COUNTERS_TRACE:
		/*
		 * Core IMC hardware mandates setting of htm_mode in specific
		 * scom ports (port_id are in htm_scom_index[])
		 */
		phys_core_id = cpu_get_core_index(c);
		port_id = phys_core_id % 4;

		if (proc_chip_quirks & QUIRK_MAMBO_CALLOUTS)
			return OPAL_SUCCESS;

		/*
		 * Enables the core imc engine by appropriately setting
		 * bits 4-9 of the HTM_MODE scom port. No initialization
		 * is done in this call. This just enables the the counters
		 * to count with the previous initialization.
		 */
		if (xscom_write(c->chip_id,
				XSCOM_ADDR_P9_EP(phys_core_id,
						htm_scom_index[port_id]),
				(u64)CORE_IMC_HTM_MODE_ENABLE)) {
			prerror("IMC OPAL_start: error in xscom_write for htm_mode\n");
			return OPAL_HARDWARE;
		}

		return OPAL_SUCCESS;
	}

	return OPAL_SUCCESS;
}
opal_call(OPAL_IMC_COUNTERS_START, opal_imc_counters_start, 2);

/* opal_imc_counters_control_stop: This call stops the nest imc engine. */
static int64_t opal_imc_counters_stop(uint32_t type, uint64_t cpu_pir)
{
	u64 op;
	struct imc_chip_cb *cb;
	struct cpu_thread *c = find_cpu_by_pir(cpu_pir);
	int port_id, phys_core_id;

	if (!c)
		return OPAL_PARAMETER;

	switch (type) {
	case OPAL_IMC_COUNTERS_NEST:
		/* Fetch the IMC control block structure */
		cb = get_imc_cb(c->chip_id);
		if (!cb)
			return OPAL_HARDWARE;

		/* Set the run command */
		op = NEST_IMC_DISABLE;

		if (proc_chip_quirks & QUIRK_MAMBO_CALLOUTS)
			return OPAL_SUCCESS;

		/* Write the command to the control block */
		cb->imc_chip_command = cpu_to_be64(op);

		return OPAL_SUCCESS;

	case OPAL_IMC_COUNTERS_CORE:
	case OPAL_IMC_COUNTERS_TRACE:
		/*
		 * Core IMC hardware mandates setting of htm_mode in specific
		 * scom ports (port_id are in htm_scom_index[])
		 */
		phys_core_id = cpu_get_core_index(c);
		port_id = phys_core_id % 4;

		if (proc_chip_quirks & QUIRK_MAMBO_CALLOUTS)
			return OPAL_SUCCESS;

		/*
		 * Disables the core imc engine by clearing
		 * bits 4-9 of the HTM_MODE scom port.
		 */
		if (xscom_write(c->chip_id,
				XSCOM_ADDR_P9_EP(phys_core_id,
						htm_scom_index[port_id]),
				(u64) CORE_IMC_HTM_MODE_DISABLE)) {
			prerror("error in xscom_write for htm_mode\n");
			return OPAL_HARDWARE;
		}

		return OPAL_SUCCESS;
	}

	return OPAL_SUCCESS;
}
opal_call(OPAL_IMC_COUNTERS_STOP, opal_imc_counters_stop, 2);
