/* Copyright 2016 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "common.h"
#include "console.h"
#include "init_chip.h"
#include "registers.h"
#include "task.h"

#define CPRINTS(format, args...) cprints(CC_USB, format, ## args)

void init_jittery_clock(int highsec)
{
	unsigned trimfast = GR_FUSE(RC_JTR_OSC60_CC_TRIM);
	unsigned trim48 = GR_FUSE(RC_JTR_OSC48_CC_TRIM);
	unsigned delta = (trim48 - trimfast);
	/* For metastability reasons, avoid clk_jtr ~= clk_timer, make
	 * a keepout region around 24MHz of about 0.75MHz, about 3/16 of the
	 * the delta from trimfast and trim48 */
	unsigned skiplow = (trim48 << 4) - (delta * 6);
	unsigned skiphigh = (trim48 << 4) + (delta * 6);
	unsigned setting = trimfast << 4;
	unsigned stepx16;
	unsigned bankval;
	int bank;

	if (highsec)
		stepx16 = (delta * 7) >> 1;
	else
		stepx16 = 2 * (trim48 - trimfast);

	for (bank = 0; bank < 16; bank++) {
		/* saturate at 0xff */
		bankval = (setting > 0xfff) ? 0xff : (setting >> 4);

		if (runlevel_is_high())
			GR_XO_JTR_JITTERY_TRIM_BANK(bank) = bankval;

		setting += stepx16;
		if ((setting > skiplow) && (setting < skiphigh))
			setting = skiphigh;
	}

	GWRITE_FIELD(XO, CLK_JTR_TRIM_CTRL, RC_COARSE_TRIM_SRC, 2);
	GWRITE_FIELD(XO, CLK_JTR_TRIM_CTRL, RC_INITIAL_TRIM_PERIOD, 100);
	GWRITE_FIELD(XO, CLK_JTR_TRIM_CTRL, RC_TRIM_EN, 1);
	GREG32(XO, CLK_JTR_JITTERY_TRIM_EN) = 1;
	GREG32(XO, CLK_JTR_SYNC_CONTENTS) = 0;

	/* Writing any value locks things until the next hard reboot */
	/* crosbug.com/p/54916
	   GREG32(XO, CFG_WR_EN) = 0;
	   GREG32(XO, JTR_CTRL_EN) = 0;
	*/
}

