/* -*- mode: c; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* vi: set ts=8 sw=8 sts=8: */
/*************************************************************************/ /*!
@Codingstyle    LinuxKernel
@Copyright      Copyright (c) Imagination Technologies Ltd. All Rights Reserved
@License        Dual MIT/GPLv2

The contents of this file are subject to the MIT license as set out below.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 2 ("GPL") in which case the provisions
of GPL are applicable instead of those above.

If you wish to allow use of your version of this file only under the terms of
GPL, and not to allow others to use your version of this file under the terms
of the MIT license, indicate your decision by deleting the provisions above
and replace them with the notice and other provisions required by GPL as set
out in the file called "GPL-COPYING" included in this distribution. If you do
not delete the provisions above, a recipient may use your version of this file
under the terms of either the MIT license or GPL.

This License is also included in this distribution in the file called
"MIT-COPYING".

EXCEPT AS OTHERWISE STATED IN A NEGOTIATED AGREEMENT: (A) THE SOFTWARE IS
PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT; AND (B) IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ /**************************************************************************/

/*
 * This is a device driver for the odin testchip framework. It creates
 * platform devices for the pdp and ext sub-devices, and exports functions
 * to manage the shared interrupt handling
 */

#include <linux/version.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/version.h>
#include <linux/delay.h>

#include "tc_drv_internal.h"
#include "tc_odin.h"
#include "tc_ion.h"

/* Odin (3rd gen TCF FPGA) */
#include "odin_defs.h"
#include "odin_regs.h"
#include "bonnie_tcf.h"

