/* SPDX-License-Identifier: GPL-2.0+
 *
 * Copyright 2019 Broadcom Ltd.
 */
/*
<:copyright-BRCM:2013:DUAL/GPL:standard

   Copyright (c) 2013 Broadcom 
   All Rights Reserved

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2, as published by
the Free Software Foundation (the "GPL").

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.


A copy of the GPL is available at http://www.broadcom.com/licenses/GPLv2.php, or by
writing to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.

:>
*/

#include <debug.h>
#include <platform_def.h>
#include <delay_timer.h>
#include "pmc_drv.h"
#include "BPCM.h"
#include "command.h"

#if IS_BCMCHIP(6858) || defined(PMC_IMPL_3_X)
#include "clk_rst.h"
#endif

int pmc_mode = PMC_MODE_DQM;

static int SendAndWait(TCommand * cmd, TCommand * rsp)
{
#if defined(PMC_ON_HOSTCPU)
	rsp->u.cmdGenericParams.params[0] = 0;
	rsp->u.cmdGenericParams.params[1] = 0;
	return 0;
#else
#if IS_BCMCHIP(63138)
	/* translate new cmdID into old cmdID that the pmc will understand NB:
	 * requires cmdIDs below to be the new versions
	 */

	static const unsigned char newToOldcmdIDMap[] = {
		[cmdSetRunState] = 64,	// cmdSetRunState,
		[cmdSetPowerState] = 65,	// cmdSetPowerState,
		[cmdShutdownAllowed] = 66,	// cmdShutdownAllowed,
		[cmdGetSelect0] = 67,	// cmdGetSelect0,
		[cmdGetSelect3] = 68,	// cmdGetSelect3,
		[cmdGetAvsDisableState] = 69,	// cmdGetAvsDisableState,
		[cmdGetPVT] = 70,	// cmdGetPVT,
		[cmdPowerDevOnOff] = 129,	// cmdPowerDevOnOff,
		[cmdPowerZoneOnOff] = 130,	// cmdPowerZoneOnOff,
		[cmdResetDevice] = 131,	// cmdResetDevice,
		[cmdResetZone] = 132,	// cmdResetZone,
		[cmdAllocateG2UDQM] = 133,	// cmdAllocateG2UDQM,
		[cmdQSMAvailable] = 134,	// cmdQSMAvailable,
		[cmdRevision] = 135,	// cmdRevision,
	};

	static int pmc_remap = 0;
#endif
	static uint32_t reqdID = 1;
	int status = kPMC_COMMAND_TIMEOUT;
	TCommand dummy;

#if defined(BOOT_MEMC_SRAM)
	reqdID = 1;
#endif
	pmc_spin_lock();

	/* clear previous rsp data if any */
	while (PMC->dqm.notEmptySts & PMC_DQM_RPL_STS) {
		if (!rsp)
			rsp = &dummy;

		rsp->word0.Reg32 = PMC->dqmQData[PMC_DQM_RPL_NUM].word[0];
		rsp->word1.Reg32 = PMC->dqmQData[PMC_DQM_RPL_NUM].word[1];
		rsp->u.cmdGenericParams.params[0] =
		    PMC->dqmQData[PMC_DQM_RPL_NUM].word[2];
		rsp->u.cmdGenericParams.params[1] =
		    PMC->dqmQData[PMC_DQM_RPL_NUM].word[3];

		printk
		    ("PMC reqdID=%d previous rsp.word[0-3]=0x[%08x %08x %08x %08x] status=%d\n",
		     reqdID, rsp->word0.Reg32, rsp->word1.Reg32,
		     rsp->u.cmdGenericParams.params[0],
		     rsp->u.cmdGenericParams.params[1], rsp->word0.Bits.error);
	}

#if IS_BCMCHIP(63138)
	if (pmc_remap && cmd->word0.Bits.cmdID < sizeof newToOldcmdIDMap &&
	    newToOldcmdIDMap[cmd->word0.Bits.cmdID])
		cmd->word0.Bits.cmdID = newToOldcmdIDMap[cmd->word0.Bits.cmdID];
#endif

#ifdef PMC_LOG_IN_DTCM
	if (cmd->word0.Bits.cmdID == cmdCloseAVS)
		PMC->ctrl.hostMboxOut = 1; // request sync dtcm log
#endif

	cmd->word0.Bits.msgID = reqdID;

	/* send the command */
	PMC->dqmQData[PMC_DQM_REQ_NUM].word[0] = cmd->word0.Reg32;
	PMC->dqmQData[PMC_DQM_REQ_NUM].word[1] = cmd->word1.Reg32;
	PMC->dqmQData[PMC_DQM_REQ_NUM].word[2] =
	    cmd->u.cmdGenericParams.params[0];
	PMC->dqmQData[PMC_DQM_REQ_NUM].word[3] =
	    cmd->u.cmdGenericParams.params[1];

#ifdef CONFIG_BRCM_IKOS
	/* We do not enable PMC TIMER here for IKOS, or it will wait forever */
	while (!(PMC->dqm.notEmptySts & PMC_DQM_RPL_STS)) ;
#elif defined(PMC_IMPL_3_X)
#ifdef PMC_LOG_IN_DTCM
	if (cmd->word0.Bits.cmdID == cmdCloseAVS) {
		while (!(PMC->dqm.notEmptySts & PMC_DQM_RPL_STS))
			pmc_show_log_item();

		PMC->ctrl.hostMboxOut = 0; // ignore dtcm log
	} 
	else
#endif // #ifdef PMC_LOG_IN_DTCM
	{
		PMC->ctrl.gpTmr0Ctl = ((1 << 31) | (1 << 29) |
				((400000 << 1) & 0x1fffffff));	// 400ms

		while (!(PMC->dqm.notEmptySts & PMC_DQM_RPL_STS) &&
				(PMC->ctrl.gpTmr0Ctl & (1 << 31))) ;
	}
#else
	PMC->ctrl.gpTmr2Ctl = ((1 << 31) | (1 << 29) | 400000);	// 400ms

	while (!(PMC->dqm.notEmptySts & PMC_DQM_RPL_STS) &&
	       (PMC->ctrl.gpTmr2Ctl & (1 << 31))) {
#if !defined(_CFE_) && (IS_BCMCHIP(63148) || IS_BCMCHIP(4908))
		/* Do not tight poll the PMC registers for longer command */
		if (cmd->word0.Bits.cmdID == cmdCloseAVS)
			udelay(1000);
#endif
	}
#endif /* CONFIG_BRCM_IKOS */

	if (PMC->dqm.notEmptySts & PMC_DQM_RPL_STS) {
		if (!rsp)
			rsp = &dummy;

		/* command didn't timeout, fill in the response */
		rsp->word0.Reg32 = PMC->dqmQData[PMC_DQM_RPL_NUM].word[0];
		rsp->word1.Reg32 = PMC->dqmQData[PMC_DQM_RPL_NUM].word[1];
		rsp->u.cmdGenericParams.params[0] =
		    PMC->dqmQData[PMC_DQM_RPL_NUM].word[2];
		rsp->u.cmdGenericParams.params[1] =
		    PMC->dqmQData[PMC_DQM_RPL_NUM].word[3];

		if (rsp->word0.Bits.msgID == reqdID)
			status = rsp->word0.Bits.error;
		else
			status = kPMC_MESSAGE_ID_MISMATCH;

		if (status != kPMC_NO_ERROR)
			printk
			    ("PMC reqdID=%d error=%d rsp.word[0-3]=0x[%08x %08x %08x %08x]\n",
			     reqdID, status, rsp->word0.Reg32, rsp->word1.Reg32,
			     rsp->u.cmdGenericParams.params[0],
			     rsp->u.cmdGenericParams.params[1]);
	}

	reqdID = (reqdID + 1) & 0xff;

	pmc_spin_unlock();

	return status;
#endif
}

