rk3188: add clock support

This commit is contained in:
chenxing
2013-12-20 19:07:23 +08:00
parent 95a82fdada
commit a456b372ec
10 changed files with 3162 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
/dts-v1/;
#include "rk3188.dtsi"
#include "rk3188-clocks.dtsi"
/ {
memory {

View File

@@ -20,6 +20,7 @@ obj-$(CONFIG_ARCH_U300) += clk-u300.o
obj-$(CONFIG_COMMON_CLK_VERSATILE) += versatile/
obj-$(CONFIG_ARCH_PRIMA2) += clk-prima2.o
obj-$(CONFIG_PLAT_ORION) += mvebu/
obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/
ifeq ($(CONFIG_COMMON_CLK), y)
obj-$(CONFIG_ARCH_MMP) += mmp/
endif

View File

@@ -220,6 +220,7 @@ static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
val = readl(divider->reg);
val &= ~(div_mask(divider) << divider->shift);
val |= value << divider->shift;
val |= (div_mask(divider) << (divider->shift + 16));
writel(val, divider->reg);
if (divider->lock)

View File

@@ -89,6 +89,8 @@ static int clk_mux_set_parent(struct clk_hw *hw, u8 index)
val = readl(mux->reg);
val &= ~(mux->mask << mux->shift);
val |= index << mux->shift;
val |= (mux->mask << (mux->shift + 16));
writel(val, mux->reg);
if (mux->lock)

View File

@@ -0,0 +1,2 @@
obj-y += clk.o
obj-y += clk-ops.o

View File

@@ -0,0 +1,814 @@
#include <linux/clk.h>
#include <linux/clkdev.h>
#include <linux/io.h>
#include <linux/clk-provider.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/clk-private.h>
#include "clk-ops.h"
#include <linux/delay.h>
/* mux_ops */
struct clk_ops_table rk_clk_mux_ops_table[] = {
{.index = CLKOPS_TABLE_END},
};
#define to_clk_divider(_hw) container_of(_hw, struct clk_divider, hw)
#define div_mask(d) ((1 << ((d)->width)) - 1)
#define MHZ (1000 * 1000)
static u32 clk_gcd(u32 numerator, u32 denominator)
{
u32 a, b;
if (!numerator || !denominator)
return 0;
if (numerator > denominator) {
a = numerator;
b = denominator;
} else {
a = denominator;
b = numerator;
}
while (b != 0) {
int r = b;
b = a % b;
a = r;
}
return a;
}
static int clk_fracdiv_get_config(unsigned long rate_out, unsigned long rate,
u32 *numerator, u32 *denominator)
{
u32 gcd_val;
gcd_val = clk_gcd(rate, rate_out);
clk_debug("%s: frac_get_seting rate=%lu, parent=%lu, gcd=%d\n",
__func__, rate_out, rate, gcd_val);
if (!gcd_val) {
clk_err("gcd=0, i2s frac div is not be supported\n");
return -EINVAL;
}
*numerator = rate_out / gcd_val;
*denominator = rate / gcd_val;
clk_debug("%s: frac_get_seting numerator=%d, denominator=%d, times=%d\n",
__func__, *numerator, *denominator,
*denominator / *numerator);
if (*numerator > 0xffff || *denominator > 0xffff ||
(*denominator / (*numerator)) < 20) {
clk_err("can't get a available nume and deno\n");
return -EINVAL;
}
return 0;
}
static int clk_fracdiv_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
u32 numerator, denominator;
struct clk_divider *div = to_clk_divider(hw);
struct clk *clk_parent = hw->clk->parent;
if(clk_fracdiv_get_config(rate, parent_rate,
&numerator, &denominator) == 0) {
clk_parent->ops->set_rate(clk_parent->hw,
clk_parent->parent->rate,
clk_parent->parent->rate);
writel(numerator << 16 | denominator, div->reg);
clk_err("%s set rate=%lu,is ok\n", hw->clk->name, rate);
} else {
clk_err("clk_frac_div can't get rate=%lu,%s\n",
rate, hw->clk->name);
return -ENOENT;
}
return 0;
}
static unsigned long clk_fracdiv_recalc(struct clk_hw *hw,
unsigned long parent_rate)
{
unsigned long rate;
u64 rate64;
struct clk_divider *div = to_clk_divider(hw);
u32 numerator, denominator, reg_val;
reg_val = readl(div->reg);
if (reg_val == 0)
return parent_rate;
numerator = reg_val >> 16;
denominator = reg_val & 0xFFFF;
rate64 = (u64)parent_rate * numerator;
do_div(rate64, denominator);
rate = rate64;
clk_debug("%s: %s new clock rate is %lu, prate %lu (frac %u/%u)\n",
__func__, hw->clk->name, rate, parent_rate,
numerator, denominator);
return rate;
}
static long clk_fracdiv_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
return rate;
}
/*************************************************************************/
/* rate_ops */
#define PARENTS_NUM_MAX 3
/*
* get the best rate from array of available rates, regarding rate which is smaller than
* and most close to the set_rate as the best.
*/
static long get_best_rate(unsigned long array[],unsigned int num, int *n, long rate)
{
int i = 0;
unsigned long best_rate = 0;
for(i = 0; i < num; i++){
if(array[i] == rate){
*n = i;
return array[i];
}else if((array[i] < rate) && (array[i] > best_rate)){
best_rate = array[i];
*n = i;
}
}
if(best_rate == 0){
clk_err("NOT array rate is <= %lu\n", rate);
}else{
clk_debug("get the best available rate,but it != %lu you want to set!\n", rate);
}
return best_rate;
}
static struct clk *clk_get_best_parent(struct clk_hw *hw, unsigned long rate,
unsigned int *div_out)
{
struct clk *clk = hw->clk;
u32 div[PARENTS_NUM_MAX] = {0};
unsigned long new_rate[PARENTS_NUM_MAX] = {0};
unsigned long best_rate;
u32 i;
memset(div, 0, sizeof(div));
memset(new_rate, 0, sizeof(new_rate));
if(clk->rate == rate)
return clk->parent;
for(i = 0; i < clk->num_parents; i++) {
new_rate[i] = clk_divider_ops.round_rate(hw, rate,
&(clk->parents[i]->rate));
div[i] = (clk->parents[i]->rate)/new_rate[i];
if(new_rate[i] == rate) {
*div_out = div[i];
return clk->parents[i];
}
}
best_rate = get_best_rate(new_rate, PARENTS_NUM_MAX, &i, rate);
if(best_rate == 0){
clk_err("NOT rate is good!\n");
return NULL;
}
*div_out = div[i];
return clk->parents[i];
}
static long clk_div_round_rate_autosel_parents(struct clk_hw *hw,
unsigned long rate, unsigned long *prate)
{
struct clk *clk = hw->clk;
struct clk *new_parent;
int new_div;
if(clk->rate == rate)
return rate;
new_parent = clk_get_best_parent(hw, rate, &new_div);
if(!new_parent || (new_div <= 0)){
clk_err("%s: clk %s could not get new_parent or new_div\n",
__func__,clk->name);
return -EINVAL;
}
return (new_parent->rate)/new_div;
}
static int clk_div_set_rate_autosel_parents(struct clk_hw *hw,
unsigned long rate, unsigned long parent_rate)
{
//struct clk_divider *divider = to_clk_divider(hw);
struct clk *clk = hw->clk;
struct clk *new_parent;
unsigned int new_div,old_div;
unsigned long new_rate;
int ret = 0;
u8 index;
int i;
if(clk->rate == rate)
goto out;
new_parent = clk_get_best_parent(hw, rate, &new_div);
if(!new_parent || (new_div == 0)){
clk_err("%s: clk %s could not get new_parent or get "
"new_div = 0\n", __func__,clk->name);
ret = -EINVAL;
goto out;
}
old_div = (clk->parent->rate)/(clk->rate);
clk_debug("%s:%d: %s: %lu\n", __func__, __LINE__,
clk->parent->name, new_parent->rate);
if(new_div > old_div){
new_rate = (clk->parent->rate)/new_div;
ret = clk_divider_ops.set_rate(hw, new_rate,
(clk->parent->rate));
if(ret)
goto out;
}
if(clk->parent != new_parent){
for(i=0; i<clk->num_parents; i++){
if(new_parent == clk->parents[i]){
index = i;
break;
}
}
/*
* ret = clk->ops->set_parent(clk->hw, index);
* if(ret)
* goto out;
*/
clk_set_parent(clk, new_parent);
clk->ops->recalc_rate(clk->hw, clk->parent->rate);
}
if(new_div <= old_div){
new_rate = (clk->parent->rate)/new_div;
ret = clk_divider_ops.set_rate(hw, new_rate,
(clk->parent->rate));
if(ret)
goto out;
}
out:
return ret;
}
static unsigned long clk_divider_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return clk_divider_ops.recalc_rate(hw, hw->clk->parent->rate);
}
const struct clk_ops clkops_rate_auto_parent = {
.recalc_rate = clk_divider_recalc_rate,
.round_rate = clk_div_round_rate_autosel_parents,
.set_rate = clk_div_set_rate_autosel_parents,
};
static long clk_div_round_rate_even(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
int i = 0;
struct clk_divider *divider =to_clk_divider(hw);
int max_div = 1 << divider->width;
for (i = 1; i < max_div; i++) {
if (i > 1 && (i % 2 != 0))
continue;
if (rate >= *prate / i)
return *prate / i;
}
return -EINVAL;
}
static int clk_div_set_rate_even(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
return clk_divider_ops.set_rate(hw, rate, hw->clk->parent->rate);
}
const struct clk_ops clkops_rate_evendiv = {
.recalc_rate = clk_divider_recalc_rate,
.round_rate = clk_div_round_rate_even,
.set_rate = clk_div_set_rate_even,
};
static long dclk_lcdc_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
long ret = 0;
if (rate == 27 * MHZ) {
ret = clk_div_round_rate_autosel_parents(hw, rate, prate);
} else {
ret = clk_div_round_rate_autosel_parents(hw, rate, prate);
}
return ret;
}
static int dclk_lcdc_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
return clk_div_set_rate_autosel_parents(hw, rate, parent_rate);
}
const struct clk_ops clkops_rate_dclk_lcdc = {
.recalc_rate = clk_divider_recalc_rate,
.round_rate = dclk_lcdc_round_rate,
.set_rate = dclk_lcdc_set_rate,
};
#define CIF_OUT_SRC_DIV (0x0)
#define CIF_OUT_SRC_24M (0x1)
static unsigned long cif_out_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return hw->clk->parent->rate;
}
static long cif_out_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
struct clk *clk = hw->clk;
struct clk *parent;
if (rate == clk->parents[CIF_OUT_SRC_24M]->rate) {
return rate;
} else {
parent = clk->parents[CIF_OUT_SRC_DIV];
return parent->ops->round_rate(parent->hw, rate,
&(parent->parent->rate));
}
}
static int cif_out_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk *clk = hw->clk;
struct clk *parent;
int ret = 0;
if (rate == clk->parents[CIF_OUT_SRC_24M]->rate) {
parent = clk->parents[CIF_OUT_SRC_24M];
} else {
parent = clk->parents[CIF_OUT_SRC_DIV];
ret = parent->ops->set_rate(parent->hw, rate,
parent->parent->rate);
if (ret)
goto out;
else
parent->rate = rate;
}
if(clk->parent != parent){
ret = clk_set_parent(clk, parent);
#if 0
for(i=0; i<clk->num_parents; i++){
if(parent == clk->parents[i]){
index = i;
break;
}
}
ret = clk->ops->set_parent(clk->hw, index);
#endif
if(ret)
goto out;
}
out:
return ret;
}
const struct clk_ops clkops_rate_cif_out = {
.recalc_rate = cif_out_recalc_rate,
.round_rate = cif_out_round_rate,
.set_rate = cif_out_set_rate,
};
static int clk_i2s_fracdiv_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
u32 numerator, denominator;
struct clk *clk_parent;
int i = 10;
struct clk_divider *div = to_clk_divider(hw);
clk_parent = hw->clk->parent;
if(clk_fracdiv_get_config(rate, parent_rate,
&numerator, &denominator) == 0) {
clk_parent->ops->set_rate(clk_parent->hw,
clk_parent->parent->rate,
clk_parent->parent->rate);
while (i--) {
writel((numerator - 1) << 16 | denominator, div->reg);
mdelay(1);
writel(numerator << 16 | denominator, div->reg);
mdelay(1);
}
clk_err("%s set rate=%lu,is ok\n", hw->clk->name, rate);
} else {
clk_err("%s: can't get rate=%lu,%s\n", __func__, rate, hw->clk->name);
return -ENOENT;
}
return 0;
}
const struct clk_ops clkops_rate_i2s_frac = {
.recalc_rate = clk_fracdiv_recalc,
.round_rate = clk_fracdiv_round_rate,
.set_rate = clk_i2s_fracdiv_set_rate,
};
static unsigned long clk_i2s_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return hw->clk->parent->rate;
}
static long clk_i2s_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
return rate;
}
#define I2S_SRC_DIV (0x0)
#define I2S_SRC_FRAC (0x1)
#define I2S_SRC_12M (0x2)
static int clk_i2s_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
int ret = -EINVAL;
u8 p_index = 0;
struct clk *parent_tmp, *parent;
struct clk *clk = hw->clk;
if (rate == clk->parents[I2S_SRC_12M]->rate) {
parent = clk->parents[I2S_SRC_12M];
p_index = I2S_SRC_12M;
goto set_parent;
}
parent_tmp = clk->parents[I2S_SRC_DIV];
if(parent_tmp->ops->round_rate(parent_tmp->hw, rate,
&parent_tmp->parent->rate) == rate) {
parent = clk->parents[I2S_SRC_DIV];
p_index = I2S_SRC_DIV;
goto set;
}
parent = clk->parents[I2S_SRC_FRAC];
p_index = I2S_SRC_FRAC;
//ret = clk_set_rate(parent_tmp, parent_tmp->parent->rate);
ret = parent_tmp->ops->set_rate(parent_tmp->hw,
parent_tmp->parent->rate,
parent_tmp->parent->rate);
parent_tmp->rate = parent_tmp->ops->recalc_rate(parent_tmp->hw,
parent_tmp->parent->rate);
//ret = parent->ops->set_rate(parent->hw, rate, parent->parent->rate);
if (ret) {
clk_debug("%s set rate%lu err\n", clk->name, rate);
return ret;
}
set:
clk_debug(" %s set rate=%lu parent %s(old %s)\n",
clk->name, rate, parent->name, clk->parent->name);
ret = clk_set_rate(parent, rate);
//ret = parent->ops->set_rate(parent->hw, rate, parent->parent->rate);
if (ret) {
clk_debug("%s set rate%lu err\n", clk->name, rate);
return ret;
}
set_parent:
clk_debug("%s: set parent\n", __func__);
if (clk->parent != parent) {
ret = clk_set_parent(clk, parent);
/*
* clk->ops->set_parent(hw, p_index);
*/
if (ret) {
clk_debug("%s can't get rate%lu,reparent err\n",
clk->name, rate);
return ret;
}
}
return ret;
}
const struct clk_ops clkops_rate_i2s = {
.recalc_rate = clk_i2s_recalc_rate,
.round_rate = clk_i2s_round_rate,
.set_rate = clk_i2s_set_rate,
};
const struct clk_ops clkops_rate_hsadc_frac = {
.recalc_rate = clk_fracdiv_recalc,
.round_rate = clk_fracdiv_round_rate,
.set_rate = clk_fracdiv_set_rate,
};
const struct clk_ops clkops_rate_uart_frac = {
.recalc_rate = clk_fracdiv_recalc,
.round_rate = clk_fracdiv_round_rate,
.set_rate = clk_fracdiv_set_rate,
};
static unsigned long clk_uart_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return hw->clk->parent->rate;
}
static long clk_uart_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
return rate;
}
#define UART_SRC_DIV (0x0)
#define UART_SRC_FRAC (0x1)
#define UART_SRC_24M (0x2)
static int clk_uart_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
int ret = -EINVAL;
u8 p_index = 0;
struct clk *parent_tmp, *parent;
struct clk *clk = hw->clk;
if (rate == clk->parents[UART_SRC_24M]->rate) {
parent = clk->parents[UART_SRC_24M];
p_index = UART_SRC_24M;
goto set_parent;
}
parent_tmp = clk->parents[UART_SRC_DIV];
if(parent_tmp->ops->round_rate(parent_tmp->hw, rate,
&parent_tmp->parent->rate) == rate) {
parent = clk->parents[UART_SRC_DIV];
p_index = UART_SRC_DIV;
goto set;
}
parent = clk->parents[UART_SRC_FRAC];
p_index = UART_SRC_FRAC;
/*
* ret = clk_set_rate(parent_tmp, parent_tmp->parent->rate);
*/
ret = parent_tmp->ops->set_rate(parent_tmp->hw,
parent_tmp->parent->rate,
parent_tmp->parent->rate);
parent_tmp->rate = parent_tmp->ops->recalc_rate(parent_tmp->hw,
parent_tmp->parent->rate);
//ret = parent->ops->set_rate(parent->hw, rate, parent->parent->rate);
if (ret) {
clk_debug("%s set rate%lu err\n", clk->name, rate);
return ret;
}
set:
clk_debug(" %s set rate=%lu parent %s(old %s)\n",
clk->name, rate, parent->name, clk->parent->name);
ret = clk_set_rate(parent, rate);
//ret = parent->ops->set_rate(parent->hw, rate, parent->parent->rate);
if (ret) {
clk_debug("%s set rate%lu err\n", clk->name, rate);
return ret;
}
set_parent:
clk_debug("%s: set parent\n", __func__);
if (clk->parent != parent) {
ret = clk_set_parent(clk, parent);
/*
* clk->ops->set_parent(hw, p_index);
*/
if (ret) {
clk_debug("%s can't get rate%lu,reparent err\n",
clk->name, rate);
return ret;
}
}
return ret;
}
const struct clk_ops clkops_rate_uart = {
.recalc_rate = clk_uart_recalc_rate,
.round_rate = clk_uart_round_rate,
.set_rate = clk_uart_set_rate,
};
static unsigned long clk_hsadc_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return hw->clk->parent->rate;
}
static long clk_hsadc_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
return rate;
}
#define HSADC_SRC_DIV (0x0)
#define HSADC_SRC_FRAC (0x1)
#define HSADC_SRC_EXT (0x2)
static int clk_hsadc_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
int ret = -EINVAL;
u8 p_index = 0;
struct clk *parent_tmp, *parent;
struct clk *clk = hw->clk;
if (rate == clk->parents[HSADC_SRC_EXT]->rate) {
parent = clk->parents[HSADC_SRC_EXT];
p_index = HSADC_SRC_EXT;
goto set_parent;
}
parent_tmp = clk->parents[HSADC_SRC_DIV];
if(parent_tmp->ops->round_rate(parent_tmp->hw, rate,
&parent_tmp->parent->rate) == rate) {
parent = clk->parents[HSADC_SRC_DIV];
p_index = HSADC_SRC_DIV;
goto set;
}
parent = clk->parents[HSADC_SRC_FRAC];
p_index = HSADC_SRC_FRAC;
/*
* ret = clk_set_rate(parent_tmp, parent_tmp->parent->rate);
*/
ret = parent_tmp->ops->set_rate(parent_tmp->hw,
parent_tmp->parent->rate,
parent_tmp->parent->rate);
parent_tmp->rate = parent_tmp->ops->recalc_rate(parent_tmp->hw,
parent_tmp->parent->rate);
//ret = parent->ops->set_rate(parent->hw, rate, parent->parent->rate);
if (ret) {
clk_debug("%s set rate%lu err\n", clk->name, rate);
return ret;
}
set:
clk_debug(" %s set rate=%lu parent %s(old %s)\n",
clk->name, rate, parent->name, clk->parent->name);
ret = clk_set_rate(parent, rate);
//ret = parent->ops->set_rate(parent->hw, rate, parent->parent->rate);
if (ret) {
clk_debug("%s set rate%lu err\n", clk->name, rate);
return ret;
}
set_parent:
clk_debug("%s: set parent\n", __func__);
if (clk->parent != parent) {
ret = clk_set_parent(clk, parent);
/*
* clk->ops->set_parent(hw, p_index);
*/
if (ret) {
clk_debug("%s can't get rate%lu,reparent err\n",
clk->name, rate);
return ret;
}
}
return ret;
}
const struct clk_ops clkops_rate_hsadc = {
.recalc_rate = clk_hsadc_recalc_rate,
.round_rate = clk_hsadc_round_rate,
.set_rate = clk_hsadc_set_rate,
};
static unsigned long clk_mac_ref_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return hw->clk->parent->rate;
}
static long clk_mac_ref_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
return rate;
}
#define MAC_SRC_DIV (0x0)
#define RMII_CLKIN (0x1)
static int clk_mac_ref_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
int ret = -EINVAL;
u8 p_index = 0;
struct clk *parent;
struct clk *clk = hw->clk;
clk_debug("%s: rate %lu\n", __func__, rate);
if (rate == clk->parents[RMII_CLKIN]->rate) {
parent = clk->parents[RMII_CLKIN];
p_index = RMII_CLKIN;
goto set_parent;
}
parent = clk->parents[MAC_SRC_DIV];
p_index = MAC_SRC_DIV;
clk_debug(" %s set rate=%lu parent %s(old %s)\n",
clk->name, rate, parent->name, clk->parent->name);
/*
* ret = clk_set_rate(parent, rate);
*/
ret = parent->ops->set_rate(parent->hw,
rate,
parent->parent->rate);
parent->rate = parent->ops->recalc_rate(parent->hw,
parent->parent->rate);
//ret = parent->ops->set_rate(parent->hw, rate, parent->parent->rate);
if (ret) {
clk_debug("%s set rate%lu err\n", clk->name, rate);
return ret;
}
set_parent:
clk_debug("%s: set parent\n", __func__);
if (clk->parent != parent) {
ret = clk_set_parent(clk, parent);
/*
* clk->ops->set_parent(hw, p_index);
*/
if (ret) {
clk_debug("%s can't get rate%lu,reparent err\n",
clk->name, rate);
return ret;
}
}
return ret;
}
const struct clk_ops clkops_rate_mac_ref = {
.recalc_rate = clk_mac_ref_recalc_rate,
.round_rate = clk_mac_ref_round_rate,
.set_rate = clk_mac_ref_set_rate,
};
struct clk_ops_table rk_clkops_rate_table[] = {
{.index = CLKOPS_RATE_MUX_DIV, .clk_ops = &clkops_rate_auto_parent},
{.index = CLKOPS_RATE_EVENDIV, .clk_ops = &clkops_rate_evendiv},
{.index = CLKOPS_RATE_DCLK_LCDC, .clk_ops = &clkops_rate_dclk_lcdc},
{.index = CLKOPS_RATE_CIFOUT, .clk_ops = &clkops_rate_cif_out},
{.index = CLKOPS_RATE_I2S_FRAC, .clk_ops = &clkops_rate_i2s_frac},
{.index = CLKOPS_RATE_I2S, .clk_ops = &clkops_rate_i2s},
{.index = CLKOPS_RATE_HSADC_FRAC, .clk_ops = &clkops_rate_hsadc_frac},
{.index = CLKOPS_RATE_UART_FRAC, .clk_ops = &clkops_rate_uart_frac},
{.index = CLKOPS_RATE_UART, .clk_ops = &clkops_rate_uart},
{.index = CLKOPS_RATE_HSADC, .clk_ops = &clkops_rate_hsadc},
{.index = CLKOPS_RATE_MAC_REF, .clk_ops = &clkops_rate_mac_ref},
{.index = CLKOPS_TABLE_END},
};
const struct clk_ops *rk_get_clkops(u32 idx)
{
return rk_clkops_rate_table[idx].clk_ops;
}
EXPORT_SYMBOL_GPL(rk_get_clkops);

