/**
 *  Copyright (C) 2017 Xilinx, Inc. All rights reserved.
 *
 *  Code borrowed from Xilinx SDAccel XDMA driver
 *  Author: Umang Parekh
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  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.
 */

#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/pci.h>


#include "mgmt-core.h"
#include "mgmt-ioctl.h"
#include "mgmt-cw.h"


#define AWSMGMT_NUM_ACTUAL_CLOCKS 3

extern void __iomem *pf1_addr_reg;
static inline void pf1_iowrite32(u32 value, u32 addr)
{
	iowrite32(addr, pf1_addr_reg);
	iowrite32(value, (pf1_addr_reg + 0x4));
}

static inline u32 pf1_ioread32(u32 addr)
{
	iowrite32(addr, pf1_addr_reg);
	return ioread32((pf1_addr_reg + 0x4));
}
/*
 * Precomputed table with config0 and config2 register values together with target
 * frequency. The steps are about 10 MHz apart. Table is generated by wiz.pl.
 */
const static struct awsmgmt_ocl_clockwiz frequency_table[] = {
	{/*800*/	80,	0x04020301,	0x0000000a},
	{/*800*/	88,	0x04020301,	0x00000009},
	{/*1000*/	100,	0x00000401,	0x0000000a},
	{/*1100*/	110,	0x04040401,	0x0000000a},
	{/*1200*/	120,	0x04080401,	0x0000000a},
	{/*1000*/	125,	0x00000401,	0x00000008},
	{/*1300*/	130,	0x04020501,	0x0000000a},
	{/*1100*/	137,	0x04040401,	0x00000008},
	{/*1000*/	142,	0x00000401,	0x00000007},
	{/*1500*/	150,	0x00000601,	0x0000000a},
	{/*1400*/	155,	0x04060501,	0x00000009},
	{/*1600*/	160,	0x04040601,	0x0000000a},
	{/*1500*/	166,	0x00000601,	0x00000009},
	{/*1200*/	171,	0x04080401,	0x00000007},
	{/*1600*/	177,	0x04040601,	0x00000009},
	{/*1100*/	183,	0x04040401,	0x00000006},
	{/*1600*/	200,	0x04040601,	0x00000008},
	{/*1500*/	214,	0x00000601,	0x00000007},
	{/*1100*/	220,	0x04040401,	0x00000005},
	{/*900*/	225,	0x04060301,	0x00000004},
	{/*1400*/	233,	0x04060501,	0x00000006},
	{/*1200*/	240,	0x04080401,	0x00000005},
	{/*1500*/	250,	0x00000601,	0x00000006},
	{/*1300*/	260,	0x04020501,	0x00000005},
	{/*1600*/	266,	0x04040601,	0x00000006},
	{/*1100*/	275,	0x04040401,	0x00000004},
	{/*1400*/	280,	0x04060501,	0x00000005},
	{/*1500*/	300,	0x00000601,	0x00000005},
	{/*1600*/	320,	0x04040601,	0x00000005},
	{/*1300*/	325,	0x04020501,	0x00000004},
	{/*1000*/	333,	0x00000401,	0x00000003},
	{/*1400*/	350,	0x04060501,	0x00000004},
	{/*1100*/	366,	0x04040401,	0x00000003},
	{/*1500*/	375,	0x00000601,	0x00000004},
	{/*1600*/	400,	0x04040601,	0x00000004},
	{/*1300*/	433,	0x04020501,	0x00000003},
	{/*900*/	450,	0x04060301,	0x00000002},
	{/*1400*/	466,	0x04060501,	0x00000003},
	{/*1500*/	500,	0x00000601,	0x00000003},
};


const static unsigned clock_baseaddr[AWSMGMT_NUM_ACTUAL_CLOCKS] = {
	OCL_CLKWIZ_BASEADDR,
	OCL_CLKWIZ_BASEADDR2,
        OCL_CLKWIZ_SYSCLOCK
};

