714 lines
20 KiB
C
714 lines
20 KiB
C
/******************************************************************************
|
|
*
|
|
* File name : clock-stx5197.c
|
|
* Description : Low Level API - 5197 specific implementation
|
|
*
|
|
* COPYRIGHT (C) 2009 STMicroelectronics - All Rights Reserved
|
|
* May be copied or modified under the terms of the GNU General Public
|
|
* License v2 _ONLY_. See linux/COPYING for more information.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
/* ----- Modification history (most recent first)----
|
|
22/mar/10 fabrice.charpentier@st.com
|
|
Replaced use of clk_pll800_freq() by clk_pll800_get_rate().
|
|
04/mar/10 fabrice.charpentier@st.com
|
|
FSA & FSB identifiers removed. clkgen_fs_init()/clkgen_fs_recalc()
|
|
revisited.
|
|
30/nov/09 francesco.virlinzi@st.com
|
|
14/aug/09 fabrice.charpentier@st.com
|
|
Added PLLA/PLLB power down/up + clkgen_pll_set_parent() capabilities.
|
|
20/jul/09 francesco.virlinzi@st.com
|
|
Redesigned all the implementation
|
|
09/jul/09 fabrice.charpentier@st.com
|
|
Revisited for LLA & Linux compliancy.
|
|
*/
|
|
|
|
/* Includes --------------------------------------------------------------- */
|
|
#include <linux/clk.h>
|
|
#include <linux/stm/clk.h>
|
|
#include <linux/stm/stx5197.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/io.h>
|
|
#include "clock-stx5197.h"
|
|
#include "clock-regs-stx5197.h"
|
|
|
|
#include "clock-oslayer.h"
|
|
#include "clock-common.h"
|
|
#include "clock-utils.h"
|
|
|
|
#define CRYSTAL 30000000
|
|
|
|
/* Private Function prototypes -------------------------------------------- */
|
|
|
|
static void sys_service_lock(int lock_enable);
|
|
static int clkgen_xtal_init(clk_t *clk_p);
|
|
static int clkgen_pll_init(clk_t *clk_p);
|
|
static int clkgen_pll_set_parent(clk_t *clk_p, clk_t *src_p);
|
|
static int clkgen_pll_set_rate(clk_t *clk_p, unsigned long rate);
|
|
static int clkgen_pll_recalc(clk_t *clk_p);
|
|
static int clkgen_pll_enable(clk_t *clk_p);
|
|
static int clkgen_pll_disable(clk_t *clk_p);
|
|
static int clkgen_pll_observe(clk_t *clk_p, unsigned long *divd);
|
|
static int clkgen_fs_set_rate(clk_t *clk_p, unsigned long rate);
|
|
static int clkgen_fs_enable(clk_t *clk_p);
|
|
static int clkgen_fs_disable(clk_t *clk_p);
|
|
static int clkgen_fs_observe(clk_t *clk_p, unsigned long *freq);
|
|
static int clkgen_fs_init(clk_t *clk_p);
|
|
static int clkgen_fs_recalc(clk_t *clk_p);
|
|
|
|
/*---------------------------------------------------------------------*/
|
|
|
|
#define FRAC(whole, half) ((whole << 1) | (half ? 1 : 0))
|
|
|
|
#define DIVIDER(depth, seq, hno, even) \
|
|
((hno << 25) | (even << 24) | (depth << 20) | (seq << 0))
|
|
|
|
#define COMBINE_DIVIDER(depth, seq, hno, even) \
|
|
.value = DIVIDER(depth, seq, hno, even), \
|
|
.cfg_0 = (seq & 0xffff), \
|
|
.cfg_1 = (seq >> 16), \
|
|
.cfg_2 = (depth | (even << 5) | (hno << 6))
|
|
|
|
static const struct {
|
|
unsigned long ratio, value;
|
|
unsigned short cfg_0;
|
|
unsigned char cfg_1, cfg_2;
|
|
} divide_table[] = {
|
|
{
|
|
FRAC(2, 0), COMBINE_DIVIDER(0x01, 0x00AAA, 0x1, 0x1)}, {
|
|
FRAC(2, 5), COMBINE_DIVIDER(0x04, 0x05AD6, 0x1, 0x0)}, {
|
|
FRAC(3, 0), COMBINE_DIVIDER(0x01, 0x00DB6, 0x0, 0x0)}, {
|
|
FRAC(3, 5), COMBINE_DIVIDER(0x03, 0x0366C, 0x1, 0x0)}, {
|
|
FRAC(4, 0), COMBINE_DIVIDER(0x05, 0x0CCCC, 0x1, 0x1)}, {
|
|
FRAC(4, 5), COMBINE_DIVIDER(0x07, 0x3399C, 0x1, 0x0)}, {
|
|
FRAC(5, 0), COMBINE_DIVIDER(0x04, 0x0739C, 0x0, 0x0)}, {
|
|
FRAC(5, 5), COMBINE_DIVIDER(0x00, 0x0071C, 0x1, 0x0)}, {
|
|
FRAC(6, 0), COMBINE_DIVIDER(0x01, 0x00E38, 0x1, 0x1)}, {
|
|
FRAC(6, 5), COMBINE_DIVIDER(0x02, 0x01C78, 0x1, 0x0)}, {
|
|
FRAC(7, 0), COMBINE_DIVIDER(0x03, 0x03C78, 0x0, 0x0)}, {
|
|
FRAC(7, 5), COMBINE_DIVIDER(0x04, 0x07878, 0x1, 0x0)}, {
|
|
FRAC(8, 0), COMBINE_DIVIDER(0x05, 0x0F0F0, 0x1, 0x1)}, {
|
|
FRAC(8, 5), COMBINE_DIVIDER(0x06, 0x1E1F0, 0x1, 0x0)}, {
|
|
FRAC(9, 0), COMBINE_DIVIDER(0x07, 0x3E1F0, 0x0, 0x0)}, {
|
|
FRAC(9, 5), COMBINE_DIVIDER(0x08, 0x7C1F0, 0x1, 0x0)}, {
|
|
FRAC(10, 0), COMBINE_DIVIDER(0x09, 0xF83E0, 0x1, 0x1)}, {
|
|
FRAC(11, 0), COMBINE_DIVIDER(0x00, 0x007E0, 0x0, 0x0)}, {
|
|
FRAC(12, 0), COMBINE_DIVIDER(0x01, 0x00FC0, 0x1, 0x1)}, {
|
|
FRAC(13, 0), COMBINE_DIVIDER(0x02, 0x01FC0, 0x0, 0x0)}, {
|
|
FRAC(14, 0), COMBINE_DIVIDER(0x03, 0x03F80, 0x1, 0x1)}, {
|
|
FRAC(15, 0), COMBINE_DIVIDER(0x04, 0x07F80, 0x0, 0x0)}, {
|
|
FRAC(16, 0), COMBINE_DIVIDER(0x05, 0x0FF00, 0x1, 0x1)}, {
|
|
FRAC(17, 0), COMBINE_DIVIDER(0x06, 0x1FF00, 0x0, 0x0)}, {
|
|
FRAC(18, 0), COMBINE_DIVIDER(0x07, 0x3FE00, 0x1, 0x1)}, {
|
|
FRAC(19, 0), COMBINE_DIVIDER(0x08, 0x7FE00, 0x0, 0x0)}, {
|
|
FRAC(20, 0), COMBINE_DIVIDER(0x09, 0xFFC00, 0x1, 0x1)}
|
|
};
|
|
|
|
_CLK_OPS(Top,
|
|
"Top clocks",
|
|
clkgen_xtal_init,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL, /* No measure function */
|
|
NULL
|
|
);
|
|
|
|
_CLK_OPS(PLL,
|
|
"PLL",
|
|
clkgen_pll_init,
|
|
clkgen_pll_set_parent,
|
|
clkgen_pll_set_rate,
|
|
clkgen_pll_recalc,
|
|
clkgen_pll_enable,
|
|
clkgen_pll_disable,
|
|
clkgen_pll_observe,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
_CLK_OPS(FS,
|
|
"FS",
|
|
clkgen_fs_init,
|
|
NULL,
|
|
clkgen_fs_set_rate,
|
|
clkgen_fs_recalc,
|
|
clkgen_fs_enable,
|
|
clkgen_fs_disable,
|
|
clkgen_fs_observe,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
/* Clocks identifier list */
|
|
|
|
/* Physical clocks description */
|
|
clk_t clk_clocks[] = {
|
|
/* clkID Ops Nominalrate Flags */
|
|
_CLK(OSC_REF, &Top, CRYSTAL, CLK_RATE_PROPAGATES | CLK_ALWAYS_ENABLED),
|
|
|
|
/* PLLs */
|
|
_CLK_P(PLLA, &PLL, 700000000, CLK_RATE_PROPAGATES, &clk_clocks[OSC_REF]),
|
|
_CLK_P(PLLB, &PLL, 800000000, CLK_RATE_PROPAGATES, &clk_clocks[OSC_REF]),
|
|
/* PLL child */
|
|
_CLK(PLL_CPU, &PLL, 800000000, 0),
|
|
_CLK(PLL_LMI, &PLL, 200000000, 0),
|
|
_CLK(PLL_BIT, &PLL, 200000000, 0),
|
|
_CLK(PLL_SYS, &PLL, 133000000, CLK_RATE_PROPAGATES | CLK_ALWAYS_ENABLED),
|
|
_CLK(PLL_FDMA, &PLL, 350000000, CLK_RATE_PROPAGATES),
|
|
_CLK(PLL_DDR, &PLL, 0, 0),
|
|
_CLK(PLL_AV, &PLL, 100000000, 0),
|
|
_CLK(PLL_SPARE, &PLL, 50000000, 0),
|
|
_CLK(PLL_ETH, &PLL, 100000000, 0),
|
|
_CLK(PLL_ST40_ICK, &PLL, 350000000, CLK_RATE_PROPAGATES | CLK_ALWAYS_ENABLED),
|
|
_CLK(PLL_ST40_PCK, &PLL, 133000000, CLK_RATE_PROPAGATES | CLK_ALWAYS_ENABLED),
|
|
|
|
/*FS A */
|
|
_CLK_P(FSA_SPARE, &FS, 36000000, 0, &clk_clocks[OSC_REF]),
|
|
_CLK_P(FSA_PCM, &FS, 72000000, 0, &clk_clocks[OSC_REF]),
|
|
_CLK_P(FSA_SPDIF, &FS, 36000000, 0, &clk_clocks[OSC_REF]),
|
|
_CLK_P(FSA_DSS, &FS, 13800000, 0, &clk_clocks[OSC_REF]),
|
|
|
|
/*FS B */
|
|
_CLK_P(FSB_PIX, &FS, 13800000, 0, &clk_clocks[OSC_REF]),
|
|
_CLK_P(FSB_FDMA_FS, &FS, 13800000, 0, &clk_clocks[OSC_REF]),
|
|
_CLK_P(FSB_AUX, &FS, 27000000, 0, &clk_clocks[OSC_REF]),
|
|
_CLK_P(FSB_USB, &FS, 48000000, 0, &clk_clocks[OSC_REF]),
|
|
|
|
};
|
|
|
|
int __init plat_clk_init(void)
|
|
{
|
|
return clk_register_table(clk_clocks, ARRAY_SIZE(clk_clocks), 1);
|
|
}
|
|
|
|
|
|
static const short pll_cfg0_offset[] = {
|
|
CLKDIV0_CONFIG0, /* cpu */
|
|
CLKDIV1_CONFIG0, /* lmi */
|
|
CLKDIV2_CONFIG0, /* blit */
|
|
CLKDIV3_CONFIG0, /* sys */
|
|
CLKDIV4_CONFIG0, /* fdma */
|
|
-1, /* no configuration for ddr clk */
|
|
CLKDIV6_CONFIG0, /* av */
|
|
CLKDIV7_CONFIG0, /* spare */
|
|
CLKDIV8_CONFIG0, /* eth */
|
|
CLKDIV9_CONFIG0, /* st40_ick */
|
|
CLKDIV10_CONFIG0 /* st40_pck */
|
|
};
|
|
|
|
static void sys_service_lock(int lock_enable)
|
|
{
|
|
if (lock_enable) {
|
|
CLK_WRITE(SYS_SERVICE_ADDR + CLK_LOCK_CFG, 0x100);
|
|
return;
|
|
}
|
|
CLK_WRITE(SYS_SERVICE_ADDR + CLK_LOCK_CFG, 0xF0);
|
|
CLK_WRITE(SYS_SERVICE_ADDR + CLK_LOCK_CFG, 0x0F);
|
|
}
|
|
|
|
static unsigned long pll_hw_evaluate(unsigned long input, unsigned long div_num)
|
|
{
|
|
short offset;
|
|
unsigned long config0, config1, config2;
|
|
unsigned long seq, depth, hno, even;
|
|
unsigned long combined, i;
|
|
|
|
offset = pll_cfg0_offset[div_num];
|
|
|
|
config0 = CLK_READ(SYS_SERVICE_ADDR + offset);
|
|
config1 = CLK_READ(SYS_SERVICE_ADDR + offset + 0x4);
|
|
config2 = CLK_READ(SYS_SERVICE_ADDR + offset + 0x8);
|
|
|
|
seq = (config0 & 0xffff) | ((config1 & 0xf) << 16);
|
|
depth = config2 & 0xf;
|
|
hno = (config2 & (1 << 6)) ? 1 : 0;
|
|
even = (config2 & (1 << 5)) ? 1 : 0;
|
|
combined = DIVIDER(depth, seq, hno, even);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(divide_table); i++)
|
|
if (divide_table[i].value == combined)
|
|
return (input * 2) / divide_table[i].ratio;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int clkgen_pll_recalc(clk_t *clk_p)
|
|
{
|
|
if (clk_p->id < PLL_CPU || clk_p->id > PLL_ST40_PCK)
|
|
return CLK_ERR_BAD_PARAMETER;
|
|
|
|
if (clk_p->id == PLL_DDR) {
|
|
clk_p->rate = clk_p->parent->rate;
|
|
return 0;
|
|
}
|
|
clk_p->rate = pll_hw_evaluate(clk_p->parent->rate, clk_p->id - PLL_CPU);
|
|
return 0;
|
|
}
|
|
|
|
/*==========================================================
|
|
Name: clkgen_xtal_init
|
|
description Top Level System Lock
|
|
===========================================================*/
|
|
|
|
static int clkgen_xtal_init(clk_t *clk_p)
|
|
{
|
|
if (!clk_p)
|
|
return CLK_ERR_BAD_PARAMETER;
|
|
|
|
/* Top recalc function */
|
|
if (clk_p->id == OSC_REF)
|
|
clk_p->rate = CRYSTAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned long clkgen_pll_eval(unsigned long input, int id)
|
|
{
|
|
unsigned long config0, config1, rate;
|
|
unsigned long pll_num = id - PLLA;
|
|
|
|
config0 = CLK_READ(SYS_SERVICE_ADDR + 8 * pll_num);
|
|
config1 = CLK_READ(SYS_SERVICE_ADDR + 8 * pll_num + 4);
|
|
|
|
#define CLK_PLL_CONFIG1_POFF (1 << 13)
|
|
if (config1 & CLK_PLL_CONFIG1_POFF)
|
|
return 0;
|
|
|
|
if (clk_pll800_get_rate(input, config0 & 0xff, (config0 >> 8) & 0xff,
|
|
config1 & 0x7, &rate) != 0)
|
|
return 0;
|
|
return rate;
|
|
}
|
|
|
|
/*==========================================================
|
|
Name: clkgen_pll_init
|
|
description PLL clocks init
|
|
===========================================================*/
|
|
|
|
static int clkgen_pll_init(clk_t *clk_p)
|
|
{
|
|
unsigned long data;
|
|
|
|
if (!clk_p || clk_p->id < PLLA || clk_p->id > PLL_ST40_PCK)
|
|
return CLK_ERR_BAD_PARAMETER;
|
|
|
|
/* 1. set the right parent */
|
|
if (clk_p->id < PLL_CPU) { /* PLLA and PLLB */
|
|
clk_p->rate = clkgen_pll_eval(clk_p->parent->rate, clk_p->id);
|
|
return 0;
|
|
}
|
|
|
|
data = CLK_READ(SYS_SERVICE_ADDR + PLL_SELECT_CFG) >> 1;
|
|
clk_p->parent = &clk_clocks[PLLA +
|
|
(data & (1 << (clk_p->id - PLL_CPU)) ? 1 : 0)];
|
|
|
|
/* 2. evaluate the rate */
|
|
clkgen_pll_recalc(clk_p);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*==========================================================
|
|
Name: clkgen_pll_set_parent
|
|
description PLL clocks set parent
|
|
===========================================================*/
|
|
|
|
static int clkgen_pll_set_parent(clk_t *clk_p, clk_t *src_p)
|
|
{
|
|
unsigned long val;
|
|
|
|
if (!clk_p || !src_p)
|
|
return CLK_ERR_BAD_PARAMETER;
|
|
if ((clk_p->id < PLL_CPU) || (clk_p->id > PLL_ST40_PCK))
|
|
return CLK_ERR_BAD_PARAMETER;
|
|
if ((src_p->id < PLLA) || (src_p->id > PLLB))
|
|
return CLK_ERR_BAD_PARAMETER;
|
|
|
|
if (src_p->id == PLLA) {
|
|
val = CLK_READ(SYS_SERVICE_ADDR +
|
|
PLL_SELECT_CFG) & ~(1 << (clk_p->id - PLL_CPU +
|
|
1));
|
|
clk_p->parent = &clk_clocks[PLLA];
|
|
} else {
|
|
val = CLK_READ(SYS_SERVICE_ADDR +
|
|
PLL_SELECT_CFG) | (1 << (clk_p->id - PLL_CPU + 1));
|
|
clk_p->parent = &clk_clocks[PLLB];
|
|
}
|
|
|
|
sys_service_lock(0);
|
|
CLK_WRITE(SYS_SERVICE_ADDR + PLL_SELECT_CFG, val);
|
|
sys_service_lock(1);
|
|
|
|
/* Updating the rate */
|
|
clkgen_pll_recalc(clk_p);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void pll_hw_set(unsigned long addr, unsigned long cfg0,
|
|
unsigned long cfg1, unsigned long cfg2)
|
|
{
|
|
unsigned long flags;
|
|
addr += SYS_SERVICE_ADDR;
|
|
|
|
sys_service_lock(0);
|
|
|
|
/*
|
|
* On the 5197 platform it's mandatory change the clock setting with an
|
|
* asm code because in X1 mode all the clocks are routed on Xtal
|
|
* and it could be dangerous a memory access
|
|
*
|
|
* All the code is self-contained in a single icache line
|
|
*/
|
|
local_irq_save(flags);
|
|
|
|
asm volatile (".balign 32 \n"
|
|
"mov.l %5, @%4 \n" /* in X1 mode */
|
|
"mov.l %1, @(0,%0)\n" /* set */
|
|
"mov.l %2, @(4,%0)\n" /* the */
|
|
"mov.l %3, @(8,%0)\n" /* ratio */
|
|
"mov.l %6, @%4 \n" /* in Prog mode */
|
|
"tst %7, %7 \n" /* wait stable signal */
|
|
"2: \n"
|
|
"bf/s 2b \n"
|
|
" dt %7 \n"
|
|
: : "r" (addr), "r" (cfg0),
|
|
"r" (cfg1), "r" (cfg2), /* with enable */
|
|
"r" (SYS_SERVICE_ADDR + MODE_CONTROL),
|
|
"r" (MODE_CTRL_X1),
|
|
"r" (MODE_CTRL_PROG), "r"(1000000)
|
|
: "memory");
|
|
|
|
local_irq_restore(flags);
|
|
|
|
sys_service_lock(1);
|
|
}
|
|
|
|
static int clkgen_pll_set_rate(clk_t *clk_p, unsigned long rate)
|
|
{
|
|
unsigned long i;
|
|
short offset;
|
|
|
|
if (clk_p->id < PLL_CPU)
|
|
return CLK_ERR_BAD_PARAMETER;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(divide_table); i++)
|
|
if (((clk_get_rate(clk_p->parent) * 2) /
|
|
divide_table[i].ratio) == rate)
|
|
break;
|
|
|
|
if (i == ARRAY_SIZE(divide_table)) /* not found! */
|
|
return CLK_ERR_BAD_PARAMETER;
|
|
|
|
offset = pll_cfg0_offset[clk_p->id - PLL_CPU];
|
|
|
|
if (offset == -1) /* ddr case */
|
|
return CLK_ERR_BAD_PARAMETER;
|
|
|
|
pll_hw_set(offset, divide_table[i].cfg_0,
|
|
divide_table[i].cfg_1, divide_table[i].cfg_2 | (1 << 4));
|
|
|
|
clk_p->rate = rate;
|
|
return 0;
|
|
}
|
|
|
|
static int clkgen_xable_pll(clk_t *clk_p, unsigned long enable)
|
|
{
|
|
unsigned long reg_cfg0, reg_cfg1, reg_cfg2;
|
|
short offset;
|
|
|
|
if (!clk_p || (clk_p->id < PLLA) || (clk_p->id > PLL_ST40_PCK))
|
|
return CLK_ERR_BAD_PARAMETER;
|
|
|
|
/* Some clocks should never switched off */
|
|
if (!enable && clk_p->flags & CLK_ALWAYS_ENABLED)
|
|
return 0;
|
|
|
|
/* PLL power down/up support for PLLA & PLLB */
|
|
if ((clk_p->id == PLLA) || (clk_p->id == PLLB)) {
|
|
offset = PLL_CONFIG(clk_p->id - PLLA, 1);
|
|
if (enable)
|
|
reg_cfg1 =
|
|
CLK_READ(SYS_SERVICE_ADDR + offset) & ~(1 << 13);
|
|
else
|
|
reg_cfg1 =
|
|
CLK_READ(SYS_SERVICE_ADDR + offset) | (1 << 13);
|
|
sys_service_lock(0);
|
|
CLK_WRITE(SYS_SERVICE_ADDR + offset, reg_cfg1);
|
|
sys_service_lock(1);
|
|
if (enable)
|
|
clk_p->rate =
|
|
clkgen_pll_eval(clk_p->parent->rate, clk_p->id);
|
|
else
|
|
clk_p->rate = 0;
|
|
return 0;
|
|
}
|
|
|
|
/* Other clocks */
|
|
offset = pll_cfg0_offset[clk_p->id - PLL_CPU];
|
|
|
|
if (offset == -1) { /* ddr case */
|
|
clk_p->rate = clk_p->parent->rate;
|
|
return 0;
|
|
}
|
|
|
|
reg_cfg0 = CLK_READ(SYS_SERVICE_ADDR + offset);
|
|
reg_cfg1 = CLK_READ(SYS_SERVICE_ADDR + offset + 4);
|
|
reg_cfg2 = CLK_READ(SYS_SERVICE_ADDR + offset + 8);
|
|
|
|
if (enable)
|
|
reg_cfg2 |= (1 << 4);
|
|
else
|
|
reg_cfg2 &= ~(1 << 4);
|
|
|
|
pll_hw_set(offset, reg_cfg0, reg_cfg1, reg_cfg2);
|
|
|
|
clk_p->rate =
|
|
(enable ? pll_hw_evaluate(clk_p->parent->rate, clk_p->id - PLL_CPU)
|
|
: 0);
|
|
return 0;
|
|
}
|
|
|
|
static int clkgen_pll_enable(clk_t *clk_p)
|
|
{
|
|
return clkgen_xable_pll(clk_p, 1);
|
|
}
|
|
|
|
static int clkgen_pll_disable(clk_t *clk_p)
|
|
{
|
|
return clkgen_xable_pll(clk_p, 0);
|
|
}
|
|
|
|
/*==========================================================
|
|
Name: clkgen_fs_init
|
|
description Sets the parent of the FS channel and recalculates its freq
|
|
===========================================================*/
|
|
|
|
static int clkgen_fs_init(clk_t *clk_p)
|
|
{
|
|
if (!clk_p || clk_p->id < FSA_SPARE || clk_p->id > FSB_USB)
|
|
return CLK_ERR_BAD_PARAMETER;
|
|
|
|
/* Parents are statically set */
|
|
|
|
/* Computing rate */
|
|
return clkgen_fs_recalc(clk_p);
|
|
}
|
|
|
|
/*==========================================================
|
|
Name: clkgen_fs_set_rate
|
|
description Sets the freq of the FS channels
|
|
===========================================================*/
|
|
|
|
static int clkgen_fs_set_rate(clk_t *clk_p, unsigned long rate)
|
|
{
|
|
unsigned long md, pe, sdiv, val;
|
|
unsigned long setup0, used_dco = 0;
|
|
|
|
if (!clk_p || clk_p->id < FSA_SPARE || clk_p->id > FSB_USB)
|
|
return CLK_ERR_BAD_PARAMETER;
|
|
|
|
if ((clk_fsyn_get_params(clk_p->parent->rate, rate, &md, &pe, &sdiv)) !=
|
|
0)
|
|
return CLK_ERR_BAD_PARAMETER;
|
|
|
|
setup0 = CLK_FS_SETUP(clk_p->id - FSA_SPARE);
|
|
|
|
md &= 0x1f; /* fix sign */
|
|
sdiv &= 0x7; /* fix sign */
|
|
pe &= 0xffff; /* fix sign */
|
|
|
|
val = md | (sdiv << 6); /* set [md, sdiv] */
|
|
val |= FS_SEL_OUT | FS_OUT_ENABLED;
|
|
|
|
sys_service_lock(0);
|
|
|
|
if (clk_p->id == FSA_PCM || clk_p->id == FSA_SPDIF ||
|
|
clk_p->id == FSB_PIX) {
|
|
CLK_WRITE(SYS_SERVICE_ADDR + DCO_MODE_CFG, 0);
|
|
used_dco++;
|
|
}
|
|
|
|
CLK_WRITE(SYS_SERVICE_ADDR + setup0 + 4, pe);
|
|
CLK_WRITE(SYS_SERVICE_ADDR + setup0, val);
|
|
CLK_WRITE(SYS_SERVICE_ADDR + setup0, val | FS_PROG_EN);
|
|
|
|
if (used_dco) {
|
|
CLK_WRITE(SYS_SERVICE_ADDR + DCO_MODE_CFG, 1 | FS_PROG_EN);
|
|
CLK_WRITE(SYS_SERVICE_ADDR + DCO_MODE_CFG, 0);
|
|
}
|
|
|
|
CLK_WRITE(SYS_SERVICE_ADDR + setup0, val);
|
|
sys_service_lock(1);
|
|
|
|
clk_p->rate = rate;
|
|
return 0;
|
|
}
|
|
|
|
/*==========================================================
|
|
Name: clkgen_fs_clk_enable
|
|
description enables the FS channels
|
|
===========================================================*/
|
|
|
|
static int clkgen_fs_enable(clk_t *clk_p)
|
|
{
|
|
unsigned long fs_setup, fs_value;
|
|
unsigned long setup0, setup0_value;
|
|
|
|
if (!clk_p || clk_p->id < FSA_SPARE || clk_p->id > FSB_USB)
|
|
return CLK_ERR_BAD_PARAMETER;
|
|
|
|
fs_setup = (clk_p->id < FSB_PIX ? FSA_SETUP : FSB_SETUP);
|
|
fs_value = CLK_READ(SYS_SERVICE_ADDR + fs_setup);
|
|
|
|
/* not reset */
|
|
fs_value |= FS_NOT_RESET;
|
|
|
|
/* enable analog part in fbX_setup */
|
|
fs_value &= ~FS_ANALOG_POFF;
|
|
|
|
/* enable i-th digital part in fbX_setup */
|
|
fs_value |= FS_DIGITAL_PON((clk_p->id - FSA_SPARE) % 4);
|
|
|
|
setup0 = CLK_FS_SETUP(clk_p->id - FSA_SPARE);
|
|
setup0_value = CLK_READ(SYS_SERVICE_ADDR + setup0);
|
|
setup0_value |= FS_SEL_OUT | FS_OUT_ENABLED;
|
|
|
|
sys_service_lock(0);
|
|
CLK_WRITE(SYS_SERVICE_ADDR + fs_setup, fs_value);
|
|
CLK_WRITE(SYS_SERVICE_ADDR + setup0, setup0_value);
|
|
sys_service_lock(1);
|
|
|
|
return clkgen_fs_recalc(clk_p);
|
|
}
|
|
|
|
/*==========================================================
|
|
Name: clkgen_fs_clk_disable
|
|
description disables the individual channels of the FSA
|
|
===========================================================*/
|
|
|
|
static int clkgen_fs_disable(clk_t *clk_p)
|
|
{
|
|
unsigned long setup0, tmp, fs_setup;
|
|
|
|
if (!clk_p || clk_p->id < FSA_SPARE || clk_p->id > FSB_USB)
|
|
return CLK_ERR_BAD_PARAMETER;
|
|
|
|
setup0 = CLK_FS_SETUP(clk_p->id - FSA_SPARE);
|
|
|
|
sys_service_lock(0);
|
|
|
|
tmp = CLK_READ(SYS_SERVICE_ADDR + setup0);
|
|
/* output disabled */
|
|
CLK_WRITE(SYS_SERVICE_ADDR + setup0, tmp & ~FS_OUT_ENABLED);
|
|
|
|
fs_setup = (clk_p->id < FSB_PIX ? FSA_SETUP : FSB_SETUP);
|
|
tmp = CLK_READ(SYS_SERVICE_ADDR + fs_setup);
|
|
/* disable the i-th digital part */
|
|
tmp &= ~FS_DIGITAL_PON((clk_p->id - FSA_SPARE) % 4);
|
|
|
|
if ((tmp & (0xf << 8)) == (0xf << 8))
|
|
/* disable analog and digital parts */
|
|
CLK_WRITE(SYS_SERVICE_ADDR + fs_setup, tmp | FS_ANALOG_POFF);
|
|
else
|
|
/* disable only digital part */
|
|
CLK_WRITE(SYS_SERVICE_ADDR + fs_setup, tmp);
|
|
|
|
sys_service_lock(1);
|
|
clk_p->rate = 0;
|
|
return 0;
|
|
}
|
|
|
|
/*==========================================================
|
|
Name: clkgen_fs_recalc
|
|
description Tells the programmed freq of the FS channel
|
|
===========================================================*/
|
|
|
|
static int clkgen_fs_recalc(clk_t *clk_p)
|
|
{
|
|
unsigned long md = 0x1f;
|
|
unsigned long sdiv = 0x1C0;
|
|
unsigned long pe = 0xFFFF;
|
|
unsigned long fs_setup, setup0, val;
|
|
|
|
if (!clk_p || clk_p->id < FSA_SPARE || clk_p->id > FSB_USB)
|
|
return CLK_ERR_BAD_PARAMETER;
|
|
|
|
/* Is the FS analog part ON ? */
|
|
fs_setup = (clk_p->id < FSB_PIX ? FSA_SETUP : FSB_SETUP);
|
|
val = CLK_READ(SYS_SERVICE_ADDR + fs_setup);
|
|
if (val & 1 << 3) {
|
|
clk_p->rate = 0;
|
|
return 0;
|
|
}
|
|
|
|
setup0 = CLK_FS_SETUP(clk_p->id - FSA_SPARE);
|
|
val = CLK_READ(SYS_SERVICE_ADDR + setup0);
|
|
|
|
/* Is this channel enabled ? */
|
|
if ((val & 1<<11) == 0) {
|
|
clk_p->rate = 0;
|
|
return 0;
|
|
}
|
|
|
|
/* Ok, channel enabled. Let's compute its frequency */
|
|
md &= val; /* 0-4 bits */
|
|
sdiv &= val; /* 6-8 bits */
|
|
sdiv >>= 6;
|
|
pe &= CLK_READ(SYS_SERVICE_ADDR + setup0 + 4);
|
|
return clk_fsyn_get_rate(clk_p->parent->rate, pe, md,
|
|
sdiv, &clk_p->rate);
|
|
}
|
|
|
|
/*********************************************************************
|
|
|
|
Functions to observe the clk on the test point provided on the
|
|
board-Debug Functions
|
|
|
|
**********************************************************************/
|
|
|
|
static int clkgen_fs_observe(clk_t *clk_p, unsigned long *freq)
|
|
{
|
|
static const unsigned long obs_table[] = {
|
|
11, 12, 13, 14, 15, 16, -1, 17 };
|
|
unsigned long clk_out_sel = 0;
|
|
|
|
if (!clk_p || clk_p->id < FSA_SPARE || clk_p->id > FSB_USB)
|
|
return CLK_ERR_BAD_PARAMETER;
|
|
|
|
clk_out_sel = obs_table[clk_p->id - FSA_SPARE];
|
|
|
|
sys_service_lock(0);
|
|
if (clk_out_sel == -1) /* o_f_synth_6 */
|
|
clk_out_sel = 0;
|
|
else
|
|
clk_out_sel |= (1 << 5);
|
|
CLK_WRITE(SYS_SERVICE_ADDR + CLK_OBS_CFG, clk_out_sel);
|
|
sys_service_lock(1);
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int clkgen_pll_observe(clk_t *clk_p, unsigned long *divd)
|
|
{
|
|
unsigned long clk_out_sel;
|
|
|
|
if (!clk_p)
|
|
return CLK_ERR_BAD_PARAMETER;
|
|
|
|
if (clk_p->id < PLL_CPU || clk_p->id > PLL_ST40_PCK)
|
|
return CLK_ERR_FEATURE_NOT_SUPPORTED;
|
|
|
|
clk_out_sel = clk_p->id - PLL_CPU;
|
|
|
|
sys_service_lock(0);
|
|
CLK_WRITE(SYS_SERVICE_ADDR + CLK_OBS_CFG, clk_out_sel | (1 << 5));
|
|
sys_service_lock(1);
|
|
return 0;
|
|
}
|