/* 2008-01-18: File added and changed by Sony Corporation */
/*
 * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved.
 */

/*
 * The code contained herein is licensed under the GNU General Public
 * License. You may obtain a copy of the GNU General Public License
 * Version 2 or later at the following locations:
 *
 * http://www.opensource.org/licenses/gpl-license.html
 * http://www.gnu.org/copyleft/gpl.html
 */

#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <asm/io.h>
#include <asm/arch/clock.h>
#include <asm/div64.h>

#include "crm_regs.h"

#define PRE_DIV_MIN_FREQ    10000000	/* Minimum Frequency after Predivider */
#define PROPAGATE_RATE_DIS  2

static int cpu_clk_set_wp(int wp);
struct timer_list dptcen_timer;

static void __calc_pre_post_dividers(u32 div, u32 * pre, u32 * post)
{
	u32 min_pre, temp_pre, old_err, err;

	if (div >= 512) {
		*pre = 8;
		*post = 64;
	} else if (div >= 64) {
		min_pre = (div - 1) / 64 + 1;
		old_err = 8;
		for (temp_pre = 8; temp_pre >= min_pre; temp_pre--) {
			err = div % temp_pre;
			if (err == 0) {
				*pre = temp_pre;
				break;
			}
			err = temp_pre - err;
			if (err < old_err) {
				old_err = err;
				*pre = temp_pre;
			}
		}
		*post = (div + *pre - 1) / *pre;
	} else if (div <= 8) {
		*pre = div;
		*post = 1;
	} else {
		*pre = 1;
		*post = div;
	}
}

static struct clk mcu_pll_clk;
static struct clk mcu_main_clk;
static struct clk usb_pll_clk;
static struct clk serial_pll_clk;
static struct clk ipg_clk;
static struct clk ckih_clk;
static struct clk ahb_clk;

static int _clk_enable(struct clk *clk)
{
	u32 reg;
	reg = __raw_readl(clk->enable_reg);
	reg |= 3 << clk->enable_shift;
	__raw_writel(reg, clk->enable_reg);

	return 0;
}

static void _clk_disable(struct clk *clk)
{
	u32 reg;

	reg = __raw_readl(clk->enable_reg);
	reg &= ~(3 << clk->enable_shift);
	__raw_writel(reg, clk->enable_reg);
}

static void _clk_emi_disable(struct clk *clk)
{
	u32 reg;

	reg = __raw_readl(clk->enable_reg);
	reg &= ~(3 << clk->enable_shift);
	reg |= (1 << clk->enable_shift);
	__raw_writel(reg, clk->enable_reg);
}

static int _clk_pll_set_rate(struct clk *clk, unsigned long rate)
{
	u32 reg;
	signed long pd = 1;	/* Pre-divider */
	signed long mfi;	/* Multiplication Factor (Integer part) */
	signed long mfn;	/* Multiplication Factor (Integer part) */
	signed long mfd;	/* Multiplication Factor (Denominator Part) */
	signed long tmp;
	u32 ref_freq = clk->parent->rate;

	while (((ref_freq / pd) * 10) > rate) {
		pd++;
	}

	if ((ref_freq / pd) < PRE_DIV_MIN_FREQ) {
		return -EINVAL;
	}

	/* the ref_freq/2 in the following is to round up */
	mfi = (((rate / 2) * pd) + (ref_freq / 2)) / ref_freq;
	if (mfi < 5 || mfi > 15) {
		return -EINVAL;
	}

	/* pick a mfd value that will work
	 * then solve for mfn */
	mfd = ref_freq / 50000;

	/*
	 *          pll_freq * pd * mfd
	 *   mfn = --------------------  -  (mfi * mfd)
	 *           2 * ref_freq
	 */
	/* the tmp/2 is for rounding */
	tmp = ref_freq / 10000;
	mfn =
	    ((((((rate / 2) + (tmp / 2)) / tmp) * pd) * mfd) / 10000) -
	    (mfi * mfd);

	mfn = mfn & 0x3ff;
	pd--;
	mfd--;

	/* Change the Pll value */
	reg = (mfi << MXC_CCM_PCTL_MFI_OFFSET) |
	    (mfn << MXC_CCM_PCTL_MFN_OFFSET) |
	    (mfd << MXC_CCM_PCTL_MFD_OFFSET) | (pd << MXC_CCM_PCTL_PD_OFFSET);

	if (clk == &mcu_pll_clk) {
		__raw_writel(reg, MXC_CCM_MPCTL);
	} else if (clk == &usb_pll_clk) {
		__raw_writel(reg, MXC_CCM_UPCTL);
	} else if (clk == &serial_pll_clk) {
		__raw_writel(reg, MXC_CCM_SRPCTL);
	}

	clk->rate = rate;
	return 0;
}

static int _clk_cpu_set_rate(struct clk *clk, unsigned long rate)
{
	if ((rate < ahb_clk.rate) || (rate % ahb_clk.rate != 0)) {
		printk(KERN_ERR "Wrong rate %lu in _clk_cpu_set_rate\n", rate);
		return -EINVAL;
	}

	cpu_clk_set_wp(rate / ahb_clk.rate - 1);

	return PROPAGATE_RATE_DIS;
}