/* Macro's to set and get register fields */
#define REG_FIELD_GET(v, str) \
	(u32)(((v) & (s##_MASK)) >> (s##_SHIFT))
#define REG_FIELD_SET(v, f, str) \
	v = (u32)(((v) & (u32)~(str##_MASK)) | \
		  (u32)(((f) << (str##_SHIFT)) & (str##_MASK)))

#define SAI_STATUS_UNALIGNED 0
#define SAI_STATUS_ALIGNED   1
#define SAI_STATUS_ERROR     2

#if defined(SUPPORT_RGX)

static void spi_write(struct tc_device *tc, u32 off, u32 val)
{
	iowrite32(off, tc->tcf.registers
		  + ODN_REG_BANK_TCF_SPI_MASTER
		  + ODN_SPI_MST_ADDR_RDNWR);
	iowrite32(val, tc->tcf.registers
		  + ODN_REG_BANK_TCF_SPI_MASTER
		  + ODN_SPI_MST_WDATA);
	iowrite32(0x1, tc->tcf.registers
		  + ODN_REG_BANK_TCF_SPI_MASTER
		  + ODN_SPI_MST_GO);
	udelay(1000);
}

static int spi_read(struct tc_device *tc, u32 off, u32 *val)
{
	int cnt = 0;
	u32 spi_mst_status;

	iowrite32(0x40000 | off, tc->tcf.registers
		  + ODN_REG_BANK_TCF_SPI_MASTER
		  + ODN_SPI_MST_ADDR_RDNWR);
	iowrite32(0x1, tc->tcf.registers
		  + ODN_REG_BANK_TCF_SPI_MASTER
		  + ODN_SPI_MST_GO);
	udelay(100);

	do {
		spi_mst_status = ioread32(tc->tcf.registers
					  + ODN_REG_BANK_TCF_SPI_MASTER
					  + ODN_SPI_MST_STATUS);

		if (cnt++ > 10000) {
			dev_err(&tc->pdev->dev,
				"spi_read: Time out reading SPI reg (0x%x)\n",
				off);
			return -1;
		}

	} while (spi_mst_status != 0x08);

	*val = ioread32(tc->tcf.registers
			+ ODN_REG_BANK_TCF_SPI_MASTER
			+ ODN_SPI_MST_RDATA);

	return 0;
}

/* returns 1 for aligned, 0 for unaligned */
static int get_odin_sai_status(struct tc_device *tc, int bank)
{
	void __iomem *bank_addr = tc->tcf.registers
					+ ODN_REG_BANK_SAI_RX_DDR(bank);
	void __iomem *reg_addr;
	u32 eyes;
	u32 clk_taps;
	u32 train_ack;

	reg_addr = bank_addr + ODN_SAI_RX_DEBUG_SAI_EYES;
	eyes = ioread32(reg_addr);

	reg_addr = bank_addr + ODN_SAI_RX_DEBUG_SAI_CLK_TAPS;
	clk_taps = ioread32(reg_addr);

	reg_addr = bank_addr + ODN_SAI_RX_DEBUG_SAI_TRAIN_ACK;
	train_ack = ioread32(reg_addr);

#if 0 /* enable this to get debug info if the board is not aligning */
	dev_info(&tc->pdev->dev,
		"odin bank %d align: eyes=%08x clk_taps=%08x train_ack=%08x\n",
		bank, eyes, clk_taps, train_ack);
#endif

	if (tc_is_interface_aligned(eyes, clk_taps, train_ack))
		return SAI_STATUS_ALIGNED;

	dev_warn(&tc->pdev->dev, "odin bank %d is unaligned\n", bank);
	return SAI_STATUS_UNALIGNED;
}

/* Read the odin multi clocked bank align status.
 * Returns 1 for aligned, 0 for unaligned
 */
static int read_odin_mca_status(struct tc_device *tc)
{
	void __iomem *bank_addr = tc->tcf.registers
					+ ODN_REG_BANK_MULTI_CLK_ALIGN;
	void __iomem *reg_addr = bank_addr + ODN_MCA_DEBUG_MCA_STATUS;
	u32 mca_status;

	mca_status = ioread32(reg_addr);

#if 0 /* Enable this if there are alignment issues */
	dev_info(&tc->pdev->dev,
		"Odin MCA_STATUS = %08x\n", mca_status);
#endif
	return mca_status & ODN_ALIGNMENT_FOUND_MASK;
}

/* Read the DUT multi clocked bank align status.
 * Returns 1 for aligned, 0 for unaligned
 */
static int read_dut_mca_status(struct tc_device *tc)
{
	u32 mca_status;
	const int mca_status_register_offset = 1; /* not in bonnie_tcf.h */
	int spi_address = DWORD_OFFSET(BONNIE_TCF_OFFSET_MULTI_CLK_ALIGN);

	spi_address = DWORD_OFFSET(BONNIE_TCF_OFFSET_MULTI_CLK_ALIGN)
			+ mca_status_register_offset;

	spi_read(tc, spi_address, &mca_status);

#if 0 /* Enable this if there are alignment issues */
	dev_info(&tc->pdev->dev,
		"DUT MCA_STATUS = %08x\n", mca_status);
#endif
	return mca_status & 1;  /* 'alignment found' status is in bit 1 */
}

/* returns 1 for aligned, 0 for unaligned */
static int get_dut_sai_status(struct tc_device *tc, int bank)
{
	u32 eyes;
	u32 clk_taps;
	u32 train_ack;
	const u32 bank_base = DWORD_OFFSET(BONNIE_TCF_OFFSET_SAI_RX_1
				+ (BONNIE_TCF_OFFSET_SAI_RX_DELTA * bank));
	int spi_timeout;

	spi_timeout = spi_read(tc, bank_base
		+ DWORD_OFFSET(BONNIE_TCF_OFFSET_SAI_EYES), &eyes);
	if (spi_timeout)
		return SAI_STATUS_ERROR;

	spi_read(tc, bank_base
		+ DWORD_OFFSET(BONNIE_TCF_OFFSET_SAI_CLK_TAPS), &clk_taps);
	spi_read(tc, bank_base
		+ DWORD_OFFSET(BONNIE_TCF_OFFSET_SAI_TRAIN_ACK), &train_ack);

#if 0 /* enable this to get debug info if the board is not aligning */
	dev_info(&tc->pdev->dev,
		"dut  bank %d align: eyes=%08x clk_taps=%08x train_ack=%08x\n",
		bank, eyes, clk_taps, train_ack);
#endif

	if (tc_is_interface_aligned(eyes, clk_taps, train_ack))
		return SAI_STATUS_ALIGNED;

	dev_warn(&tc->pdev->dev, "dut bank %d is unaligned\n", bank);
	return SAI_STATUS_UNALIGNED;
}

/*
 * Returns the divider group register fields for the specified counter value.
 * See Xilinx Application Note xapp888.
 */
static void odin_mmcm_reg_param_calc(u32 value, u32 *low, u32 *high,
				     u32 *edge, u32 *no_count)
{
	if (value == 1U) {
		*no_count = 1U;
		*edge = 0;
		*high = 0;
		*low = 0;
	} else {
		*no_count = 0;
		*edge = value % 2U;
		*high = value >> 1;
		*low = (value + *edge) >> 1U;
	}
}

/*
 * Returns the MMCM Input Divider, FB Multiplier and Output Divider values for
 * the specified input frequency and target output frequency.
 * Function doesn't support fractional values for multiplier and output divider
 * As per Xilinx 7 series FPGAs clocking resources user guide, aims for highest
 * VCO and smallest D and M.
 * Configured for Xilinx Virtex7 speed grade 2.
 */
static int odin_mmcm_counter_calc(struct device *dev,
				  u32 freq_input, u32 freq_output,
				  u32 *d, u32 *m, u32 *o)
{
	u32 d_min, d_max;
	u32 m_min, m_max, m_ideal;
	u32 d_cur, m_cur, o_cur;
	u32 best_diff, d_best, m_best, o_best;

	/*
	 * Check specified input frequency is within range
	 */
	if (freq_input < ODN_INPUT_CLOCK_SPEED_MIN) {
		dev_err(dev, "Input frequency (%u hz) below minimum supported value (%u hz)\n",
			freq_input, ODN_INPUT_CLOCK_SPEED_MIN);
		return -EINVAL;
	}
	if (freq_input > ODN_INPUT_CLOCK_SPEED_MAX) {
		dev_err(dev, "Input frequency (%u hz) above maximum supported value (%u hz)\n",
			freq_input, ODN_INPUT_CLOCK_SPEED_MAX);
		return -EINVAL;
	}

	/*
	 * Check specified target frequency is within range
	 */
	if (freq_output < ODN_OUTPUT_CLOCK_SPEED_MIN) {
		dev_err(dev, "Output frequency (%u hz) below minimum supported value (%u hz)\n",
			freq_input, ODN_OUTPUT_CLOCK_SPEED_MIN);
		return -EINVAL;
	}
	if (freq_output > ODN_OUTPUT_CLOCK_SPEED_MAX) {
		dev_err(dev, "Output frequency (%u hz) above maximum supported value (%u hz)\n",
			freq_output, ODN_OUTPUT_CLOCK_SPEED_MAX);
		return -EINVAL;
	}

	/*
	 * Calculate min and max for Input Divider.
	 * Refer Xilinx 7 series FPGAs clocking resources user guide
	 * equation 3-6 and 3-7
	 */
	d_min = DIV_ROUND_UP(freq_input, ODN_PFD_MAX);
	d_max = min(freq_input/ODN_PFD_MIN, (u32)ODN_DREG_VALUE_MAX);

	/*
	 * Calculate min and max for Input Divider.
	 * Refer Xilinx 7 series FPGAs clocking resources user guide.
	 * equation 3-8 and 3-9
	 */
	m_min = DIV_ROUND_UP((ODN_VCO_MIN * d_min), freq_input);
	m_max = min(((ODN_VCO_MAX * d_max) / freq_input),
		    (u32)ODN_MREG_VALUE_MAX);

	for (d_cur = d_min; d_cur <= d_max; d_cur++) {
		/*
		 * Refer Xilinx 7 series FPGAs clocking resources user guide.
		 * equation 3-10
		 */
		m_ideal = min(((d_cur * ODN_VCO_MAX)/freq_input), m_max);

		for (m_cur = m_ideal; m_cur >= m_min; m_cur -= 1) {
			/**
			 * Skip if VCO for given 'm' and 'd' value is not an
			 * integer since fractional component is not supported
			 */
			if (((freq_input * m_cur) % d_cur) != 0)
				continue;

			/**
			 * Skip if divider for given 'm' and 'd' value is not
			 * an integer since fractional component is not
			 * supported
			 */
			if ((freq_input * m_cur) % (d_cur * freq_output) != 0)
				continue;

			/**
			 * Calculate output divider value.
			 */
			o_cur = (freq_input * m_cur)/(d_cur * freq_output);

			*d = d_cur;
			*m = m_cur;
			*o = o_cur;
			return 0;
		}
	}

	/* Failed to find exact optimal solution with high VCO. Brute-force find a suitable config,
	 * again prioritising high VCO, to get lowest jitter */
	d_min = 1; d_max = (u32)ODN_DREG_VALUE_MAX;
	m_min = 1; m_max = (u32)ODN_MREG_VALUE_MAX;
	best_diff = 0xFFFFFFFF;

	for (d_cur = d_min; d_cur <= d_max; d_cur++) {
		for (m_cur = m_max; m_cur >= m_min; m_cur -= 1) {
			u32 pfd, vco, o_avg, o_min, o_max;

			pfd = freq_input / d_cur;
			vco = pfd * m_cur;

			if(pfd < ODN_PFD_MIN) continue;
			if(pfd > ODN_PFD_MAX) continue;
			if(vco < ODN_VCO_MIN) continue;
			if(vco > ODN_VCO_MAX) continue;

			/* A range of -1/+3 around o_avg gives us 100kHz granularity. It can be extended further. */
			o_avg = vco / freq_output;
			o_min = (o_avg >= 2) ? (o_avg - 1) : 1;
			o_max = o_avg + 3;
			if(o_max > (u32)ODN_OREG_VALUE_MAX) o_max = (u32)ODN_OREG_VALUE_MAX;

			for(o_cur = o_min;o_cur <= o_max; o_cur++){
				u32 freq_cur, diff_cur;

				freq_cur = vco / o_cur;

				if(freq_cur > freq_output) continue;
				diff_cur = freq_output - freq_cur;

				if(diff_cur == 0){
					/* Found an exact match */
					*d = d_cur;
					*m = m_cur;
					*o = o_cur;
					return 0;
				}

				if(diff_cur < best_diff){
					best_diff = diff_cur;
					d_best = d_cur;
					m_best = m_cur;
					o_best = o_cur;
				}
			}
		}
	}

	if(best_diff != 0xFFFFFFFF){
		dev_warn(dev, "Odin: Found similar freq of %u Hz\n", freq_output - best_diff);
		*d = d_best;
		*m = m_best;
		*o = o_best;
		return 0;
	}

	dev_err(dev, "Odin: Unable to find integer values for d, m and o for requested frequency (%u)\n",
		freq_output);

	return -ERANGE;
}

static int odin_fpga_set_dut_core_clk(struct tc_device *tc,
				      u32 input_clk, u32 output_clk)
{
	int err = 0;
	u32 in_div, mul, out_div;
	u32 high_time, low_time, edge, no_count;
	u32 value;
	void __iomem *base = tc->tcf.registers;
	void __iomem *clk_blk_base = base + ODN_REG_BANK_ODN_CLK_BLK;
	struct device *dev = &tc->pdev->dev;

	err = odin_mmcm_counter_calc(dev, input_clk, output_clk, &in_div,
				     &mul, &out_div);
	if (err != 0)
		return err;

	/* Put DUT into reset */
	iowrite32(ODN_EXTERNAL_RESETN_DUT_SPI_MASK,
		  base + ODN_CORE_EXTERNAL_RESETN);
	msleep(20);

	/* Put DUT Core MMCM into reset */
	iowrite32(ODN_CLK_GEN_RESET_DUT_CORE_MMCM_MASK,
		  base + ODN_CORE_CLK_GEN_RESET);
	msleep(20);

	/* Calculate the register fields for output divider */
	odin_mmcm_reg_param_calc(out_div, &high_time, &low_time,
				 &edge, &no_count);

	/* Read-modify-write the required fields to output divider register 1 */
	value = ioread32(clk_blk_base + ODN_DUT_CORE_CLK_OUT_DIVIDER1);
	REG_FIELD_SET(value, high_time,
			ODN_DUT_CORE_CLK_OUT_DIVIDER1_HI_TIME);
	REG_FIELD_SET(value, low_time,
			ODN_DUT_CORE_CLK_OUT_DIVIDER1_LO_TIME);
	iowrite32(value, clk_blk_base + ODN_DUT_CORE_CLK_OUT_DIVIDER1);

	/* Read-modify-write the required fields to output divider register 2 */
	value = ioread32(clk_blk_base + ODN_DUT_CORE_CLK_OUT_DIVIDER2);
	REG_FIELD_SET(value, edge,
			ODN_DUT_CORE_CLK_OUT_DIVIDER2_EDGE);
	REG_FIELD_SET(value, no_count,
			ODN_DUT_CORE_CLK_OUT_DIVIDER2_NOCOUNT);
	iowrite32(value, clk_blk_base + ODN_DUT_CORE_CLK_OUT_DIVIDER2);

	/* Calculate the register fields for multiplier */
	odin_mmcm_reg_param_calc(mul, &high_time, &low_time,
				 &edge, &no_count);

	/* Read-modify-write the required fields to multiplier register 1*/
	value = ioread32(clk_blk_base + ODN_DUT_CORE_CLK_MULTIPLIER1);
	REG_FIELD_SET(value, high_time,
			ODN_DUT_CORE_CLK_MULTIPLIER1_HI_TIME);
	REG_FIELD_SET(value, low_time,
			ODN_DUT_CORE_CLK_MULTIPLIER1_LO_TIME);
	iowrite32(value, clk_blk_base + ODN_DUT_CORE_CLK_MULTIPLIER1);

	/* Read-modify-write the required fields to multiplier register 2 */
	value = ioread32(clk_blk_base + ODN_DUT_CORE_CLK_MULTIPLIER2);
	REG_FIELD_SET(value, edge,
			ODN_DUT_CORE_CLK_MULTIPLIER2_EDGE);
	REG_FIELD_SET(value, no_count,
			ODN_DUT_CORE_CLK_MULTIPLIER2_NOCOUNT);
	iowrite32(value, clk_blk_base + ODN_DUT_CORE_CLK_MULTIPLIER2);

	/* Calculate the register fields for input divider */
	odin_mmcm_reg_param_calc(in_div, &high_time, &low_time,
				 &edge, &no_count);

	/* Read-modify-write the required fields to input divider register 1 */
	value = ioread32(clk_blk_base + ODN_DUT_CORE_CLK_IN_DIVIDER1);
	REG_FIELD_SET(value, high_time,
			 ODN_DUT_CORE_CLK_IN_DIVIDER1_HI_TIME);
	REG_FIELD_SET(value, low_time,
			 ODN_DUT_CORE_CLK_IN_DIVIDER1_LO_TIME);
	REG_FIELD_SET(value, edge,
			 ODN_DUT_CORE_CLK_IN_DIVIDER1_EDGE);
	REG_FIELD_SET(value, no_count,
			 ODN_DUT_CORE_CLK_IN_DIVIDER1_NOCOUNT);
	iowrite32(value, clk_blk_base + ODN_DUT_CORE_CLK_IN_DIVIDER1);

	/* Bring DUT clock MMCM out of reset */
	iowrite32(0, tc->tcf.registers + ODN_CORE_CLK_GEN_RESET);

	err = tc_iopol32_nonzero(ODN_MMCM_LOCK_STATUS_DUT_CORE,
				 base + ODN_CORE_MMCM_LOCK_STATUS);
	if (err != 0) {
		dev_err(dev, "MMCM failed to lock for DUT core\n");
		return err;
	}

	/* Bring DUT out of reset */
	iowrite32(ODN_EXTERNAL_RESETN_DUT_SPI_MASK |
		  ODN_EXTERNAL_RESETN_DUT_MASK,
		  tc->tcf.registers + ODN_CORE_EXTERNAL_RESETN);
	msleep(20);

	dev_info(dev, "DUT core clock set-up successful\n");

	return err;
}

static int odin_fpga_set_dut_if_clk(struct tc_device *tc,
				    u32 input_clk, u32 output_clk)
{
	int err = 0;
	u32 in_div, mul, out_div;
	u32 high_time, low_time, edge, no_count;
	u32 value;
	void __iomem *base = tc->tcf.registers;
	void __iomem *clk_blk_base = base + ODN_REG_BANK_ODN_CLK_BLK;
	struct device *dev = &tc->pdev->dev;

	err = odin_mmcm_counter_calc(dev, input_clk, output_clk,
				     &in_div, &mul, &out_div);
	if (err != 0)
		return err;

	/* Put DUT into reset */
	iowrite32(ODN_EXTERNAL_RESETN_DUT_SPI_MASK,
		  base + ODN_CORE_EXTERNAL_RESETN);
	msleep(20);

	/* Put DUT Core MMCM into reset */
	iowrite32(ODN_CLK_GEN_RESET_DUT_IF_MMCM_MASK,
		  base + ODN_CORE_CLK_GEN_RESET);
	msleep(20);

	/* Calculate the register fields for output divider */
	odin_mmcm_reg_param_calc(out_div, &high_time, &low_time,
				 &edge, &no_count);

	/* Read-modify-write the required fields to output divider register 1 */
	value = ioread32(clk_blk_base + ODN_DUT_IFACE_CLK_OUT_DIVIDER1);
	REG_FIELD_SET(value, high_time,
			ODN_DUT_IFACE_CLK_OUT_DIVIDER1_HI_TIME);
	REG_FIELD_SET(value, low_time,
			ODN_DUT_IFACE_CLK_OUT_DIVIDER1_LO_TIME);
	iowrite32(value, clk_blk_base + ODN_DUT_IFACE_CLK_OUT_DIVIDER1);

	/* Read-modify-write the required fields to output divider register 2 */
	value = ioread32(clk_blk_base + ODN_DUT_IFACE_CLK_OUT_DIVIDER2);
	REG_FIELD_SET(value, edge,
			ODN_DUT_IFACE_CLK_OUT_DIVIDER2_EDGE);
	REG_FIELD_SET(value, no_count,
			ODN_DUT_IFACE_CLK_OUT_DIVIDER2_NOCOUNT);
	iowrite32(value, clk_blk_base + ODN_DUT_IFACE_CLK_OUT_DIVIDER2);

	/* Calculate the register fields for multiplier */
	odin_mmcm_reg_param_calc(mul, &high_time, &low_time, &edge, &no_count);

	/* Read-modify-write the required fields to multiplier register 1*/
	value = ioread32(clk_blk_base + ODN_DUT_IFACE_CLK_MULTIPLIER1);
	REG_FIELD_SET(value, high_time,
			ODN_DUT_IFACE_CLK_MULTIPLIER1_HI_TIME);
	REG_FIELD_SET(value, low_time,
			ODN_DUT_IFACE_CLK_MULTIPLIER1_LO_TIME);
	iowrite32(value, clk_blk_base + ODN_DUT_IFACE_CLK_MULTIPLIER1);

	/* Read-modify-write the required fields to multiplier register 2 */
	value = ioread32(clk_blk_base + ODN_DUT_IFACE_CLK_MULTIPLIER2);
	REG_FIELD_SET(value, edge,
			ODN_DUT_IFACE_CLK_MULTIPLIER2_EDGE);
	REG_FIELD_SET(value, no_count,
			ODN_DUT_IFACE_CLK_MULTIPLIER2_NOCOUNT);
	iowrite32(value, clk_blk_base + ODN_DUT_IFACE_CLK_MULTIPLIER2);

	/* Calculate the register fields for input divider */
	odin_mmcm_reg_param_calc(in_div, &high_time, &low_time,
				 &edge, &no_count);

	/* Read-modify-write the required fields to input divider register 1 */
	value = ioread32(clk_blk_base + ODN_DUT_IFACE_CLK_IN_DIVIDER1);
	REG_FIELD_SET(value, high_time,
			 ODN_DUT_IFACE_CLK_IN_DIVIDER1_HI_TIME);
	REG_FIELD_SET(value, low_time,
			 ODN_DUT_IFACE_CLK_IN_DIVIDER1_LO_TIME);
	REG_FIELD_SET(value, edge,
			 ODN_DUT_IFACE_CLK_IN_DIVIDER1_EDGE);
	REG_FIELD_SET(value, no_count,
			 ODN_DUT_IFACE_CLK_IN_DIVIDER1_NOCOUNT);
	iowrite32(value, clk_blk_base + ODN_DUT_IFACE_CLK_IN_DIVIDER1);

	/* Bring DUT interface clock MMCM out of reset */
	iowrite32(0, tc->tcf.registers + ODN_CORE_CLK_GEN_RESET);

	err = tc_iopol32_nonzero(ODN_MMCM_LOCK_STATUS_DUT_IF,
				 base + ODN_CORE_MMCM_LOCK_STATUS);
	if (err != 0) {
		dev_err(dev, "MMCM failed to lock for DUT IF\n");
		return err;
	}

	/* Bring DUT out of reset */
	iowrite32(ODN_EXTERNAL_RESETN_DUT_SPI_MASK |
		  ODN_EXTERNAL_RESETN_DUT_MASK,
		  tc->tcf.registers + ODN_CORE_EXTERNAL_RESETN);
	msleep(20);

	dev_info(dev, "DUT IF clock set-up successful\n");

	return err;
}

static void odin_fpga_update_dut_clk_freq(struct tc_device *tc,
					  int *core_clock, int *mem_clock)
{
	struct device *dev = &tc->pdev->dev;

#if defined(SUPPORT_FPGA_DUT_CLK_INFO)
	int dut_clk_info = ioread32(tc->tcf.registers + ODN_CORE_DUT_CLK_INFO);

	if ((dut_clk_info != 0) && (dut_clk_info != 0xbaadface) && (dut_clk_info != 0xffffffff))
	{
		dev_info(dev, "ODN_DUT_CLK_INFO = %08x\n", dut_clk_info);
		dev_info(dev, "Overriding provided DUT clock values: core %i, mem %i\n",
			 *core_clock, *mem_clock);

		*core_clock = ((dut_clk_info & ODN_DUT_CLK_INFO_CORE_MASK)
			       >> ODN_DUT_CLK_INFO_CORE_SHIFT) * 1000000;

		*mem_clock = ((dut_clk_info & ODN_DUT_CLK_INFO_MEM_MASK)
			       >> ODN_DUT_CLK_INFO_MEM_SHIFT) * 1000000;
	}
#endif

	dev_info(dev, "DUT clock values: core %i, mem %i\n",
		 *core_clock, *mem_clock);
}

static int odin_hard_reset_fpga(struct tc_device *tc,
				int core_clock, int mem_clock)
{
	int err = 0;

	odin_fpga_update_dut_clk_freq(tc, &core_clock, &mem_clock);

	err = odin_fpga_set_dut_core_clk(tc, ODN_INPUT_CLOCK_SPEED, core_clock);
	if (err != 0)
		goto err_out;

	err = odin_fpga_set_dut_if_clk(tc, ODN_INPUT_CLOCK_SPEED, mem_clock);

err_out:
	return err;
}

static int odin_hard_reset_bonnie(struct tc_device *tc)
{
	int reset_cnt = 0;
	bool aligned = false;
	int alignment_found;

	msleep(100);

	/* It is essential to do an SPI reset once on power-up before
	 * doing any DUT reads via the SPI interface.
	 */
	iowrite32(1, tc->tcf.registers		/* set bit 1 low */
			+ ODN_CORE_EXTERNAL_RESETN);
	msleep(20);

	iowrite32(3, tc->tcf.registers		/* set bit 1 high */
			+ ODN_CORE_EXTERNAL_RESETN);
	msleep(20);

	while (!aligned && (reset_cnt < 20)) {

		int bank;

		/* Reset the DUT to allow the SAI to retrain */
		iowrite32(2, /* set bit 0 low */
			tc->tcf.registers
			+ ODN_CORE_EXTERNAL_RESETN);

		/* Hold the DUT in reset for 50mS */
		msleep(50);

		/* Take the DUT out of reset */
		iowrite32(3, /* set bit 0 hi */
			tc->tcf.registers
			+ ODN_CORE_EXTERNAL_RESETN);
		reset_cnt++;

		/* Wait 200mS for the DUT to stabilise */
		msleep(200);

		/* Check the odin Multi Clocked bank Align status */
		alignment_found = read_odin_mca_status(tc);
		dev_info(&tc->pdev->dev,
				"Odin mca_status indicates %s\n",
				(alignment_found)?"aligned":"UNALIGNED");

		/* Check the DUT MCA status */
		alignment_found = read_dut_mca_status(tc);
		dev_info(&tc->pdev->dev,
				"DUT mca_status indicates %s\n",
				(alignment_found)?"aligned":"UNALIGNED");

		/* If all banks have aligned then the reset was successful */
		for (bank = 0; bank < 10; bank++) {

			int dut_aligned = 0;
			int odin_aligned = 0;

			odin_aligned = get_odin_sai_status(tc, bank);
			dut_aligned = get_dut_sai_status(tc, bank);

			if (dut_aligned == SAI_STATUS_ERROR)
				return SAI_STATUS_ERROR;

			if (!dut_aligned || !odin_aligned) {
				aligned = false;
				break;
			}
			aligned = true;
		}

		if (aligned) {
			dev_info(&tc->pdev->dev,
				"all banks have aligned\n");
			break;
		}

		dev_warn(&tc->pdev->dev,
			"Warning- not all banks have aligned. Trying again.\n");
	}

	if (!aligned)
		dev_warn(&tc->pdev->dev, "odin_hard_reset failed\n");

	return (aligned) ? 0 : 1; /* return 0 for success */
}

static void odin_set_mem_latency(struct tc_device *tc,
				 int mem_latency, int mem_wresp_latency)
{
	u32 regval = 0;

	if (mem_latency <= 4)
		/* The total memory read latency cannot be lower than the
		 * amount of cycles consumed by the hardware to do a read.
		 * Set the memory read latency to 0 cycles.
		 */
		mem_latency = 0;
	else
	{
		mem_latency -= 4;

		dev_info(&tc->pdev->dev,
			 "Setting memory read latency to %i cycles\n",
			 mem_latency);
	}

	if (mem_wresp_latency <= 2)
		/* The total memory write latency cannot be lower than the
		 * amount of cycles consumed by the hardware to do a write.
		 * Set the memory write latency to 0 cycles.
		 */
		mem_wresp_latency = 0;
	else
	{
		mem_wresp_latency -= 2;

		dev_info(&tc->pdev->dev,
			 "Setting memory write response latency to %i cycles\n",
			 mem_wresp_latency);
	}

	mem_latency |= mem_wresp_latency << 16;

	spi_write(tc, 0x1009, mem_latency);

	if (spi_read(tc, 0x1009, &regval) != 0) {
		dev_err(&tc->pdev->dev,
			"Failed to read back memory latency register");
		return;
	}

	if (mem_latency != regval) {
		dev_err(&tc->pdev->dev,
			"Memory latency register doesn't match requested value"
			" (actual: %#08x, expected: %#08x)\n",
			regval, mem_latency);
	}
}

#endif /* defined(SUPPORT_RGX) */

static void odin_set_mem_mode(struct tc_device *tc)
{
	u32 val;

	if (tc->version != ODIN_VERSION_FPGA)
		return;

	/* Enable memory offset to be applied to DUT and PDP1 */
	iowrite32(0x80000A10, tc->tcf.registers + ODN_CORE_DUT_CTRL1);

	/* Apply memory offset to GPU and PDP1 to point to DDR memory.
	 * Enable HDMI.
	 */
	val = (0x4 << ODN_CORE_CONTROL_DUT_OFFSET_SHIFT) |
	      (0x4 << ODN_CORE_CONTROL_PDP1_OFFSET_SHIFT) |
	      (0x2 << ODN_CORE_CONTROL_HDMI_MODULE_EN_SHIFT) |
	      (0x1 << ODN_CORE_CONTROL_MCU_COMMUNICATOR_EN_SHIFT);
	iowrite32(val, tc->tcf.registers + ODN_CORE_CORE_CONTROL);
}

/* Do a hard reset on the DUT */
static int odin_hard_reset(struct tc_device *tc, int core_clock, int mem_clock)
{
#if defined(SUPPORT_RGX)
	if (tc->version == ODIN_VERSION_TCF_BONNIE)
		return odin_hard_reset_bonnie(tc);
	if (tc->version == ODIN_VERSION_FPGA)
		return odin_hard_reset_fpga(tc, core_clock, mem_clock);

	dev_err(&tc->pdev->dev, "Invalid Odin version");
	return 1;
#else /* defined(SUPPORT_RGX) */
	return 0;
#endif /* defined(SUPPORT_RGX) */
}

static int odin_hw_init(struct tc_device *tc, int core_clock, int mem_clock,
			int mem_latency, int mem_wresp_latency)
{
	int err;

	err = odin_hard_reset(tc, core_clock, mem_clock);
	if (err) {
		dev_err(&tc->pdev->dev, "Failed to initialise Odin");
		goto err_out;
	}

	odin_set_mem_mode(tc);

#if defined(SUPPORT_RGX)
	if (tc->version == ODIN_VERSION_FPGA)
		odin_set_mem_latency(tc, mem_latency, mem_wresp_latency);
#endif /* defined(SUPPORT_RGX) */

err_out:
	return err;
}

static int odin_enable_irq(struct tc_device *tc)
{
	int err = 0;

#if defined(TC_FAKE_INTERRUPTS)
	setup_timer(&tc->timer, tc_irq_fake_wrapper,
		(unsigned long)tc);
	mod_timer(&tc->timer,
		jiffies + msecs_to_jiffies(FAKE_INTERRUPT_TIME_MS));
#else
	iowrite32(0, tc->tcf.registers +
		ODN_CORE_INTERRUPT_ENABLE);
	iowrite32(0xffffffff, tc->tcf.registers +
		ODN_CORE_INTERRUPT_CLR);

	dev_info(&tc->pdev->dev,
		"Registering IRQ %d for use by Odin\n",
		tc->pdev->irq);

	err = request_irq(tc->pdev->irq, odin_irq_handler,
		IRQF_SHARED, DRV_NAME, tc);

	if (err) {
		dev_err(&tc->pdev->dev,
			"Error - IRQ %d failed to register\n",
			tc->pdev->irq);
	} else {
		dev_info(&tc->pdev->dev,
			"IRQ %d was successfully registered for use by Odin\n",
			tc->pdev->irq);
	}
#endif
	return err;
}

static void odin_disable_irq(struct tc_device *tc)
{
#if defined(TC_FAKE_INTERRUPTS)
	del_timer_sync(&tc->timer);
#else
	iowrite32(0, tc->tcf.registers +
			ODN_CORE_INTERRUPT_ENABLE);
	iowrite32(0xffffffff, tc->tcf.registers +
			ODN_CORE_INTERRUPT_CLR);

	free_irq(tc->pdev->irq, tc);
#endif
}

static enum tc_version_t
odin_detect_daughterboard_version(struct tc_device *tc)
{
	u32 reg = ioread32(tc->tcf.registers + ODN_REG_BANK_DB_TYPE_ID);
	u32 val = reg;

	val = (val & ODN_REG_BANK_DB_TYPE_ID_TYPE_MASK) >>
		ODN_REG_BANK_DB_TYPE_ID_TYPE_SHIFT;

	switch (val) {
	default:
		dev_err(&tc->pdev->dev,
			"Unknown odin version ID type %#x "
			"(DB_TYPE_ID: %#08x)\n",
			val, reg);
		return TC_INVALID_VERSION;
	case 1:
		dev_info(&tc->pdev->dev, "DUT: Bonnie TC\n");
		return ODIN_VERSION_TCF_BONNIE;
	case 2:
	case 3:
		dev_info(&tc->pdev->dev, "DUT: FPGA\n");
		return ODIN_VERSION_FPGA;
	}
}

static int odin_dev_init(struct tc_device *tc, struct pci_dev *pdev,
			 int pdp_mem_size, int secure_mem_size)
{
	int err;
	u32 val;

	/* Reserve and map the tcf system registers */
	err = setup_io_region(pdev, &tc->tcf,
		ODN_SYS_BAR, ODN_SYS_REGS_OFFSET, ODN_SYS_REGS_SIZE);
	if (err)
		goto err_out;

	tc->version = odin_detect_daughterboard_version(tc);
	if(tc->version == TC_INVALID_VERSION) {
		err = -EIO;
		goto err_odin_unmap_sys_registers;
	}

	/* Setup card memory */
	tc->tc_mem.base = pci_resource_start(pdev, ODN_DDR_BAR);
	tc->tc_mem.size = pci_resource_len(pdev, ODN_DDR_BAR);

	if (tc->tc_mem.size < pdp_mem_size) {
		dev_err(&pdev->dev,
			"Odin MEM region (bar %d) has size of %lu which is smaller than the requested PDP heap of %lu",
			ODN_DDR_BAR,
			(unsigned long)tc->tc_mem.size,
			(unsigned long)pdp_mem_size);

		err = -EIO;
		goto err_odin_unmap_sys_registers;
	}

#if defined(SUPPORT_FAKE_SECURE_ION_HEAP)
	if (tc->tc_mem.size <
	    (pdp_mem_size + secure_mem_size)) {
		dev_err(&pdev->dev,
			"Odin MEM region (bar %d) has size of %lu which is smaller than the requested PDP heap of %lu plus the requested secure heap size %lu",
			ODN_DDR_BAR,
			(unsigned long)tc->tc_mem.size,
			(unsigned long)pdp_mem_size,
			(unsigned long)secure_mem_size);
		err = -EIO;
		goto err_odin_unmap_sys_registers;
	}
#endif

	err = tc_mtrr_setup(tc);
	if (err)
		goto err_odin_unmap_sys_registers;

	/* Setup ranges for the device heaps */
	tc->pdp_heap_mem_size = pdp_mem_size;

	/* We know ext_heap_mem_size won't underflow as we've compared
	 * tc_mem.size against the pdp_mem_size value earlier
	 */
	tc->ext_heap_mem_size =
		tc->tc_mem.size - tc->pdp_heap_mem_size;

#if defined(SUPPORT_FAKE_SECURE_ION_HEAP)
	tc->ext_heap_mem_size -= secure_mem_size;
#endif

	if (tc->ext_heap_mem_size < TC_EXT_MINIMUM_MEM_SIZE) {
		dev_warn(&pdev->dev,
			"Odin MEM region (bar 4) has size of %lu, with %lu pdp_mem_size only %lu bytes are left for ext device, which looks too small",
			(unsigned long)tc->tc_mem.size,
			(unsigned long)pdp_mem_size,
			(unsigned long)tc->ext_heap_mem_size);
		/* Continue as this is only a 'helpful warning' not a hard
		 * requirement
		 */
	}
	tc->ext_heap_mem_base = tc->tc_mem.base;
	tc->pdp_heap_mem_base =
		tc->tc_mem.base + tc->ext_heap_mem_size;
#if defined(SUPPORT_FAKE_SECURE_ION_HEAP)
	tc->secure_heap_mem_base = tc->pdp_heap_mem_base +
		tc->pdp_heap_mem_size;
	tc->secure_heap_mem_size = secure_mem_size;
#endif

#if defined(SUPPORT_ION)
	err = tc_ion_init(tc, ODN_DDR_BAR);
	if (err) {
		dev_err(&pdev->dev, "Failed to initialise ION\n");
		goto err_odin_unmap_sys_registers;
	}
#endif

	val = ioread32(tc->tcf.registers + ODN_CORE_REVISION);
	dev_info(&pdev->dev, "ODN_CORE_REVISION = %08x\n", val);

	val = ioread32(tc->tcf.registers + ODN_CORE_CHANGE_SET);
	dev_info(&pdev->dev, "ODN_CORE_CHANGE_SET = %08x\n", val);

	val = ioread32(tc->tcf.registers + ODN_CORE_USER_ID);
	dev_info(&pdev->dev, "ODN_CORE_USER_ID = %08x\n", val);

	val = ioread32(tc->tcf.registers + ODN_CORE_USER_BUILD);
	dev_info(&pdev->dev, "ODN_CORE_USER_BUILD = %08x\n", val);

err_out:
	return err;

err_odin_unmap_sys_registers:
	dev_info(&pdev->dev,
		"odin_dev_init failed. unmapping the io regions.\n");

	iounmap(tc->tcf.registers);
	release_pci_io_addr(pdev, ODN_SYS_BAR,
			 tc->tcf.region.base, tc->tcf.region.size);
	goto err_out;
}

static void odin_dev_cleanup(struct tc_device *tc)
{
#if defined(SUPPORT_ION)
	tc_ion_deinit(tc, ODN_DDR_BAR);
#endif

	tc_mtrr_cleanup(tc);

	iounmap(tc->tcf.registers);

	release_pci_io_addr(tc->pdev,
			ODN_SYS_BAR,
			tc->tcf.region.base,
			tc->tcf.region.size);
}

static u32 odin_interrupt_id_to_flag(int interrupt_id)
{
	switch (interrupt_id) {
	case TC_INTERRUPT_PDP:
		return ODN_INTERRUPT_ENABLE_PDP1;
	case TC_INTERRUPT_EXT:
		return ODN_INTERRUPT_ENABLE_DUT;
	default:
		BUG();
	}
}

int odin_init(struct tc_device *tc, struct pci_dev *pdev,
	      int core_clock, int mem_clock,
	      int pdp_mem_size, int secure_mem_size,
	      int mem_latency, int mem_wresp_latency)
{
	int err = 0;

	err = odin_dev_init(tc, pdev, pdp_mem_size, secure_mem_size);
	if (err) {
		dev_err(&pdev->dev, "odin_dev_init failed\n");
		goto err_out;
	}

	err = odin_hw_init(tc, core_clock, mem_clock,
			   mem_latency, mem_wresp_latency);
	if (err) {
		dev_err(&pdev->dev, "odin_hw_init failed\n");
		goto err_dev_cleanup;
	}

	err = odin_enable_irq(tc);
	if (err) {
		dev_err(&pdev->dev,
			"Failed to initialise IRQ\n");
		goto err_dev_cleanup;
	}

err_out:
	return err;

err_dev_cleanup:
	odin_dev_cleanup(tc);
	goto err_out;
}

int odin_cleanup(struct tc_device *tc)
{
	odin_disable_irq(tc);
	odin_dev_cleanup(tc);

	return 0;
}

int odin_register_pdp_device(struct tc_device *tc)
{
	int err = 0;
	resource_size_t reg_start = pci_resource_start(tc->pdev, ODN_SYS_BAR);
	struct resource pdp_resources_odin[] = {
		DEFINE_RES_MEM_NAMED(reg_start +
				ODN_PDP_REGS_OFFSET, /* start */
				ODN_PDP_REGS_SIZE, /* size */
				"pdp-regs"),
		DEFINE_RES_MEM_NAMED(reg_start +
				ODN_SYS_REGS_OFFSET +
				ODN_REG_BANK_ODN_CLK_BLK +
				ODN_PDP_P_CLK_OUT_DIVIDER_REG1, /* start */
				ODN_PDP_P_CLK_IN_DIVIDER_REG -
				ODN_PDP_P_CLK_OUT_DIVIDER_REG1 + 4, /* size */
				"pll-regs"),
		DEFINE_RES_MEM_NAMED(reg_start +
				ODN_SYS_REGS_OFFSET +
				ODN_REG_BANK_CORE, /* start */
				ODN_CORE_MMCM_LOCK_STATUS + 4, /* size */
				"odn-core"),
	};

	struct tc_pdp_platform_data pdata = {
#if defined(SUPPORT_ION) && (LINUX_VERSION_CODE < KERNEL_VERSION(4, 12, 0))
		.ion_device = tc->ion_device,
		.ion_heap_id = ION_HEAP_TC_PDP,
#endif
		.memory_base = tc->tc_mem.base,
		.pdp_heap_memory_base = tc->pdp_heap_mem_base,
		.pdp_heap_memory_size = tc->pdp_heap_mem_size,
	};
	struct platform_device_info pdp_device_info = {
		.parent = &tc->pdev->dev,
		.name = ODN_DEVICE_NAME_PDP,
		.id = -2,
		.data = &pdata,
		.size_data = sizeof(pdata),
#if (TC_MEMORY_CONFIG == TC_MEMORY_LOCAL) || \
	(TC_MEMORY_CONFIG == TC_MEMORY_HYBRID)
		/*
		 * The PDP cannot address system memory, so there is no
		 * DMA limitation.
		 */
		.dma_mask = DMA_BIT_MASK(64),
#else
		.dma_mask = DMA_BIT_MASK(32),
#endif
	};

	pdp_device_info.res = pdp_resources_odin;
	pdp_device_info.num_res = ARRAY_SIZE(pdp_resources_odin);

	tc->pdp_dev = platform_device_register_full(&pdp_device_info);
	if (IS_ERR(tc->pdp_dev)) {
		err = PTR_ERR(tc->pdp_dev);
		dev_err(&tc->pdev->dev,
			"Failed to register PDP device (%d)\n", err);
		tc->pdp_dev = NULL;
		goto err_out;
	}

err_out:
	return err;
}

int odin_register_ext_device(struct tc_device *tc)
{
#if defined(SUPPORT_RGX)
	int err = 0;
	struct resource odin_rogue_resources[] = {
		DEFINE_RES_MEM_NAMED(pci_resource_start(tc->pdev,
							ODN_DUT_SOCIF_BAR),
				     ODN_DUT_SOCIF_SIZE, "rogue-regs"),
	};
	struct tc_rogue_platform_data pdata = {
#if defined(SUPPORT_ION) && (LINUX_VERSION_CODE < KERNEL_VERSION(4, 12, 0))
		.ion_device = tc->ion_device,
		.ion_heap_id = ION_HEAP_TC_ROGUE,
#endif
		.tc_memory_base = tc->tc_mem.base,
		.pdp_heap_memory_base = tc->pdp_heap_mem_base,
		.pdp_heap_memory_size = tc->pdp_heap_mem_size,
		.rogue_heap_memory_base = tc->ext_heap_mem_base,
		.rogue_heap_memory_size = tc->ext_heap_mem_size,
#if defined(SUPPORT_FAKE_SECURE_ION_HEAP)
		.secure_heap_memory_base = tc->secure_heap_mem_base,
		.secure_heap_memory_size = tc->secure_heap_mem_size,
#endif
	};
	struct platform_device_info odin_rogue_dev_info = {
		.parent = &tc->pdev->dev,
		.name = TC_DEVICE_NAME_ROGUE,
		.id = -2,
		.res = odin_rogue_resources,
		.num_res = ARRAY_SIZE(odin_rogue_resources),
		.data = &pdata,
		.size_data = sizeof(pdata),
#if (TC_MEMORY_CONFIG == TC_MEMORY_LOCAL)
		/*
		 * The FPGA cannot address system memory, so there is no DMA
		 * limitation.
		 */
		.dma_mask = DMA_BIT_MASK(64),
#else
		.dma_mask = DMA_BIT_MASK(32),
#endif
	};

	tc->ext_dev
		= platform_device_register_full(&odin_rogue_dev_info);

	if (IS_ERR(tc->ext_dev)) {
		err = PTR_ERR(tc->ext_dev);
		dev_err(&tc->pdev->dev,
			"Failed to register rogue device (%d)\n", err);
		tc->ext_dev = NULL;
	}
	return err;
#else /* defined(SUPPORT_RGX) */
	return 0;
#endif /* defined(SUPPORT_RGX) */
}

void odin_enable_interrupt_register(struct tc_device *tc,
				    int interrupt_id)
{
	u32 val;
	u32 flag;

	switch (interrupt_id) {
	case TC_INTERRUPT_PDP:
		dev_info(&tc->pdev->dev,
			"Enabling Odin PDP interrupts\n");
		break;
	case TC_INTERRUPT_EXT:
		dev_info(&tc->pdev->dev,
			"Enabling Odin DUT interrupts\n");
		break;
	default:
		dev_err(&tc->pdev->dev,
			"Error - illegal interrupt id\n");
		return;
	}

	val = ioread32(tc->tcf.registers +
		       ODN_CORE_INTERRUPT_ENABLE);
	flag = odin_interrupt_id_to_flag(interrupt_id);
	val |= flag;
	iowrite32(val, tc->tcf.registers +
		  ODN_CORE_INTERRUPT_ENABLE);
}

void odin_disable_interrupt_register(struct tc_device *tc,
				     int interrupt_id)
{
	u32 val;

	switch (interrupt_id) {
	case TC_INTERRUPT_PDP:
		dev_info(&tc->pdev->dev,
			"Disabling Odin PDP interrupts\n");
		break;
	case TC_INTERRUPT_EXT:
		dev_info(&tc->pdev->dev,
			"Disabling Odin DUT interrupts\n");
		break;
	default:
		dev_err(&tc->pdev->dev,
			"Error - illegal interrupt id\n");
		return;
	}
	val = ioread32(tc->tcf.registers +
		       ODN_CORE_INTERRUPT_ENABLE);
	val &= ~(odin_interrupt_id_to_flag(interrupt_id));
	iowrite32(val, tc->tcf.registers +
		  ODN_CORE_INTERRUPT_ENABLE);
}

irqreturn_t odin_irq_handler(int irq, void *data)
{
	u32 interrupt_status;
	u32 interrupt_clear = 0;
	unsigned long flags;
	irqreturn_t ret = IRQ_NONE;
	struct tc_device *tc = (struct tc_device *)data;

	spin_lock_irqsave(&tc->interrupt_handler_lock, flags);

#if defined(TC_FAKE_INTERRUPTS)
	/* If we're faking interrupts pretend we got both ext and PDP ints */
	interrupt_status = ODN_INTERRUPT_STATUS_DUT
		| ODN_INTERRUPT_STATUS_PDP1;
#else
	interrupt_status = ioread32(tc->tcf.registers +
				    ODN_CORE_INTERRUPT_STATUS);
#endif

	if (interrupt_status & ODN_INTERRUPT_STATUS_DUT) {
		struct tc_interrupt_handler *ext_int =
			&tc->interrupt_handlers[TC_INTERRUPT_EXT];

		if (ext_int->enabled && ext_int->handler_function) {
			ext_int->handler_function(ext_int->handler_data);
			interrupt_clear |= ODN_INTERRUPT_CLEAR_DUT;
		}
		ret = IRQ_HANDLED;
	}
	if (interrupt_status & ODN_INTERRUPT_STATUS_PDP1) {
		struct tc_interrupt_handler *pdp_int =
			&tc->interrupt_handlers[TC_INTERRUPT_PDP];

		if (pdp_int->enabled && pdp_int->handler_function) {
			pdp_int->handler_function(pdp_int->handler_data);
			interrupt_clear |= ODN_INTERRUPT_CLEAR_PDP1;
		}
		ret = IRQ_HANDLED;
	}

	if (interrupt_clear)
		iowrite32(interrupt_clear,
			  tc->tcf.registers + ODN_CORE_INTERRUPT_CLR);

	spin_unlock_irqrestore(&tc->interrupt_handler_lock, flags);

	return ret;
}

int odin_sys_info(struct tc_device *tc, u32 *tmp, u32 *pll)
{
	*tmp = 0;
	*pll = 0;
	return 0;
}

int odin_sys_strings(struct tc_device *tc,
		     char *str_fpga_rev, size_t size_fpga_rev,
		     char *str_tcf_core_rev, size_t size_tcf_core_rev,
		     char *str_tcf_core_target_build_id,
		     size_t size_tcf_core_target_build_id,
		     char *str_pci_ver, size_t size_pci_ver,
		     char *str_macro_ver, size_t size_macro_ver)
{
	u32 val;
	char temp_str[12];

	/* Read the Odin major and minor revision ID register Rx-xx */
	val = ioread32(tc->tcf.registers + ODN_CORE_REVISION);

	snprintf(str_tcf_core_rev,
		 size_tcf_core_rev,
		 "%d.%d",
		 HEX2DEC((val & ODN_REVISION_MAJOR_MASK)
			 >> ODN_REVISION_MAJOR_SHIFT),
		 HEX2DEC((val & ODN_REVISION_MINOR_MASK)
			 >> ODN_REVISION_MINOR_SHIFT));

	dev_info(&tc->pdev->dev, "Odin core revision %s\n",
		 str_tcf_core_rev);

	/* Read the Odin register containing the Perforce changelist
	 * value that the FPGA build was generated from
	 */
	val = ioread32(tc->tcf.registers + ODN_CORE_CHANGE_SET);

	snprintf(str_tcf_core_target_build_id,
		 size_tcf_core_target_build_id,
		 "%d",
		 (val & ODN_CHANGE_SET_SET_MASK)
		 >> ODN_CHANGE_SET_SET_SHIFT);

	/* Read the Odin User_ID register containing the User ID for
	 * identification of a modified build
	 */
	val = ioread32(tc->tcf.registers + ODN_CORE_USER_ID);

	snprintf(temp_str,
		 sizeof(temp_str),
		 "%d",
		 HEX2DEC((val & ODN_USER_ID_ID_MASK)
			 >> ODN_USER_ID_ID_SHIFT));

	/* Read the Odin User_Build register containing the User build
	 * number for identification of modified builds
	 */
	val = ioread32(tc->tcf.registers + ODN_CORE_USER_BUILD);

	snprintf(temp_str,
		 sizeof(temp_str),
		 "%d",
		 HEX2DEC((val & ODN_USER_BUILD_BUILD_MASK)
			 >> ODN_USER_BUILD_BUILD_SHIFT));

	return 0;
}
