rockbox/firmware/target/arm/imx233/clkctrl-imx233.c

333 lines
11 KiB
C
Raw Normal View History

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright © 2011 by Amaury Pouly
*
* 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "clkctrl-imx233.h"
#define __CLK_CLKGATE (1 << 31)
#define __CLK_BUSY (1 << 29)
void imx233_clkctrl_enable_xtal(enum imx233_xtal_clk_t xtal_clk, bool enable)
{
if(enable)
HW_CLKCTRL_XTAL_CLR = xtal_clk;
else
HW_CLKCTRL_XTAL_SET = xtal_clk;
}
bool imx233_clkctrl_is_xtal_enable(enum imx233_xtal_clk_t clk)
{
return HW_CLKCTRL_XTAL & clk;
}
void imx233_clkctrl_enable_clock(enum imx233_clock_t clk, bool enable)
{
bool gate = !enable;
switch(clk)
{
case CLK_PIX: BF_WR(CLKCTRL_PIX, CLKGATE, gate); break;
case CLK_SSP: BF_WR(CLKCTRL_SSP, CLKGATE, gate); break;
case CLK_PLL:
/* pll is a special case */
if(enable)
{
BF_SET(CLKCTRL_PLLCTRL0, POWER);
while(!BF_RD(CLKCTRL_PLLCTRL1, LOCK));
}
else
BF_CLR(CLKCTRL_PLLCTRL0, POWER);
break;
default:
break;
}
}
bool imx233_clkctrl_is_clock_enabled(enum imx233_clock_t clk)
{
switch(clk)
{
case CLK_PLL: return BF_RD(CLKCTRL_PLLCTRL0, POWER);
case CLK_PIX: return !BF_RD(CLKCTRL_PIX, CLKGATE);
case CLK_SSP: return !BF_RD(CLKCTRL_SSP, CLKGATE);
default: return true;
}
}
void imx233_clkctrl_set_clock_divisor(enum imx233_clock_t clk, int div)
{
/* warning: some registers like HW_CLKCTRL_PIX don't have a CLR/SET variant !
* assume that we always derive emi and cpu from ref_XX */
switch(clk)
{
case CLK_PIX: BF_WR(CLKCTRL_PIX, DIV, div); break;
case CLK_CPU: BF_WR(CLKCTRL_CPU, DIV_CPU, div); break;
case CLK_EMI: BF_WR(CLKCTRL_EMI, DIV_EMI, div); break;
case CLK_SSP: BF_WR(CLKCTRL_SSP, DIV, div); break;
case CLK_HBUS: BF_WR(CLKCTRL_HBUS, DIV, div); break;
case CLK_XBUS: BF_WR(CLKCTRL_XBUS, DIV, div); break;
default: return;
}
}
int imx233_clkctrl_get_clock_divisor(enum imx233_clock_t clk)
{
switch(clk)
{
case CLK_PIX: return BF_RD(CLKCTRL_PIX, DIV);
case CLK_CPU: return BF_RD(CLKCTRL_CPU, DIV_CPU);
case CLK_EMI: return BF_RD(CLKCTRL_EMI, DIV_EMI);
case CLK_SSP: return BF_RD(CLKCTRL_SSP, DIV);
case CLK_HBUS: return BF_RD(CLKCTRL_HBUS, DIV);
case CLK_XBUS: return BF_RD(CLKCTRL_XBUS, DIV);
default: return 0;
}
}
void imx233_clkctrl_set_fractional_divisor(enum imx233_clock_t clk, int fracdiv)
{
#define handle_frac(dev) \
case CLK_##dev: \
if(fracdiv == 0) \
BF_SET(CLKCTRL_FRAC, CLKGATE##dev); \
else { \
BF_WR(CLKCTRL_FRAC, dev##FRAC, fracdiv); \
BF_CLR(CLKCTRL_FRAC, CLKGATE##dev); } \
break;
switch(clk)
{
handle_frac(PIX)
handle_frac(IO)
handle_frac(CPU)
handle_frac(EMI)
default: break;
}
#undef handle_frac
}
int imx233_clkctrl_get_fractional_divisor(enum imx233_clock_t clk)
{
#define handle_frac(dev) \
case CLK_##dev:\
if(BF_RD(CLKCTRL_FRAC, CLKGATE##dev)) \
return 0; \
else \
return BF_RD(CLKCTRL_FRAC, dev##FRAC);
switch(clk)
{
handle_frac(PIX)
handle_frac(IO)
handle_frac(CPU)
handle_frac(EMI)
default: return 0;
}
#undef handle_frac
}
void imx233_clkctrl_set_bypass_pll(enum imx233_clock_t clk, bool bypass)
{
uint32_t msk;
switch(clk)
{
case CLK_PIX: msk = BM_CLKCTRL_CLKSEQ_BYPASS_PIX; break;
case CLK_SSP: msk = BM_CLKCTRL_CLKSEQ_BYPASS_SSP; break;
case CLK_CPU: msk = BM_CLKCTRL_CLKSEQ_BYPASS_CPU; break;
case CLK_EMI: msk = BM_CLKCTRL_CLKSEQ_BYPASS_EMI; break;
default: return;
}
if(bypass)
HW_CLKCTRL_CLKSEQ_SET = msk;
else
HW_CLKCTRL_CLKSEQ_CLR = msk;
}
bool imx233_clkctrl_get_bypass_pll(enum imx233_clock_t clk)
{
switch(clk)
{
case CLK_PIX: return BF_RD(CLKCTRL_CLKSEQ, BYPASS_PIX);
case CLK_SSP: return BF_RD(CLKCTRL_CLKSEQ, BYPASS_SSP);
case CLK_CPU: return BF_RD(CLKCTRL_CLKSEQ, BYPASS_CPU);
case CLK_EMI: return BF_RD(CLKCTRL_CLKSEQ, BYPASS_EMI);
default: return false;
}
}
void imx233_clkctrl_enable_usb_pll(bool enable)
{
if(enable)
BF_SET(CLKCTRL_PLLCTRL0, EN_USB_CLKS);
else
BF_CLR(CLKCTRL_PLLCTRL0, EN_USB_CLKS);
}
bool imx233_clkctrl_is_usb_pll_enabled(void)
{
return BF_RD(CLKCTRL_PLLCTRL0, EN_USB_CLKS);
}
void imx233_clkctrl_set_auto_slow_divisor(enum imx233_as_div_t div)
{
/* the SLOW_DIV must only be set when auto-slow is disabled */
bool old_status = imx233_clkctrl_is_auto_slow_enabled();
imx233_clkctrl_enable_auto_slow(false);
BF_WR(CLKCTRL_HBUS, SLOW_DIV, div);
imx233_clkctrl_enable_auto_slow(old_status);
}
enum imx233_as_div_t imx233_clkctrl_get_auto_slow_divisor(void)
{
return BF_RD(CLKCTRL_HBUS, SLOW_DIV);
}
void imx233_clkctrl_enable_auto_slow(bool enable)
{
BF_WR(CLKCTRL_HBUS, AUTO_SLOW_MODE, enable);
}
bool imx233_clkctrl_is_auto_slow_enabled(void)
{
return BF_RD(CLKCTRL_HBUS, AUTO_SLOW_MODE);
}
void imx233_clkctrl_enable_auto_slow_monitor(enum imx233_as_monitor_t monitor, bool enable)
{
if(enable)
HW_CLKCTRL_HBUS_SET = monitor;
else
HW_CLKCTRL_HBUS_CLR = monitor;
}
bool imx233_clkctrl_is_auto_slow_monitor_enabled(enum imx233_as_monitor_t monitor)
{
return HW_CLKCTRL_HBUS & monitor;
}
bool imx233_clkctrl_is_emi_sync_enabled(void)
{
return BF_RD(CLKCTRL_EMI, SYNC_MODE_EN);
}
unsigned imx233_clkctrl_get_clock_freq(enum imx233_clock_t clk)
{
switch(clk)
{
case CLK_PLL: /* PLL: 480MHz when enable */
return imx233_clkctrl_is_clock_enabled(CLK_PLL) ? 480000 : 0;
case CLK_XTAL: /* crystal: 24MHz */
return 24000;
case CLK_CPU:
{
unsigned ref;
/* In bypass mode: clk_p derived from clk_xtal via int/binfrac divider
* otherwise, clk_p derived from clk_cpu via int div and clk_cpu
* derived from clk_pll fracdiv */
if(imx233_clkctrl_get_bypass_pll(CLK_CPU))
{
ref = imx233_clkctrl_get_clock_freq(CLK_XTAL);
/* Integer divide mode vs fractional divide mode */
if(BF_RD(CLKCTRL_CPU, DIV_XTAL_FRAC_EN))
return (ref * BF_RD(CLKCTRL_CPU, DIV_XTAL)) / 32;
else
return ref / imx233_clkctrl_get_clock_divisor(CLK_CPU);
}
else
{
ref = imx233_clkctrl_get_clock_freq(CLK_PLL);
/* fractional divider enable ? */
if(imx233_clkctrl_get_fractional_divisor(CLK_CPU) != 0)
ref = (ref * 18) / imx233_clkctrl_get_fractional_divisor(CLK_CPU);
return ref / imx233_clkctrl_get_clock_divisor(CLK_CPU);
}
}
case CLK_HBUS:
{
/* Derived from clk_p via integer/fractional div */
unsigned ref = imx233_clkctrl_get_clock_freq(CLK_CPU);
if(imx233_clkctrl_get_fractional_divisor(CLK_HBUS) != 0)
ref = (ref * imx233_clkctrl_get_fractional_divisor(CLK_HBUS)) / 32;
if(imx233_clkctrl_get_clock_divisor(CLK_HBUS) != 0)
ref /= imx233_clkctrl_get_clock_divisor(CLK_HBUS);
return ref;
}
case CLK_IO:
{
/* Derived from clk_pll via fracdiv */
unsigned ref = imx233_clkctrl_get_clock_freq(CLK_PLL);
if(imx233_clkctrl_get_fractional_divisor(CLK_IO) != 0)
ref = (ref * 18) / imx233_clkctrl_get_fractional_divisor(CLK_IO);
return ref;
}
case CLK_PIX:
{
unsigned ref;
/* Derived from clk_pll or clk_xtal */
if(!imx233_clkctrl_is_clock_enabled(CLK_PIX))
ref = 0;
else if(imx233_clkctrl_get_bypass_pll(CLK_PIX))
ref = imx233_clkctrl_get_clock_freq(CLK_XTAL);
else
{
ref = imx233_clkctrl_get_clock_freq(CLK_PLL);
if(imx233_clkctrl_get_fractional_divisor(CLK_PIX) != 0)
ref = (ref * 18) / imx233_clkctrl_get_fractional_divisor(CLK_PIX);
}
return ref / imx233_clkctrl_get_clock_divisor(CLK_PIX);
}
case CLK_SSP:
{
unsigned ref;
/* Derived from clk_pll or clk_xtal */
if(!imx233_clkctrl_is_clock_enabled(CLK_SSP))
ref = 0;
else if(imx233_clkctrl_get_bypass_pll(CLK_SSP))
ref = imx233_clkctrl_get_clock_freq(CLK_XTAL);
else
ref = imx233_clkctrl_get_clock_freq(CLK_IO);
return ref / imx233_clkctrl_get_clock_divisor(CLK_SSP);
}
case CLK_EMI:
{
unsigned ref;
/* Derived from clk_pll or clk_xtal */
if(imx233_clkctrl_get_bypass_pll(CLK_EMI))
{
ref = imx233_clkctrl_get_clock_freq(CLK_XTAL);
if(BF_RD(CLKCTRL_EMI, CLKGATE))
return 0;
else
return ref / BF_RD(CLKCTRL_EMI, DIV_XTAL);
}
else
{
ref = imx233_clkctrl_get_clock_freq(CLK_PLL);
if(imx233_clkctrl_get_fractional_divisor(CLK_EMI) != 0)
ref = (ref * 18) / imx233_clkctrl_get_fractional_divisor(CLK_EMI);
return ref / imx233_clkctrl_get_clock_divisor(CLK_EMI);
}
}
case CLK_XBUS:
return imx233_clkctrl_get_clock_freq(CLK_XTAL) /
imx233_clkctrl_get_clock_divisor(CLK_XBUS);
default:
return 0;
}
}