static unsigned find_matching_freq_config(unsigned freq)
{
	unsigned start = 0;
	unsigned end = ARRAY_SIZE(frequency_table) - 1;
	unsigned idx = ARRAY_SIZE(frequency_table) - 1;
	if (freq < frequency_table[0].ocl)
		return 0;
	if (freq > frequency_table[ARRAY_SIZE(frequency_table) - 1].ocl)
		return ARRAY_SIZE(frequency_table) - 1;

	while (start < end) {
		printk(KERN_INFO "Idx %d Target %d Table %d\n", idx, freq, frequency_table[idx].ocl);
		if (freq == frequency_table[idx].ocl)
			break;
		if (freq < frequency_table[idx].ocl) {
			end = idx;
		}
		else {
			start = idx + 1;
		}
		idx = start + (end - start)/2;
	}
	if (freq < frequency_table[idx].ocl)
		idx--;
	printk(KERN_INFO "Matched Idx %d Target %d Table %d\n", idx, freq, frequency_table[idx].ocl);
	return idx;
}


static unsigned get_ocl_frequency(const struct awsmgmt_dev *lro, unsigned offset)
{
	u32 val;
	const u64 input = AMAZON_INPUT_FREQ;
	u32 mul0, div0;
	u32 mul_frac0 = 0;
	u32 div1;
	u32 div_frac1 = 0;
	u64 freq;

	printk(KERN_INFO "%s:%s offset: %x\n", DRV_NAME, __FUNCTION__, offset);
	val = pf1_ioread32(offset + OCL_CLKWIZ_STATUS_OFFSET);
	printk(KERN_INFO "%s: ClockWiz SR %x\n", DRV_NAME, val);
	if ((val & 1) == 0)
		return 0;

	val = pf1_ioread32(offset + OCL_CLKWIZ_CONFIG_OFFSET(0));
	printk(KERN_INFO "%s: ClockWiz CONFIG(0) %x\n", DRV_NAME, val);

	div0 = val & 0xff;
	mul0 = (val & 0xff00) >> 8;
	if (val & BIT(26)) {
		mul_frac0 = val >> 16;
		mul_frac0 &= 0x3ff;
    printk(KERN_INFO "%s: Doing fractional mult: %x\n", DRV_NAME, mul_frac0);
	}

	/*
	 * Multiply both numerator (mul0) and the denominator (div0) with 1000 to
	 * account for fractional portion of multiplier
	 */
	mul0 *= 1000;
	mul0 += (mul_frac0 * 100);
	div0 *= 1000;

	val = pf1_ioread32(offset + OCL_CLKWIZ_CONFIG_OFFSET(2));
	printk(KERN_INFO "%s: ClockWiz CONFIG(2) %x\n", DRV_NAME, val);
	div1 = val &0xff;
	if (val & BIT(18)) {
		div_frac1 = val >> 8;
		div_frac1 &= 0x3ff;
	}

	/*
	 * Multiply both numerator (mul0) and the denominator (div1) with 1000 to
	 * account for fractional portion of divider
	 */

	div1 *= 1000;
	div1 += div_frac1;
	printk(KERN_INFO "%s: CLKOUT0_DIVIDE_F %d\n", DRV_NAME, div1);
	printk(KERN_INFO "%s: CLKFBOUT_MULT_F %d\n", DRV_NAME, mul0);
	printk(KERN_INFO "%s: DIVCLK_DIVIDE %d\n", DRV_NAME, div0);
	div0 *= div1;
	mul0 *= 1000;
	if (div0 == 0) {
		printk(KERN_ERR "%s: ClockWiz Invalid divider 0\n", DRV_NAME);
		return 0;
	}
	freq = (input * mul0)/div0;
	printk(KERN_INFO "%s: ClockWiz OCL Frequency %lld\n", DRV_NAME, freq);
	return freq;

}

/*
 * Based on Clocking Wizard v5.1, section Dynamic Reconfiguration through AXI4-Lite
 */
