/* Copyright 2013-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.
 */

#include <skiboot.h>
#include <xscom.h>
#include <io.h>
#include <cpu.h>
#include <chip.h>
#include <mem_region.h>
#include <hostservices.h>

#define P8_PBA_BAR0		0x2013f00
#define P8_PBA_BARMASK0		0x2013f04

#define P9_PBA_BAR0		0x5012B00
#define P9_PBA_BARMASK0		0x5012B04

#define PBA_MASK_ALL_BITS 0x000001FFFFF00000ULL /* Bits 23:43 */

enum P8_BAR {
	P8_BAR_HOMER = 0,
	P8_BAR_CENTAUR = 1,
	P8_BAR_SLW = 2,
	P8_BAR_OCC_COMMON = 3,
};

enum P9_BAR {
	P9_BAR_HOMER = 0,
	P9_BAR_CENTAUR = 1,
	P9_BAR_OCC_COMMON = 2,
	P9_BAR_SBE = 3,
};

static u64 pba_bar0, pba_barmask0;
static u8 bar_homer, bar_slw, bar_occ_common;

static bool read_pba_bar(struct proc_chip *chip, unsigned int bar_no,
			 uint64_t *base, uint64_t *size)
{
	uint64_t bar, mask;
	int rc;

	rc = xscom_read(chip->id, pba_bar0 + bar_no, &bar);
	if (rc) {
		prerror("SLW: Error %d reading PBA BAR%d on chip %d\n",
			rc, bar_no, chip->id);
		return false;
	}
	rc = xscom_read(chip->id, pba_barmask0 + bar_no, &mask);
	if (rc) {
		prerror("SLW: Error %d reading PBA BAR MASK%d on chip %d\n",
			rc, bar_no, chip->id);
		return false;
	}
	prlog(PR_DEBUG, "  PBA BAR%d : 0x%016llx\n", bar_no, bar);
	prlog(PR_DEBUG, "  PBA MASK%d: 0x%016llx\n", bar_no, mask);

	if (mask == PBA_MASK_ALL_BITS) {
		/*
		 * This could happen if all HOMER users are not enabled during
		 * early system bringup. Skip using the PBA BAR.
		 */
		mask = 0;
		bar = 0;
		prerror("  PBA MASK%d uninitalized skipping BAR\n", bar_no);
	}

	*base = bar & 0x0ffffffffffffffful;
	*size = (mask | 0xfffff) + 1;

	return (*base) != 0;
}

static void homer_init_chip(struct proc_chip *chip)
{
	uint64_t hbase = 0, hsize = 0;
	uint64_t sbase, ssize, obase, osize;

	/*
	 * PBA BARs assigned by HB:
	 *
	 * P8:
	 *   0 : Entire HOMER
	 *   1 : OCC to Centaur path (we don't care)
	 *   2 : SLW image
	 *   3 : OCC Common area
	 *
	 * We need to reserve the memory covered by BAR 0 and BAR 3, however
	 * on earlier HBs, BAR0 isn't set so we need BAR 2 instead in that
	 * case to cover SLW (OCC not running).
	 *
	 * P9:
	 *   0 : Entire HOMER
	 *   1 : OCC to Centaur path (Cumulus only)
	 *   2 : OCC Common area
	 *   3 : SBE communication
	 *
	 */
	if (read_pba_bar(chip, bar_homer, &hbase, &hsize)) {
		prlog(PR_DEBUG, "  HOMER Image at 0x%llx size %lldMB\n",
		      hbase, hsize / 0x100000);

		if (!mem_range_is_reserved(hbase, hsize)) {
			prlog(PR_WARNING,
				"HOMER image is not reserved! Reserving\n");
			mem_reserve_fw("ibm,homer-image", hbase, hsize);
		}

		chip->homer_base = hbase;
		chip->homer_size = hsize;
	}

	/*
	 * We always read the SLW BAR since we need to grab info about the
	 * SLW image in the struct proc_chip for use by the slw.c code
	 */
	if (proc_gen == proc_gen_p8 &&
	    read_pba_bar(chip, bar_slw, &sbase, &ssize)) {
		prlog(PR_DEBUG, "  SLW Image at 0x%llx size %lldMB\n",
		      sbase, ssize / 0x100000);

		/*
		 * Only reserve it if we have no homer image or if it
		 * doesn't fit in it (only check the base).
		 */
		if ((sbase < hbase || sbase > (hbase + hsize) ||
				(hbase == 0 && sbase > 0)) &&
				!mem_range_is_reserved(sbase, ssize)) {
			prlog(PR_WARNING,
				"SLW image is not reserved! Reserving\n");
			mem_reserve_fw("ibm,slw-image", sbase, ssize);
		}

		chip->slw_base = sbase;
		chip->slw_bar_size = ssize;
		chip->slw_image_size = ssize; /* will be adjusted later */
	}

	if (read_pba_bar(chip, bar_occ_common, &obase, &osize)) {
		prlog(PR_DEBUG, "  OCC Common Area at 0x%llx size %lldMB\n",
		      obase, osize / 0x100000);
		chip->occ_common_base = obase;
		chip->occ_common_size = osize;
	}
}


