/* Copyright 2014-2016 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
 * imitations under the License.
 */

#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <time.h>

#include "xscom.h"

#define DBG(fmt...)	do { if (verbose) printf(fmt); } while(0)
#define ERR(fmt...)	do { fprintf(stderr, fmt); } while(0)

#define OCB_PIB_BASE_P8 0x0006B000
#define OCB_PIB_BASE_P9 0x0006D000

#define OCBCSR0         0x11
#define OCBCSR0_AND     0x12
#define OCBCSR0_OR      0x13
#define   OCB_STREAM_MODE			PPC_BIT(4)
#define   OCB_STREAM_TYPE			PPC_BIT(5)
#define OCBAR0          0x10
#define OCBDR0          0x15

#define PVR_TYPE_P8E    0x004b /* Murano */
#define PVR_TYPE_P8     0x004d /* Venice */
#define PVR_TYPE_P8NVL  0x004c /* Naples */
#define PVR_TYPE_P9     0x004e
#define PVR_TYPE_P9P    0x004f /* Axone */

#ifdef __powerpc__
static uint64_t get_xscom_base(void)
{
	unsigned int pvr;

	asm volatile("mfpvr %0" : "=r" (pvr));

	switch (pvr >> 16) {
	case PVR_TYPE_P9:
	case PVR_TYPE_P9P:
		return OCB_PIB_BASE_P9;

	case PVR_TYPE_P8E:
	case PVR_TYPE_P8:
	case PVR_TYPE_P8NVL:
		return OCB_PIB_BASE_P8;
	}

	ERR("Unknown processor, exiting\n");
	exit(1);
	return 0;
}
#else
/* Just so it compiles on x86 */
static uint64_t get_xscom_base(void) { return 0; }
#endif

int sram_read(uint32_t chip_id, int chan, uint32_t addr, uint64_t *val)
{
	uint64_t sdat, base = get_xscom_base();
	uint32_t coff = chan * 0x20;
	int rc;

	/* Read for debug purposes */
	rc = xscom_read(chip_id, base + OCBCSR0 + coff, &sdat);
	if (rc) {
		ERR("xscom OCBCSR0 read error %d\n", rc);
		return -1;
	}

	/* Create an AND mask to clear bit 4 and 5 and poke the AND register */
	sdat = ~(OCB_STREAM_MODE | OCB_STREAM_TYPE);
	rc = xscom_write(chip_id, base + OCBCSR0_AND + coff, sdat);
	if (rc) {
		ERR("xscom OCBCSR0_AND write error %d\n", rc);
		return -1;
	}

	sdat = ((uint64_t)addr) << 32;
	rc = xscom_write(chip_id, base + OCBAR0 + coff, sdat);
	if (rc) {
		ERR("xscom OCBAR0 write error %d\n", rc);
		return -1;
	}

	rc = xscom_read(chip_id, base + OCBDR0 + coff, val);
	if (rc) {
		ERR("xscom OCBDR0 read error %d\n", rc);
		return -1;
	}
	return 0;
}

int sram_write(uint32_t chip_id, int chan, uint32_t addr, uint64_t val)
{
	uint64_t sdat, base = get_xscom_base();
	uint32_t coff = chan * 0x20;
	int rc;

#if 0
	if (dummy) {
		printf("[dummy] write chip %d OCC sram 0x%08x = %016lx\n",
		       chip_id, addr, val);
		return 0;
	}
#endif

	/* Read for debug purposes */
	rc = xscom_read(chip_id, base + OCBCSR0 + coff, &sdat);
	if (rc) {
		ERR("xscom OCBCSR0 read error %d\n", rc);
		return -1;
	}

	/* Create an AND mask to clear bit 4 and 5 and poke the AND register */
	sdat = ~(OCB_STREAM_MODE | OCB_STREAM_TYPE);
	rc = xscom_write(chip_id, base + OCBCSR0_AND + coff, sdat);
	if (rc) {
		ERR("xscom OCBCSR0_AND write error %d\n", rc);
		return -1;
	}

	sdat = ((uint64_t)addr) << 32;
	rc = xscom_write(chip_id, base + OCBAR0 + coff, sdat);
	if (rc) {
		ERR("xscom OCBAR0 write error %d\n", rc);
		return -1;
	}

	rc = xscom_write(chip_id, base + OCBDR0 + coff, val);
	if (rc) {
		ERR("xscom OCBDR0 write error %d\n", rc);
		return -1;
	}
	return 0;
}