static void _clk_pll_recalc(struct clk *clk)
{
	long mfi, mfn, mfd, pdf, ref_clk, mfn_abs;
	volatile unsigned long reg, ccmr;
	s64 temp;
	unsigned int prcs;

	ccmr = __raw_readl(MXC_CCM_CCMR);
	prcs = (ccmr & MXC_CCM_CCMR_PRCS_MASK) >> MXC_CCM_CCMR_PRCS_OFFSET;
	if (prcs == 0x1) {
		ref_clk = CKIL_CLK_FREQ * 1024;
	} else {
		ref_clk = ckih_clk.rate;
	}

	if (clk == &mcu_pll_clk) {
		if ((ccmr & MXC_CCM_CCMR_MPE) == 0) {
			clk->rate = ref_clk;
			return;
		}
		if ((ccmr & MXC_CCM_CCMR_MDS) != 0) {
			clk->rate = ref_clk;
			return;
		}
		reg = __raw_readl(MXC_CCM_MPCTL);
	} else if (clk == &usb_pll_clk) {
		reg = __raw_readl(MXC_CCM_UPCTL);
	} else if (clk == &serial_pll_clk) {
		reg = __raw_readl(MXC_CCM_SRPCTL);
	} else {
		BUG();
	}
	pdf = (reg & MXC_CCM_PCTL_PD_MASK) >> MXC_CCM_PCTL_PD_OFFSET;
	mfd = (reg & MXC_CCM_PCTL_MFD_MASK) >> MXC_CCM_PCTL_MFD_OFFSET;
	mfi = (reg & MXC_CCM_PCTL_MFI_MASK) >> MXC_CCM_PCTL_MFI_OFFSET;
	mfi = (mfi <= 5) ? 5 : mfi;
	mfn = mfn_abs = reg & MXC_CCM_PCTL_MFN_MASK;

	if (mfn >= 0x200) {
		mfn |= 0xFFFFFE00;
		mfn_abs = -mfn;
	}

	ref_clk *= 2;
	ref_clk /= pdf + 1;

	temp = (u64) ref_clk *mfn_abs;
	do_div(temp, mfd + 1);
	if (mfn < 0)
		temp = -temp;
	temp = (ref_clk * mfi) + temp;

	clk->rate = temp;
}

static int _clk_usb_pll_enable(struct clk *clk)
{
	u32 reg;
	reg = __raw_readl(MXC_CCM_CCMR);
	reg |= MXC_CCM_CCMR_UPE;
	__raw_writel(reg, MXC_CCM_CCMR);

	/* No lock bit on MX31, so using max time from spec */
	udelay(80);

	return 0;
}

static void _clk_usb_pll_disable(struct clk *clk)
{
	u32 reg;

	reg = __raw_readl(MXC_CCM_CCMR);
	reg &= ~MXC_CCM_CCMR_UPE;
	__raw_writel(reg, MXC_CCM_CCMR);
}

static int _clk_serial_pll_enable(struct clk *clk)
{
	u32 reg;
	reg = __raw_readl(MXC_CCM_CCMR);
	reg |= MXC_CCM_CCMR_SPE;
	__raw_writel(reg, MXC_CCM_CCMR);

	/* No lock bit on MX31, so using max time from spec */
	udelay(80);

	return 0;
}

static void _clk_serial_pll_disable(struct clk *clk)
{
	u32 reg;

	reg = __raw_readl(MXC_CCM_CCMR);
	reg &= ~MXC_CCM_CCMR_SPE;
	__raw_writel(reg, MXC_CCM_CCMR);
}

#define PDR0(mask, off) ((__raw_readl(MXC_CCM_PDR0) & mask) >> off)
#define PDR1(mask, off) ((__raw_readl(MXC_CCM_PDR1) & mask) >> off)
#define PDR2(mask, off) ((__raw_readl(MXC_CCM_PDR2) & mask) >> off)

static void _clk_mcu_main_recalc(struct clk *clk)
{
	u32 pmcr0 = __raw_readl(MXC_CCM_PMCR0);

	if ((pmcr0 & MXC_CCM_PMCR0_DFSUP1) == MXC_CCM_PMCR0_DFSUP1_SPLL) {
		serial_pll_clk.recalc(&serial_pll_clk);
		clk->rate = serial_pll_clk.rate;
	} else {
		mcu_pll_clk.recalc(&mcu_pll_clk);
		clk->rate = mcu_pll_clk.rate;
	}
}

static void _clk_cpu_recalc(struct clk *clk)
{
	unsigned long mcu_pdf;

	mcu_pdf = PDR0(MXC_CCM_PDR0_MCU_PODF_MASK,
		       MXC_CCM_PDR0_MCU_PODF_OFFSET);
	clk->rate = clk->parent->rate / (mcu_pdf + 1);
}

static void _clk_hclk_recalc(struct clk *clk)
{
	unsigned long max_pdf;

	max_pdf = PDR0(MXC_CCM_PDR0_MAX_PODF_MASK,
		       MXC_CCM_PDR0_MAX_PODF_OFFSET);
	clk->rate = clk->parent->rate / (max_pdf + 1);
}

static void _clk_ipg_recalc(struct clk *clk)
{
	unsigned long ipg_pdf;

	ipg_pdf = PDR0(MXC_CCM_PDR0_IPG_PODF_MASK,
		       MXC_CCM_PDR0_IPG_PODF_OFFSET);
	clk->rate = clk->parent->rate / (ipg_pdf + 1);
}

static void _clk_nfc_recalc(struct clk *clk)
{
	unsigned long nfc_pdf;

	nfc_pdf = PDR0(MXC_CCM_PDR0_NFC_PODF_MASK,
		       MXC_CCM_PDR0_NFC_PODF_OFFSET);
	clk->rate = clk->parent->rate / (nfc_pdf + 1);
}

static void _clk_hsp_recalc(struct clk *clk)
{
	unsigned long hsp_pdf;

	hsp_pdf = PDR0(MXC_CCM_PDR0_HSP_PODF_MASK,
		       MXC_CCM_PDR0_HSP_PODF_OFFSET);
	clk->rate = clk->parent->rate / (hsp_pdf + 1);
}

static void _clk_usb_recalc(struct clk *clk)
{
	unsigned long usb_pdf, usb_prepdf;

	usb_pdf = PDR1(MXC_CCM_PDR1_USB_PODF_MASK,
		       MXC_CCM_PDR1_USB_PODF_OFFSET);
	usb_prepdf = PDR1(MXC_CCM_PDR1_USB_PRDF_MASK,
			  MXC_CCM_PDR1_USB_PRDF_OFFSET);
	clk->rate = clk->parent->rate / (usb_prepdf + 1) / (usb_pdf + 1);
}

static void _clk_csi_recalc(struct clk *clk)
{
	u32 reg;
	u32 pre, post;

	reg = __raw_readl(MXC_CCM_PDR0);
	pre = (reg & MXC_CCM_PDR0_CSI_PRDF_MASK) >>
	    MXC_CCM_PDR0_CSI_PRDF_OFFSET;
	pre++;
	post = (reg & MXC_CCM_PDR0_CSI_PODF_MASK) >>
	    MXC_CCM_PDR0_CSI_PODF_OFFSET;
	post++;
	clk->rate = clk->parent->rate / (pre * post);
}