static int SendCmd(TCommand * cmd, int cmdID, int devAddr, int zone, int island,
		   TCommand * rsp)
{
	cmd->word0.Reg32 = 0;
	cmd->word0.Bits.cmdID = cmdID;
	cmd->word1.Reg32 = 0;
	cmd->word1.Bits.devAddr = devAddr;
	cmd->word1.Bits.zoneIdx = zone;
	cmd->word1.Bits.island = island;

	return SendAndWait(cmd, rsp);
}

int SendCommand(int cmdID, int devAddr, int zone, int island, uint32_t word2,
		uint32_t word3, TCommand * rsp)
{
	TCommand cmd;

	cmd.u.cmdGenericParams.params[0] = word2;
	cmd.u.cmdGenericParams.params[1] = word3;

	return SendCmd(&cmd, cmdID, devAddr, zone, island, rsp);
}


#if defined(PMC_IMPL_3_X) || defined(PMC_ON_HOSTCPU)
#ifdef PMC_ON_HOSTCPU
#define KEYHOLE_IDX 1
#else
#define KEYHOLE_IDX 0
#endif
int read_bpcm_reg_direct(int devAddr, int wordOffset, uint32_t * value)
{
	int status = kPMC_NO_ERROR;
	int bus = (devAddr >> PMB_BUS_ID_SHIFT) & 0x3;
	uint32_t address, ctlSts;
	volatile PMB_keyhole_reg *keyhole = &PMB->keyhole[KEYHOLE_IDX];


	address =
	    ((devAddr & 0xff) *
	     ((PMB->
	       config >> PMB_NUM_REGS_SHIFT) & PMB_NUM_REGS_MASK)) |
	    (wordOffset);

	keyhole->control =
	    PMC_PMBM_START | (bus << PMC_PMBM_BUS_SHIFT) | (PMC_PMBM_Read) |
	    address;
	ctlSts = keyhole->control;
	while (ctlSts & PMC_PMBM_BUSY)
		ctlSts = keyhole->control;	/*wait for completion */

	if (ctlSts & PMC_PMBM_TIMEOUT)
		status = kPMC_COMMAND_TIMEOUT;
	else
		*value = keyhole->rd_data;

	return status;
}