void init_sof_clock(void)
{
	/* Copy fuse value into software registers, both coarse and fine */
	unsigned coarseTrimVal = GR_FUSE(RC_TIMER_OSC48_CC_TRIM);
	unsigned fineTrimVal = GR_FUSE(RC_TIMER_OSC48_FC_TRIM);

	/* We think SOF toggle happens once every mS, or ~24000 clock ticks */
	unsigned targetCnt = PCLK_FREQ / 1000;

	/* The possible operations of a particular calibration bucket */
	unsigned binaryDnOp = 0x1 | 0x1 << 4;
	unsigned binaryUpOp = 0x1 | 0x0 << 4;
	unsigned subOp      = 0x3 | 0x1 << 4;
	unsigned addOp      = 0x2 | 0x1 << 4;
	unsigned nop        = 0;

	GREG32(XO, CLK_TIMER_RC_COARSE_ATE_TRIM) = coarseTrimVal;
	GREG32(XO, CLK_TIMER_RC_FINE_ATE_TRIM) = fineTrimVal;

	/* Coarse trim values come from software */
	GWRITE_FIELD(XO, CLK_TIMER_TRIM_CTRL, RC_COARSE_TRIM_SRC, 0);

	/* enable error interrupts
	 * This enables underrun and overflow interrupts */
	GREG32(XO, DXO_INT_ENABLE) = 0xC;

	/* Setup SOF calibration buckets and associated operations */
	GREG32(XO, CLK_TIMER_SLOW_CALIB0) = targetCnt * 70 / 100;
	GREG32(XO, CLK_TIMER_SLOW_CALIB1) = targetCnt * 80 / 100;
	GREG32(XO, CLK_TIMER_SLOW_CALIB2) = targetCnt * 90 / 100;
	GREG32(XO, CLK_TIMER_SLOW_CALIB3) =
		targetCnt * (1000000 - 1250) / 1000000;
	GREG32(XO, CLK_TIMER_SLOW_CALIB4) = targetCnt;
	GREG32(XO, CLK_TIMER_SLOW_CALIB5) =
		targetCnt * (1000000 + 1250) / 1000000;
	GREG32(XO, CLK_TIMER_SLOW_CALIB6) = targetCnt * 110 / 100;
	GREG32(XO, CLK_TIMER_SLOW_CALIB7) = targetCnt * 120 / 100;

	/* This is a work-around for the screwy SOF */
	GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL0) = nop;
	GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL1) = binaryDnOp;
	GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL2) = binaryDnOp;
	GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL3) = subOp;
	GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL4) = nop;
	GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL5) = nop;
	GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL6) = addOp;
	GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL7) = binaryUpOp;
	GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL8) = binaryUpOp;

	/* Set the calibration mode */
	GWRITE_FIELD(XO, CLK_TIMER_CALIB_TRIM_CTRL, ENABLE_FAST, 0);
	GWRITE_FIELD(XO, CLK_TIMER_CALIB_TRIM_CTRL, ENABLE_SLOW, 1);
	GWRITE_FIELD(XO, CLK_TIMER_CALIB_TRIM_CTRL, SLOW_MODE_SEL, 0); /* SOF */
	GWRITE_FIELD(XO, CLK_TIMER_CALIB_TRIM_CTRL, MAX_TRIM_SEL, 1);
	/* Don't stop when a NOP operation is seen, keep on calibrating */
	GWRITE_FIELD(XO, CLK_TIMER_CALIB_TRIM_CTRL, STOP_ON_NOP, 0);

	/* Set source of trim codes:
	 * coarse trim comes from software
	 * fine trim comes from calibration engine */
	GWRITE_FIELD(XO, CLK_TIMER_TRIM_CTRL, RC_COARSE_TRIM_SRC, 0);
	GWRITE_FIELD(XO, CLK_TIMER_TRIM_CTRL, RC_FINE_TRIM_SRC, 1);

	/* Enable dynamic trim */
	GWRITE_FIELD(XO, CLK_TIMER_TRIM_CTRL, RC_TRIM_EN, 1);

	/* Sync everything! */
	GREG32(XO, CLK_TIMER_SYNC_CONTENTS) = 1;

	/* Enable interrupts */
	task_enable_irq(GC_IRQNUM_XO0_SLOW_CALIB_UNDERRUN_INT);
	task_enable_irq(GC_IRQNUM_XO0_SLOW_CALIB_OVERFLOW_INT);
}

/* When the calibration under runs, it means the fine trim code
 * has reached 0, but the clock is still too slow.  Thus,
 * software must reduce the coarse trim code by 1 */
static void timer_sof_calibration_underrun_int(void)
{
	unsigned coarseTrimValue = GREG32(XO, CLK_TIMER_RC_COARSE_ATE_TRIM);

	if (coarseTrimValue > 0x00) {
		CPRINTS("%s: 0x%02x", __func__, coarseTrimValue);
		GREG32(XO, CLK_TIMER_RC_COARSE_ATE_TRIM) = coarseTrimValue - 1;
	}

	GREG32(XO, DXO_INT_STATE) =
		GC_XO_DXO_INT_STATE_SLOW_CALIB_UNDERRUN_MASK;
}
DECLARE_IRQ(GC_IRQNUM_XO0_SLOW_CALIB_UNDERRUN_INT,
	    timer_sof_calibration_underrun_int, 1);

/* When the calibration overflows, it means the fine trim code
 * has reached 0x1F, but the clock is still too fast.  Thus,
 * software must increase the coarse trim code by 1 */