static unsigned long _clk_csi_round_rate(struct clk *clk, unsigned long rate)
{
	u32 pre, post;
	u32 div = clk->parent->rate / rate;
	if (clk->parent->rate % rate)
		div++;

	__calc_pre_post_dividers(div, &pre, &post);

	return clk->parent->rate / (pre * post);
}

static int _clk_csi_set_rate(struct clk *clk, unsigned long rate)
{
	u32 reg;
	u32 div;
	u32 pre, post;

	div = clk->parent->rate / rate;

	if ((clk->parent->rate / div) != rate)
		return -EINVAL;

	__calc_pre_post_dividers(div, &pre, &post);

	/* Set CSI clock divider */
	reg = __raw_readl(MXC_CCM_PDR0) &
	    ~(MXC_CCM_PDR0_CSI_PODF_MASK | MXC_CCM_PDR0_CSI_PRDF_MASK);
	reg |= (post - 1) << MXC_CCM_PDR0_CSI_PODF_OFFSET;
	reg |= (pre - 1) << MXC_CCM_PDR0_CSI_PRDF_OFFSET;
	__raw_writel(reg, MXC_CCM_PDR0);

	clk->rate = rate;
	return 0;
}

static void _clk_per_recalc(struct clk *clk)
{
	unsigned long per_pdf;

	per_pdf = PDR0(MXC_CCM_PDR0_PER_PODF_MASK,
		       MXC_CCM_PDR0_PER_PODF_OFFSET);
	clk->rate = clk->parent->rate / (per_pdf + 1);
}

static void _clk_ssi1_recalc(struct clk *clk)
{
	unsigned long ssi1_pdf, ssi1_prepdf;

	ssi1_pdf = PDR1(MXC_CCM_PDR1_SSI1_PODF_MASK,
			MXC_CCM_PDR1_SSI1_PODF_OFFSET);
	ssi1_prepdf = PDR1(MXC_CCM_PDR1_SSI1_PRE_PODF_MASK,
			   MXC_CCM_PDR1_SSI1_PRE_PODF_OFFSET);
	clk->rate = clk->parent->rate / (ssi1_prepdf + 1) / (ssi1_pdf + 1);
}

static void _clk_ssi2_recalc(struct clk *clk)
{
	unsigned long ssi2_pdf, ssi2_prepdf;

	ssi2_pdf = PDR1(MXC_CCM_PDR1_SSI2_PODF_MASK,
			MXC_CCM_PDR1_SSI2_PODF_OFFSET);
	ssi2_prepdf = PDR1(MXC_CCM_PDR1_SSI2_PRE_PODF_MASK,
			   MXC_CCM_PDR1_SSI2_PRE_PODF_OFFSET);
	clk->rate = clk->parent->rate / (ssi2_prepdf + 1) / (ssi2_pdf + 1);
}

static void _clk_firi_recalc(struct clk *clk)
{
	unsigned long firi_pdf, firi_prepdf;

	firi_pdf = PDR1(MXC_CCM_PDR1_FIRI_PODF_MASK,
			MXC_CCM_PDR1_FIRI_PODF_OFFSET);
	firi_prepdf = PDR1(MXC_CCM_PDR1_FIRI_PRE_PODF_MASK,
			   MXC_CCM_PDR1_FIRI_PRE_PODF_OFFSET);
	clk->rate = clk->parent->rate / (firi_prepdf + 1) / (firi_pdf + 1);
}

static unsigned long _clk_firi_round_rate(struct clk *clk, unsigned long rate)
{
	u32 pre, post;
	u32 div = clk->parent->rate / rate;
	if (clk->parent->rate % rate)
		div++;

	__calc_pre_post_dividers(div, &pre, &post);

	return clk->parent->rate / (pre * post);

}

static int _clk_firi_set_rate(struct clk *clk, unsigned long rate)
{
	u32 reg;
	u32 div, pre, post;

	div = clk->parent->rate / rate;

	if ((clk->parent->rate / div) != rate)
		return -EINVAL;

	__calc_pre_post_dividers(div, &pre, &post);

	/* Set FIRI clock divider */
	reg = __raw_readl(MXC_CCM_PDR1) &
	    ~(MXC_CCM_PDR1_FIRI_PODF_MASK | MXC_CCM_PDR1_FIRI_PRE_PODF_MASK);
	reg |= (pre - 1) << MXC_CCM_PDR1_FIRI_PRE_PODF_OFFSET;
	reg |= (post - 1) << MXC_CCM_PDR1_FIRI_PODF_OFFSET;
	__raw_writel(reg, MXC_CCM_PDR1);

	clk->rate = rate;
	return 0;
}

static void _clk_mbx_recalc(struct clk *clk)
{
	clk->rate = clk->parent->rate / 2;
}

static void _clk_mstick1_recalc(struct clk *clk)
{
	unsigned long msti_pdf;

	msti_pdf = PDR2(MXC_CCM_PDR2_MST1_PDF_MASK,
			MXC_CCM_PDR2_MST1_PDF_OFFSET);
	clk->rate = clk->parent->rate / (msti_pdf + 1);
}

static void _clk_mstick2_recalc(struct clk *clk)
{
	unsigned long msti_pdf;

	msti_pdf = PDR2(MXC_CCM_PDR2_MST2_PDF_MASK,
			MXC_CCM_PDR2_MST2_PDF_OFFSET);
	clk->rate = clk->parent->rate / (msti_pdf + 1);
}

static struct clk ckih_clk = {
	.name = "ckih",
	.rate = 0,		/* determined later (26 or 27 MHz) */
	.flags = RATE_PROPAGATES,
};

static struct clk ckil_clk = {
	.name = "ckil",
	.rate = CKIL_CLK_FREQ,
	.flags = RATE_PROPAGATES,
};

static struct clk mcu_pll_clk = {
	.name = "mcu_pll",
	.parent = &ckih_clk,
	.set_rate = _clk_pll_set_rate,
	.recalc = _clk_pll_recalc,
	.flags = RATE_PROPAGATES,
};

static struct clk mcu_main_clk = {
	.name = "mcu_main_clk",
	.parent = &mcu_pll_clk,
	.recalc = _clk_mcu_main_recalc,
};

