/* $Id: clock_prep.c,v 1.3 2013/10/07 17:36:40 matt Exp $ */

/*
 * Copyright (c) 2012 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Petri Laakso.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/types.h>

#include <arm/imx/imx23_clkctrlreg.h>

#include <lib/libsa/stand.h>

#include "common.h"

#define CLKCTRL_HBUS	(HW_CLKCTRL_BASE + HW_CLKCTRL_HBUS)
#define CLKCTRL_PLL0	(HW_CLKCTRL_BASE + HW_CLKCTRL_PLLCTRL0)
#define CLKCTRL_PLL0_S	(HW_CLKCTRL_BASE + HW_CLKCTRL_PLLCTRL0_SET)
#define CLKCTRL_PLL0_C	(HW_CLKCTRL_BASE + HW_CLKCTRL_PLLCTRL0_CLR)
#define CLKCTRL_PLL1	(HW_CLKCTRL_BASE + HW_CLKCTRL_PLLCTRL1)
#define CLKCTRL_FRAC	(HW_CLKCTRL_BASE + HW_CLKCTRL_FRAC)
#define CLKCTRL_FRAC_S	(HW_CLKCTRL_BASE + HW_CLKCTRL_FRAC_SET)
#define CLKCTRL_FRAC_C	(HW_CLKCTRL_BASE + HW_CLKCTRL_FRAC_CLR)
#define CLKCTRL_SEQ	(HW_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ)
#define CLKCTRL_SEQ_S	(HW_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ_SET)
#define CLKCTRL_SEQ_C	(HW_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ_CLR)
#define CLKCTRL_EMI	(HW_CLKCTRL_BASE + HW_CLKCTRL_EMI)
#define CLKCTRL_SSP	(HW_CLKCTRL_BASE + HW_CLKCTRL_SSP)

void en_pll(void);
void set_hbus_div(unsigned int);
void set_cpu_frac(unsigned int);
void bypass_cpu(void);
void set_emi_div(unsigned int);
void set_emi_frac(unsigned int);
void bypass_emi(void);

/*
 * Power on 480 MHz PLL.
 */
void
en_pll(void)
{

	REG_WR(CLKCTRL_PLL0_S, HW_CLKCTRL_PLLCTRL0_POWER);
	while(!(REG_RD(CLKCTRL_PLL1) & HW_CLKCTRL_PLLCTRL1))
		;

	return;
}
void
bypass_cpu(void)
{

	REG_WR(CLKCTRL_SEQ_C, HW_CLKCTRL_CLKSEQ_BYPASS_CPU);

	return;
}
void
bypass_emi(void)
{
	REG_WR(CLKCTRL_SEQ_C, HW_CLKCTRL_CLKSEQ_BYPASS_EMI);

	return;
}
void
bypass_ssp(void)
{
	REG_WR(CLKCTRL_SEQ_C, HW_CLKCTRL_CLKSEQ_BYPASS_SSP);

	return;
}
void
bypass_saif(void)
{
	REG_WR(CLKCTRL_SEQ_C, HW_CLKCTRL_CLKSEQ_BYPASS_SAIF);
	
	return;
}
/*
 * Set HBUS divider value.
 */
void
set_hbus_div(unsigned int div)
{
	uint32_t tmp_r;

	while (REG_RD(CLKCTRL_HBUS) & HW_CLKCTRL_HBUS_BUSY)
		;

	tmp_r = REG_RD(CLKCTRL_HBUS);
	tmp_r &= ~HW_CLKCTRL_HBUS_DIV;
	tmp_r |= __SHIFTIN(div, HW_CLKCTRL_HBUS_DIV);
	REG_WR(CLKCTRL_HBUS, tmp_r);

	while (REG_RD(CLKCTRL_HBUS) & HW_CLKCTRL_HBUS_BUSY)
		;

	return;
}
/*
 * Set CPU frac.
 */
void
set_cpu_frac(unsigned int frac)
{
	uint8_t tmp_r;

	tmp_r = REG_RD_BYTE(CLKCTRL_FRAC);
	tmp_r &= ~(HW_CLKCTRL_FRAC_CLKGATECPU | HW_CLKCTRL_FRAC_CPUFRAC);
	tmp_r |= __SHIFTIN(frac, HW_CLKCTRL_FRAC_CPUFRAC);
	REG_WR_BYTE(CLKCTRL_FRAC, tmp_r);

	return;
}
/*
 * Set EMI frac.
 */
void
set_emi_frac(unsigned int frac)
{
	uint8_t *emi_frac;
	uint16_t tmp_r;

	emi_frac = (uint8_t *)(CLKCTRL_FRAC);
	emi_frac++;
	
	tmp_r = *emi_frac<<8;
	tmp_r &= ~(HW_CLKCTRL_FRAC_CLKGATEEMI | HW_CLKCTRL_FRAC_EMIFRAC);
	tmp_r |= __SHIFTIN(frac, HW_CLKCTRL_FRAC_EMIFRAC);

	*emi_frac = tmp_r>>8;

	return;
}
/*
 * Set EMU divider value.
 */
void
set_emi_div(unsigned int div)
{
	uint32_t tmp_r;

	while (REG_RD(CLKCTRL_EMI) &
		(HW_CLKCTRL_EMI_BUSY_REF_XTAL | HW_CLKCTRL_EMI_BUSY_REF_EMI))
		;

	tmp_r = REG_RD(CLKCTRL_EMI);
	tmp_r &= ~(HW_CLKCTRL_EMI_CLKGATE | HW_CLKCTRL_EMI_DIV_EMI);
	tmp_r |= __SHIFTIN(div, HW_CLKCTRL_EMI_DIV_EMI);
	REG_WR(CLKCTRL_EMI, tmp_r);
	
	return;
}
/*
 * Set SSP divider value.
 */
void
set_ssp_div(unsigned int div)
{
	uint32_t tmp_r;

	tmp_r = REG_RD(CLKCTRL_SSP);
	tmp_r &= ~HW_CLKCTRL_SSP_CLKGATE;
	REG_WR(CLKCTRL_SSP, tmp_r);

	while (REG_RD(CLKCTRL_SSP) & HW_CLKCTRL_SSP_BUSY)
		;

	tmp_r = REG_RD(CLKCTRL_SSP);
	tmp_r &= ~HW_CLKCTRL_SSP_DIV;
	tmp_r |= __SHIFTIN(div, HW_CLKCTRL_SSP_DIV);
	REG_WR(CLKCTRL_SSP, tmp_r);

	while (REG_RD(CLKCTRL_SSP) & HW_CLKCTRL_SSP_BUSY)
		;

	return;
}
/*
 * Set IO frac.
 */
void
set_io_frac(unsigned int frac)
{
	uint8_t *io_frac;
	uint32_t tmp_r;
	
	io_frac = (uint8_t *)(CLKCTRL_FRAC);
	io_frac++; /* emi */
	io_frac++; /* pix */
	io_frac++; /* io */
	tmp_r = (*io_frac)<<24;
	tmp_r &= ~(HW_CLKCTRL_FRAC_CLKGATEIO | HW_CLKCTRL_FRAC_IOFRAC);
	tmp_r |= __SHIFTIN(frac, HW_CLKCTRL_FRAC_IOFRAC);

	*io_frac = (uint8_t)(tmp_r>>24);

	return;
}