int write_bpcm_reg_direct(int devAddr, int wordOffset, uint32_t value)
{
	int status = kPMC_NO_ERROR;
	int bus = (devAddr >> PMB_BUS_ID_SHIFT) & 0x3;
	uint32_t address, ctlSts;
	volatile PMB_keyhole_reg *keyhole = &PMB->keyhole[KEYHOLE_IDX];

	address =
	    ((devAddr & 0xff) *
	     ((PMB->
	       config >> PMB_NUM_REGS_SHIFT) & PMB_NUM_REGS_MASK)) |
	    (wordOffset);
	keyhole->wr_data = value;
	keyhole->control =
	    PMC_PMBM_START | (bus << PMC_PMBM_BUS_SHIFT) | (PMC_PMBM_Write) |
	    address;

	ctlSts = keyhole->control;
	while (ctlSts & PMC_PMBM_BUSY)
		ctlSts = keyhole->control;	/*wait for completion */

	if (ctlSts & PMC_PMBM_TIMEOUT)
		status = kPMC_COMMAND_TIMEOUT;

	return status;
}


#else // #if defined(PMC_IMPL_3_X) || defined(PMC_ON_HOSTCPU)
int read_bpcm_reg_direct(int devAddr, int wordOffset, uint32_t * value)
{
	int status = kPMC_NO_ERROR;
	int bus = (devAddr >> PMB_BUS_ID_SHIFT) & 0x3;
	volatile PMBMaster *pmbm_ptr;

	if (bus >= PMB_BUS_MAX)
		return kPMC_INVALID_BUS;

	pmbm_ptr = &(PROCMON->PMBM[bus]);

	/* Make sure PMBM is not busy */

	pmbm_ptr->ctrl = PMC_PMBM_START | PMC_PMBM_Read |
	    ((devAddr & 0xff) << 12) | wordOffset;

	while (pmbm_ptr->ctrl & PMC_PMBM_START) ;

	if (pmbm_ptr->ctrl & PMC_PMBM_TIMEOUT)
		status = kPMC_COMMAND_TIMEOUT;
	else
		*value = pmbm_ptr->rd_data;

	return status;
}

