857 lines
22 KiB
C
857 lines
22 KiB
C
|
/*
|
||
|
* Copyright (C) 2007, 2011 STMicroelectronics Limited
|
||
|
*
|
||
|
* May be copied or modified under the terms of the GNU General Public
|
||
|
* License. See linux/COPYING for more information.
|
||
|
*
|
||
|
* Code to handle the clockgen hardware on the STx7200.
|
||
|
*/
|
||
|
|
||
|
#include <linux/init.h>
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/errno.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/io.h>
|
||
|
#include <linux/stm/clk.h>
|
||
|
|
||
|
#include "clock-common.h"
|
||
|
#include "clock-utils.h"
|
||
|
#include "clock-oslayer.h"
|
||
|
|
||
|
#include "clock-stx7200.h"
|
||
|
#include "clock-regs-stx7200.h"
|
||
|
|
||
|
/* Values for mb519 */
|
||
|
#define SYSACLKIN 27000000
|
||
|
#define SYSBCLKIN 30000000
|
||
|
|
||
|
#define CLOCKGEN_BASE_ADDR 0xfd700000 /* Clockgen A */
|
||
|
#define CLOCKGENB_BASE_ADDR 0xfd701000 /* Clockgen B */
|
||
|
#define CLOCKGENC_BASE_ADDR 0xfd601000 /* Clockgen C */
|
||
|
|
||
|
/* Alternate clock for clockgen A, B and C respectivly */
|
||
|
/* B & C come from SYSCLKINALT pin, SYSCLKINALT2 from PIO2[2] */
|
||
|
unsigned long sysclkinalt[3] = { 0, 0, 0};
|
||
|
|
||
|
#define CLOCKGEN_PLL_CFG(pll) (CLOCKGEN_BASE_ADDR + ((pll)*0x4))
|
||
|
#define CLOCKGEN_PLL_CFG_BYPASS (1<<20)
|
||
|
#define CLOCKGEN_MUX_CFG (CLOCKGEN_BASE_ADDR + 0x0c)
|
||
|
#define CLOCKGEN_MUX_CFG_SYSCLK_SRC (1<<0)
|
||
|
#define CLOCKGEN_MUX_CFG_PLL_SRC(pll) (1<<((pll)+1))
|
||
|
#define CLOCKGEN_MUX_CFG_DIV_SRC(pll) (1<<((pll)+4))
|
||
|
#define CLOCKGEN_MUX_CFG_FDMA_SRC(fdma) (1<<((fdma)+7))
|
||
|
#define CLOCKGEN_MUX_CFG_IC_REG_SRC (1<<9)
|
||
|
#define CLOCKGEN_DIV_CFG (CLOCKGEN_BASE_ADDR + 0x10)
|
||
|
#define CLOCKGEN_DIV2_CFG (CLOCKGEN_BASE_ADDR + 0x14)
|
||
|
#define CLOCKGEN_CLKOBS_MUX_CFG (CLOCKGEN_BASE_ADDR + 0x18)
|
||
|
#define CLOCKGEN_POWER_CFG (CLOCKGEN_BASE_ADDR + 0x1c)
|
||
|
|
||
|
#define CLOCKGENB_PLL0_CFG (CLOCKGENB_BASE_ADDR + 0x3c)
|
||
|
#define CLOCKGENB_IN_MUX_CFG (CLOCKGENB_BASE_ADDR + 0x44)
|
||
|
#define CLOCKGENB_IN_MUX_CFG_PLL_SRC (1<<0)
|
||
|
#define CLOCKGENB_DIV_CFG (CLOCKGENB_BASE_ADDR + 0x4c)
|
||
|
#define CLOCKGENB_OUT_MUX_CFG (CLOCKGENB_BASE_ADDR + 0x48)
|
||
|
#define CLOCKGENB_OUT_MUX_CFG_DIV_SRC (1<<0)
|
||
|
#define CLOCKGENB_DIV2_CFG (CLOCKGENB_BASE_ADDR + 0x50)
|
||
|
#define CLOCKGENB_CLKOBS_MUX_CFG (CLOCKGENB_BASE_ADDR + 0x54)
|
||
|
#define CLOCKGENB_POWER_CFG (CLOCKGENB_BASE_ADDR + 0x58)
|
||
|
|
||
|
/* 0 1 2 3 4 5 6 7 */
|
||
|
static const unsigned int ratio1[] = { 1, 2, 3, 4, 6, 8, 1024, 1 };
|
||
|
static const unsigned int ratio2[] = { 0, 1, 2, 1024, 3, 3, 3, 3 };
|
||
|
|
||
|
static unsigned long final_divider(unsigned long input, int div_ratio, int div)
|
||
|
{
|
||
|
switch (div_ratio) {
|
||
|
case 1:
|
||
|
return input / 1024;
|
||
|
case 2:
|
||
|
case 3:
|
||
|
return input / div;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static unsigned long pll02_freq(unsigned long input, unsigned long cfg)
|
||
|
{
|
||
|
unsigned long freq, ndiv, pdiv, mdiv;
|
||
|
|
||
|
mdiv = (cfg >> 0) & 0xff;
|
||
|
ndiv = (cfg >> 8) & 0xff;
|
||
|
pdiv = (cfg >> 16) & 0x7;
|
||
|
freq = (((2 * (input / 1000) * ndiv) / mdiv) /
|
||
|
(1 << pdiv)) * 1000;
|
||
|
|
||
|
return freq;
|
||
|
}
|
||
|
|
||
|
static unsigned long pll1_freq(unsigned long input, unsigned long cfg)
|
||
|
{
|
||
|
unsigned long freq, ndiv, mdiv;
|
||
|
|
||
|
mdiv = (cfg >> 0) & 0x7;
|
||
|
ndiv = (cfg >> 8) & 0xff;
|
||
|
freq = (((input / 1000) * ndiv) / mdiv) * 1000;
|
||
|
|
||
|
return freq;
|
||
|
}
|
||
|
|
||
|
/* Note this returns the PLL frequency _after_ the bypass logic. */
|
||
|
static unsigned long pll_freq(int pll_num)
|
||
|
{
|
||
|
unsigned long sysabclkin, input, output;
|
||
|
unsigned long mux_cfg, pll_cfg;
|
||
|
|
||
|
mux_cfg = readl(CLOCKGEN_MUX_CFG);
|
||
|
if ((mux_cfg & CLOCKGEN_MUX_CFG_SYSCLK_SRC) == 0)
|
||
|
sysabclkin = SYSACLKIN;
|
||
|
else
|
||
|
sysabclkin = SYSBCLKIN;
|
||
|
|
||
|
if (mux_cfg & CLOCKGEN_MUX_CFG_PLL_SRC(pll_num))
|
||
|
input = sysclkinalt[0];
|
||
|
else
|
||
|
input = sysabclkin;
|
||
|
|
||
|
|
||
|
pll_cfg = readl(CLOCKGEN_PLL_CFG(pll_num));
|
||
|
if (pll_num == 1)
|
||
|
output = pll1_freq(input, pll_cfg);
|
||
|
else
|
||
|
output = pll02_freq(input, pll_cfg);
|
||
|
|
||
|
if ((pll_cfg & CLOCKGEN_PLL_CFG_BYPASS) == 0)
|
||
|
return output;
|
||
|
else if ((mux_cfg & CLOCKGEN_MUX_CFG_DIV_SRC(pll_num)) == 0)
|
||
|
return input;
|
||
|
else
|
||
|
return sysabclkin;
|
||
|
|
||
|
}
|
||
|
|
||
|
static int pll_clk_init(struct clk *clk)
|
||
|
{
|
||
|
clk->rate = pll_freq((int)clk->private_data);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct clk_ops pll_clk_ops = {
|
||
|
.init = pll_clk_init,
|
||
|
};
|
||
|
|
||
|
#define CLK_PLL(_name, _id) \
|
||
|
{ .name = _name, \
|
||
|
.ops = &pll_clk_ops, \
|
||
|
.private_data = (void *)(_id), \
|
||
|
}
|
||
|
|
||
|
static struct clk pllclks[3] = {
|
||
|
CLK_PLL("pll0_clk", 0),
|
||
|
CLK_PLL("pll1_clk", 1),
|
||
|
CLK_PLL("pll2_clk", 2),
|
||
|
};
|
||
|
|
||
|
/* Note we ignore the possibility that we are in SH4 mode.
|
||
|
* Should check DIV_CFG.sh4_clk_ctl and switch to FRQCR mode. */
|
||
|
static int sh4_clk_recalc(struct clk *clk)
|
||
|
{
|
||
|
unsigned long shift = (unsigned long)clk->private_data;
|
||
|
unsigned long div_cfg = readl(CLOCKGEN_DIV_CFG);
|
||
|
unsigned long div1 = 1, div2;
|
||
|
|
||
|
switch ((div_cfg >> 20) & 3) {
|
||
|
case 0:
|
||
|
return 0;
|
||
|
case 1:
|
||
|
div1 = 1;
|
||
|
break;
|
||
|
case 2:
|
||
|
case 3:
|
||
|
div1 = 2;
|
||
|
break;
|
||
|
}
|
||
|
if (cpu_data->cut_major < 2)
|
||
|
div2 = ratio1[(div_cfg >> shift) & 7];
|
||
|
else
|
||
|
div2 = ratio2[(div_cfg >> shift) & 7];
|
||
|
clk->rate = (clk->parent->rate / div1) / div2;
|
||
|
|
||
|
/* Note clk_sh4 and clk_sh4_ic have an extra clock gating
|
||
|
* stage here based on DIV2_CFG bits 0 and 1. clk_sh4_per (aka
|
||
|
* module_clock) doesn't.
|
||
|
*
|
||
|
* However if we ever implement this, remember that fdma0/1
|
||
|
* may use clk_sh4 prior to the clock gating.
|
||
|
*/
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct clk_ops sh4_clk_ops = {
|
||
|
.init = sh4_clk_recalc,
|
||
|
.recalc = sh4_clk_recalc,
|
||
|
};
|
||
|
|
||
|
#define SH4_CLK(_name, _shift) \
|
||
|
{ .name = _name, \
|
||
|
.parent = &pllclks[0], \
|
||
|
.ops = &sh4_clk_ops, \
|
||
|
.private_data = (void *)(_shift), \
|
||
|
}
|
||
|
|
||
|
static struct clk sh4clks[3] = {
|
||
|
SH4_CLK("st40_clk", 1),
|
||
|
SH4_CLK("st40_ic_clk", 4),
|
||
|
SH4_CLK("st40_per_clk", 7),
|
||
|
};
|
||
|
|
||
|
struct fdmalxclk {
|
||
|
char fdma_num;
|
||
|
char div_cfg_reg;
|
||
|
char div_cfg_shift;
|
||
|
char normal_div;
|
||
|
};
|
||
|
|
||
|
static int fdma_clk_init(struct clk *clk)
|
||
|
{
|
||
|
struct fdmalxclk *fdmaclk = (struct fdmalxclk *)clk->private_data;
|
||
|
unsigned long mux_cfg = readl(CLOCKGEN_MUX_CFG);
|
||
|
|
||
|
if ((mux_cfg & CLOCKGEN_MUX_CFG_FDMA_SRC(fdmaclk->fdma_num)) == 0)
|
||
|
clk->parent = &sh4clks[0];
|
||
|
else
|
||
|
clk->parent = &pllclks[1];
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int fdmalx_clk_recalc(struct clk *clk)
|
||
|
{
|
||
|
struct fdmalxclk *fdmalxclk = (struct fdmalxclk *)clk->private_data;
|
||
|
unsigned long div_cfg;
|
||
|
unsigned long div_ratio;
|
||
|
unsigned long normal_div;
|
||
|
|
||
|
div_cfg = readl(CLOCKGEN_DIV_CFG + fdmalxclk->div_cfg_reg);
|
||
|
div_ratio = (div_cfg >> fdmalxclk->div_cfg_shift) & 3;
|
||
|
normal_div = fdmalxclk->normal_div;
|
||
|
clk->rate = final_divider(clk->parent->rate, div_ratio, normal_div);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void lx_clk_XXable(struct clk *clk, int enable)
|
||
|
{
|
||
|
struct fdmalxclk *fdmalxclk = (struct fdmalxclk *)clk->private_data;
|
||
|
unsigned long div_cfg = readl(CLOCKGEN_DIV_CFG +
|
||
|
fdmalxclk->div_cfg_reg);
|
||
|
if (enable) {
|
||
|
writel(div_cfg |
|
||
|
(fdmalxclk->normal_div << fdmalxclk->div_cfg_shift),
|
||
|
CLOCKGEN_DIV_CFG + fdmalxclk->div_cfg_reg);
|
||
|
fdmalx_clk_recalc(clk); /* to evaluate the rate */
|
||
|
} else {
|
||
|
writel(div_cfg & ~(0x3<<fdmalxclk->div_cfg_shift),
|
||
|
CLOCKGEN_DIV_CFG + fdmalxclk->div_cfg_reg);
|
||
|
clk->rate = 0;
|
||
|
}
|
||
|
}
|
||
|
static int lx_clk_enable(struct clk *clk)
|
||
|
{
|
||
|
lx_clk_XXable(clk, 1);
|
||
|
return 0;
|
||
|
}
|
||
|
static int lx_clk_disable(struct clk *clk)
|
||
|
{
|
||
|
lx_clk_XXable(clk, 0);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct clk_ops fdma_clk_ops = {
|
||
|
.init = fdma_clk_init,
|
||
|
.recalc = fdmalx_clk_recalc,
|
||
|
};
|
||
|
|
||
|
static struct clk_ops lx_clk_ops = {
|
||
|
.recalc = fdmalx_clk_recalc,
|
||
|
.enable = lx_clk_enable,
|
||
|
.disable = lx_clk_disable,
|
||
|
};
|
||
|
|
||
|
static int ic266_clk_recalc(struct clk *clk)
|
||
|
{
|
||
|
unsigned long div_cfg;
|
||
|
unsigned long div_ratio;
|
||
|
|
||
|
div_cfg = readl(CLOCKGEN_DIV2_CFG);
|
||
|
div_ratio = ((div_cfg & (1<<5)) == 0) ? 1024 : 3;
|
||
|
clk->rate = clk->parent->rate / div_ratio;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct clk_ops ic266_clk_ops = {
|
||
|
.recalc = ic266_clk_recalc,
|
||
|
};
|
||
|
|
||
|
#define CLKGENA(_name, _parent, _ops, _flags) \
|
||
|
{ \
|
||
|
.name = #_name, \
|
||
|
.parent = _parent, \
|
||
|
.ops = &_ops, \
|
||
|
}
|
||
|
|
||
|
static struct clk miscclks[1] = {
|
||
|
CLKGENA(ic_266, &pllclks[2], ic266_clk_ops, 0),
|
||
|
};
|
||
|
|
||
|
#define CLKGENA_FDMALX(_name, _parent, _ops, _fdma_num, \
|
||
|
_div_cfg_reg, _div_cfg_shift, _normal_div) \
|
||
|
{ \
|
||
|
.name = #_name, \
|
||
|
.parent = _parent, \
|
||
|
.ops = &_ops, \
|
||
|
.private_data = (void *) &(struct fdmalxclk) \
|
||
|
{ \
|
||
|
.fdma_num = _fdma_num, \
|
||
|
.div_cfg_reg = _div_cfg_reg - CLOCKGEN_DIV_CFG, \
|
||
|
.div_cfg_shift = _div_cfg_shift, \
|
||
|
.normal_div = _normal_div, \
|
||
|
} \
|
||
|
}
|
||
|
|
||
|
#define CLKGENA_FDMA(name, num) \
|
||
|
CLKGENA_FDMALX(name, NULL, fdma_clk_ops, num, \
|
||
|
CLOCKGEN_DIV_CFG, 10, 1)
|
||
|
|
||
|
#define CLKGENA_LX(name, shift) \
|
||
|
CLKGENA_FDMALX(name, &pllclks[1], lx_clk_ops, 0, \
|
||
|
CLOCKGEN_DIV_CFG, shift, 1)
|
||
|
|
||
|
#define CLKGENA_MISCDIV(name, shift, ratio) \
|
||
|
CLKGENA_FDMALX(name, &pllclks[2], lx_clk_ops, 0, \
|
||
|
CLOCKGEN_DIV2_CFG, shift, ratio)
|
||
|
|
||
|
static struct clk fdma_lx_miscdiv_clks[] = {
|
||
|
CLKGENA_FDMA(fdma_clk0, 0),
|
||
|
CLKGENA_FDMA(fdma_clk1, 1),
|
||
|
|
||
|
CLKGENA_LX(lx_aud0_cpu_clk, 12),
|
||
|
CLKGENA_LX(lx_aud1_cpu_clk, 14),
|
||
|
CLKGENA_LX(lx_dmu0_cpu_clk, 16),
|
||
|
CLKGENA_LX(lx_dmu1_cpu_clk, 18),
|
||
|
|
||
|
CLKGENA_MISCDIV(dmu0_266, 18, 3),
|
||
|
CLKGENA_MISCDIV(disp_266, 22, 3),
|
||
|
CLKGENA_MISCDIV(bdisp_200, 6, 4),
|
||
|
CLKGENA_MISCDIV(fdma_200, 14, 4)
|
||
|
};
|
||
|
|
||
|
enum clockgen2B_ID {
|
||
|
DIV2_B_BDISP266_ID = 0,
|
||
|
DIV2_B_COMPO200_ID,
|
||
|
DIV2_B_DISP200_ID,
|
||
|
DIV2_B_VDP200_ID,
|
||
|
DIV2_B_DMU1266_ID,
|
||
|
};
|
||
|
|
||
|
enum clockgen3B_ID{
|
||
|
MISC_B_ICREG_ID = 0,
|
||
|
MISC_B_ETHERNET_ID,
|
||
|
MISC_B_EMIMASTER_ID
|
||
|
};
|
||
|
|
||
|
static int pll_clkB_init(struct clk *clk)
|
||
|
{
|
||
|
unsigned long input, output;
|
||
|
unsigned long mux_cfg, pll_cfg;
|
||
|
|
||
|
/* FIXME: probably needs more work! */
|
||
|
|
||
|
mux_cfg = readl(CLOCKGENB_IN_MUX_CFG);
|
||
|
if (mux_cfg & CLOCKGENB_IN_MUX_CFG_PLL_SRC)
|
||
|
input = sysclkinalt[1];
|
||
|
else
|
||
|
input = SYSBCLKIN;
|
||
|
|
||
|
|
||
|
pll_cfg = readl(CLOCKGENB_PLL0_CFG);
|
||
|
output = pll02_freq(input, pll_cfg);
|
||
|
|
||
|
if (!(pll_cfg & CLOCKGEN_PLL_CFG_BYPASS))
|
||
|
clk->rate = output;
|
||
|
else if (!(mux_cfg & CLOCKGENB_OUT_MUX_CFG_DIV_SRC))
|
||
|
clk->rate = input;
|
||
|
else
|
||
|
clk->rate = SYSBCLKIN;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void pll_clkB_XXable(struct clk *clk, int enable)
|
||
|
{
|
||
|
unsigned long bps = readl(CLOCKGENB_PLL0_CFG);
|
||
|
unsigned long pwr = readl(CLOCKGENB_POWER_CFG);
|
||
|
|
||
|
if (enable) {
|
||
|
writel(pwr & ~(1<<15), CLOCKGENB_POWER_CFG); /* turn-on */
|
||
|
mdelay(1);
|
||
|
writel(bps & ~(1<<20), CLOCKGENB_PLL0_CFG); /* bypass off*/
|
||
|
pll_clkB_init(clk); /* to evaluate the rate */
|
||
|
} else {
|
||
|
writel(bps | 1<<20, CLOCKGENB_PLL0_CFG); /* bypass on */
|
||
|
writel(pwr | 1<<15, CLOCKGENB_POWER_CFG); /* turn-off */
|
||
|
clk->rate = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int pll_clkB_enable(struct clk *clk)
|
||
|
{
|
||
|
pll_clkB_XXable(clk, 1);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int pll_clkB_disable(struct clk *clk)
|
||
|
{
|
||
|
pll_clkB_XXable(clk, 0);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct clk_ops pll_clkB_ops = {
|
||
|
.init = pll_clkB_init,
|
||
|
.enable = pll_clkB_enable,
|
||
|
.disable = pll_clkB_disable,
|
||
|
};
|
||
|
|
||
|
static struct clk clkB_pllclks[1] = {
|
||
|
{
|
||
|
.name = "b_pll0_clk",
|
||
|
.ops = &pll_clkB_ops,
|
||
|
.private_data = NULL,
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
struct clkgenBdiv2 {
|
||
|
char div_cfg_shift;
|
||
|
char normal_div;
|
||
|
};
|
||
|
|
||
|
#define CLKGENB(_id, _name, _ops, _flags) \
|
||
|
{ \
|
||
|
.name = #_name, \
|
||
|
.parent = &clkB_pllclks[0], \
|
||
|
.ops = &_ops, \
|
||
|
.id = (_id), \
|
||
|
}
|
||
|
|
||
|
#define CLKGENB_DIV2(_id, _name, _div_cfg_shift, _normal_div) \
|
||
|
{ \
|
||
|
.name = #_name, \
|
||
|
.parent = &clkB_pllclks[0], \
|
||
|
.ops = &clkgenb_div2_ops, \
|
||
|
.id = _id, \
|
||
|
.private_data = (void *) &(struct clkgenBdiv2) \
|
||
|
{ \
|
||
|
.div_cfg_shift = _div_cfg_shift, \
|
||
|
.normal_div = _normal_div, \
|
||
|
} \
|
||
|
}
|
||
|
|
||
|
static int clkgenb_div2_recalc(struct clk *clk)
|
||
|
{
|
||
|
struct clkgenBdiv2 *clkgenBdiv2 =
|
||
|
(struct clkgenBdiv2 *)clk->private_data;
|
||
|
unsigned long div_cfg;
|
||
|
unsigned long div_ratio;
|
||
|
|
||
|
div_cfg = readl(CLOCKGENB_DIV2_CFG);
|
||
|
div_ratio = (div_cfg >> clkgenBdiv2->div_cfg_shift) & 3;
|
||
|
clk->rate = final_divider(clk->parent->rate, div_ratio,
|
||
|
clkgenBdiv2->normal_div);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int clkgenb_div2_XXable(struct clk *clk, int enable)
|
||
|
{
|
||
|
struct clkgenBdiv2 *clkgenBdiv2 =
|
||
|
(struct clkgenBdiv2 *)clk->private_data;
|
||
|
unsigned long div_cfg = readl(CLOCKGENB_DIV2_CFG);
|
||
|
unsigned long div_ratio = (div_cfg >> clkgenBdiv2->div_cfg_shift) & 3;
|
||
|
|
||
|
if (enable) {
|
||
|
div_cfg |= clkgenBdiv2->normal_div <<
|
||
|
clkgenBdiv2->div_cfg_shift;
|
||
|
writel(div_cfg, CLOCKGENB_DIV2_CFG);
|
||
|
final_divider(clk->parent->rate, div_ratio,
|
||
|
clkgenBdiv2->normal_div); /* to evaluate the rate */
|
||
|
} else {
|
||
|
div_cfg &= ~(0x3<<clkgenBdiv2->div_cfg_shift);
|
||
|
writel(div_cfg, CLOCKGENB_DIV2_CFG);
|
||
|
clk->rate = 0;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int clkgenb_div2_enable(struct clk *clk)
|
||
|
{
|
||
|
return clkgenb_div2_XXable(clk, 1);
|
||
|
}
|
||
|
static int clkgenb_div2_disable(struct clk *clk)
|
||
|
{
|
||
|
return clkgenb_div2_XXable(clk, 0);
|
||
|
}
|
||
|
|
||
|
static struct clk_ops clkgenb_div2_ops = {
|
||
|
.enable = clkgenb_div2_enable,
|
||
|
.disable = clkgenb_div2_disable,
|
||
|
.recalc = clkgenb_div2_recalc,
|
||
|
};
|
||
|
|
||
|
struct clk clkB_div2clks[5] = {
|
||
|
CLKGENB_DIV2(DIV2_B_BDISP266_ID, bdisp_266, 6, 3),
|
||
|
CLKGENB_DIV2(DIV2_B_COMPO200_ID, compo_200, 8, 4),
|
||
|
CLKGENB_DIV2(DIV2_B_DISP200_ID, disp_200, 10, 4),
|
||
|
CLKGENB_DIV2(DIV2_B_VDP200_ID, vdp_200, 12, 4),
|
||
|
CLKGENB_DIV2(DIV2_B_DMU1266_ID, dmu1_266, 20, 3)
|
||
|
};
|
||
|
|
||
|
static int icreg_emi_eth_clk_recalc(struct clk *clk)
|
||
|
{
|
||
|
unsigned long mux_cfg;
|
||
|
unsigned long div_ratio;
|
||
|
|
||
|
mux_cfg = readl(CLOCKGEN_MUX_CFG);
|
||
|
div_ratio = ((mux_cfg & (CLOCKGEN_MUX_CFG_IC_REG_SRC)) == 0) ? 8 : 6;
|
||
|
clk->rate = clk->parent->rate / div_ratio;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int icreg_emi_eth_clk_XXable(struct clk *clk, int enable)
|
||
|
{
|
||
|
unsigned long id = (unsigned long)clk->private_data;
|
||
|
unsigned long tmp = readl(CLOCKGENB_DIV2_CFG);
|
||
|
if (enable) {
|
||
|
writel(tmp & ~(1<<(id+2)), CLOCKGENB_DIV2_CFG);
|
||
|
icreg_emi_eth_clk_recalc(clk); /* to evaluate the rate */
|
||
|
} else {
|
||
|
writel(tmp | (1<<(id+2)), CLOCKGENB_DIV2_CFG);
|
||
|
clk->rate = 0;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int icreg_emi_eth_clk_enable(struct clk *clk)
|
||
|
{
|
||
|
return icreg_emi_eth_clk_XXable(clk, 1);
|
||
|
}
|
||
|
static int icreg_emi_eth_clk_disable(struct clk *clk)
|
||
|
{
|
||
|
return icreg_emi_eth_clk_XXable(clk, 0);
|
||
|
}
|
||
|
|
||
|
static struct clk_ops icreg_emi_eth_clk_ops = {
|
||
|
.init = icreg_emi_eth_clk_recalc,
|
||
|
.recalc = icreg_emi_eth_clk_recalc,
|
||
|
#if 0
|
||
|
/* I have to check why the following function have problem on cut 2 */
|
||
|
.enable = icreg_emi_eth_clk_enable,
|
||
|
.disable = icreg_emi_eth_clk_disable
|
||
|
#endif
|
||
|
};
|
||
|
|
||
|
static struct clk clkB_miscclks[3] = {
|
||
|
/* Propages to comms_clk */
|
||
|
CLKGENB(MISC_B_ICREG_ID, ic_reg, icreg_emi_eth_clk_ops, CLK_RATE_PROPAGATES),
|
||
|
CLKGENB(MISC_B_ETHERNET_ID, ethernet, icreg_emi_eth_clk_ops, 0),
|
||
|
CLKGENB(MISC_B_EMIMASTER_ID, emi_master, icreg_emi_eth_clk_ops, 0),
|
||
|
};
|
||
|
|
||
|
/******************************************************************************
|
||
|
CLOCKGEN C (audio)
|
||
|
******************************************************************************/
|
||
|
|
||
|
/* ========================================================================
|
||
|
Name: clkgenc_fsyn_recalc
|
||
|
Description: Get CKGC FSYN clocks frequencies function
|
||
|
Returns: 0=NO error
|
||
|
======================================================================== */
|
||
|
static int clkgenc_init(clk_t *clk_p);
|
||
|
static int clkgenc_enable(clk_t *clk_p);
|
||
|
static int clkgenc_disable(clk_t *clk_p);
|
||
|
static int clkgenc_recalc(clk_t *clk_p);
|
||
|
static int clkgenc_set_rate(clk_t *clk_p, unsigned long freq);
|
||
|
|
||
|
_CLK_OPS(clkgenc,
|
||
|
"Clockgen C/Audio",
|
||
|
clkgenc_init,
|
||
|
NULL,
|
||
|
clkgenc_set_rate,
|
||
|
clkgenc_recalc,
|
||
|
clkgenc_enable,
|
||
|
clkgenc_disable,
|
||
|
NULL,
|
||
|
NULL, /* No measure function */
|
||
|
NULL /* No observation point */
|
||
|
);
|
||
|
|
||
|
static clk_t clk_clocks[] = {
|
||
|
/* Clockgen C (AUDIO) */
|
||
|
_CLK_P(CLKC_REF, &clkgenc, 0, CLK_RATE_PROPAGATES | CLK_ALWAYS_ENABLED, NULL),
|
||
|
_CLK_P(CLKC_FS0_CH1, &clkgenc, 0, 0, &clk_clocks[CLKC_REF]),
|
||
|
_CLK_P(CLKC_FS0_CH2, &clkgenc, 0, 0, &clk_clocks[CLKC_REF]),
|
||
|
_CLK_P(CLKC_FS0_CH3, &clkgenc, 0, 0, &clk_clocks[CLKC_REF]),
|
||
|
_CLK_P(CLKC_FS0_CH4, &clkgenc, 0, 0, &clk_clocks[CLKC_REF]),
|
||
|
_CLK_P(CLKC_FS1_CH1, &clkgenc, 0, 0, &clk_clocks[CLKC_REF]),
|
||
|
_CLK_P(CLKC_FS1_CH2, &clkgenc, 0, 0, &clk_clocks[CLKC_REF]),
|
||
|
_CLK_P(CLKC_FS1_CH3, &clkgenc, 0, 0, &clk_clocks[CLKC_REF]),
|
||
|
_CLK_P(CLKC_FS1_CH4, &clkgenc, 0, 0, &clk_clocks[CLKC_REF]),
|
||
|
};
|
||
|
|
||
|
/******************************************************************************
|
||
|
CLOCKGEN C (audio)
|
||
|
******************************************************************************/
|
||
|
|
||
|
/* ========================================================================
|
||
|
Name: clkgenc_fsyn_recalc
|
||
|
Description: Get CKGC FSYN clocks frequencies function
|
||
|
Returns: 0=NO error
|
||
|
======================================================================== */
|
||
|
|
||
|
static int clkgenc_fsyn_recalc(clk_t *clk_p)
|
||
|
{
|
||
|
int bank, channel;
|
||
|
unsigned long cfg, dig_bit;
|
||
|
unsigned long pe, md, sdiv;
|
||
|
int err;
|
||
|
|
||
|
if (!clk_p)
|
||
|
return CLK_ERR_BAD_PARAMETER;
|
||
|
if (clk_p->id < CLKC_FS0_CH1 || clk_p->id > CLKC_FS1_CH4)
|
||
|
return CLK_ERR_BAD_PARAMETER;
|
||
|
|
||
|
bank = (clk_p->id - CLKC_FS0_CH1) / 4;
|
||
|
channel = (clk_p->id - CLKC_FS0_CH1) % 4;
|
||
|
|
||
|
/* Is FSYN analog UP ? */
|
||
|
cfg = CLK_READ(CLOCKGENC_BASE_ADDR + CKGC_FS_CFG(bank));
|
||
|
if (!(cfg & (1 << 14))) { /* Analog power down */
|
||
|
clk_p->rate = 0;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Is FSYN digital part UP ? */
|
||
|
dig_bit = 10 + channel;
|
||
|
|
||
|
if ((cfg & (1 << dig_bit)) == 0) { /* digital part in standby */
|
||
|
clk_p->rate = 0;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* FSYN up & running.
|
||
|
Computing frequency */
|
||
|
pe = CLK_READ(CLOCKGENC_BASE_ADDR + CKGC_FS_PE(bank, channel));
|
||
|
md = CLK_READ(CLOCKGENC_BASE_ADDR + CKGC_FS_MD(bank, channel));
|
||
|
sdiv = CLK_READ(CLOCKGENC_BASE_ADDR + CKGC_FS_SDIV(bank, channel));
|
||
|
err = clk_fsyn_get_rate(clk_p->parent->rate, pe, md,
|
||
|
sdiv, &clk_p->rate);
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
/* ========================================================================
|
||
|
Name: clkgenc_recalc
|
||
|
Description: Get CKGC clocks frequencies function
|
||
|
Returns: 0=NO error
|
||
|
======================================================================== */
|
||
|
|
||
|
static int clkgenc_recalc(clk_t *clk_p)
|
||
|
{
|
||
|
if (!clk_p)
|
||
|
return CLK_ERR_BAD_PARAMETER;
|
||
|
|
||
|
switch (clk_p->id) {
|
||
|
case CLKC_REF:
|
||
|
clk_p->rate = CONFIG_SH_EXTERNAL_CLOCK;
|
||
|
break;
|
||
|
case CLKC_FS0_CH1 ... CLKC_FS0_CH4: /* FS0 clocks */
|
||
|
case CLKC_FS1_CH1 ... CLKC_FS1_CH4: /* FS1 clocks */
|
||
|
return clkgenc_fsyn_recalc(clk_p);
|
||
|
default:
|
||
|
return CLK_ERR_BAD_PARAMETER;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* ========================================================================
|
||
|
Name: clkgenc_set_rate
|
||
|
Description: Set CKGC clocks frequencies
|
||
|
Returns: 0=NO error
|
||
|
======================================================================== */
|
||
|
|
||
|
static int clkgenc_set_rate(clk_t *clk_p, unsigned long freq)
|
||
|
{
|
||
|
unsigned long md, pe, sdiv;
|
||
|
unsigned long reg_value = 0;
|
||
|
int bank, channel;
|
||
|
static const unsigned char set_rate_table[] = {
|
||
|
0x04, 0x08, 0x10, 0x20 };
|
||
|
|
||
|
if (!clk_p)
|
||
|
return CLK_ERR_BAD_PARAMETER;
|
||
|
if (clk_p->id < CLKC_FS0_CH1 || clk_p->id > CLKC_FS1_CH4)
|
||
|
return CLK_ERR_BAD_PARAMETER;
|
||
|
|
||
|
/* Computing FSyn params. Should be common function with FSyn type */
|
||
|
if (clk_fsyn_get_params(clk_p->parent->rate, freq, &md, &pe, &sdiv))
|
||
|
return CLK_ERR_BAD_PARAMETER;
|
||
|
|
||
|
bank = (clk_p->id - CLKC_FS0_CH1) / 4;
|
||
|
channel = (clk_p->id - CLKC_FS0_CH1) % 4;
|
||
|
|
||
|
reg_value = CLK_READ(CLOCKGENC_BASE_ADDR + CKGC_FS_CFG(bank));
|
||
|
reg_value &= ~set_rate_table[channel];
|
||
|
|
||
|
/* Select FS clock only for the clock specified */
|
||
|
CLK_WRITE(CLOCKGENC_BASE_ADDR + CKGC_FS_CFG(bank), reg_value);
|
||
|
|
||
|
CLK_WRITE(CLOCKGENC_BASE_ADDR + CKGC_FS_PE(bank, channel), pe);
|
||
|
CLK_WRITE(CLOCKGENC_BASE_ADDR + CKGC_FS_MD(bank, channel), md);
|
||
|
CLK_WRITE(CLOCKGENC_BASE_ADDR + CKGC_FS_SDIV(bank, channel), sdiv);
|
||
|
CLK_WRITE(CLOCKGENC_BASE_ADDR + CKGC_FS_EN_PRG(bank, channel), 0x01);
|
||
|
CLK_WRITE(CLOCKGENC_BASE_ADDR + CKGC_FS_EN_PRG(bank, channel), 0x00);
|
||
|
|
||
|
return clkgenc_recalc(clk_p);
|
||
|
}
|
||
|
|
||
|
static int clkgenc_init(clk_t *clk_p)
|
||
|
{
|
||
|
int err;
|
||
|
|
||
|
if (!clk_p)
|
||
|
return CLK_ERR_BAD_PARAMETER;
|
||
|
|
||
|
clkgenc_recalc(clk_p);
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
/* ========================================================================
|
||
|
Name: clkgenc_xable_fsyn
|
||
|
Description: Enable/Disable FSYN. If all channels OFF, FSYN is powered
|
||
|
down.
|
||
|
Returns: 'clk_err_t' error code
|
||
|
======================================================================== */
|
||
|
|
||
|
static int clkgenc_xable_fsyn(clk_t *clk_p, unsigned long enable)
|
||
|
{
|
||
|
int bank, channel;
|
||
|
unsigned long val;
|
||
|
/* Digital standby bits table.
|
||
|
Warning: enum order: CLKC_FS0_CH1 ... CLKC_FS0_CH3
|
||
|
CLKC_FS1_CH1 ... CLKC_FS1_CH3 */
|
||
|
static const unsigned char dig_bit[] = {10, 11, 12, 13};
|
||
|
|
||
|
if (!clk_p)
|
||
|
return CLK_ERR_BAD_PARAMETER;
|
||
|
if (clk_p->id < CLKC_FS0_CH1 || clk_p->id > CLKC_FS1_CH4)
|
||
|
return CLK_ERR_BAD_PARAMETER;
|
||
|
|
||
|
bank = (clk_p->id - CLKC_FS0_CH1) / 4;
|
||
|
channel = (clk_p->id - CLKC_FS0_CH1) % 4;
|
||
|
|
||
|
val = CLK_READ(CLOCKGENC_BASE_ADDR + CKGC_FS_CFG(bank));
|
||
|
|
||
|
/* Powering down/up digital part */
|
||
|
if (enable) {
|
||
|
val |= (1 << dig_bit[channel]);
|
||
|
} else {
|
||
|
val &= ~(1 << dig_bit[channel]);
|
||
|
}
|
||
|
|
||
|
/* Powering down/up analog part */
|
||
|
if (enable)
|
||
|
val |= (1 << 14);
|
||
|
else {
|
||
|
/* If all channels are off then power down the fsynth */
|
||
|
if ((val & 0x3fc0) == 0)
|
||
|
val &= ~(1 << 14);
|
||
|
}
|
||
|
|
||
|
CLK_WRITE(CLOCKGENC_BASE_ADDR + CKGC_FS_CFG(bank), val);
|
||
|
|
||
|
/* Freq recalc required only if a channel is enabled */
|
||
|
if (enable)
|
||
|
return clkgenc_fsyn_recalc(clk_p);
|
||
|
else
|
||
|
clk_p->rate = 0;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* ========================================================================
|
||
|
Name: clkgenc_enable
|
||
|
Description: Enable clock
|
||
|
Returns: 'clk_err_t' error code.
|
||
|
======================================================================== */
|
||
|
|
||
|
static int clkgenc_enable(clk_t *clk_p)
|
||
|
{
|
||
|
return clkgenc_xable_fsyn(clk_p, 1);
|
||
|
}
|
||
|
|
||
|
/* ========================================================================
|
||
|
Name: clkgenc_disable
|
||
|
Description: Disable clock
|
||
|
Returns: 'clk_err_t' error code.
|
||
|
======================================================================== */
|
||
|
|
||
|
static int clkgenc_disable(clk_t *clk_p)
|
||
|
{
|
||
|
return clkgenc_xable_fsyn(clk_p, 0);
|
||
|
}
|
||
|
|
||
|
int __init plat_clk_init(void)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
/* Clockgen A */
|
||
|
ret = clk_register_table(pllclks, ARRAY_SIZE(pllclks), 1);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
if (cpu_data->cut_major < 2) {
|
||
|
ret = clk_register_table(sh4clks, ARRAY_SIZE(sh4clks), 1);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
} else {
|
||
|
ret = clk_register_table(sh4clks, 1, 1);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ret = clk_register_table(fdma_lx_miscdiv_clks,
|
||
|
ARRAY_SIZE(fdma_lx_miscdiv_clks), 1);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
/* Clockgen B */
|
||
|
writel(readl(CLOCKGENB_IN_MUX_CFG) & ~0xf, CLOCKGENB_IN_MUX_CFG);
|
||
|
|
||
|
ret = clk_register_table(clkB_pllclks, ARRAY_SIZE(clkB_pllclks), 1);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
ret = clk_register_table(clkB_div2clks, ARRAY_SIZE(clkB_div2clks), 1);
|
||
|
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
ret = clk_register_table(clkB_miscclks, ARRAY_SIZE(clkB_miscclks), 1);
|
||
|
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
/* clock gen C */
|
||
|
ret = clk_register_table(clk_clocks, ARRAY_SIZE(clk_clocks), 0);
|
||
|
|
||
|
return 0;
|
||
|
}
|