static struct clk serial_pll_clk = {
	.name = "serial_pll",
	.parent = &ckih_clk,
	.set_rate = _clk_pll_set_rate,
	.recalc = _clk_pll_recalc,
	.enable = _clk_serial_pll_enable,
	.disable = _clk_serial_pll_disable,
	.flags = RATE_PROPAGATES,
};

static struct clk usb_pll_clk = {
	.name = "usb_pll",
	.parent = &ckih_clk,
	.set_rate = _clk_pll_set_rate,
	.recalc = _clk_pll_recalc,
	.enable = _clk_usb_pll_enable,
	.disable = _clk_usb_pll_disable,
	.flags = RATE_PROPAGATES,
};

static struct clk cpu_clk = {
	.name = "cpu_clk",
	.parent = &mcu_main_clk,
	.recalc = _clk_cpu_recalc,
	.set_rate = _clk_cpu_set_rate,
};

static struct clk ahb_clk = {
	.name = "ahb_clk",
	.parent = &mcu_main_clk,
	.recalc = _clk_hclk_recalc,
	.flags = RATE_PROPAGATES,
};

static struct clk per_clk = {
	.name = "per_clk",
	.parent = &usb_pll_clk,
	.recalc = _clk_per_recalc,
	.flags = RATE_PROPAGATES,
};

static struct clk perclk_clk = {
	.name = "perclk_clk",
	.parent = &ipg_clk,
	.flags = RATE_PROPAGATES,
};

static struct clk cspi_clk[] = {
	{
	 .name = "cspi_clk",
	 .id = 0,
	 .parent = &ipg_clk,
	 .enable = _clk_enable,
	 .enable_reg = MXC_CCM_CGR2,
	 .enable_shift = MXC_CCM_CGR2_CSPI1_OFFSET,
	 .disable = _clk_disable,},
	{
	 .name = "cspi_clk",
	 .id = 1,
	 .parent = &ipg_clk,
	 .enable = _clk_enable,
	 .enable_reg = MXC_CCM_CGR2,
	 .enable_shift = MXC_CCM_CGR2_CSPI2_OFFSET,
	 .disable = _clk_disable,},
	{
	 .name = "cspi_clk",
	 .id = 2,
	 .parent = &ipg_clk,
	 .enable = _clk_enable,
	 .enable_reg = MXC_CCM_CGR0,
	 .enable_shift = MXC_CCM_CGR0_CSPI3_OFFSET,
	 .disable = _clk_disable,},
};

static struct clk ipg_clk = {
	.name = "ipg_clk",
	.parent = &ahb_clk,
	.recalc = _clk_ipg_recalc,
	.flags = RATE_PROPAGATES,
};

static struct clk emi_clk = {
	.name = "emi_clk",
	.parent = &ahb_clk,
	.enable = _clk_enable,
	.enable_reg = MXC_CCM_CGR2,
	.enable_shift = MXC_CCM_CGR2_EMI_OFFSET,
	.disable = _clk_emi_disable,
};

static struct clk gpt_clk = {
	.name = "gpt_clk",
	.parent = &perclk_clk,
	.enable = _clk_enable,
	.enable_reg = MXC_CCM_CGR0,
	.enable_shift = MXC_CCM_CGR0_GPT_OFFSET,
	.disable = _clk_disable,
};

static struct clk pwm_clk = {
	.name = "pwm_clk",
	.parent = &perclk_clk,
	.enable = _clk_enable,
	.enable_reg = MXC_CCM_CGR0,
	.enable_shift = MXC_CCM_CGR1_PWM_OFFSET,
	.disable = _clk_disable,
};

static struct clk epit_clk[] = {
	{
	 .name = "epit_clk",
	 .id = 0,
	 .parent = &perclk_clk,
	 .enable = _clk_enable,
	 .enable_reg = MXC_CCM_CGR0,
	 .enable_shift = MXC_CCM_CGR0_EPIT1_OFFSET,
	 .disable = _clk_disable,},
	{
	 .name = "epit_clk",
	 .id = 1,
	 .parent = &perclk_clk,
	 .enable = _clk_enable,
	 .enable_reg = MXC_CCM_CGR0,
	 .enable_shift = MXC_CCM_CGR0_EPIT2_OFFSET,
	 .disable = _clk_disable,},
};

static struct clk nfc_clk = {
	.name = "nfc_clk",
	.parent = &ahb_clk,
	.recalc = _clk_nfc_recalc,
};

static struct clk scc_clk = {
	.name = "scc_clk",
	.parent = &ipg_clk,
};

static struct clk ipu_clk = {
	.name = "ipu_clk",
	.parent = &mcu_main_clk,
	.recalc = _clk_hsp_recalc,
	.enable = _clk_enable,
	.enable_reg = MXC_CCM_CGR1,
	.enable_shift = MXC_CCM_CGR1_IPU_OFFSET,
	.disable = _clk_disable,
};

static struct clk kpp_clk = {
	.name = "kpp_clk",
	.parent = &ipg_clk,
	.enable = _clk_enable,
	.enable_reg = MXC_CCM_CGR1,
	.enable_shift = MXC_CCM_CGR1_KPP_OFFSET,
	.disable = _clk_disable,
};

static struct clk wdog_clk = {
	.name = "wdog_clk",
	.parent = &ipg_clk,
	.enable = _clk_enable,
	.enable_reg = MXC_CCM_CGR1,
	.enable_shift = MXC_CCM_CGR1_WDOG_OFFSET,
	.disable = _clk_disable,
};
static struct clk rtc_clk = {
	.name = "rtc_clk",
	.parent = &ipg_clk,
	.enable = _clk_enable,
	.enable_reg = MXC_CCM_CGR1,
	.enable_shift = MXC_CCM_CGR1_RTC_OFFSET,
	.disable = _clk_disable,
};

static struct clk usb_clk[] = {
	{
	 .name = "usb_clk",
	 .parent = &usb_pll_clk,
	 .recalc = _clk_usb_recalc,},
	{
	 .name = "usb_ahb_clk",
	 .parent = &ahb_clk,
	 .enable = _clk_enable,
	 .enable_reg = MXC_CCM_CGR1,
	 .enable_shift = MXC_CCM_CGR1_USBOTG_OFFSET,
	 .disable = _clk_disable,},
};