static void timer_sof_calibration_overflow_int(void)
{
	unsigned coarseTrimValue = GREG32(XO, CLK_TIMER_RC_COARSE_ATE_TRIM);

	/* Coarse trim range is 0..0xff. */
	if (coarseTrimValue < 0xff) {
		CPRINTS("%s: 0x%02x", __func__, coarseTrimValue);
		GREG32(XO, CLK_TIMER_RC_COARSE_ATE_TRIM) = coarseTrimValue + 1;
	}

	GREG32(XO, DXO_INT_STATE) =
		GC_XO_DXO_INT_STATE_SLOW_CALIB_OVERFLOW_MASK;
}
DECLARE_IRQ(GC_IRQNUM_XO0_SLOW_CALIB_OVERFLOW_INT,
	    timer_sof_calibration_overflow_int, 1);

#ifdef DEBUG_ME
static int command_sof(int argc, char **argv)
{
	ccprintf("FUSE_RC_TIMER_OSC48_CC_TRIM) 0x%08x\n",
		 GR_FUSE(RC_TIMER_OSC48_CC_TRIM));
	ccprintf("FUSE_RC_TIMER_OSC48_FC_TRIM) 0x%08x\n",
		 GR_FUSE(RC_TIMER_OSC48_FC_TRIM));

	ccprintf("CLK_TIMER_RC_COARSE_ATE_TRIM 0x%08x\n",
		 GREG32(XO, CLK_TIMER_RC_COARSE_ATE_TRIM));
	ccprintf("CLK_TIMER_RC_FINE_ATE_TRIM   0x%08x\n",
		 GREG32(XO, CLK_TIMER_RC_FINE_ATE_TRIM));

	ccprintf("CLK_TIMER_TRIM_CTRL          0x%08x\n",
		 GREG32(XO, CLK_TIMER_TRIM_CTRL));

	ccprintf("CLK_TIMER_CALIB_TRIM_CTRL    0x%08x\n",
		 GREG32(XO, CLK_TIMER_CALIB_TRIM_CTRL));

	ccprintf("DXO_INT_ENABLE               0x%08x\n",
		 GREG32(XO, DXO_INT_ENABLE));

	ccprintf("CLK_TIMER_SLOW_CALIB\n");
	ccprintf("  0: 0x%04x\n", GREG32(XO, CLK_TIMER_SLOW_CALIB0));
	ccprintf("  1: 0x%04x\n", GREG32(XO, CLK_TIMER_SLOW_CALIB1));
	ccprintf("  2: 0x%04x\n", GREG32(XO, CLK_TIMER_SLOW_CALIB2));
	ccprintf("  3: 0x%04x\n", GREG32(XO, CLK_TIMER_SLOW_CALIB3));
	ccprintf("  4: 0x%04x\n", GREG32(XO, CLK_TIMER_SLOW_CALIB4));
	ccprintf("  5: 0x%04x\n", GREG32(XO, CLK_TIMER_SLOW_CALIB5));
	ccprintf("  6: 0x%04x\n", GREG32(XO, CLK_TIMER_SLOW_CALIB6));
	ccprintf("  7: 0x%04x\n", GREG32(XO, CLK_TIMER_SLOW_CALIB7));

	ccprintf("CLK_TIMER_SLOW_CALIB_CTRL\n");
	ccprintf("  0: 0x%02x\n", GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL0));
	ccprintf("  1: 0x%02x\n", GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL1));
	ccprintf("  2: 0x%02x\n", GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL2));
	ccprintf("  3: 0x%02x\n", GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL3));
	ccprintf("  4: 0x%02x\n", GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL4));
	ccprintf("  5: 0x%02x\n", GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL5));
	ccprintf("  6: 0x%02x\n", GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL6));
	ccprintf("  7: 0x%02x\n", GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL7));
	ccprintf("  8: 0x%02x\n", GREG32(XO, CLK_TIMER_SLOW_CALIB_CTRL8));

	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(sof, command_sof,
			"",
			"Display the SoF clock stuff");
#endif	/* DEBUG_ME */