long ocl_freqscaling(struct awsmgmt_dev *lro, bool force)
{
	unsigned curr_freq;
	u32 config;
	int i;
	int j = 0;
	u32 val = 0;
	unsigned idx = 0;
	long err = 0;

	for(i = 0; i < AWSMGMT_NUM_ACTUAL_CLOCKS; ++i) {
		// A value of zero means skip scaling for this clock index
		if (!lro->ocl_frequency[i])
			continue;

		idx = find_matching_freq_config(lro->ocl_frequency[i]);
		curr_freq = get_ocl_frequency(lro, clock_baseaddr[i]);

		/* If current frequency is in the same step as the requested frequency then nothing to do */
		if (!force && (find_matching_freq_config(curr_freq) == idx))
			continue;

		val = pf1_ioread32(clock_baseaddr[i] + OCL_CLKWIZ_STATUS_OFFSET);
		if (val != 1) {
			printk(KERN_INFO "%s: ClockWiz BUSY %x\n", DRV_NAME, val);
			err = -EBUSY;
			break;;
		}

		config = frequency_table[idx].config0;
		pf1_iowrite32(config, clock_baseaddr[i] + OCL_CLKWIZ_CONFIG_OFFSET(0));
		config = frequency_table[idx].config2;
		pf1_iowrite32(config, clock_baseaddr[i] + OCL_CLKWIZ_CONFIG_OFFSET(2));
		msleep(10);
		pf1_iowrite32(0x00000007, clock_baseaddr[i] + OCL_CLKWIZ_CONFIG_OFFSET(23));
		msleep(1);
		pf1_iowrite32(0x00000002, clock_baseaddr[i] + OCL_CLKWIZ_CONFIG_OFFSET(23));
		printk(KERN_INFO "%s: ClockWiz waiting for locked signal\n", DRV_NAME);
		msleep(100);
		for (j = 0; j < 100; j++) {
			val = pf1_ioread32(clock_baseaddr[i] + OCL_CLKWIZ_STATUS_OFFSET);
			if (val != 1) {
				msleep(100);
				continue;
			}
		}
		if (val != 1) {
			printk(KERN_ERR "%s: ClockWiz MMCM/PLL did not lock after %d ms, restoring the original configuration\n", DRV_NAME, 100 * 100);
			/* restore the original clock configuration */
			pf1_iowrite32(0x00000004, clock_baseaddr[i] + OCL_CLKWIZ_CONFIG_OFFSET(23));
			msleep(10);
			pf1_iowrite32(0x00000000, clock_baseaddr[i] + OCL_CLKWIZ_CONFIG_OFFSET(23));
			err = -ETIMEDOUT;
			break;
		}
		val = pf1_ioread32(clock_baseaddr[i] + OCL_CLKWIZ_CONFIG_OFFSET(0));
		printk(KERN_INFO "%s: ClockWiz CONFIG(0) %x\n", DRV_NAME, val);
		val = pf1_ioread32(clock_baseaddr[i] + OCL_CLKWIZ_CONFIG_OFFSET(2));
		printk(KERN_INFO "%s: ClockWiz CONFIG(2) %x\n", DRV_NAME, val);
	}
	return err;
}

int ocl_freqscaling_ioctl(struct awsmgmt_dev *lro, const void __user *arg)
{
	struct xclmgmt_ioc_freqscaling freq_obj;
	int i = 0;
	u32 val = 0;
	long err = 0;

	printk(KERN_INFO "%s: ocl_freqscaling_ioctl called\n", DRV_NAME);

	if (copy_from_user((void *)&freq_obj, arg, sizeof(struct xclmgmt_ioc_freqscaling)))
		return -EFAULT;

	for(i = 0; i < AWSMGMT_NUM_ACTUAL_CLOCKS; ++i) {
		printk(KERN_INFO "%s: Target freq[%d] : %d\n", DRV_NAME, i, freq_obj.ocl_target_freq[i]);

		if (!freq_obj.ocl_target_freq[i])
			continue;
		val = pf1_ioread32(clock_baseaddr[i] + OCL_CLKWIZ_STATUS_OFFSET);
		if ((val & 0x1) == 0)
			return -EBUSY;
	}

	memcpy(lro->ocl_frequency, freq_obj.ocl_target_freq, sizeof(freq_obj.ocl_target_freq));
	/*
	 * TODO:
	 * Need to lock the device so that another thread is not fiddling with the device at
	 * the same time, like downloading bitstream or starting kernel, etc.
	 */
//	freezeAXIGate(lro);
	err = ocl_freqscaling(lro, false);
//	freeAXIGate(lro);
	return err;
}

void fill_frequency_info(struct awsmgmt_dev *lro, struct xclmgmt_ioc_info *obj)
{
	int i;
	for(i = 0; i < AWSMGMT_NUM_ACTUAL_CLOCKS; ++i) {
		obj->ocl_frequency[i] = get_ocl_frequency(lro, clock_baseaddr[i]);
	}
}