static struct clk csi_clk = {
	.name = "csi_clk",
	.parent = &serial_pll_clk,
	.recalc = _clk_csi_recalc,
	.round_rate = _clk_csi_round_rate,
	.set_rate = _clk_csi_set_rate,
	.enable = _clk_enable,
	.enable_reg = MXC_CCM_CGR1,
	.enable_shift = MXC_CCM_CGR1_CSI_OFFSET,
	.disable = _clk_disable,
};

static struct clk uart_clk[] = {
	{
	 .name = "uart_clk",
	 .id = 0,
	 .parent = &perclk_clk,
	 .enable = _clk_enable,
	 .enable_reg = MXC_CCM_CGR0,
	 .enable_shift = MXC_CCM_CGR0_UART1_OFFSET,
	 .disable = _clk_disable,},
	{
	 .name = "uart_clk",
	 .id = 1,
	 .parent = &perclk_clk,
	 .enable = _clk_enable,
	 .enable_reg = MXC_CCM_CGR0,
	 .enable_shift = MXC_CCM_CGR0_UART2_OFFSET,
	 .disable = _clk_disable,},
	{
	 .name = "uart_clk",
	 .id = 2,
	 .parent = &perclk_clk,
	 .enable = _clk_enable,
	 .enable_reg = MXC_CCM_CGR1,
	 .enable_shift = MXC_CCM_CGR1_UART3_OFFSET,
	 .disable = _clk_disable,},
	{
	 .name = "uart_clk",
	 .id = 3,
	 .parent = &perclk_clk,
	 .enable = _clk_enable,
	 .enable_reg = MXC_CCM_CGR1,
	 .enable_shift = MXC_CCM_CGR1_UART4_OFFSET,
	 .disable = _clk_disable,},
	{
	 .name = "uart_clk",
	 .id = 4,
	 .parent = &perclk_clk,
	 .enable = _clk_enable,
	 .enable_reg = MXC_CCM_CGR1,
	 .enable_shift = MXC_CCM_CGR1_UART5_OFFSET,
	 .disable = _clk_disable,},
};

static struct clk i2c_clk[] = {
	{
	 .name = "i2c_clk",
	 .id = 0,
	 .parent = &perclk_clk,
	 .enable = _clk_enable,
	 .enable_reg = MXC_CCM_CGR0,
	 .enable_shift = MXC_CCM_CGR0_I2C1_OFFSET,
	 .disable = _clk_disable,},
	{
	 .name = "i2c_clk",
	 .id = 1,
	 .parent = &perclk_clk,
	 .enable = _clk_enable,
	 .enable_reg = MXC_CCM_CGR0,
	 .enable_shift = MXC_CCM_CGR0_I2C2_OFFSET,
	 .disable = _clk_disable,},
	{
	 .name = "i2c_clk",
	 .id = 2,
	 .parent = &perclk_clk,
	 .enable = _clk_enable,
	 .enable_reg = MXC_CCM_CGR0,
	 .enable_shift = MXC_CCM_CGR0_I2C3_OFFSET,
	 .disable = _clk_disable,},
};

static struct clk owire_clk = {
	.name = "owire_clk",
	.parent = &perclk_clk,
	.enable_reg = MXC_CCM_CGR1,
	.enable_shift = MXC_CCM_CGR1_OWIRE_OFFSET,
	.enable = _clk_enable,
	.disable = _clk_disable,
};

static struct clk sdhc_clk[] = {
	{
	 .name = "sdhc_clk",
	 .id = 0,
	 .parent = &perclk_clk,
	 .enable = _clk_enable,
	 .enable_reg = MXC_CCM_CGR0,
	 .enable_shift = MXC_CCM_CGR0_SD_MMC1_OFFSET,
	 .disable = _clk_disable,},
	{
	 .name = "sdhc_clk",
	 .id = 1,
	 .parent = &perclk_clk,
	 .enable = _clk_enable,
	 .enable_reg = MXC_CCM_CGR0,
	 .enable_shift = MXC_CCM_CGR0_SD_MMC2_OFFSET,
	 .disable = _clk_disable,},
};

static struct clk ssi_clk[] = {
	{
	 .name = "ssi_clk",
	 .parent = &serial_pll_clk,
	 .recalc = _clk_ssi1_recalc,
	 .enable = _clk_enable,
	 .enable_reg = MXC_CCM_CGR0,
	 .enable_shift = MXC_CCM_CGR0_SSI1_OFFSET,
	 .disable = _clk_disable,},
	{
	 .name = "ssi_clk",
	 .id = 1,
	 .parent = &serial_pll_clk,
	 .recalc = _clk_ssi2_recalc,
	 .enable = _clk_enable,
	 .enable_reg = MXC_CCM_CGR2,
	 .enable_shift = MXC_CCM_CGR2_SSI2_OFFSET,
	 .disable = _clk_disable,},
};

static struct clk firi_clk = {
	.name = "firi_clk",
	.parent = &usb_pll_clk,
	.round_rate = _clk_firi_round_rate,
	.set_rate = _clk_firi_set_rate,
	.recalc = _clk_firi_recalc,
	.enable = _clk_enable,
	.enable_reg = MXC_CCM_CGR2,
	.enable_shift = MXC_CCM_CGR2_FIRI_OFFSET,
	.disable = _clk_disable,
};

static struct clk ata_clk = {
	.name = "ata_clk",
	.parent = &ipg_clk,
	.enable = _clk_enable,
	.enable_reg = MXC_CCM_CGR0,
	.enable_shift = MXC_CCM_CGR0_ATA_OFFSET,
	.disable = _clk_disable,
};

static struct clk mbx_clk = {
	.name = "mbx_clk",
	.parent = &ahb_clk,
	.enable = _clk_enable,
	.enable_reg = MXC_CCM_CGR2,
	.enable_shift = MXC_CCM_CGR2_GACC_OFFSET,
	.recalc = _clk_mbx_recalc,
};

static struct clk vpu_clk = {
	.name = "vpu_clk",
	.parent = &ahb_clk,
	.enable = _clk_enable,
	.enable_reg = MXC_CCM_CGR2,
	.enable_shift = MXC_CCM_CGR2_GACC_OFFSET,
	.recalc = _clk_mbx_recalc,
};

static struct clk rtic_clk = {
	.name = "rtic_clk",
	.parent = &ahb_clk,
	.enable = _clk_enable,
	.enable_reg = MXC_CCM_CGR2,
	.enable_shift = MXC_CCM_CGR2_RTIC_OFFSET,
	.disable = _clk_disable,
};