int write_bpcm_reg_direct(int devAddr, int wordOffset, uint32_t value)
{
	int bus = (devAddr >> PMB_BUS_ID_SHIFT) & 0x3;
	int status = kPMC_NO_ERROR;
	volatile PMBMaster *pmbm_ptr;
	if (bus >= PMB_BUS_MAX)
		return kPMC_INVALID_BUS;

	pmbm_ptr = &(PROCMON->PMBM[bus]);

	pmbm_ptr->wr_data = value;
	pmbm_ptr->ctrl = PMC_PMBM_START | PMC_PMBM_Write |
	    ((devAddr & 0xff) << 12) | wordOffset;

	while (pmbm_ptr->ctrl & PMC_PMBM_START) ;

	if (pmbm_ptr->ctrl & PMC_PMBM_TIMEOUT)
		status = kPMC_COMMAND_TIMEOUT;

	return status;
}
#endif // #if defined(PMC_IMPL_3_X) || defined(PMC_ON_HOSTCPU)

/* note: all the [Read|Write][BPCM|Zone]Register functions are different from
 * how they are defined in firmware code.  In the driver code, it takes in
 * wordOffset as the argument, but in the firmware code, it uses byteOffset */
int ReadBPCMRegister(int devAddr, int wordOffset, uint32_t * value)
{
	int status = kPMC_INVALID_STATE;

	if (PMC_ACCESS_BPCM_DIRECT || pmc_mode == PMC_MODE_PMB_DIRECT) {
		pmc_spin_lock();
		status = read_bpcm_reg_direct(devAddr, wordOffset, value);
		pmc_spin_unlock();
	} else if (pmc_mode == PMC_MODE_DQM) {
		TCommand rsp;
		status =
		    SendCommand(cmdReadBpcmReg, devAddr, 0, 0, wordOffset, 0,
				&rsp);

		if (status == kPMC_NO_ERROR)
			*value = rsp.u.cmdResponse.word2;
	}

	return status;
}

int WriteBPCMRegister(int devAddr, int wordOffset, uint32_t value)
{
	int status = kPMC_INVALID_STATE;

	if (PMC_ACCESS_BPCM_DIRECT || pmc_mode == PMC_MODE_PMB_DIRECT) {
		pmc_spin_lock();
		status = write_bpcm_reg_direct(devAddr, wordOffset, value);
		pmc_spin_unlock();
	} else if (pmc_mode == PMC_MODE_DQM) {
		status =
		    SendCommand(cmdWriteBpcmReg, devAddr, 0, 0, wordOffset,
				value, 0);
	}

	return status;
}


int PowerOnZone(int devAddr, int zone)
{
	BPCM_PWR_ZONE_N_CONTROL reg;
	int status;

#if IS_BCMCHIP(6858)
	/* Do not use DQM command cmdPowerZoneOnOff for non 6858 because this command is only available if a
	   PMC application has been uploaded to expand the PMC boot rom functionality */
	if (pmc_mode == PMC_MODE_DQM) {
		TCommand cmd = {0};
		cmd.u.cmdStateOnlyParam.state = 1;
		return SendCmd(&cmd, cmdPowerZoneOnOff, devAddr, zone, 0, 0);
	}
#endif

	status =
	    ReadBPCMRegister(devAddr, BPCMRegOffset(zones[zone].control),
			     &reg.Reg32);
	if (status == kPMC_NO_ERROR && reg.Bits.pwr_on_state == 0) {
		reg.Bits.pwr_dn_req = 0;
		reg.Bits.dpg_ctl_en = 1;
		reg.Bits.pwr_up_req = 1;
		reg.Bits.mem_pwr_ctl_en = 1;
		reg.Bits.blk_reset_assert = 1;
		status =
		    WriteBPCMRegister(devAddr,
				      BPCMRegOffset(zones[zone].control),
				      reg.Reg32);
	}
	return status;
}