View File

@@ -0,0 +1,19 @@
#ifndef __RK_CLK_OPS_H
#define __RK_CLK_OPS_H
#include "clkops-dtsi.h"
struct clk_ops_table {
unsigned int index;
const struct clk_ops *clk_ops;
};
const struct clk_ops *rk_get_clkops(u32 idx);
//#define RKCLK_DEBUG
#if defined(RKCLK_DEBUG)
#define clk_debug(fmt, args...) printk(KERN_INFO "rkclk: "fmt, ##args)
#else
#define clk_debug(fmt, args...) do {} while(0)
#endif
#define clk_err(fmt, args...) printk(KERN_ERR "rkclk: "fmt, ##args)
#endif /* __RK_CLKOPS_H */

924
drivers/clk/rockchip/clk.c Normal file
View File

@@ -0,0 +1,924 @@
/*
* Copyright (C) 2013 ROCKCHIP, Inc.
* Author: chenxing <chenxing@rock-chips.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/clk-provider.h>
#include <linux/clkdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include "clk-ops.h"
#include <linux/clk-private.h>
#include <asm/io.h>
static DEFINE_SPINLOCK(clk_lock);
struct rkclk_divmap_table {
u32 reg_val;
u32 div_val;
};
struct rkclk_divinfo {
struct clk_divider *div;
void __iomem *addr;
u32 shift;
u32 width;
u32 div_type;
u32 max_div;
u32 fixed_div_val;
u32 clkops_idx;
const char *clk_name;
const char *parent_name;
struct clk_div_table *div_table;
struct list_head node;
};
struct rkclk_muxinfo {
struct clk_mux *mux;
void __iomem *addr;
u32 shift;
u32 width;
u32 parent_num;
u32 clkops_idx;
const char *clk_name;
const char **parent_names;
struct list_head node;
};
struct rkclk_fracinfo {
struct clk_hw hw;
void __iomem *addr;
u32 shift;
u32 width;
u32 frac_type;
u32 clkops_idx;
const char *clk_name;
const char *parent_name;
struct list_head node;
};
struct rkclk_gateinfo {
struct clk_gate *gate;
void __iomem *addr;
u32 shift;
u32 clkops_idx;
const char *clk_name;
const char *parent_name;
};
struct rkclk_pllinfo {
struct clk_hw hw;
struct clk_ops *ops;
void __iomem *addr;
u32 width;
const char *clk_name;
const char *parent_name;
/*
* const char **clkout_names;
*/
struct list_head node;
};
struct rkclk {
const char *clk_name;
u32 clk_type;
/*
* store nodes creat this rkclk
* */
struct device_node *np;
struct rkclk_pllinfo *pll_info;
struct rkclk_muxinfo *mux_info;
struct rkclk_divinfo *div_info;
struct rkclk_fracinfo *frac_info;
struct rkclk_gateinfo *gate_info;
struct list_head node;
};
LIST_HEAD(rk_clks);
void __iomem *reg_start = 0;
#define RKCLK_PLL_TYPE (1 << 0)
#define RKCLK_MUX_TYPE (1 << 1)
#define RKCLK_DIV_TYPE (1 << 2)
#define RKCLK_FRAC_TYPE (1 << 3)
#define RKCLK_GATE_TYPE (1 << 4)
#define CLK_GATE_SET_TO_DISABLE BIT(0)
#define CLK_GATE_HIWORD_MASK BIT(1)
static int rkclk_init_muxinfo(struct device_node *np,
struct rkclk_muxinfo *mux, void __iomem *addr)
{
int cnt, i, ret = 0;
u8 found = 0;
struct rkclk *rkclk;
mux = kzalloc(sizeof(struct rkclk_muxinfo), GFP_KERNEL);
if (!mux)
return -ENOMEM;
/*
* Get control bit addr
*/
ret = of_property_read_u32_index(np, "rockchip,bits", 0, &mux->shift);
if (ret != 0)
return -EINVAL;
ret = of_property_read_u32(np, "rockchip,clkops-idx", &mux->clkops_idx);
if (ret != 0)
mux->clkops_idx = CLKOPS_TABLE_END;
ret = of_property_read_u32_index(np, "rockchip,bits", 1, &mux->width);
if (ret != 0)
return -EINVAL;
mux->addr = addr;
ret = of_property_read_string(np, "clock-output-names", &mux->clk_name);
if (ret != 0)
return -EINVAL;
/*
* Get parents' cnt
*/
cnt = of_count_phandle_with_args(np, "clocks", "#clock-cells");
if (cnt< 0)
return -EINVAL;
mux->parent_num = cnt;
mux->parent_names = kzalloc(cnt * sizeof(char *), GFP_KERNEL);
clk_debug("%s: parent cnt = %d\n", __func__, cnt);
for (i = 0; i < cnt ; i++) {
mux->parent_names[i] = of_clk_get_parent_name(np, i);
}
found = 0;
list_for_each_entry(rkclk, &rk_clks, node) {
if (strcmp(mux->clk_name, rkclk->clk_name) == 0) {
if (rkclk->mux_info != NULL)
clk_err("%s(%d): This clk(%s) has been used\n",
__func__, __LINE__, mux->clk_name);
clk_debug("%s: find match %s\n", __func__, rkclk->clk_name);
found = 1;
rkclk->mux_info = mux;
rkclk->clk_type |= RKCLK_MUX_TYPE;
break;
}
}
if (!found) {
rkclk = kzalloc(sizeof(struct rkclk), GFP_KERNEL);
rkclk->clk_name = mux->clk_name;
rkclk->mux_info = mux;
rkclk->clk_type |= RKCLK_MUX_TYPE;
rkclk->np = np;
clk_debug("%s: creat %s\n", __func__, rkclk->clk_name);
list_add_tail(&rkclk->node, &rk_clks);
}
return 0;
}
static int rkclk_init_divinfo(struct device_node *np,
struct rkclk_divinfo *div, void __iomem *addr)
{
int cnt = 0, i = 0, ret = 0;
struct rkclk *rkclk;
u8 found = 0;
div = kzalloc(sizeof(struct rkclk_divinfo), GFP_KERNEL);
if (!div)
return -ENOMEM;
of_property_read_u32_index(np, "rockchip,bits", 0, &div->shift);
of_property_read_u32_index(np, "rockchip,bits", 1, &div->width);
div->addr = addr;
of_property_read_u32(np, "rockchip,div-type", &div->div_type);
ret = of_property_read_u32(np, "rockchip,clkops-idx", &div->clkops_idx);
if (ret != 0)
div->clkops_idx = CLKOPS_TABLE_END;
cnt = of_property_count_strings(np, "clock-output-names");
if (cnt <= 0)
div->clk_name = of_clk_get_parent_name(np, 0);
else {
ret = of_property_read_string(np, "clock-output-names", &div->clk_name);
if (ret != 0)
return -EINVAL;
div->parent_name = of_clk_get_parent_name(np, 0);
}
switch (div->div_type) {
case CLK_DIVIDER_PLUS_ONE:
case CLK_DIVIDER_ONE_BASED:
case CLK_DIVIDER_POWER_OF_TWO:
break;
case CLK_DIVIDER_FIXED:
of_property_read_u32_index(np, "rockchip,div-relations", 0,
&div->fixed_div_val);
clk_debug("%s:%s fixed_div = %d\n", __func__,
div->clk_name, div->fixed_div_val);
break;
case CLK_DIVIDER_USER_DEFINE:
of_get_property(np, "rockchip,div-relations", &cnt);
cnt /= 4 * 2;
div->div_table = kzalloc(cnt * sizeof(struct clk_div_table),
GFP_KERNEL);
for (i = 0; i < cnt; i++) {
of_property_read_u32_index(np, "rockchip,div-relations", i * 2,
&div->div_table[i].val);
of_property_read_u32_index(np, "rockchip,div-relations", i * 2 + 1,
&div->div_table[i].div);
clk_debug("\tGet div table %d: val=%d, div=%d\n",
i, div->div_table[i].val,
div->div_table[i].div);
}
break;
default:
clk_err("%s: %s: unknown rockchip,div-type, please check dtsi\n",
__func__, div->clk_name);
break;
}
found = 0;
list_for_each_entry(rkclk, &rk_clks, node) {
if (strcmp(div->clk_name, rkclk->clk_name) == 0) {
if (rkclk->div_info != NULL)
clk_err("%s(Line %d): This clk(%s) has been used\n",
__func__, __LINE__, rkclk->clk_name);
clk_debug("%s: find match %s\n", __func__, rkclk->clk_name);
found = 1;
rkclk->div_info = div;
rkclk->clk_type |= RKCLK_DIV_TYPE;
break;
}
}
if (!found) {
rkclk = kzalloc(sizeof(struct rkclk), GFP_KERNEL);
rkclk->clk_name = div->clk_name;
rkclk->div_info = div;
rkclk->clk_type |= RKCLK_DIV_TYPE;
rkclk->np = np;
clk_debug("%s: creat %s\n", __func__, rkclk->clk_name);
list_add_tail(&rkclk->node, &rk_clks);
}
return 0;
}
static int rkclk_init_fracinfo(struct device_node *np,
struct rkclk_fracinfo *frac, void __iomem *addr)
{
struct rkclk *rkclk;
u8 found = 0;
int ret = 0;
frac = kzalloc(sizeof(struct rkclk_fracinfo), GFP_KERNEL);
if (!frac)
return -ENOMEM;
of_property_read_u32_index(np, "rockchip,bits", 0, &frac->shift);
of_property_read_u32_index(np, "rockchip,bits", 1, &frac->width);
frac->addr = addr;
ret = of_property_read_u32(np, "rockchip,clkops-idx", &frac->clkops_idx);
if (ret != 0)
frac->clkops_idx = CLKOPS_TABLE_END;
frac->parent_name = of_clk_get_parent_name(np, 0);
ret = of_property_read_string(np, "clock-output-names", &frac->clk_name);
if (ret != 0)
return -EINVAL;
found = 0;
list_for_each_entry(rkclk, &rk_clks, node) {
if (strcmp(frac->clk_name, rkclk->clk_name) == 0) {
if (rkclk->frac_info != NULL)
clk_err("%s(%d): This clk(%s) has been used\n",
__func__, __LINE__, frac->clk_name);
clk_debug("%s: find match %s\n", __func__, rkclk->clk_name);
found = 1;
rkclk->frac_info = frac;
rkclk->clk_type |= RKCLK_FRAC_TYPE;
break;
}
}
if (!found) {
rkclk = kzalloc(sizeof(struct rkclk), GFP_KERNEL);
rkclk->clk_name = frac->clk_name;
rkclk->frac_info = frac;
rkclk->clk_type |= RKCLK_FRAC_TYPE;
rkclk->np = np;
clk_debug("%s: creat %s\n", __func__, rkclk->clk_name);
list_add_tail(&rkclk->node, &rk_clks);
}
return 0;
}
static int __init rkclk_init_selcon(struct device_node *np)
{
struct device_node *node_con, *node;
void __iomem *reg = 0;
struct rkclk_divinfo *divinfo;
struct rkclk_muxinfo *muxinfo;
struct rkclk_fracinfo *fracinfo;
for_each_available_child_of_node(np, node_con) {
reg = of_iomap(node_con, 0);
for_each_available_child_of_node(node_con, node) {
if (of_device_is_compatible(node, "rockchip,rk3188-div-con"))
rkclk_init_divinfo(node, divinfo, reg);
else if (of_device_is_compatible(node, "rockchip,rk3188-mux-con"))
rkclk_init_muxinfo(node, muxinfo, reg);
else if (of_device_is_compatible(node, "rockchip,rk3188-frac-con"))
rkclk_init_fracinfo(node, fracinfo, reg);
else if (of_device_is_compatible(node, "rockchip,rk3188-inv-con"))
clk_debug("INV clk\n");
else
clk_err("%s: unknown controler type, plz check dtsi "
"or add type support\n", __func__);
}
}
return 0;
}
static int __init rkclk_init_gatecon(struct device_node *np)
{
struct clk_onecell_data *clk_data;
struct device_node *node;
const char *clk_parent;
const char *clk_name;
void __iomem *reg;
void __iomem *reg_idx;
int flags;
int cnt;
int reg_bit;
int clkflags = CLK_SET_RATE_PARENT;
int i;
struct rkclk_gateinfo *gateinfo;
u8 found = 0;
struct rkclk *rkclk;
for_each_available_child_of_node(np, node) {
cnt = of_property_count_strings(node, "clock-output-names");
if (cnt < 0) {
clk_err("%s: error in clock-output-names %d\n",
__func__, cnt);
continue;
}
if (cnt == 0) {
pr_info("%s: nothing to do\n", __func__);
continue;
}
reg = of_iomap(node, 0);
clk_data = kzalloc(sizeof(struct clk_onecell_data), GFP_KERNEL);
if (!clk_data)
return -ENOMEM;
clk_data->clks = kzalloc(cnt * sizeof(struct clk *), GFP_KERNEL);
if (!clk_data->clks) {
kfree(clk_data);
return -ENOMEM;
}
flags = CLK_GATE_HIWORD_MASK | CLK_GATE_SET_TO_DISABLE;
for (i = 0; i < cnt; i++) {
of_property_read_string_index(node, "clock-output-names",
i, &clk_name);
/* ignore empty slots */
if (!strcmp("reserved", clk_name))
continue;
clk_parent = of_clk_get_parent_name(node, i);
clkflags |= CLK_IGNORE_UNUSED;
reg_idx = reg + (4 * (i / 16));
reg_bit = (i % 16);
gateinfo = kzalloc(sizeof(struct rkclk_gateinfo), GFP_KERNEL);
gateinfo->clk_name = clk_name;
gateinfo->parent_name = clk_parent;
gateinfo->addr = reg;
gateinfo->shift = reg_bit;
found = 0;
list_for_each_entry(rkclk, &rk_clks, node) {
if (strcmp(clk_name, rkclk->clk_name) == 0) {
if (rkclk->gate_info != NULL)
clk_err("%s(%d): This clk(%s) has been used\n",
__func__, __LINE__, clk_name);
clk_debug("%s: find match %s\n", __func__, rkclk->clk_name);
found = 1;
rkclk->gate_info = gateinfo;
rkclk->clk_type |= RKCLK_GATE_TYPE;
break;
}
}
if (!found) {
rkclk = kzalloc(sizeof(struct rkclk), GFP_KERNEL);
rkclk->clk_name = gateinfo->clk_name;
rkclk->gate_info = gateinfo;
rkclk->clk_type |= RKCLK_GATE_TYPE;
rkclk->np = np;
clk_debug("%s: creat %s\n", __func__, rkclk->clk_name);
list_add_tail(&rkclk->node, &rk_clks);
}
}
}
return 0;
}
static int __init rkclk_init_pllcon(struct device_node *np)
{
struct rkclk_pllinfo *pllinfo;
struct device_node *node;
struct rkclk *rkclk;
void __iomem *reg;
int i = 0;
int ret = 0, clknum = 0;
u8 found = 0;
for_each_available_child_of_node(np, node) {
clknum = of_property_count_strings(node, "clock-output-names");
if (clknum < 0) {
clk_err("%s: error in get clock-output-names numbers = %d\n",
__func__, clknum);
return -EINVAL;
}
reg = of_iomap(node, 0);
if (reg_start == 0)
reg_start = reg;
for (i = 0; i < clknum; i++) {
pllinfo = kzalloc(sizeof(struct rkclk_pllinfo), GFP_KERNEL);
if (!pllinfo)
return -ENOMEM;
/*
* Get pll parent name
*/
pllinfo->parent_name = of_clk_get_parent_name(node, i);
/*
* Get pll output name
*/
of_property_read_string_index(node, "clock-output-names",
i, &pllinfo->clk_name);
pllinfo->addr = reg;
ret = of_property_read_u32_index(node, "reg", 1, &pllinfo->width);
if (ret != 0) {
clk_err("%s: cat not get reg info\n", __func__);
}
clk_debug("%s: parent=%s, pllname=%s, reg =%08x, cnt=%d\n", __func__,
pllinfo->parent_name, pllinfo->clk_name,
(u32)pllinfo->addr, pllinfo->width);
found = 0;
list_for_each_entry(rkclk, &rk_clks, node) {
if (strcmp(pllinfo->clk_name, rkclk->clk_name) == 0) {
if (rkclk->pll_info != NULL)
clk_err("%s(%d): This clk(%s) has been used\n",
__func__, __LINE__, pllinfo->clk_name);
clk_debug("%s: find match %s\n", __func__, rkclk->clk_name);
found = 1;
rkclk->pll_info = pllinfo;
rkclk->clk_type |= RKCLK_PLL_TYPE;
break;
}
}
if (!found) {
rkclk = kzalloc(sizeof(struct rkclk), GFP_KERNEL);
rkclk->clk_name = pllinfo->clk_name;
rkclk->pll_info = pllinfo;
rkclk->clk_type |= RKCLK_PLL_TYPE;
rkclk->np = np;
list_add_tail(&rkclk->node, &rk_clks);
}
}
}
return 0;
}
#define MHZ (1000 * 1000)
static unsigned long clk_pll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
clk_debug("%s\n", __func__);
if (strncmp(hw->clk->name, "clk_apll", sizeof("clk_apll")) == 0) {
return 600 * MHZ;
} else if (strncmp(hw->clk->name, "clk_dpll", sizeof("clk_dpll")) == 0) {
return 300 * MHZ;
}else if (strncmp(hw->clk->name, "clk_cpll", sizeof("clk_cpll")) == 0) {
return 132 * MHZ;
}else if (strncmp(hw->clk->name, "clk_gpll", sizeof("clk_gpll")) == 0) {
return 891 * MHZ;
} else
clk_err("Unknown PLL\n");
return -EINVAL;
}
static long clk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
clk_debug("%s\n", __func__);
return rate;
}
static int clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
clk_debug("%s\n", __func__);
return 0;
}
const struct clk_ops clk_pll_ops = {
.recalc_rate = clk_pll_recalc_rate,
.round_rate = clk_pll_round_rate,
.set_rate = clk_pll_set_rate,
};
static unsigned long clk_frac_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return parent_rate;
}
static long clk_frac_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
return rate;
}
static int clk_frac_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
return 0;
}
const struct clk_ops clk_frac_ops = {
.recalc_rate = clk_frac_recalc_rate,
.round_rate = clk_frac_round_rate,
.set_rate = clk_frac_set_rate,
};
static unsigned long clk_div_special_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return parent_rate;
}
static long clk_div_special_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
return rate;
}
static int clk_div_special_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
return 0;
}
// For fixed div clks and For user defined div clk
const struct clk_ops clk_div_special_ops = {
.recalc_rate = clk_div_special_recalc_rate,
.round_rate = clk_div_special_round_rate,
.set_rate = clk_div_special_set_rate,
};
static int rkclk_register(struct rkclk *rkclk)
{
struct clk_mux *mux = NULL;
struct clk_divider *div = NULL;
struct clk_gate *gate = NULL;
const struct clk_ops *rate_ops = NULL;
const struct clk_ops *mux_ops = NULL;
struct clk *clk = NULL;
const char **parent_names = NULL;
struct clk_hw *rate_hw;
int parent_num;
struct device_node *node = rkclk->np;
/* Single clk */
clk_debug("%s: %s clk_type=%x\n", __func__,
rkclk->clk_name, rkclk->clk_type);
if (rkclk->clk_type & RKCLK_PLL_TYPE) {
div = kzalloc(sizeof(struct clk_divider), GFP_KERNEL);
rate_ops = &clk_pll_ops;
div->reg = rkclk->pll_info->addr;
div->shift = 0;
div->width = rkclk->pll_info->width;
rate_hw = &div->hw;
parent_num = 1;
parent_names = &rkclk->pll_info->parent_name;
} else if (rkclk->clk_type & RKCLK_FRAC_TYPE) {
div = kzalloc(sizeof(struct clk_divider), GFP_KERNEL);
if (rkclk->frac_info->clkops_idx != CLKOPS_TABLE_END)
rate_ops = rk_get_clkops(rkclk->frac_info->clkops_idx);
else
rate_ops = &clk_frac_ops;
div->reg = rkclk->frac_info->addr;
div->shift = (u8)rkclk->frac_info->shift;
div->width = rkclk->frac_info->width;
rate_hw = &div->hw;
parent_num = 1;
parent_names = &rkclk->frac_info->parent_name;
} else if (rkclk->clk_type & RKCLK_DIV_TYPE) {
div = kzalloc(sizeof(struct clk_divider), GFP_KERNEL);
if (rkclk->div_info->clkops_idx != CLKOPS_TABLE_END)
rate_ops = rk_get_clkops(rkclk->div_info->clkops_idx);
else
rate_ops = &clk_divider_ops;
div->reg = rkclk->div_info->addr;
div->shift = (u8)rkclk->div_info->shift;
div->width = rkclk->div_info->width;
div->flags = rkclk->div_info->div_type;
rate_hw = &div->hw;
if (rkclk->div_info->div_table)
div->table = rkclk->div_info->div_table;
parent_num = 1;
parent_names = &rkclk->div_info->parent_name;
if (rkclk->clk_type != (rkclk->clk_type & CLK_DIVIDER_MASK)) {
// FIXME: fixed div add here
clk_err("%s: %d, unknown clk_type=%x\n",
__func__, __LINE__, rkclk->clk_type);
}
}
if (rkclk->clk_type & RKCLK_MUX_TYPE) {
mux = kzalloc(sizeof(struct clk_mux), GFP_KERNEL);
mux->reg = rkclk->mux_info->addr;
mux->shift = (u8)rkclk->mux_info->shift;
mux->mask = (1 << rkclk->mux_info->width) - 1;
mux->flags = 0;
mux_ops = &clk_mux_ops;
if (rkclk->mux_info->clkops_idx != CLKOPS_TABLE_END) {
rate_hw = kzalloc(sizeof(struct clk_hw), GFP_KERNEL);
rate_ops = rk_get_clkops(rkclk->mux_info->clkops_idx);
}
parent_num = rkclk->mux_info->parent_num;
parent_names = rkclk->mux_info->parent_names;
}
if (rkclk->clk_type & RKCLK_GATE_TYPE) {
gate = kzalloc(sizeof(struct clk_gate), GFP_KERNEL);
gate->reg = rkclk->gate_info->addr;
gate->bit_idx = rkclk->gate_info->shift;
gate->flags = CLK_GATE_HIWORD_MASK | CLK_GATE_SET_TO_DISABLE;
}
// FIXME: flag(CLK_IGNORE_UNUSED) may need an input argument
if (rkclk->clk_type == RKCLK_MUX_TYPE
&& rkclk->mux_info->clkops_idx == CLKOPS_TABLE_END) {
clk = clk_register_mux(NULL, rkclk->clk_name,
rkclk->mux_info->parent_names,
(u8)rkclk->mux_info->parent_num,
CLK_SET_RATE_PARENT,
mux->reg, mux->shift, mux->mask,
0, &clk_lock);
} else if (rkclk->clk_type == RKCLK_DIV_TYPE) {
clk = clk_register_divider(NULL, rkclk->clk_name,
rkclk->div_info->parent_name,
CLK_SET_RATE_PARENT, div->reg, div->shift,
div->width, div->flags, &clk_lock);
} else if (rkclk->clk_type == RKCLK_GATE_TYPE) {
clk = clk_register_gate(NULL, rkclk->clk_name,
rkclk->gate_info->parent_name,
CLK_IGNORE_UNUSED, gate->reg,
gate->bit_idx,
gate->flags, &clk_lock);
} else {
int i = 0;
clk_debug("%s: composite clk(\"%s\") parents:\n",
__func__, rkclk->clk_name);
for (i = 0; i < parent_num; i++) {
clk_debug("\t\t%s: parent[%d]=%s\n", __func__,
i, parent_names[i]);
}
clk = clk_register_composite(NULL, rkclk->clk_name,
parent_names, parent_num,
mux ? &mux->hw : NULL, mux ? mux_ops : NULL,
rate_hw, rate_ops,
gate ? &gate->hw : NULL, gate ? &clk_gate_ops : NULL,
CLK_IGNORE_UNUSED);
}
if (clk) {
of_clk_add_provider(node, of_clk_src_simple_get, clk);
clk_register_clkdev(clk, rkclk->clk_name, NULL);
} else {
clk_err("%s: clk(\"%s\") register clk error\n",
__func__, rkclk->clk_name);
}
return 0;
}
struct test_table {
const char *name;
u32 rate;
};
struct test_table t_table[] = {
{.name = "clk_gpu", .rate = 297000000},
{.name = "dclk_lcdc0", .rate = 297000000},
{.name = "clk_i2s", .rate = 11289600},
{.name = "clk_spdif", .rate = 11289600},
{.name = "clk_sdmmc", .rate = 50000000},
{.name = "clk_emmc", .rate = 50000000},
{.name = "clk_sdio", .rate = 50000000},
{.name = "clk_uart0", .rate = 12288000},
{.name = "clk_hsadc", .rate = 12288000},
{.name = "clk_mac", .rate = 50000000},
{.name = "clk_cif0", .rate = 12000000},
{.name = "aclk_lcdc0", .rate = 297000000},
};
#ifdef RKCLK_DEBUG
void rk_clk_test(void)
{
const char *clk_name;
struct clk *clk;
u32 rate = 0;
u32 i = 0, j = 0;
for (j = 0; j < ARRAY_SIZE(t_table); j++) {
clk_name = t_table[j].name;
rate = t_table[j].rate;
clk = clk_get(NULL, clk_name);
if (IS_ERR(clk)) {
clk_err("%s: clk(\"%s\") \tclk_get error\n",
__func__, clk_name);
} else
clk_debug("%s: clk(\"%s\") \tclk_get success\n",
__func__, __clk_get_name(clk));
/* TEST: clk_set_rate */
if (clk->ops->set_rate) {
if (0 != clk_set_rate(clk, rate)) {
clk_err("%s: clk(\"%s\") \tclk_set_rate error\n",
__func__, clk_name);
} else {
clk_debug("%s: clk(\"%s\") \tclk_set_rate success\n",
__func__, __clk_get_name(clk));
}
} else {
clk_debug("%s: clk(\"%s\") have no set ops\n",
__func__, clk->name);
}
for (i = 0; i * 4 <= 0xf4; i++) {
if (i % 4 == 0)
printk("\n%s: \t[0x%08x]: ",
__func__, 0x20000000 + i * 4);
printk("%08x ", readl(reg_start + i * 4));
}
printk("\n\n");
}
}
EXPORT_SYMBOL_GPL(rk_clk_test);
#else
void rk_clk_test(void){};
EXPORT_SYMBOL_GPL(rk_clk_test);
#endif
extern void clk_dump_tree(void);
static void __init rkclk_init(struct device_node *np)
{
struct device_node *node;
struct rkclk *rkclk;
for_each_available_child_of_node(np, node) {
if (!ERR_PTR(of_property_match_string(node,
"compatible",
"fixed-clock"))) {
continue;
} else if (!ERR_PTR(of_property_match_string(node,
"compatible",
"rockchip,rk-pll-cons"))) {
if (ERR_PTR(rkclk_init_pllcon(node))) {
clk_err("%s: init pll clk err\n", __func__);
return ;
}
} else if (!ERR_PTR(of_property_match_string(node,
"compatible",
"rockchip,rk-sel-cons"))) {
if (ERR_PTR(rkclk_init_selcon(node))) {
clk_err("%s: init sel cons err\n", __func__);
return ;
}
} else if (!ERR_PTR(of_property_match_string(node,
"compatible",
"rockchip,rk-gate-cons"))) {
if (ERR_PTR(rkclk_init_gatecon(node))) {
clk_err("%s: init gate cons err\n", __func__);
return ;
}
} else {
clk_err("%s: unknown\n", __func__);
}
};
#if 0
list_for_each_entry(rkclk, &rk_clks, node) {
int i;
clk_debug("%s: clkname = %s; type=%d\n",
__func__, rkclk->clk_name,
rkclk->clk_type);
if (rkclk->pll_info) {
clk_debug("\t\tpll: name=%s, parent=%s\n",
rkclk->pll_info->clk_name,
rkclk->pll_info->parent_name);
}
if (rkclk->mux_info) {
for (i = 0; i < rkclk->mux_info->parent_num; i++)
clk_debug("\t\tmux name=%s, parent: %s\n",
rkclk->mux_info->clk_name,
rkclk->mux_info->parent_names[i]);
}
if (rkclk->div_info) {
clk_debug("\t\tdiv name=%s\n",
rkclk->div_info->clk_name);
}
if (rkclk->frac_info) {
clk_debug("\t\tfrac name=%s\n",
rkclk->frac_info->clk_name);
}
if (rkclk->gate_info) {
clk_debug("\t\tgate name=%s, \taddr=%08x, \tshift=%d\n",
rkclk->gate_info->clk_name,
(u32)rkclk->gate_info->addr,
rkclk->gate_info->shift);
}
}
#endif
list_for_each_entry(rkclk, &rk_clks, node) {
rkclk_register(rkclk);
}
/* check clock parents init */
list_for_each_entry(rkclk, &rk_clks, node) {
struct clk *clk;
int i = 0;
const char *clk_name = rkclk->clk_name;
clk = clk_get(NULL, clk_name);
if (IS_ERR(clk)) {
clk_err("%s: clk(\"%s\") \tclk_get error\n",
__func__, clk_name);
continue;
} else {
clk_debug("%s: clk(\"%s\") \tclk_get success\n",
__func__, __clk_get_name(clk));
}
if (clk->parents) {
for (i = 0; i < clk->num_parents; i++) {
if (clk->parents[i])
clk_debug("\t\tclk(\"%s\"): init parent:%s\n",
clk->name,
clk->parents[i]->name);
else {
clk->parents[i] = clk_get(NULL, clk->parent_names[i]);
clk_debug("\t\tclk(\"%s\"): init parent:%s\n",
clk->name,
clk->parents[i]->name);
}
}
} else {
clk_debug("\t\tNOT A MUX CLK, parent num=%d\n", clk->num_parents);
}
}
}
CLK_OF_DECLARE(rk_clocks, "rockchip,rk-clock-regs", rkclk_init);