static struct clk rng_clk = {
	.name = "rng_clk",
	.parent = &ipg_clk,
	.enable = _clk_enable,
	.enable_reg = MXC_CCM_CGR0,
	.enable_shift = MXC_CCM_CGR0_RNG_OFFSET,
	.disable = _clk_disable,
};

static struct clk sdma_clk[] = {
	{
	 .name = "sdma_ahb_clk",
	 .parent = &ahb_clk,
	 .enable = _clk_enable,
	 .enable_reg = MXC_CCM_CGR0,
	 .enable_shift = MXC_CCM_CGR0_SDMA_OFFSET,
	 .disable = _clk_disable,},
	{
	 .name = "sdma_ipg_clk",
	 .parent = &ipg_clk,}
};

static struct clk mpeg4_clk = {
	.name = "mpeg4_clk",
	.parent = &ahb_clk,
	.enable = _clk_enable,
	.enable_reg = MXC_CCM_CGR1,
	.enable_shift = MXC_CCM_CGR1_HANTRO_OFFSET,
	.disable = _clk_disable,
};

static struct clk vl2cc_clk = {
	.name = "vl2cc_clk",
	.parent = &ahb_clk,
	.enable = _clk_enable,
	.enable_reg = MXC_CCM_CGR1,
	.enable_shift = MXC_CCM_CGR1_HANTRO_OFFSET,
	.disable = _clk_disable,
};

static struct clk mstick_clk[] = {
	{
	 .name = "mstick_clk",
	 .id = 0,
	 .parent = &usb_pll_clk,
	 .recalc = _clk_mstick1_recalc,
	 .enable = _clk_enable,
	 .enable_reg = MXC_CCM_CGR1,
	 .enable_shift = MXC_CCM_CGR1_MEMSTICK1_OFFSET,
	 .disable = _clk_disable,},
	{
	 .name = "mstick_clk",
	 .id = 1,
	 .parent = &usb_pll_clk,
	 .recalc = _clk_mstick2_recalc,
	 .enable = _clk_enable,
	 .enable_reg = MXC_CCM_CGR1,
	 .enable_shift = MXC_CCM_CGR1_MEMSTICK2_OFFSET,
	 .disable = _clk_disable,},
};

static struct clk iim_clk = {
	.name = "iim_clk",
	.parent = &ipg_clk,
	.enable = _clk_enable,
	.enable_reg = MXC_CCM_CGR0,
	.enable_shift = MXC_CCM_CGR0_IIM_OFFSET,
	.disable = _clk_disable,
};

static unsigned long _clk_cko1_round_rate(struct clk *clk, unsigned long rate)
{
	u32 div;

	div = clk->parent->rate / rate;
	if (clk->parent->rate % rate)
		div++;

	if (div > 8) {
		div = 16;
	} else if (div > 4) {
		div = 8;
	} else if (div > 2) {
		div = 4;
	}

	return clk->parent->rate / div;
}

static int _clk_cko1_set_rate(struct clk *clk, unsigned long rate)
{
	u32 reg;
	u32 div;

	div = clk->parent->rate / rate;

	if (div == 16) {
		div = 4;
	} else if (div == 8) {
		div = 3;
	} else if (div == 4) {
		div = 2;
	} else if (div == 2) {
		div = 1;
	} else if (div == 1) {
		div = 0;
	} else {
		return -EINVAL;
	}

	reg = __raw_readl(MXC_CCM_COSR) & ~MXC_CCM_COSR_CLKOUTDIV_MASK;
	reg |= div << MXC_CCM_COSR_CLKOUTDIV_OFFSET;
	__raw_writel(reg, MXC_CCM_COSR);

	return 0;
}

static void _clk_cko1_recalc(struct clk *clk)
{
	u32 div;

	div = __raw_readl(MXC_CCM_COSR) & MXC_CCM_COSR_CLKOUTDIV_MASK >>
	    MXC_CCM_COSR_CLKOUTDIV_OFFSET;

	clk->rate = clk->parent->rate / (1 << div);
}

static int _clk_cko1_set_parent(struct clk *clk, struct clk *parent)
{
	u32 reg;

	reg = __raw_readl(MXC_CCM_COSR) & ~MXC_CCM_COSR_CLKOSEL_MASK;

	if (parent == &mcu_main_clk) {
		reg |= 0 << MXC_CCM_COSR_CLKOSEL_OFFSET;
	} else if (parent == &ipg_clk) {
		reg |= 1 << MXC_CCM_COSR_CLKOSEL_OFFSET;
	} else if (parent == &usb_pll_clk) {
		reg |= 2 << MXC_CCM_COSR_CLKOSEL_OFFSET;
	} else if (parent == mcu_main_clk.parent) {
		reg |= 3 << MXC_CCM_COSR_CLKOSEL_OFFSET;
	} else if (parent == &ahb_clk) {
		reg |= 5 << MXC_CCM_COSR_CLKOSEL_OFFSET;
	} else if (parent == &cpu_clk) {
		reg |= 6 << MXC_CCM_COSR_CLKOSEL_OFFSET;
	} else if (parent == &serial_pll_clk) {
		reg |= 7 << MXC_CCM_COSR_CLKOSEL_OFFSET;
	} else if (parent == &ckih_clk) {
		reg |= 8 << MXC_CCM_COSR_CLKOSEL_OFFSET;
	} else if (parent == &emi_clk) {
		reg |= 9 << MXC_CCM_COSR_CLKOSEL_OFFSET;
	} else if (parent == &ipu_clk) {
		reg |= 0xA << MXC_CCM_COSR_CLKOSEL_OFFSET;
	} else if (parent == &nfc_clk) {
		reg |= 0xB << MXC_CCM_COSR_CLKOSEL_OFFSET;
	} else if (parent == &uart_clk[0]) {
		reg |= 0xC << MXC_CCM_COSR_CLKOSEL_OFFSET;
	} else {
		return -EINVAL;
	}

	__raw_writel(reg, MXC_CCM_COSR);

	return 0;
}

static int _clk_cko1_enable(struct clk *clk)
{
	u32 reg;

	reg = __raw_readl(MXC_CCM_COSR) | MXC_CCM_COSR_CLKOEN;
	__raw_writel(reg, MXC_CCM_COSR);

	return 0;
}