int PowerOnDevice(int devAddr)
{
	if (PMC_ACCESS_BPCM_DIRECT || pmc_mode == PMC_MODE_PMB_DIRECT) {
		int ix, status;
		BPCM_CAPABILITES_REG capabilities;

		status =
		    ReadBPCMRegister(devAddr, BPCMRegOffset(capabilities),
				     &capabilities.Reg32);
		for (ix = 0;
		     (ix < capabilities.Bits.num_zones)
		     && (status == kPMC_NO_ERROR); ix++) {
			status = PowerOnZone(devAddr, ix);
		}

		return status;
	}

	if (pmc_mode == PMC_MODE_DQM) {
		TCommand cmd = {0};
		cmd.u.cmdPowerDevice.state = 1;
		return SendCmd(&cmd, cmdPowerDevOnOff, devAddr, 0, 0, 0);
	}

	return kPMC_INVALID_STATE;
}

int PowerOffDevice(int devAddr, int repower)
{
	if (PMC_ACCESS_BPCM_DIRECT || pmc_mode == PMC_MODE_PMB_DIRECT) {
		/* we can power off the entire device by powering off the 0th zone. */
		BPCM_PWR_ZONE_N_CONTROL reg;
		int status;

		status =
		    ReadBPCMRegister(devAddr, BPCMRegOffset(zones[0].control),
				     &reg.Reg32);

		if (status == kPMC_NO_ERROR && reg.Bits.pwr_off_state == 0) {
			reg.Bits.pwr_dn_req = 1;
			WriteBPCMRegister(devAddr,
					  BPCMRegOffset(zones[0].control),
					  reg.Reg32);
		}

		return status;
	}

	if (pmc_mode == PMC_MODE_DQM) {
		TCommand cmd = {0};
		cmd.u.cmdPowerDevice.state = 0;
		cmd.u.cmdPowerDevice.restore = repower;
		return SendCmd(&cmd, cmdPowerDevOnOff, devAddr, 0, 0, 0);
	}

	return kPMC_INVALID_STATE;
}

int ResetDevice(int devAddr)
{
	/* all zones had their blk_reset_assert bits set at initial config time */
	BPCM_PWR_ZONE_N_CONTROL reg;
	int status;

#if IS_BCMCHIP(6858)
	/* Do not use DQM command cmdResetDevice for non 6858 because this command is only available if a
	   PMC application has been uploaded to expand the PMC boot rom functionality */
	if (pmc_mode == PMC_MODE_DQM)
		return SendCommand(cmdResetDevice, devAddr, 0, 0, 0, 0, 0);
#endif

	status = PowerOffDevice(devAddr, 0);
	do {
		status =
		    ReadBPCMRegister(devAddr, BPCMRegOffset(zones[0].control),
				     &reg.Reg32);
	} while ((reg.Bits.pwr_off_state != 1) && (status == kPMC_NO_ERROR));
	if (status == kPMC_NO_ERROR)
		status = PowerOnDevice(devAddr);
	return status;
}

// initalize pmc_mode (possibly) before printk available
void pmc_initmode(void)
{
#if defined MISC_STRAP_BUS_PMC_ROM_BOOT
	/* read the strap pin and based on the strap pin, choose the mode */
	if ((MISC->miscStrapBus & MISC_STRAP_BUS_PMC_ROM_BOOT) == 0)
		pmc_mode = PMC_MODE_PMB_DIRECT;
#else // #if defined MISC_STRAP_BUS_PMC_ROM_BOOT
#if IS_BCMCHIP(6858)
	pmc_mode = PMC_MODE_DQM;
#else
	pmc_mode = PMC_MODE_PMB_DIRECT;
#ifdef CFG_RAMAPP
#if IS_BCMCHIP(63158)
	/* MIPS based PMC */
	if (PMC->ctrl.softResets == 0)
		pmc_mode = PMC_MODE_DQM;
#elif defined(PMC_IMPL_3_X)
	/* Maestro based PMC) */
	if (PROCMON->maestroReg.coreCtrl.coreEnable == 1)
		pmc_mode = PMC_MODE_DQM;
#endif // #if IS_BCMCHIP(63158)
#endif // #ifdef CFG_RAMAPP
#endif // #if IS_BCMCHIP(6858)
#endif // #if defined MISC_STRAP_BUS_PMC_ROM_BOOT
}