static void host_services_occ_base_setup(void)
{
	struct proc_chip *chip;
	uint64_t occ_common;

	chip = next_chip(NULL); /* Frist chip */
	occ_common = (uint64_t) local_alloc(chip->id, OCC_COMMON_SIZE, OCC_COMMON_SIZE);

	for_each_chip(chip) {
		chip->occ_common_base = occ_common;
		chip->occ_common_size = OCC_COMMON_SIZE;

		chip->homer_base = (uint64_t) local_alloc(chip->id, HOMER_IMAGE_SIZE,
							HOMER_IMAGE_SIZE);
		chip->homer_size = HOMER_IMAGE_SIZE;
		memset((void *)chip->homer_base, 0, chip->homer_size);

		prlog(PR_DEBUG, "HBRT: Chip %d HOMER base %016llx : %08llx\n",
		      chip->id, chip->homer_base, chip->homer_size);
		prlog(PR_DEBUG, "HBRT: OCC common base %016llx : %08llx\n",
		      chip->occ_common_base, chip->occ_common_size);
	}
}

void homer_init(void)
{
	struct proc_chip *chip;

	if (chip_quirk(QUIRK_NO_PBA))
		return;

	switch (proc_gen) {
	case proc_gen_p8:
		pba_bar0 = P8_PBA_BAR0;
		pba_barmask0 = P8_PBA_BARMASK0;
		bar_homer = P8_BAR_HOMER;
		bar_slw = P8_BAR_SLW;
		bar_occ_common = P8_BAR_OCC_COMMON;
		break;
	case proc_gen_p9:
		pba_bar0 = P9_PBA_BAR0;
		pba_barmask0 = P9_PBA_BARMASK0;
		bar_homer = P9_BAR_HOMER;
		bar_occ_common = P9_BAR_OCC_COMMON;
		break;
	default:
		return;
	};

	/*
	 * XXX This is temporary, on P8 we look for any configured
	 * SLW/OCC BAR and reserve the memory. Eventually, this will be
	 * done via HostBoot using the device-tree "reserved-ranges"
	 * or we'll load the SLW & OCC images ourselves using Host Services.
	 */
	for_each_chip(chip) {
		prlog(PR_DEBUG, "HOMER: Init chip %d\n", chip->id);
		homer_init_chip(chip);
	}

	/*
	 * Check is PBA BARs are already loaded with HOMER and
	 * skip host services.
	 */

	chip = next_chip(NULL);
	/* Both HOMER images and OCC areas are setup */
	if (chip->homer_base && chip->occ_common_base) {
		/* Reserve OCC common area from BAR */
		if (!mem_range_is_reserved(chip->occ_common_base,
					chip->occ_common_size)) {
			prlog(PR_WARNING,
				"OCC common area is not reserved! Reserving\n");
			mem_reserve_fw("ibm,occ-common-area",
						chip->occ_common_base,
						chip->occ_common_size);
		}
	} else if (chip->homer_base) {
		/*
		 * HOMER is setup but not OCC!! Do not allocate HOMER
		 * regions.  This case is possible during early system
		 * bringup where OCC images are not yet operational.
		 */
	} else {
		/* Allocate memory for HOMER and OCC common area */
		host_services_occ_base_setup();
	}
}

