837 lines
21 KiB
C
837 lines
21 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2020 Rockchip Electronics Co., Ltd.
|
|
*
|
|
* Author: Wyon Bi <bivvy.bi@rock-chips.com>
|
|
*/
|
|
|
|
#include <linux/debugfs.h>
|
|
|
|
#include "asm-generic/errno-base.h"
|
|
#include "rk628.h"
|
|
#include "rk628_cru.h"
|
|
|
|
#define REFCLK_RATE 24000000UL
|
|
#define MIN_FREF_RATE 10000000UL
|
|
#define MAX_FREF_RATE 800000000UL
|
|
#define MIN_FREFDIV_RATE 1000000UL
|
|
#define MAX_FREFDIV_RATE 100000000UL
|
|
#define MIN_FVCO_RATE 600000000UL
|
|
#define MAX_FVCO_RATE 1600000000UL
|
|
#define MIN_FOUTPOSTDIV_RATE 12000000UL
|
|
#define MAX_FOUTPOSTDIV_RATE 1600000000UL
|
|
|
|
static void rational_best_approximation(unsigned long given_numerator,
|
|
unsigned long given_denominator,
|
|
unsigned long max_numerator,
|
|
unsigned long max_denominator,
|
|
unsigned long *best_numerator,
|
|
unsigned long *best_denominator)
|
|
{
|
|
unsigned long n, d, n0, d0, n1, d1;
|
|
|
|
n = given_numerator;
|
|
d = given_denominator;
|
|
n0 = d1 = 0;
|
|
n1 = d0 = 1;
|
|
for (;;) {
|
|
unsigned long t, a;
|
|
|
|
if ((n1 > max_numerator) || (d1 > max_denominator)) {
|
|
n1 = n0;
|
|
d1 = d0;
|
|
break;
|
|
}
|
|
if (d == 0)
|
|
break;
|
|
t = d;
|
|
a = n / d;
|
|
d = n % d;
|
|
n = t;
|
|
t = n0 + a * n1;
|
|
n0 = n1;
|
|
n1 = t;
|
|
t = d0 + a * d1;
|
|
d0 = d1;
|
|
d1 = t;
|
|
}
|
|
*best_numerator = n1;
|
|
*best_denominator = d1;
|
|
}
|
|
|
|
static unsigned long rk628_cru_clk_get_rate_pll(struct rk628 *rk628,
|
|
unsigned int id)
|
|
{
|
|
unsigned long parent_rate = REFCLK_RATE;
|
|
u32 postdiv1, fbdiv, dsmpd, postdiv2, refdiv, frac, bypass;
|
|
u32 con0, con1, con2;
|
|
u64 foutvco, foutpostdiv;
|
|
u32 offset, val;
|
|
|
|
if (id == CGU_CLK_APLL && rk628->version < RK628F_VERSION)
|
|
return 0;
|
|
|
|
rk628_i2c_read(rk628, CRU_MODE_CON00, &val);
|
|
if (id == CGU_CLK_CPLL) {
|
|
val &= CLK_CPLL_MODE_MASK;
|
|
val >>= CLK_CPLL_MODE_SHIFT;
|
|
if (val == CLK_CPLL_MODE_OSC)
|
|
return parent_rate;
|
|
|
|
offset = 0x00;
|
|
} else if (id == CGU_CLK_GPLL) {
|
|
val &= CLK_GPLL_MODE_MASK;
|
|
val >>= CLK_GPLL_MODE_SHIFT;
|
|
if (val == CLK_GPLL_MODE_OSC)
|
|
return parent_rate;
|
|
|
|
offset = 0x20;
|
|
} else {
|
|
val &= CLK_APLL_MODE_MASK;
|
|
val >>= CLK_APLL_MODE_SHIFT;
|
|
if (val == CLK_APLL_MODE_OSC)
|
|
return parent_rate;
|
|
|
|
offset = 0x40;
|
|
}
|
|
|
|
rk628_i2c_read(rk628, offset + CRU_CPLL_CON0, &con0);
|
|
rk628_i2c_read(rk628, offset + CRU_CPLL_CON1, &con1);
|
|
rk628_i2c_read(rk628, offset + CRU_CPLL_CON2, &con2);
|
|
|
|
bypass = (con0 & PLL_BYPASS_MASK) >> PLL_BYPASS_SHIFT;
|
|
postdiv1 = (con0 & PLL_POSTDIV1_MASK) >> PLL_POSTDIV1_SHIFT;
|
|
fbdiv = (con0 & PLL_FBDIV_MASK) >> PLL_FBDIV_SHIFT;
|
|
dsmpd = (con1 & PLL_DSMPD_MASK) >> PLL_DSMPD_SHIFT;
|
|
postdiv2 = (con1 & PLL_POSTDIV2_MASK) >> PLL_POSTDIV2_SHIFT;
|
|
refdiv = (con1 & PLL_REFDIV_MASK) >> PLL_REFDIV_SHIFT;
|
|
frac = (con2 & PLL_FRAC_MASK) >> PLL_FRAC_SHIFT;
|
|
|
|
if (bypass)
|
|
return parent_rate;
|
|
|
|
foutvco = parent_rate * fbdiv;
|
|
do_div(foutvco, refdiv);
|
|
|
|
if (!dsmpd) {
|
|
u64 frac_rate = (u64)parent_rate * frac;
|
|
|
|
do_div(frac_rate, refdiv);
|
|
foutvco += frac_rate >> 24;
|
|
}
|
|
|
|
foutpostdiv = foutvco;
|
|
do_div(foutpostdiv, postdiv1);
|
|
do_div(foutpostdiv, postdiv2);
|
|
|
|
return foutpostdiv;
|
|
}
|
|
|
|
static unsigned long rk628_cru_clk_set_rate_pll(struct rk628 *rk628,
|
|
unsigned int id,
|
|
unsigned long rate)
|
|
{
|
|
unsigned long fin = REFCLK_RATE, fout = rate;
|
|
u8 min_refdiv, max_refdiv, postdiv;
|
|
u8 dsmpd = 1, postdiv1 = 0, postdiv2 = 0, refdiv = 0;
|
|
u16 fbdiv = 0;
|
|
u32 frac = 0;
|
|
u64 foutvco, foutpostdiv, div1, div2;
|
|
u32 offset, val;
|
|
|
|
/*
|
|
* FREF : 10MHz ~ 800MHz
|
|
* FREFDIV : 1MHz ~ 40MHz
|
|
* FOUTVCO : 400MHz ~ 1.6GHz
|
|
* FOUTPOSTDIV : 8MHz ~ 1.6GHz
|
|
*/
|
|
if (fin < MIN_FREF_RATE || fin > MAX_FREF_RATE)
|
|
return 0;
|
|
|
|
if (fout < MIN_FOUTPOSTDIV_RATE || fout > MAX_FOUTPOSTDIV_RATE)
|
|
return 0;
|
|
|
|
if (id == CGU_CLK_CPLL)
|
|
offset = 0x00;
|
|
else if (id == CGU_CLK_GPLL)
|
|
offset = 0x20;
|
|
else
|
|
offset = 0x40;
|
|
|
|
if (id != CGU_CLK_APLL)
|
|
rk628_i2c_write(rk628, offset + CRU_CPLL_CON1, PLL_PD(1));
|
|
|
|
if (fin == fout) {
|
|
rk628_i2c_write(rk628, offset + CRU_CPLL_CON0, PLL_BYPASS(1));
|
|
rk628_i2c_write(rk628, offset + CRU_CPLL_CON1, PLL_PD(0));
|
|
while (1) {
|
|
rk628_i2c_read(rk628, offset + CRU_CPLL_CON1, &val);
|
|
if (val & PLL_LOCK)
|
|
break;
|
|
}
|
|
return fin;
|
|
}
|
|
|
|
min_refdiv = fin / MAX_FREFDIV_RATE + 1;
|
|
max_refdiv = fin / MIN_FREFDIV_RATE;
|
|
if (max_refdiv > 64)
|
|
max_refdiv = 64;
|
|
|
|
if (fout < MIN_FVCO_RATE) {
|
|
div1 = DIV_ROUND_UP(MIN_FVCO_RATE, fout);
|
|
div2 = DIV_ROUND_UP(MAX_FVCO_RATE, fout);
|
|
for (postdiv = div1; postdiv <= div2; postdiv++) {
|
|
/* fix prime number that can not find right div*/
|
|
for (postdiv2 = 1; postdiv2 < 8; postdiv2++) {
|
|
if (postdiv % postdiv2)
|
|
continue;
|
|
postdiv1 = postdiv / postdiv2;
|
|
if (postdiv1 > 0 && postdiv1 < 8)
|
|
break;
|
|
}
|
|
if (postdiv2 > 7)
|
|
continue;
|
|
else
|
|
break;
|
|
}
|
|
if (postdiv > div2)
|
|
return 0;
|
|
fout *= postdiv1 * postdiv2;
|
|
} else {
|
|
postdiv1 = 1;
|
|
postdiv2 = 1;
|
|
}
|
|
|
|
for (refdiv = min_refdiv; refdiv <= max_refdiv; refdiv++) {
|
|
u64 tmp, frac_rate;
|
|
|
|
if (fin % refdiv)
|
|
continue;
|
|
|
|
tmp = (u64)fout * refdiv;
|
|
do_div(tmp, fin);
|
|
fbdiv = tmp;
|
|
if (fbdiv < 10 || fbdiv > 1600)
|
|
continue;
|
|
|
|
tmp = (u64)fbdiv * fin;
|
|
do_div(tmp, refdiv);
|
|
if (fout < MIN_FVCO_RATE || fout > MAX_FVCO_RATE)
|
|
continue;
|
|
|
|
frac_rate = fout - tmp;
|
|
|
|
if (frac_rate) {
|
|
tmp = (u64)frac_rate * refdiv;
|
|
tmp <<= 24;
|
|
do_div(tmp, fin);
|
|
frac = tmp;
|
|
dsmpd = 0;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If DSMPD = 1 (DSM is disabled, "integer mode")
|
|
* FOUTVCO = FREF / REFDIV * FBDIV
|
|
* FOUTPOSTDIV = FOUTVCO / POSTDIV1 / POSTDIV2
|
|
*
|
|
* If DSMPD = 0 (DSM is enabled, "fractional mode")
|
|
* FOUTVCO = FREF / REFDIV * (FBDIV + FRAC / 2^24)
|
|
* FOUTPOSTDIV = FOUTVCO / POSTDIV1 / POSTDIV2
|
|
*/
|
|
foutvco = fin * fbdiv;
|
|
do_div(foutvco, refdiv);
|
|
|
|
if (!dsmpd) {
|
|
u64 frac_rate = (u64)fin * frac;
|
|
|
|
do_div(frac_rate, refdiv);
|
|
foutvco += frac_rate >> 24;
|
|
}
|
|
|
|
foutpostdiv = foutvco;
|
|
do_div(foutpostdiv, postdiv1);
|
|
do_div(foutpostdiv, postdiv2);
|
|
|
|
rk628_i2c_write(rk628, offset + CRU_CPLL_CON0,
|
|
PLL_BYPASS(0) | PLL_POSTDIV1(postdiv1) |
|
|
PLL_FBDIV(fbdiv));
|
|
rk628_i2c_write(rk628, offset + CRU_CPLL_CON1,
|
|
PLL_DSMPD(dsmpd) | PLL_POSTDIV2(postdiv2) |
|
|
PLL_REFDIV(refdiv));
|
|
rk628_i2c_write(rk628, offset + CRU_CPLL_CON2, PLL_FRAC(frac));
|
|
|
|
if (id != CGU_CLK_APLL)
|
|
rk628_i2c_write(rk628, offset + CRU_CPLL_CON1, PLL_PD(0));
|
|
|
|
while (1) {
|
|
rk628_i2c_read(rk628, offset + CRU_CPLL_CON1, &val);
|
|
if (val & PLL_LOCK)
|
|
break;
|
|
}
|
|
|
|
return (unsigned long)foutpostdiv;
|
|
}
|
|
|
|
static int rk628_cru_clk_get_parent_rate(struct rk628 *rk628, unsigned int id,
|
|
unsigned int *parent_id,
|
|
unsigned long *parent_rate)
|
|
{
|
|
u32 val;
|
|
int parent = -1;
|
|
|
|
switch (id) {
|
|
case CGU_CLK_RX_READ:
|
|
rk628_i2c_read(rk628, CRU_CLKSEL_CON02, &val);
|
|
val &= CLK_RX_READ_SEL_MASK;
|
|
val >>= CLK_RX_READ_SEL_SHIFT;
|
|
parent = val == CLK_RX_READ_SEL_GPLL ? CGU_CLK_GPLL : CGU_CLK_CPLL;
|
|
break;
|
|
case CGU_SCLK_VOP:
|
|
rk628_i2c_read(rk628, CRU_CLKSEL_CON02, &val);
|
|
val &= CLK_UART_SRC_SEL_MASK;
|
|
val >>= SCLK_VOP_SEL_SHIFT;
|
|
parent = val == SCLK_VOP_SEL_GPLL ? CGU_CLK_GPLL : CGU_CLK_CPLL;
|
|
break;
|
|
case CGU_CLK_UART_SRC:
|
|
rk628_i2c_read(rk628, CRU_CLKSEL_CON21, &val);
|
|
val &= SCLK_VOP_SEL_MASK;
|
|
parent = val == CLK_UART_SRC_SEL_GPLL ? CGU_CLK_GPLL : CGU_CLK_CPLL;
|
|
break;
|
|
case CGU_BT1120DEC:
|
|
rk628_i2c_read(rk628, CRU_CLKSEL_CON02, &val);
|
|
val &= CLK_BT1120DEC_SEL_MASK;
|
|
parent = val == CLK_BT1120DEC_SEL_GPLL ? CGU_CLK_GPLL : CGU_CLK_CPLL;
|
|
break;
|
|
case CGU_CLK_HDMIRX_AUD:
|
|
rk628_i2c_read(rk628, CRU_CLKSEL_CON05, &val);
|
|
if (rk628->version >= RK628F_VERSION)
|
|
val = (val & CLK_HDMIRX_AUD_SEL_MASK_V2) >> 14;
|
|
else
|
|
val = (val & CLK_HDMIRX_AUD_SEL_MASK_V1) >> 15;
|
|
switch (val) {
|
|
case 0:
|
|
parent = CGU_CLK_CPLL;
|
|
break;
|
|
case 1:
|
|
parent = CGU_CLK_GPLL;
|
|
break;
|
|
case 2:
|
|
parent = CGU_CLK_APLL;
|
|
}
|
|
break;
|
|
case CGU_CLK_IMODET:
|
|
rk628_i2c_read(rk628, CRU_CLKSEL_CON05, &val);
|
|
val &= CLK_IMODET_SEL_MASK;
|
|
val >>= CLK_IMODET_SEL_SHIFT;
|
|
parent = val == SCLK_VOP_SEL_GPLL ? CGU_CLK_GPLL : CGU_CLK_CPLL;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (parent < 0)
|
|
return -EINVAL;
|
|
|
|
if (parent_id)
|
|
*parent_id = parent;
|
|
|
|
if (parent_rate)
|
|
*parent_rate = rk628_cru_clk_get_rate(rk628, parent);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned long rk628_cru_clk_set_rate_sclk_vop(struct rk628 *rk628,
|
|
unsigned long rate)
|
|
{
|
|
unsigned long m, n, parent_rate;
|
|
int ret;
|
|
|
|
ret = rk628_cru_clk_get_parent_rate(rk628, CGU_SCLK_VOP,
|
|
NULL, &parent_rate);
|
|
if (ret)
|
|
return 0;
|
|
|
|
rational_best_approximation(rate, parent_rate,
|
|
GENMASK(15, 0), GENMASK(15, 0),
|
|
&m, &n);
|
|
rk628_i2c_write(rk628, CRU_CLKSEL_CON13, m << 16 | n);
|
|
|
|
return rate;
|
|
}
|
|
|
|
static unsigned long rk628_cru_clk_get_rate_sclk_vop(struct rk628 *rk628)
|
|
{
|
|
unsigned long rate, parent_rate, m, n;
|
|
u32 div;
|
|
int ret;
|
|
|
|
ret = rk628_cru_clk_get_parent_rate(rk628, CGU_SCLK_VOP,
|
|
NULL, &parent_rate);
|
|
if (ret)
|
|
return 0;
|
|
|
|
rk628_i2c_read(rk628, CRU_CLKSEL_CON13, &div);
|
|
m = div >> 16 & 0xffff;
|
|
n = div & 0xffff;
|
|
rate = parent_rate * m / n;
|
|
|
|
return rate;
|
|
}
|
|
|
|
static unsigned long rk628_cru_clk_get_rate_clk_imodet(struct rk628 *rk628)
|
|
{
|
|
unsigned long rate, parent_rate, n;
|
|
u32 div;
|
|
int ret;
|
|
|
|
ret = rk628_cru_clk_get_parent_rate(rk628, CGU_CLK_IMODET,
|
|
NULL, &parent_rate);
|
|
if (ret)
|
|
return 0;
|
|
|
|
rk628_i2c_read(rk628, CRU_CLKSEL_CON05, &div);
|
|
n = div & 0x1f;
|
|
rate = parent_rate / (n + 1);
|
|
|
|
return rate;
|
|
}
|
|
|
|
static unsigned long rk628_cru_clk_set_rate_rx_read(struct rk628 *rk628,
|
|
unsigned long rate)
|
|
{
|
|
unsigned long m, n, parent_rate;
|
|
int ret;
|
|
|
|
ret = rk628_cru_clk_get_parent_rate(rk628, CGU_CLK_RX_READ,
|
|
NULL, &parent_rate);
|
|
if (ret)
|
|
return 0;
|
|
|
|
rational_best_approximation(rate, parent_rate,
|
|
GENMASK(15, 0), GENMASK(15, 0),
|
|
&m, &n);
|
|
rk628_i2c_write(rk628, CRU_CLKSEL_CON14, m << 16 | n);
|
|
|
|
return rate;
|
|
}
|
|
|
|
static unsigned long rk628_cru_clk_get_rate_rx_read(struct rk628 *rk628)
|
|
{
|
|
unsigned long rate, m, n, parent_rate;
|
|
u32 div;
|
|
int ret;
|
|
|
|
ret = rk628_cru_clk_get_parent_rate(rk628, CGU_CLK_RX_READ,
|
|
NULL, &parent_rate);
|
|
if (ret)
|
|
return 0;
|
|
|
|
rk628_i2c_read(rk628, CRU_CLKSEL_CON14, &div);
|
|
m = div >> 16 & 0xffff;
|
|
n = div & 0xffff;
|
|
rate = parent_rate * m / n;
|
|
|
|
return rate;
|
|
}
|
|
|
|
static unsigned long rk628_cru_clk_get_rate_uart_src(struct rk628 *rk628)
|
|
{
|
|
unsigned long rate, parent_rate;
|
|
u32 div;
|
|
int ret;
|
|
|
|
ret = rk628_cru_clk_get_parent_rate(rk628, CGU_CLK_UART_SRC,
|
|
NULL, &parent_rate);
|
|
if (ret)
|
|
return 0;
|
|
|
|
rk628_i2c_read(rk628, CRU_CLKSEL_CON21, &div);
|
|
div &= CLK_UART_SRC_DIV_MASK;
|
|
div >>= CLK_UART_SRC_DIV_SHIFT;
|
|
rate = parent_rate / (div + 1);
|
|
|
|
return rate;
|
|
}
|
|
|
|
static unsigned long rk628_cru_clk_set_rate_sclk_uart(struct rk628 *rk628,
|
|
unsigned long rate)
|
|
{
|
|
unsigned long m, n, parent_rate;
|
|
|
|
parent_rate = rk628_cru_clk_get_rate_uart_src(rk628);
|
|
|
|
if (rate == REFCLK_RATE) {
|
|
rk628_i2c_write(rk628, CRU_CLKSEL_CON06,
|
|
SCLK_UART_SEL(SCLK_UART_SEL_OSC));
|
|
return rate;
|
|
} else if (rate == parent_rate) {
|
|
rk628_i2c_write(rk628, CRU_CLKSEL_CON06,
|
|
SCLK_UART_SEL(SCLK_UART_SEL_UART_SRC));
|
|
return rate;
|
|
}
|
|
|
|
rk628_i2c_write(rk628, CRU_CLKSEL_CON06,
|
|
SCLK_UART_SEL(SCLK_UART_SEL_UART_FRAC));
|
|
|
|
rational_best_approximation(rate, parent_rate,
|
|
GENMASK(15, 0), GENMASK(15, 0),
|
|
&m, &n);
|
|
rk628_i2c_write(rk628, CRU_CLKSEL_CON20, m << 16 | n);
|
|
|
|
return rate;
|
|
}
|
|
|
|
static unsigned long rk628_cru_clk_set_rate_sclk_hdmirx_aud(struct rk628 *rk628, unsigned long rate)
|
|
{
|
|
u64 parent_rate;
|
|
u8 div;
|
|
|
|
if (rk628->version >= RK628F_VERSION)
|
|
parent_rate = rk628_cru_clk_set_rate_pll(rk628, CGU_CLK_APLL, rate*4);
|
|
else
|
|
parent_rate = rk628_cru_clk_set_rate_pll(rk628, CGU_CLK_GPLL, rate*4);
|
|
div = DIV_ROUND_CLOSEST_ULL(parent_rate, rate);
|
|
do_div(parent_rate, div);
|
|
rate = parent_rate;
|
|
if (rk628->version >= RK628F_VERSION)
|
|
rk628_i2c_write(rk628, CRU_CLKSEL_CON05, CLK_HDMIRX_AUD_DIV(div - 1) |
|
|
CLK_HDMIRX_AUD_SEL_V2(2));
|
|
else
|
|
rk628_i2c_write(rk628, CRU_CLKSEL_CON05, CLK_HDMIRX_AUD_DIV(div - 1) |
|
|
CLK_HDMIRX_AUD_SEL_V1(1));
|
|
return rate;
|
|
}
|
|
|
|
static unsigned long rk628_cru_clk_get_rate_sclk_hdmirx_aud(struct rk628 *rk628)
|
|
{
|
|
unsigned long rate, parent_rate;
|
|
u32 div;
|
|
int ret;
|
|
|
|
ret = rk628_cru_clk_get_parent_rate(rk628, CGU_CLK_HDMIRX_AUD,
|
|
NULL, &parent_rate);
|
|
if (ret)
|
|
return 0;
|
|
|
|
rk628_i2c_read(rk628, CRU_CLKSEL_CON05, &div);
|
|
div = ((div & CLK_HDMIRX_AUD_DIV_MASK) >> 6) + 1;
|
|
rate = parent_rate / div;
|
|
|
|
return rate;
|
|
}
|
|
|
|
static unsigned long rk628_cru_clk_set_rate_bt1120_dec(struct rk628 *rk628,
|
|
unsigned long rate)
|
|
{
|
|
unsigned long parent_rate;
|
|
u32 div;
|
|
int ret;
|
|
|
|
ret = rk628_cru_clk_get_parent_rate(rk628, CGU_BT1120DEC,
|
|
NULL, &parent_rate);
|
|
if (ret)
|
|
return 0;
|
|
|
|
div = DIV_ROUND_UP(parent_rate, rate);
|
|
rk628_i2c_write(rk628, CRU_CLKSEL_CON02, CLK_BT1120DEC_DIV(div-1));
|
|
|
|
return parent_rate / div;
|
|
}
|
|
|
|
static unsigned long rk628_cru_clk_get_rate_bt1120_dec(struct rk628 *rk628)
|
|
{
|
|
unsigned long parent_rate;
|
|
u32 div;
|
|
int ret;
|
|
|
|
ret = rk628_cru_clk_get_parent_rate(rk628, CGU_BT1120DEC,
|
|
NULL, &parent_rate);
|
|
if (ret)
|
|
return 0;
|
|
|
|
rk628_i2c_read(rk628, CRU_CLKSEL_CON02, &div);
|
|
div = (div & 0x1f) + 1;
|
|
|
|
return parent_rate / div;
|
|
}
|
|
|
|
int rk628_cru_clk_set_rate(struct rk628 *rk628, unsigned int id,
|
|
unsigned long rate)
|
|
{
|
|
if (id == CGU_CLK_APLL && rk628->version < RK628F_VERSION)
|
|
return -EINVAL;
|
|
|
|
switch (id) {
|
|
case CGU_CLK_APLL:
|
|
case CGU_CLK_CPLL:
|
|
case CGU_CLK_GPLL:
|
|
rk628_cru_clk_set_rate_pll(rk628, id, rate);
|
|
break;
|
|
case CGU_CLK_RX_READ:
|
|
rk628_cru_clk_set_rate_rx_read(rk628, rate);
|
|
break;
|
|
case CGU_SCLK_VOP:
|
|
rk628_cru_clk_set_rate_sclk_vop(rk628, rate);
|
|
break;
|
|
case CGU_SCLK_UART:
|
|
rk628_cru_clk_set_rate_sclk_uart(rk628, rate);
|
|
break;
|
|
case CGU_BT1120DEC:
|
|
rk628_cru_clk_set_rate_bt1120_dec(rk628, rate);
|
|
break;
|
|
case CGU_CLK_HDMIRX_AUD:
|
|
rk628_cru_clk_set_rate_sclk_hdmirx_aud(rk628, rate);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
unsigned long rk628_cru_clk_get_rate(struct rk628 *rk628, unsigned int id)
|
|
{
|
|
unsigned long rate;
|
|
|
|
if (id == CGU_CLK_APLL && rk628->version < RK628F_VERSION)
|
|
return 0;
|
|
|
|
switch (id) {
|
|
case CGU_CLK_APLL:
|
|
case CGU_CLK_CPLL:
|
|
case CGU_CLK_GPLL:
|
|
rate = rk628_cru_clk_get_rate_pll(rk628, id);
|
|
break;
|
|
case CGU_CLK_RX_READ:
|
|
rate = rk628_cru_clk_get_rate_rx_read(rk628);
|
|
break;
|
|
case CGU_SCLK_VOP:
|
|
rate = rk628_cru_clk_get_rate_sclk_vop(rk628);
|
|
break;
|
|
case CGU_CLK_IMODET:
|
|
rate = rk628_cru_clk_get_rate_clk_imodet(rk628);
|
|
break;
|
|
case CGU_CLK_HDMIRX_AUD:
|
|
rate = rk628_cru_clk_get_rate_sclk_hdmirx_aud(rk628);
|
|
break;
|
|
case CGU_BT1120DEC:
|
|
rate = rk628_cru_clk_get_rate_bt1120_dec(rk628);
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
return rate;
|
|
}
|
|
|
|
static void rk628_cru_show_pll_tree(struct seq_file *s, unsigned int parent_id,
|
|
const char *parent_name)
|
|
{
|
|
struct rk628 *rk628 = s->private;
|
|
unsigned long rate;
|
|
unsigned int parent, i;
|
|
unsigned int id_list[] = {
|
|
CGU_CLK_RX_READ,
|
|
CGU_SCLK_VOP,
|
|
CGU_BT1120DEC,
|
|
CGU_CLK_HDMIRX_AUD,
|
|
CGU_CLK_IMODET
|
|
};
|
|
char const *id_name[] = {
|
|
"clk_rx_read",
|
|
"clk_sclk_vop",
|
|
"clk_bt1120dec",
|
|
"clk_hdmirx_aud",
|
|
"clk_imodet"
|
|
};
|
|
|
|
if (rk628->version < RK628F_VERSION && parent_id == CGU_CLK_APLL)
|
|
return;
|
|
|
|
rate = rk628_cru_clk_get_rate(rk628, parent_id);
|
|
seq_printf(s, "%-22s %10lu\n", parent_name, rate);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(id_list); ++i) {
|
|
rk628_cru_clk_get_parent_rate(rk628, id_list[i], &parent, NULL);
|
|
if (parent != parent_id)
|
|
continue;
|
|
rate = rk628_cru_clk_get_rate(rk628, id_list[i]);
|
|
seq_printf(s, " %-18s %10lu\n", id_name[i], rate);
|
|
}
|
|
}
|
|
|
|
static int rk628_cru_show_clk_tree(struct seq_file *s, void *data)
|
|
{
|
|
unsigned int pll_list[] = {CGU_CLK_CPLL, CGU_CLK_GPLL, CGU_CLK_APLL};
|
|
char const *pll_name[] = {"cpll", "gpll", "apll"};
|
|
unsigned int i;
|
|
|
|
seq_printf(s, "%-22s %10s\n", " clock", "rate ");
|
|
seq_puts(s, "---------------------------------\n");
|
|
|
|
for (i = 0; i < ARRAY_SIZE(pll_list); ++i)
|
|
rk628_cru_show_pll_tree(s, pll_list[i], pll_name[i]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rk628_clk_summary_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct rk628 *rk628 = inode->i_private;
|
|
|
|
return single_open(file, rk628_cru_show_clk_tree, rk628);
|
|
}
|
|
|
|
static const struct file_operations rk628_clk_summary_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = rk628_clk_summary_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
void rk628_cru_create_debugfs_file(struct rk628 *rk628)
|
|
{
|
|
debugfs_create_file("clk_summary", 0400, rk628->debug_dir, rk628,
|
|
&rk628_clk_summary_fops);
|
|
}
|
|
|
|
void rk628_cru_init(struct rk628 *rk628)
|
|
{
|
|
u32 val;
|
|
u8 mcu_mode;
|
|
|
|
/*
|
|
* In rk628d application, if you need to dynamically tune the cpll
|
|
* frequency, you need to mount pclk under gpll, otherwise it will
|
|
* affect the i2c use. The bt1120rx only supports 5bit integer crossover
|
|
* frequency, in order to crossover frequency accurately, you need to
|
|
* adjust the cpll frequency dynamically, so in the scenario of rk628d
|
|
* bt1120rx, mount the pclk under gpll.
|
|
*/
|
|
rk628_i2c_read(rk628, GRF_SYSTEM_STATUS0, &val);
|
|
mcu_mode = (val & I2C_ONLY_FLAG) ? 0 : 1;
|
|
if (mcu_mode || rk628->version >= RK628F_VERSION) {
|
|
rk628_i2c_write(rk628, CRU_MODE_CON00, HIWORD_UPDATE(1, 4, 4));
|
|
/*
|
|
* rk628d pclk use cpll by default, and frequency is 99MHz
|
|
* rk628f pclk use gpll by default, and frequency is 98.304MHz
|
|
*/
|
|
if (rk628_input_is_bt1120(rk628)) {
|
|
/* set pclk use gpll, and set pclk 98.304MHz */
|
|
rk628_i2c_write(rk628, CRU_CLKSEL_CON00, 0x00ff0089);
|
|
}
|
|
} else {
|
|
/* clock switch and first set gpll almost 99MHz */
|
|
rk628_i2c_write(rk628, CRU_GPLL_CON0, 0xffff701d);
|
|
mdelay(1);
|
|
/* set clk_gpll_mux from gpll */
|
|
rk628_i2c_write(rk628, CRU_MODE_CON00, 0xffff0004);
|
|
mdelay(1);
|
|
rk628_i2c_write(rk628, CRU_CLKSEL_CON00, 0x00ff0080);
|
|
/* set pclk use gpll, now div is 4 */
|
|
rk628_i2c_write(rk628, CRU_CLKSEL_CON00, 0x00ff0083);
|
|
/* set cpll almost 400MHz */
|
|
rk628_i2c_write(rk628, CRU_CPLL_CON0, 0xffff3063);
|
|
mdelay(1);
|
|
/* set clk_cpll_mux from clk_cpll */
|
|
rk628_i2c_write(rk628, CRU_MODE_CON00, 0xffff0005);
|
|
mdelay(1);
|
|
if (rk628_input_is_bt1120(rk628)) {
|
|
/* set pclk use cpll, now div is 4 */
|
|
rk628_i2c_write(rk628, CRU_CLKSEL_CON00, 0x00ff0003);
|
|
/* set pclk use cpll, now div is 10 */
|
|
rk628_i2c_write(rk628, CRU_CLKSEL_CON00, 0x00ff0009);
|
|
/* set gpll 983.04MHz */
|
|
rk628_i2c_write(rk628, CRU_GPLL_CON0, 0xffff1028);
|
|
mdelay(1);
|
|
/* set pclk use gpll, now div is 10 */
|
|
rk628_i2c_write(rk628, CRU_CLKSEL_CON00, 0x00ff0089);
|
|
/* set cpll 1188MHz */
|
|
rk628_i2c_write(rk628, CRU_CPLL_CON0, 0xffff1063);
|
|
/* final: cpll 1188MHz, gpll 983.04MHz, pclk (use gpll) 98.304MHz */
|
|
} else {
|
|
/* set pclk use cpll, now div is 4 */
|
|
rk628_i2c_write(rk628, CRU_CLKSEL_CON00, 0x00ff0003);
|
|
/* set pclk use cpll, now div is 12 */
|
|
rk628_i2c_write(rk628, CRU_CLKSEL_CON00, 0x00ff000b);
|
|
/* set gpll 983.04MHz */
|
|
rk628_i2c_write(rk628, CRU_GPLL_CON0, 0xffff1028);
|
|
mdelay(1);
|
|
/* set pclk use gpll, now div is 12 */
|
|
rk628_i2c_write(rk628, CRU_CLKSEL_CON00, 0x00ff008b);
|
|
/* set cpll 1188MHz */
|
|
rk628_i2c_write(rk628, CRU_CPLL_CON0, 0xffff1063);
|
|
mdelay(1);
|
|
/* set pclk use cpll, now div is 12 */
|
|
rk628_i2c_write(rk628, CRU_CLKSEL_CON00, 0x00ff000b);
|
|
/* final: cpll 1188MHz, gpll 983.04MHz, pclk (use cpll) 99MHz */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The sclk_vop frequency default is 594M, which exceeds the reference
|
|
* clock frequency acceptable by hdmitx phy. Therefore, in the hdmitx
|
|
* scenario, we need to set the initial frequency of the sclk_vop to a
|
|
* lower frequency, which is set to 148.5M.
|
|
*/
|
|
if (rk628_output_is_hdmi(rk628))
|
|
rk628_cru_clk_set_rate(rk628, CGU_SCLK_VOP, 148500000);
|
|
}
|
|
|
|
void rk628_cru_clk_adjust(struct rk628 *rk628)
|
|
{
|
|
struct rk628_display_mode *src = &rk628->src_mode;
|
|
const struct rk628_display_mode *dst = &rk628->dst_mode;
|
|
u64 dst_rate, src_rate;
|
|
unsigned long dec_clk_rate;
|
|
u32 val;
|
|
|
|
/*
|
|
* Try to keep cpll frequency close to 1188m (Tested bt1120rx and rk628f
|
|
* hdmirx scenarios)
|
|
*/
|
|
if ((rk628_input_is_hdmi(rk628) && rk628->version != RK628D_VERSION) ||
|
|
rk628_input_is_bt1120(rk628)) {
|
|
val = 1188000000UL / (src->clock * 1000);
|
|
if (rk628_input_is_bt1120(rk628) && val > (CLK_BT1120DEC_DIV_MAX + 1))
|
|
val = CLK_BT1120DEC_DIV_MAX + 1;
|
|
val *= src->clock * 1000;
|
|
rk628_cru_clk_set_rate(rk628, CGU_CLK_CPLL, val);
|
|
msleep(50);
|
|
dev_info(rk628->dev, "adjust cpll to %uHz", val);
|
|
}
|
|
|
|
/*
|
|
* BT1120 dec clk is a 5-bit integer division, which is inaccurate in
|
|
* most resolutions. So if the frequency division is not accurate, apply
|
|
* for a fault tolerance of up 2% in frequency setting, so that the
|
|
* obtained frequency is slightly higher than the actual required clk,
|
|
* so that the deviation between the actual clk and the required clk
|
|
* frequency is not significant.
|
|
*/
|
|
if (rk628_input_is_bt1120(rk628)) {
|
|
rk628_cru_clk_set_rate(rk628, CGU_BT1120DEC, src->clock * 1000);
|
|
dec_clk_rate = rk628_cru_clk_get_rate(rk628, CGU_BT1120DEC);
|
|
if (dec_clk_rate < src->clock * 1000)
|
|
rk628_cru_clk_set_rate(rk628, CGU_BT1120DEC, src->clock * 1020);
|
|
}
|
|
|
|
src_rate = src->clock * 1000;
|
|
dst_rate = src_rate * dst->vtotal * dst->htotal;
|
|
do_div(dst_rate, (src->vtotal * src->htotal));
|
|
do_div(dst_rate, 1000);
|
|
dev_info(rk628->dev, "src %dx%d clock:%d\n",
|
|
src->hdisplay, src->vdisplay, src->clock);
|
|
|
|
dev_info(rk628->dev, "dst %dx%d clock:%llu\n",
|
|
dst->hdisplay, dst->vdisplay, dst_rate);
|
|
|
|
rk628_cru_clk_set_rate(rk628, CGU_CLK_RX_READ, src->clock * 1000);
|
|
rk628_cru_clk_set_rate(rk628, CGU_SCLK_VOP, dst_rate * 1000);
|
|
}
|