static void _clk_cko1_disable(struct clk *clk)
{
	u32 reg;

	reg = __raw_readl(MXC_CCM_COSR) & ~MXC_CCM_COSR_CLKOEN;
	__raw_writel(reg, MXC_CCM_COSR);
}

static struct clk cko1_clk = {
	.name = "cko1_clk",
	.recalc = _clk_cko1_recalc,
	.set_rate = _clk_cko1_set_rate,
	.round_rate = _clk_cko1_round_rate,
	.set_parent = _clk_cko1_set_parent,
	.enable = _clk_cko1_enable,
	.disable = _clk_cko1_disable,
};

static struct clk *mxc_clks[] = {
	&ckih_clk,
	&ckil_clk,
	&mcu_pll_clk,
	&usb_pll_clk,
	&serial_pll_clk,
	&mcu_main_clk,
	&cpu_clk,
	&ahb_clk,
	&per_clk,
	&perclk_clk,
	&cko1_clk,
	&emi_clk,
	&cspi_clk[0],
	&cspi_clk[1],
	&cspi_clk[2],
	&ipg_clk,
	&gpt_clk,
	&pwm_clk,
	&wdog_clk,
	&rtc_clk,
	&epit_clk[0],
	&epit_clk[1],
	&nfc_clk,
	&ipu_clk,
	&kpp_clk,
	&usb_clk[0],
	&usb_clk[1],
	&csi_clk,
	&uart_clk[0],
	&uart_clk[1],
	&uart_clk[2],
	&uart_clk[3],
	&uart_clk[4],
	&i2c_clk[0],
	&i2c_clk[1],
	&i2c_clk[2],
	&owire_clk,
	&sdhc_clk[0],
	&sdhc_clk[1],
	&ssi_clk[0],
	&ssi_clk[1],
	&firi_clk,
	&ata_clk,
	&rtic_clk,
	&rng_clk,
	&sdma_clk[0],
	&sdma_clk[1],
	&mstick_clk[0],
	&mstick_clk[1],
	&scc_clk,
	&iim_clk,
};

static int cpu_curr_wp;
static struct cpu_wp *cpu_wp_tbl;

static int cpu_wp_nr;

extern void propagate_rate(struct clk *tclk);

int __init mxc_clocks_init(void)
{
	u32 reg;
	struct clk **clkp;
#ifndef CONFIG_DEBUG_LL
	struct clk *parent;
#endif /* !CONFIG_DEBUG_LL */

	for (clkp = mxc_clks; clkp < mxc_clks + ARRAY_SIZE(mxc_clks); clkp++) {
		clk_register(*clkp);
	}

	if (cpu_is_mx31()) {
		clk_register(&mpeg4_clk);
		clk_register(&mbx_clk);
	} else {
		clk_register(&vpu_clk);
		clk_register(&vl2cc_clk);
	}

#ifdef CONFIG_DEBUG_LL
	/* To keep the debug device functioning, generally one of the
	 * UARTs, we keep these clocks on. */
#else /* !CONFIG_DEBUG_LL */
	/* Turn off all possible clocks */
	__raw_writel(MXC_CCM_CGR0_GPT_MASK, MXC_CCM_CGR0);
	__raw_writel(0, MXC_CCM_CGR1);

	__raw_writel(MXC_CCM_CGR2_EMI_MASK | MXC_CCM_CGR2_IPMUX1_MASK | MXC_CCM_CGR2_IPMUX2_MASK | MXC_CCM_CGR2_MXCCLKENSEL_MASK |	/*For MX32 */
		     MXC_CCM_CGR2_CHIKCAMPEN_MASK |	/*For MX32 */
		     MXC_CCM_CGR2_OVRVPUBUSY_MASK |	/*For MX32 */
		     1 << 27 |	/*Bit 27 and 28 are not defined for MX32, but still requires to be set */
		     1 << 28, MXC_CCM_CGR2);

	cko1_clk.disable(&cko1_clk);
	usb_pll_clk.disable(&usb_pll_clk);
#endif /* CONFIG_DEBUG_LL */

	pr_info("Clock input source is %ld\n", ckih_clk.rate);

#ifdef CONFIG_DEBUG_LL
	if (__raw_readl(MXC_CCM_CCMR) & MXC_CCM_CCMR_PERCS)
		perclk_clk.parent = &ipg_clk;
	else
		perclk_clk.parent = &per_clk;
#else /* !CONFIG_DEBUG_LL */
	/* set CCMR.PERCS for perclk_clk's parent */
	reg    = __raw_readl(MXC_CCM_CCMR);
	parent = clk_get_parent(&perclk_clk);

	if ((parent == &ipg_clk) && !(reg & MXC_CCM_CCMR_PERCS))
		__raw_writel(reg | MXC_CCM_CCMR_PERCS, MXC_CCM_CCMR);
	else if ((parent == &per_clk) && (reg & MXC_CCM_CCMR_PERCS))
		__raw_writel(reg & ~MXC_CCM_CCMR_PERCS, MXC_CCM_CCMR);
#endif /* CONFIG_DEBUG_LL */

	/* This will propagate to all children and init all the clock rates */
	propagate_rate(&ckih_clk);

#ifdef CONFIG_DEBUG_LL
#if MXC_LL_UART_PADDR == UART1_BASE_ADDR
	clk_enable(&uart_clk[0]);
#else
#	error unknown MXC_LL_UART_PADDR.
#endif
#endif /* CONFIG_DEBUG_LL */

	clk_enable(&gpt_clk);
	clk_enable(&emi_clk);
	clk_enable(&iim_clk);

	clk_enable(&serial_pll_clk);

	if (cpu_is_mx31_rev(CHIP_REV_2_0) > 0 || cpu_is_mx32()) {
		reg = __raw_readl(MXC_CCM_PMCR1);
		/* No PLL restart on DVFS switch; enable auto EMI handshake */
		reg |= MXC_CCM_PMCR1_PLLRDIS | MXC_CCM_PMCR1_EMIRQ_EN;
		__raw_writel(reg, MXC_CCM_PMCR1);
	}

	cpu_curr_wp = cpu_clk.rate / ahb_clk.rate - 1;
	cpu_wp_tbl = get_cpu_wp(&cpu_wp_nr);

	/* Init serial PLL according */
	clk_set_rate(&serial_pll_clk, (cpu_wp_tbl[2].pll_rate));

	if (cpu_is_mx31_rev(CHIP_REV_2_0) < 0) {
		/* replace 399MHz wp with 266MHz one */
		memcpy(&cpu_wp_tbl[2], &cpu_wp_tbl[1], sizeof(cpu_wp_tbl[0]));
	}

	return 0;
}