View File

@@ -0,0 +1,29 @@
#ifndef __RK_CLKOPS_DTSI_H
#define __RK_CLKOPS_DTSI_H
/* rate_ops index */
#define CLKOPS_RATE_MUX_DIV 0
#define CLKOPS_RATE_EVENDIV 1
#define CLKOPS_RATE_DCLK_LCDC 2
#define CLKOPS_RATE_CIFOUT 3
#define CLKOPS_RATE_I2S_FRAC 4
#define CLKOPS_RATE_I2S 5
#define CLKOPS_RATE_HSADC_FRAC 6
#define CLKOPS_RATE_UART_FRAC 7
#define CLKOPS_RATE_UART 8
#define CLKOPS_RATE_HSADC 9
#define CLKOPS_RATE_MAC_REF 10
#define CLKOPS_TABLE_END ~0
#ifndef BIT
#define BIT(nr) (1 << (nr))
#endif
#define CLK_DIVIDER_PLUS_ONE (0)
#define CLK_DIVIDER_ONE_BASED BIT(0)
#define CLK_DIVIDER_POWER_OF_TWO BIT(1)
#define CLK_DIVIDER_ALLOW_ZERO BIT(2)
#define CLK_DIVIDER_FIXED BIT(3)
#define CLK_DIVIDER_USER_DEFINE BIT(4)
/* CLK_DIVIDER_MAX defined the bits been used above */
#define CLK_DIVIDER_MASK (0x1F)
#endif /* __RK_CLKOPS_H */