/*!
 * Function to get timer clock rate early in boot process before clock tree is
 * initialized.
 *
 * @return	Clock rate for timer
 */
unsigned long __init clk_early_get_timer_rate(void)
{
	/* Determine which high frequency clock source is coming in */
	ckih_clk.rate = board_get_ckih_rate();

	mcu_main_clk.recalc(&mcu_main_clk);
	ahb_clk.recalc(&ahb_clk);
	ipg_clk.recalc(&ipg_clk);
	return ipg_clk.rate;
}

#define MXC_PMCR0_DVFS_MASK	(MXC_CCM_PMCR0_DVSUP_MASK | \
				 MXC_CCM_PMCR0_UDSC_MASK | \
                                 MXC_CCM_PMCR0_VSCNT_MASK | \
				 MXC_CCM_PMCR0_DPVCR)

#define MXC_PDR0_MAX_MCU_MASK	(MXC_CCM_PDR0_MAX_PODF_MASK | \
				 MXC_CCM_PDR0_MCU_PODF_MASK | \
				 MXC_CCM_PDR0_HSP_PODF_MASK | \
				 MXC_CCM_PDR0_IPG_PODF_MASK | \
				 MXC_CCM_PDR0_NFC_PODF_MASK)

static DEFINE_SPINLOCK(mxc_dfs_lock);

static void dptcen_after_timeout(unsigned long ptr)
{
	u32 flags = 0;
	u32 pmcr0;
	u32 dptc_active;

	spin_lock_irqsave(&mxc_dfs_lock, flags);
	pmcr0 = __raw_readl(MXC_CCM_PMCR0);
	dptc_active = pmcr0 & MXC_CCM_PMCR0_DPTEN;

	/*
	 * If DPTC is still active and core is running in Turbo mode
	 */
	if ((dptcen_timer.data == cpu_wp_nr - 1) && dptc_active) {
		pmcr0 |= (MXC_CCM_PMCR0_DPVCR | MXC_CCM_PMCR0_DPVV);
		__raw_writel(pmcr0, MXC_CCM_PMCR0);
	}
	spin_unlock_irqrestore(&mxc_dfs_lock, flags);
}

/*!
 * Setup cpu clock based on working point.
 * @param	wp	cpu freq working point (0 is the slowest)
 * @return		0 on success or error code on failure.
 */
static int cpu_clk_set_wp(int wp)
{
	struct cpu_wp *p;
	u32 dvsup;
	u32 pmcr0;
	u32 pdr0;
	u32 vscnt = MXC_CCM_PMCR0_VSCNT_2;
	u32 udsc = MXC_CCM_PMCR0_UDSC_DOWN;

	if (wp >= cpu_wp_nr || wp < 0) {
		printk(KERN_ERR "Wrong wp: %d for cpu_clk_set_wp\n", wp);
		return -EINVAL;
	}
	if (wp == cpu_curr_wp) {
		return 0;
	}

	pmcr0 = __raw_readl(MXC_CCM_PMCR0);
	pdr0 = __raw_readl(MXC_CCM_PDR0);

	if (!(pmcr0 & MXC_CCM_PMCR0_UPDTEN)) {
		return -EBUSY;
	}

	if (wp > cpu_curr_wp) {
		/* going faster */
		if (wp == (cpu_wp_nr - 1)) {
			/* Only update vscnt going into Turbo */
			vscnt = MXC_CCM_PMCR0_VSCNT_8;
		}
		udsc = MXC_CCM_PMCR0_UDSC_UP;
	}

	p = &cpu_wp_tbl[wp];

	if (mcu_main_clk.rate == p->pll_rate) {
		/* No pll switching and relocking needed */
		pmcr0 |= MXC_CCM_PMCR0_DFSUP0_PDR;
	} else {
		/* pll switching and relocking needed */
		pmcr0 ^= MXC_CCM_PMCR0_DFSUP1;	/* flip MSB bit */
		pmcr0 &= ~(MXC_CCM_PMCR0_DFSUP0);
	}

	dvsup = (cpu_wp_nr - 1 - wp) << MXC_CCM_PMCR0_DVSUP_OFFSET;

	pmcr0 = (pmcr0 & ~MXC_PMCR0_DVFS_MASK) | dvsup | vscnt | udsc;
	/* also enable DVFS hardware */
	pmcr0 |= MXC_CCM_PMCR0_DVFEN;

	__raw_writel(pmcr0, MXC_CCM_PMCR0);

	__raw_writel((pdr0 & ~MXC_PDR0_MAX_MCU_MASK) | p->pdr0_reg,
		     MXC_CCM_PDR0);

	if ((pmcr0 & MXC_CCM_PMCR0_DFSUP0) == MXC_CCM_PMCR0_DFSUP0_PLL) {
		/* PLL and post divider update */
		if ((pmcr0 & MXC_CCM_PMCR0_DFSUP1) == MXC_CCM_PMCR0_DFSUP1_SPLL) {
			__raw_writel(p->pll_reg, MXC_CCM_SRPCTL);
			serial_pll_clk.rate = p->pll_rate;
			mcu_main_clk.parent = &serial_pll_clk;
		} else {
			__raw_writel(p->pll_reg, MXC_CCM_MPCTL);
			mcu_pll_clk.rate = p->pll_rate;
			mcu_main_clk.parent = &mcu_pll_clk;
		}
	}

	mcu_main_clk.rate = p->pll_rate;
	cpu_clk.rate = p->cpu_rate;

	cpu_curr_wp = wp;

	if (wp == cpu_wp_nr - 1) {
		init_timer(&dptcen_timer);
		dptcen_timer.expires = jiffies + 2;
		dptcen_timer.function = dptcen_after_timeout;
		dptcen_timer.data = wp;
		add_timer(&dptcen_timer);
	}

	return 0;
}
