clk/rockchip: add cru support for rk618

Change-Id: I223c85194f62fec2c22c2a013466b767a1128f9c
Signed-off-by: Wyon Bi <bivvy.bi@rock-chips.com>
This commit is contained in:
Wyon Bi
2017-10-25 15:45:34 +08:00
committed by Tao Huang
parent 2ef4ee93f4
commit d04dec1a54
11 changed files with 1660 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
Rockchip RK618 Clock and Reset Unit
This binding uses the common clock binding:
Documentation/devicetree/bindings/clock/clock-bindings.txt
Required properties :
- compatible : Should be "rockchip,rk618-cru"
- clocks : Should contain phandle and clock specifiers for the input clock:
the AP I2S master clock output(mclk) "clkin", and the AP LCDC master
dclk output(dclk) "lcdc0_dclkp".
- #clock-cells : Should be 1.
Example:
&rk618 {
CRU: cru {
compatible = "rockchip,rk618-cru";
clocks = <&cru SCLK_I2S_8CH_OUT>, <&cru DCLK_VOP>;
clock-names = "clkin", "lcdc0_dclkp";
assigned-clocks = <&CRU SCALER_PLLIN_CLK>, <&CRU VIF_PLLIN_CLK>,
<&CRU HDMI_CLK>, <&CRU SCALER_CLK>,
<&CRU CODEC_CLK>;
assigned-clock-parents = <&CRU LCDC0_CLK>, <&CRU LCDC0_CLK>,
<&CRU VIF0_CLK>, <&CRU SCALER_PLL_CLK>,
<&cru SCLK_I2S_8CH_OUT>;
#clock-cells = <1>;
status = "okay";
};
};

View File

@@ -22,3 +22,4 @@ obj-y += clk-rk3328.o
obj-y += clk-rk3366.o obj-y += clk-rk3366.o
obj-y += clk-rk3368.o obj-y += clk-rk3368.o
obj-y += clk-rk3399.o obj-y += clk-rk3399.o
obj-$(CONFIG_MFD_RK618) += rk618/

View File

@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the Rockchip RK618 CRU driver.
#
obj-$(CONFIG_MFD_RK618) += clk-regmap-mux.o \
clk-regmap-divider.o \
clk-regmap-gate.o \
clk-regmap-composite.o \
clk-regmap-pll.o \
clk-rk618.o

View File

@@ -0,0 +1,351 @@
/*
* Copyright (c) 2017 Rockchip Electronics Co. Ltd.
*
* Base on code in drivers/clk/clk-composite.c.
* See clk-composite.c for further copyright information.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This 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 "clk-regmap.h"
struct clk_regmap_composite {
struct device *dev;
struct clk_hw hw;
struct clk_ops ops;
struct clk_hw *mux_hw;
struct clk_hw *rate_hw;
struct clk_hw *gate_hw;
const struct clk_ops *mux_ops;
const struct clk_ops *rate_ops;
const struct clk_ops *gate_ops;
};
#define to_clk_regmap_composite(_hw) \
container_of(_hw, struct clk_regmap_composite, hw)
static u8 clk_regmap_composite_get_parent(struct clk_hw *hw)
{
struct clk_regmap_composite *composite = to_clk_regmap_composite(hw);
const struct clk_ops *mux_ops = composite->mux_ops;
struct clk_hw *mux_hw = composite->mux_hw;
__clk_hw_set_clk(mux_hw, hw);
return mux_ops->get_parent(mux_hw);
}
static int clk_regmap_composite_set_parent(struct clk_hw *hw, u8 index)
{
struct clk_regmap_composite *composite = to_clk_regmap_composite(hw);
const struct clk_ops *mux_ops = composite->mux_ops;
struct clk_hw *mux_hw = composite->mux_hw;
__clk_hw_set_clk(mux_hw, hw);
return mux_ops->set_parent(mux_hw, index);
}
static unsigned long clk_regmap_composite_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct clk_regmap_composite *composite = to_clk_regmap_composite(hw);
const struct clk_ops *rate_ops = composite->rate_ops;
struct clk_hw *rate_hw = composite->rate_hw;
__clk_hw_set_clk(rate_hw, hw);
return rate_ops->recalc_rate(rate_hw, parent_rate);
}
static int clk_regmap_composite_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct clk_regmap_composite *composite = to_clk_regmap_composite(hw);
const struct clk_ops *rate_ops = composite->rate_ops;
const struct clk_ops *mux_ops = composite->mux_ops;
struct clk_hw *rate_hw = composite->rate_hw;
struct clk_hw *mux_hw = composite->mux_hw;
struct clk_hw *parent;
unsigned long parent_rate;
long tmp_rate, best_rate = 0;
unsigned long rate_diff;
unsigned long best_rate_diff = ULONG_MAX;
long rate;
int i;
if (rate_hw && rate_ops && rate_ops->determine_rate) {
__clk_hw_set_clk(rate_hw, hw);
return rate_ops->determine_rate(rate_hw, req);
} else if (rate_hw && rate_ops && rate_ops->round_rate &&
mux_hw && mux_ops && mux_ops->set_parent) {
req->best_parent_hw = NULL;
if (clk_hw_get_flags(hw) & CLK_SET_RATE_NO_REPARENT) {
parent = clk_hw_get_parent(mux_hw);
req->best_parent_hw = parent;
req->best_parent_rate = clk_hw_get_rate(parent);
rate = rate_ops->round_rate(rate_hw, req->rate,
&req->best_parent_rate);
if (rate < 0)
return rate;
req->rate = rate;
return 0;
}
for (i = 0; i < clk_hw_get_num_parents(mux_hw); i++) {
parent = clk_hw_get_parent_by_index(mux_hw, i);
if (!parent)
continue;
parent_rate = clk_hw_get_rate(parent);
tmp_rate = rate_ops->round_rate(rate_hw, req->rate,
&parent_rate);
if (tmp_rate < 0)
continue;
rate_diff = abs(req->rate - tmp_rate);
if (!rate_diff || !req->best_parent_hw ||
best_rate_diff > rate_diff) {
req->best_parent_hw = parent;
req->best_parent_rate = parent_rate;
best_rate_diff = rate_diff;
best_rate = tmp_rate;
}
if (!rate_diff)
return 0;
}
req->rate = best_rate;
return 0;
} else if (mux_hw && mux_ops && mux_ops->determine_rate) {
__clk_hw_set_clk(mux_hw, hw);
return mux_ops->determine_rate(mux_hw, req);
} else {
return -EINVAL;
}
return 0;
}
static long clk_regmap_composite_round_rate(struct clk_hw *hw,
unsigned long rate,
unsigned long *prate)
{
struct clk_regmap_composite *composite = to_clk_regmap_composite(hw);
const struct clk_ops *rate_ops = composite->rate_ops;
struct clk_hw *rate_hw = composite->rate_hw;
__clk_hw_set_clk(rate_hw, hw);
return rate_ops->round_rate(rate_hw, rate, prate);
}
static int clk_regmap_composite_set_rate(struct clk_hw *hw,
unsigned long rate,
unsigned long parent_rate)
{
struct clk_regmap_composite *composite = to_clk_regmap_composite(hw);
const struct clk_ops *rate_ops = composite->rate_ops;
struct clk_hw *rate_hw = composite->rate_hw;
__clk_hw_set_clk(rate_hw, hw);
return rate_ops->set_rate(rate_hw, rate, parent_rate);
}
static int clk_regmap_composite_is_prepared(struct clk_hw *hw)
{
struct clk_regmap_composite *composite = to_clk_regmap_composite(hw);
const struct clk_ops *gate_ops = composite->gate_ops;
struct clk_hw *gate_hw = composite->gate_hw;
__clk_hw_set_clk(gate_hw, hw);
return gate_ops->is_prepared(gate_hw);
}
static int clk_regmap_composite_prepare(struct clk_hw *hw)
{
struct clk_regmap_composite *composite = to_clk_regmap_composite(hw);
const struct clk_ops *gate_ops = composite->gate_ops;
struct clk_hw *gate_hw = composite->gate_hw;
__clk_hw_set_clk(gate_hw, hw);
return gate_ops->prepare(gate_hw);
}
static void clk_regmap_composite_unprepare(struct clk_hw *hw)
{
struct clk_regmap_composite *composite = to_clk_regmap_composite(hw);
const struct clk_ops *gate_ops = composite->gate_ops;
struct clk_hw *gate_hw = composite->gate_hw;
__clk_hw_set_clk(gate_hw, hw);
gate_ops->unprepare(gate_hw);
}
struct clk *
devm_clk_regmap_register_composite(struct device *dev, const char *name,
const char *const *parent_names,
u8 num_parents, struct regmap *regmap,
u32 mux_reg, u8 mux_shift, u8 mux_width,
u32 div_reg, u8 div_shift, u8 div_width,
u32 gate_reg, u8 gate_shift,
unsigned long flags)
{
struct clk_regmap_gate *gate = NULL;
struct clk_regmap_mux *mux = NULL;
struct clk_regmap_divider *div = NULL;
const struct clk_ops *mux_ops = NULL, *div_ops = NULL, *gate_ops = NULL;
struct clk_hw *mux_hw = NULL, *div_hw = NULL, *gate_hw = NULL;
struct clk *clk;
struct clk_init_data init;
struct clk_regmap_composite *composite;
struct clk_ops *clk_composite_ops;
if (num_parents > 1) {
mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
if (!mux)
return ERR_PTR(-ENOMEM);
mux->dev = dev;
mux->regmap = regmap;
mux->reg = mux_reg;
mux->shift = mux_shift;
mux->mask = BIT(mux_width) - 1;
mux_ops = &clk_regmap_mux_ops;
mux_hw = &mux->hw;
}
if (gate_reg > 0) {
gate = devm_kzalloc(dev, sizeof(*gate), GFP_KERNEL);
if (!gate)
return ERR_PTR(-ENOMEM);
gate->dev = dev;
gate->regmap = regmap;
gate->reg = gate_reg;
gate->shift = gate_shift;
gate_ops = &clk_regmap_gate_ops;
gate_hw = &gate->hw;
}
if (div_width > 0) {
div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
if (!div)
return ERR_PTR(-ENOMEM);
div->dev = dev;
div->regmap = regmap;
div->reg = div_reg;
div->shift = div_shift;
div->width = div_width;
div_ops = &clk_regmap_divider_ops;
div_hw = &div->hw;
}
composite = devm_kzalloc(dev, sizeof(*composite), GFP_KERNEL);
if (!composite)
return ERR_PTR(-ENOMEM);
init.name = name;
init.flags = flags;
init.parent_names = parent_names;
init.num_parents = num_parents;
clk_composite_ops = &composite->ops;
if (mux_hw && mux_ops) {
if (!mux_ops->get_parent)
return ERR_PTR(-EINVAL);
composite->mux_hw = mux_hw;
composite->mux_ops = mux_ops;
clk_composite_ops->get_parent =
clk_regmap_composite_get_parent;
if (mux_ops->set_parent)
clk_composite_ops->set_parent =
clk_regmap_composite_set_parent;
if (mux_ops->determine_rate)
clk_composite_ops->determine_rate =
clk_regmap_composite_determine_rate;
}
if (div_hw && div_ops) {
if (!div_ops->recalc_rate)
return ERR_PTR(-EINVAL);
clk_composite_ops->recalc_rate =
clk_regmap_composite_recalc_rate;
if (div_ops->determine_rate)
clk_composite_ops->determine_rate =
clk_regmap_composite_determine_rate;
else if (div_ops->round_rate)
clk_composite_ops->round_rate =
clk_regmap_composite_round_rate;
/* .set_rate requires either .round_rate or .determine_rate */
if (div_ops->set_rate) {
if (div_ops->determine_rate || div_ops->round_rate)
clk_composite_ops->set_rate =
clk_regmap_composite_set_rate;
else
WARN(1, "missing round_rate op\n");
}
composite->rate_hw = div_hw;
composite->rate_ops = div_ops;
}
if (gate_hw && gate_ops) {
if (!gate_ops->is_prepared || !gate_ops->prepare ||
!gate_ops->unprepare)
return ERR_PTR(-EINVAL);
composite->gate_hw = gate_hw;
composite->gate_ops = gate_ops;
clk_composite_ops->is_prepared =
clk_regmap_composite_is_prepared;
clk_composite_ops->prepare = clk_regmap_composite_prepare;
clk_composite_ops->unprepare = clk_regmap_composite_unprepare;
}
init.ops = clk_composite_ops;
composite->dev = dev;
composite->hw.init = &init;
clk = devm_clk_register(dev, &composite->hw);
if (IS_ERR(clk))
return clk;
if (composite->mux_hw)
composite->mux_hw->clk = clk;
if (composite->rate_hw)
composite->rate_hw->clk = clk;
if (composite->gate_hw)
composite->gate_hw->clk = clk;
return clk;
}
EXPORT_SYMBOL_GPL(devm_clk_regmap_register_composite);

View File

@@ -0,0 +1,104 @@
/*
* Copyright (c) 2017 Rockchip Electronics Co. Ltd.
*
* Base on code in drivers/clk/clk-divider.c.
* See clk-divider.c for further copyright information.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This 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 "clk-regmap.h"
#define div_mask(width) ((1 << (width)) - 1)
#define to_clk_regmap_divider(_hw) \
container_of(_hw, struct clk_regmap_divider, hw)
static unsigned long
clk_regmap_divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
{
struct clk_regmap_divider *divider = to_clk_regmap_divider(hw);
unsigned int val, div;
regmap_read(divider->regmap, divider->reg, &val);
div = val >> divider->shift;
div &= div_mask(divider->width);
return divider_recalc_rate(hw, parent_rate, div, NULL,
CLK_DIVIDER_ROUND_CLOSEST);
}
static long
clk_regmap_divider_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
struct clk_regmap_divider *divider = to_clk_regmap_divider(hw);
return divider_round_rate(hw, rate, prate, NULL, divider->width,
CLK_DIVIDER_ROUND_CLOSEST);
}
static int
clk_regmap_divider_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_regmap_divider *divider = to_clk_regmap_divider(hw);
u32 val, div;
div = divider_get_val(rate, parent_rate, NULL, divider->width,
CLK_DIVIDER_ROUND_CLOSEST);
dev_dbg(divider->dev, "%s: parent_rate=%ld, div=%d, rate=%ld\n",
clk_hw_get_name(hw), parent_rate, div, rate);
val = div_mask(divider->width) << (divider->shift + 16);
val |= div << divider->shift;
return regmap_write(divider->regmap, divider->reg, val);
}
const struct clk_ops clk_regmap_divider_ops = {
.recalc_rate = clk_regmap_divider_recalc_rate,
.round_rate = clk_regmap_divider_round_rate,
.set_rate = clk_regmap_divider_set_rate,
};
EXPORT_SYMBOL_GPL(clk_regmap_divider_ops);
struct clk *
devm_clk_regmap_register_divider(struct device *dev, const char *name,
const char *parent_name, struct regmap *regmap,
u32 reg, u8 shift, u8 width,
unsigned long flags)
{
struct clk_regmap_divider *divider;
struct clk_init_data init;
divider = devm_kzalloc(dev, sizeof(*divider), GFP_KERNEL);
if (!divider)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &clk_regmap_divider_ops;
init.flags = flags;
init.parent_names = (parent_name ? &parent_name : NULL);
init.num_parents = (parent_name ? 1 : 0);
divider->dev = dev;
divider->regmap = regmap;
divider->reg = reg;
divider->shift = shift;
divider->width = width;
divider->hw.init = &init;
return devm_clk_register(dev, &divider->hw);
}
EXPORT_SYMBOL_GPL(devm_clk_regmap_register_divider);

View File

@@ -0,0 +1,82 @@
/*
* Copyright (c) 2017 Rockchip Electronics Co. Ltd.
*
* Base on code in drivers/clk/clk-gate.c.
* See clk-gate.c for further copyright information.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This 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 "clk-regmap.h"
#define to_clk_regmap_gate(_hw) container_of(_hw, struct clk_regmap_gate, hw)
static int clk_regmap_gate_prepare(struct clk_hw *hw)
{
struct clk_regmap_gate *gate = to_clk_regmap_gate(hw);
return regmap_write(gate->regmap, gate->reg,
0 | BIT(gate->shift + 16));
}
static void clk_regmap_gate_unprepare(struct clk_hw *hw)
{
struct clk_regmap_gate *gate = to_clk_regmap_gate(hw);
regmap_write(gate->regmap, gate->reg,
BIT(gate->shift) | BIT(gate->shift + 16));
}
static int clk_regmap_gate_is_prepared(struct clk_hw *hw)
{
struct clk_regmap_gate *gate = to_clk_regmap_gate(hw);
u32 val;
regmap_read(gate->regmap, gate->reg, &val);
return !(val & BIT(gate->shift));
}
const struct clk_ops clk_regmap_gate_ops = {
.prepare = clk_regmap_gate_prepare,
.unprepare = clk_regmap_gate_unprepare,
.is_prepared = clk_regmap_gate_is_prepared,
};
EXPORT_SYMBOL_GPL(clk_regmap_gate_ops);
struct clk *
devm_clk_regmap_register_gate(struct device *dev, const char *name,
const char *parent_name,
struct regmap *regmap, u32 reg, u8 shift,
unsigned long flags)
{
struct clk_regmap_gate *gate;
struct clk_init_data init;
gate = devm_kzalloc(dev, sizeof(*gate), GFP_KERNEL);
if (!gate)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &clk_regmap_gate_ops;
init.flags = flags;
init.parent_names = (parent_name ? &parent_name : NULL);
init.num_parents = (parent_name ? 1 : 0);
gate->dev = dev;
gate->regmap = regmap;
gate->reg = reg;
gate->shift = shift;
gate->hw.init = &init;
return devm_clk_register(dev, &gate->hw);
}
EXPORT_SYMBOL_GPL(devm_clk_regmap_register_gate);

View File

@@ -0,0 +1,79 @@
/*
* Copyright (c) 2017 Rockchip Electronics Co. Ltd.
*
* Base on code in drivers/clk/clk-mux.c.
* See clk-mux.c for further copyright information.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This 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 "clk-regmap.h"
#define to_clk_regmap_mux(_hw) container_of(_hw, struct clk_regmap_mux, hw)
static u8 clk_regmap_mux_get_parent(struct clk_hw *hw)
{
struct clk_regmap_mux *mux = to_clk_regmap_mux(hw);
u8 index;
u32 val;
regmap_read(mux->regmap, mux->reg, &val);
index = val >> mux->shift;
index &= mux->mask;
return index;
}
static int clk_regmap_mux_set_parent(struct clk_hw *hw, u8 index)
{
struct clk_regmap_mux *mux = to_clk_regmap_mux(hw);
return regmap_write(mux->regmap, mux->reg, (index << mux->shift) |
(mux->mask << (mux->shift + 16)));
}
const struct clk_ops clk_regmap_mux_ops = {
.set_parent = clk_regmap_mux_set_parent,
.get_parent = clk_regmap_mux_get_parent,
.determine_rate = __clk_mux_determine_rate,
};
EXPORT_SYMBOL_GPL(clk_regmap_mux_ops);
struct clk *
devm_clk_regmap_register_mux(struct device *dev, const char *name,
const char * const *parent_names, u8 num_parents,
struct regmap *regmap, u32 reg, u8 shift, u8 width,
unsigned long flags)
{
struct clk_regmap_mux *mux;
struct clk_init_data init;
mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
if (!mux)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &clk_regmap_mux_ops;
init.flags = flags;
init.parent_names = parent_names;
init.num_parents = num_parents;
mux->dev = dev;
mux->regmap = regmap;
mux->reg = reg;
mux->shift = shift;
mux->mask = BIT(width) - 1;
mux->hw.init = &init;
return devm_clk_register(dev, &mux->hw);
}
EXPORT_SYMBOL_GPL(devm_clk_regmap_register_mux);

View File

@@ -0,0 +1,341 @@
/*
* Copyright (c) 2017 Rockchip Electronics Co. Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This 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 "clk-regmap.h"
#define PLLCON_OFFSET(x) (x * 4)
#define PLL_BYPASS BIT(15)
#define PLL_POSTDIV1(x) HIWORD_UPDATE(x, 14, 12)
#define PLL_POSTDIV1_MASK GENMASK(14, 12)
#define PLL_POSTDIV1_SHIFT 12
#define PLL_FBDIV(x) HIWORD_UPDATE(x, 11, 0)
#define PLL_FBDIV_MASK GENMASK(11, 0)
#define PLL_FBDIV_SHIFT 0
#define PLL_LOCK BIT(15)
#define PLL_POWER_DOWN HIWORD_UPDATE(1, 10, 10)
#define PLL_POWER_UP HIWORD_UPDATE(0, 10, 10)
#define PLL_DSMPD_MASK BIT(9)
#define PLL_DSMPD_SHIFT 9
#define PLL_DSMPD(x) HIWORD_UPDATE(x, 9, 9)
#define PLL_POSTDIV2(x) HIWORD_UPDATE(x, 8, 6)
#define PLL_POSTDIV2_MASK GENMASK(8, 6)
#define PLL_POSTDIV2_SHIFT 6
#define PLL_REFDIV(x) HIWORD_UPDATE(x, 5, 0)
#define PLL_REFDIV_MASK GENMASK(5, 0)
#define PLL_REFDIV_SHIFT 0
#define PLL_FOUT_4PHASE_CLK_POWER_DOWN BIT(27)
#define PLL_FOUT_VCO_CLK_POWER_DOWN BIT(26)
#define PLL_FOUT_POST_DIV_POWER_DOWN BIT(25)
#define PLL_DAC_POWER_DOWN BIT(24)
#define PLL_FRAC(x) UPDATE(x, 23, 0)
#define PLL_FRAC_MASK GENMASK(23, 0)
#define PLL_FRAC_SHIFT 0
#define MIN_FREF_RATE 10000000UL
#define MAX_FREF_RATE 800000000UL
#define MIN_FREFDIV_RATE 1000000UL
#define MAX_FREFDIV_RATE 40000000UL
#define MIN_FVCO_RATE 400000000UL
#define MAX_FVCO_RATE 1600000000UL
#define MIN_FOUTPOSTDIV_RATE 8000000UL
#define MAX_FOUTPOSTDIV_RATE 1600000000UL
struct clk_regmap_pll {
struct clk_hw hw;
struct device *dev;
struct regmap *regmap;
unsigned int reg;
};
#define to_clk_regmap_pll(_hw) container_of(_hw, struct clk_regmap_pll, hw)
static unsigned long
clk_regmap_pll_recalc_rate(struct clk_hw *hw, unsigned long prate)
{
struct clk_regmap_pll *pll = to_clk_regmap_pll(hw);
unsigned int postdiv1, fbdiv, dsmpd, postdiv2, refdiv, frac;
unsigned int con0, con1, con2;
u64 foutvco, foutpostdiv;
regmap_read(pll->regmap, pll->reg + PLLCON_OFFSET(0), &con0);
regmap_read(pll->regmap, pll->reg + PLLCON_OFFSET(1), &con1);
regmap_read(pll->regmap, pll->reg + PLLCON_OFFSET(2), &con2);
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;
foutvco = prate * fbdiv;
do_div(foutvco, refdiv);
if (!dsmpd) {
u64 frac_rate = prate * 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 clk_pll_round_rate(unsigned long fin,
unsigned long fout,
u8 *refdiv, u16 *fbdiv,
u8 *postdiv1, u8 *postdiv2,
u32 *frac, u8 *dsmpd)
{
u8 min_refdiv, max_refdiv, postdiv;
u8 _dsmpd = 1, _postdiv1 = 0, _postdiv2 = 0, _refdiv = 0;
u16 _fbdiv = 0;
u32 _frac = 0;
u64 foutvco, foutpostdiv;
/*
* FREF : 10MHz ~ 800MHz
* FREFDIV : 1MHz ~ 40MHz
* FOUTVCO : 400MHz ~ 1.6GHz
* FOUTPOSTDIV : 8MHz ~ 1.6GHz
*/
if (fin < MIN_FREF_RATE || fin > MAX_FREF_RATE)
return -EINVAL;
if (fout < MIN_FOUTPOSTDIV_RATE || fout > MAX_FOUTPOSTDIV_RATE)
return -EINVAL;
min_refdiv = DIV_ROUND_UP(fin, MAX_FREFDIV_RATE);
max_refdiv = fin / MIN_FREFDIV_RATE;
if (max_refdiv > 64)
max_refdiv = 64;
if (fout < MIN_FVCO_RATE) {
postdiv = DIV_ROUND_UP_ULL(MIN_FVCO_RATE, fout);
for (_postdiv2 = 1; _postdiv2 < 8; _postdiv2++) {
if (postdiv % _postdiv2)
continue;
_postdiv1 = postdiv / _postdiv2;
if (_postdiv1 > 0 && _postdiv1 < 8)
break;
}
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 = fin * _frac;
do_div(frac_rate, _refdiv);
foutvco += frac_rate >> 24;
}
foutpostdiv = foutvco;
do_div(foutpostdiv, _postdiv1);
do_div(foutpostdiv, _postdiv2);
if (refdiv)
*refdiv = _refdiv;
if (fbdiv)
*fbdiv = _fbdiv;
if (postdiv1)
*postdiv1 = _postdiv1;
if (postdiv2)
*postdiv2 = _postdiv2;
if (frac)
*frac = _frac;
if (dsmpd)
*dsmpd = _dsmpd;
return (unsigned long)foutpostdiv;
}
static long
clk_regmap_pll_round_rate(struct clk_hw *hw, unsigned long drate,
unsigned long *prate)
{
struct clk_regmap_pll *pll = to_clk_regmap_pll(hw);
long rate;
rate = clk_pll_round_rate(*prate, drate, NULL, NULL, NULL, NULL,
NULL, NULL);
dev_dbg(pll->dev, "%s: prate=%ld, drate=%ld, rate=%ld\n",
clk_hw_get_name(hw), *prate, drate, rate);
return rate;
}
static int
clk_regmap_pll_set_rate(struct clk_hw *hw, unsigned long drate,
unsigned long prate)
{
struct clk_regmap_pll *pll = to_clk_regmap_pll(hw);
u8 refdiv, postdiv1, postdiv2, dsmpd;
u16 fbdiv;
u32 frac;
u32 v;
int ret;
ret = clk_pll_round_rate(prate, drate, &refdiv, &fbdiv, &postdiv1,
&postdiv2, &frac, &dsmpd);
if (ret < 0)
return ret;
/* When changing PLL setting, we must force PLL into power down mode. */
regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(1), PLL_POWER_DOWN);
regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(0),
PLL_POSTDIV1(postdiv1) | PLL_FBDIV(fbdiv));
regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(1),
PLL_DSMPD(dsmpd) | PLL_POSTDIV2(postdiv2) |
PLL_REFDIV(refdiv));
regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(2), PLL_FRAC(frac));
regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(1), PLL_POWER_UP);
ret = regmap_read_poll_timeout(pll->regmap,
pll->reg + PLLCON_OFFSET(1),
v, v & PLL_LOCK, 50, 50000);
if (ret)
dev_err(pll->dev, "PLL is not lock\n");
return 0;
}
static int clk_regmap_pll_prepare(struct clk_hw *hw)
{
struct clk_regmap_pll *pll = to_clk_regmap_pll(hw);
u32 v;
int ret;
regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(1), PLL_POWER_UP);
ret = regmap_read_poll_timeout(pll->regmap,
pll->reg + PLLCON_OFFSET(1),
v, v & PLL_LOCK, 50, 50000);
if (ret)
dev_err(pll->dev, "PLL is not lock\n");
return 0;
}
static void clk_regmap_pll_unprepare(struct clk_hw *hw)
{
struct clk_regmap_pll *pll = to_clk_regmap_pll(hw);
regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(1), PLL_POWER_DOWN);
}
static int clk_regmap_pll_is_prepared(struct clk_hw *hw)
{
struct clk_regmap_pll *pll = to_clk_regmap_pll(hw);
unsigned int con1;
regmap_read(pll->regmap, pll->reg + PLLCON_OFFSET(1), &con1);
return !(con1 & PLL_POWER_DOWN);
}
static const struct clk_ops clk_regmap_pll_ops = {
.recalc_rate = clk_regmap_pll_recalc_rate,
.round_rate = clk_regmap_pll_round_rate,
.set_rate = clk_regmap_pll_set_rate,
.prepare = clk_regmap_pll_prepare,
.unprepare = clk_regmap_pll_unprepare,
.is_prepared = clk_regmap_pll_is_prepared,
};
struct clk *
devm_clk_regmap_register_pll(struct device *dev, const char *name,
const char *parent_name,
struct regmap *regmap, u32 reg,
unsigned long flags)
{
struct clk_regmap_pll *pll;
struct clk_init_data init;
pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL);
if (!pll)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &clk_regmap_pll_ops;
init.flags = flags;
init.parent_names = (parent_name ? &parent_name : NULL);
init.num_parents = (parent_name ? 1 : 0);
pll->dev = dev;
pll->regmap = regmap;
pll->reg = reg;
pll->hw.init = &init;
return devm_clk_register(dev, &pll->hw);
}
EXPORT_SYMBOL_GPL(devm_clk_regmap_register_pll);

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2017 Rockchip Electronics Co. Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This 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.
*/
#ifndef __CLK_REGMAP_H__
#define __CLK_REGMAP_H__
#include <linux/regmap.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk-provider.h>
#include <linux/device.h>
#include <linux/delay.h>
#define UPDATE(x, h, l) (((x) << (l)) & GENMASK((h), (l)))
#define HIWORD_UPDATE(v, h, l) (((v) << (l)) | (GENMASK((h), (l)) << 16))
struct clk_regmap_divider {
struct clk_hw hw;
struct device *dev;
struct regmap *regmap;
u32 reg;
u8 shift;
u8 width;
};
struct clk_regmap_gate {
struct clk_hw hw;
struct device *dev;
struct regmap *regmap;
u32 reg;
u8 shift;
};
struct clk_regmap_mux {
struct clk_hw hw;
struct device *dev;
struct regmap *regmap;
u32 reg;
u32 mask;
u8 shift;
};
extern const struct clk_ops clk_regmap_mux_ops;
extern const struct clk_ops clk_regmap_divider_ops;
extern const struct clk_ops clk_regmap_gate_ops;
struct clk *
devm_clk_regmap_register_pll(struct device *dev, const char *name,
const char *parent_name,
struct regmap *regmap, u32 reg,
unsigned long flags);
struct clk *
devm_clk_regmap_register_mux(struct device *dev, const char *name,
const char * const *parent_names, u8 num_parents,
struct regmap *regmap, u32 reg, u8 shift, u8 width,
unsigned long flags);
struct clk *
devm_clk_regmap_register_divider(struct device *dev, const char *name,
const char *parent_name, struct regmap *regmap,
u32 reg, u8 shift, u8 width,
unsigned long flags);
struct clk *
devm_clk_regmap_register_gate(struct device *dev, const char *name,
const char *parent_name,
struct regmap *regmap, u32 reg, u8 shift,
unsigned long flags);
struct clk *
devm_clk_regmap_register_composite(struct device *dev, const char *name,
const char *const *parent_names,
u8 num_parents, struct regmap *regmap,
u32 mux_reg, u8 mux_shift, u8 mux_width,
u32 div_reg, u8 div_shift, u8 div_width,
u32 gate_reg, u8 gate_shift,
unsigned long flags);
#endif

View File

@@ -0,0 +1,532 @@
/*
* Copyright (c) 2017 Rockchip Electronics Co. Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This 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.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk-provider.h>
#include <linux/mfd/rk618.h>
#include "clk-regmap.h"
#define RK618_CRU_CLKSEL0 0x0058
#define RK618_CRU_CLKSEL1 0x005c
#define RK618_CRU_CLKSEL2 0x0060
#define RK618_CRU_CLKSEL3 0x0064
#define RK618_CRU_PLL0_CON0 0x0068
#define RK618_CRU_PLL0_CON1 0x006c
#define RK618_CRU_PLL0_CON2 0x0070
#define RK618_CRU_PLL1_CON0 0x0074
#define RK618_CRU_PLL1_CON1 0x0078
#define RK618_CRU_PLL1_CON2 0x007c
struct clk_pll_data {
unsigned int id;
const char *name;
const char *parent_name;
u32 reg;
unsigned long flags;
};
#define PLL(_id, _name, _parent_name, _reg, _flags) \
{ \
.id = _id, \
.name = _name, \
.parent_name = _parent_name, \
.reg = _reg, \
.flags = _flags, \
}
struct clk_mux_data {
unsigned int id;
const char *name;
const char *const *parent_names;
u8 num_parents;
u32 reg;
u8 shift;
u8 width;
unsigned long flags;
};
#define MUX(_id, _name, _parent_names, _reg, _shift, _width, _flags) \
{ \
.id = _id, \
.name = _name, \
.parent_names = _parent_names, \
.num_parents = ARRAY_SIZE(_parent_names), \
.reg = _reg, \
.shift = _shift, \
.width = _width, \
.flags = _flags, \
}
struct clk_gate_data {
unsigned int id;
const char *name;
const char *parent_name;
u32 reg;
u8 shift;
unsigned long flags;
};
#define GATE(_id, _name, _parent_name, _reg, _shift, _flags) \
{ \
.id = _id, \
.name = _name, \
.parent_name = _parent_name, \
.reg = _reg, \
.shift = _shift, \
.flags = _flags, \
}
struct clk_divider_data {
unsigned int id;
const char *name;
const char *parent_name;
u32 reg;
u8 shift;
u8 width;
unsigned long flags;
};
#define DIV(_id, _name, _parent_name, _reg, _shift, _width, _flags) \
{ \
.id = _id, \
.name = _name, \
.parent_name = _parent_name, \
.reg = _reg, \
.shift = _shift, \
.width = _width, \
.flags = _flags, \
}
struct clk_composite_data {
unsigned int id;
const char *name;
const char *const *parent_names;
u8 num_parents;
u32 mux_reg;
u8 mux_shift;
u8 mux_width;
u32 div_reg;
u8 div_shift;
u8 div_width;
u32 gate_reg;
u8 gate_shift;
unsigned long flags;
};
#define COMPOSITE(_id, _name, _parent_names, \
_mux_reg, _mux_shift, _mux_width, \
_div_reg, _div_shift, _div_width, \
_gate_reg, _gate_shift, _flags) \
{ \
.id = _id, \
.name = _name, \
.parent_names = _parent_names, \
.num_parents = ARRAY_SIZE(_parent_names), \
.mux_reg = _mux_reg, \
.mux_shift = _mux_shift, \
.mux_width = _mux_width, \
.div_reg = _div_reg, \
.div_shift = _div_shift, \
.div_width = _div_width, \
.gate_reg = _gate_reg, \
.gate_shift = _gate_shift, \
.flags = _flags, \
}
#define COMPOSITE_NODIV(_id, _name, _parent_names, \
_mux_reg, _mux_shift, _mux_width, \
_gate_reg, _gate_shift, _flags) \
{ \
.id = _id, \
.name = _name, \
.parent_names = _parent_names, \
.num_parents = ARRAY_SIZE(_parent_names), \
.mux_reg = _mux_reg, \
.mux_shift = _mux_shift, \
.mux_width = _mux_width, \
.gate_reg = _gate_reg, \
.gate_shift = _gate_shift, \
.flags = _flags, \
}
enum {
LCDC0_CLK = 1,
LCDC1_CLK,
VIF_PLLIN_CLK,
SCALER_PLLIN_CLK,
VIF_PLL_CLK,
SCALER_PLL_CLK,
VIF0_CLK,
VIF1_CLK,
SCALER_IN_CLK,
SCALER_CLK,
DITHER_CLK,
HDMI_CLK,
MIPI_CLK,
LVDS_CLK,
LVTTL_CLK,
RGB_CLK,
VIF0_PRE_CLK,
VIF1_PRE_CLK,
CODEC_CLK,
NR_CLKS,
};
struct rk618_cru {
struct device *dev;
struct rk618 *parent;
struct regmap *regmap;
struct clk_onecell_data clk_data;
};
static char clkin_name[16] = "dummy";
static char lcdc0_dclkp_name[16] = "dummy";
static char lcdc1_dclkp_name[16] = "dummy";
#define PNAME(x) static const char *const x[]
PNAME(mux_pll_in_p) = { "lcdc0_clk", "lcdc1_clk", clkin_name };
PNAME(mux_pll_src_p) = { "vif_pll_clk", "scaler_pll_clk", };
PNAME(mux_scaler_in_src_p) = { "vif0_clk", "vif1_clk" };
PNAME(mux_hdmi_src_p) = { "vif1_clk", "scaler_clk", "vif0_clk" };
PNAME(mux_dither_src_p) = { "vif0_clk", "scaler_clk" };
PNAME(mux_vif0_src_p) = { "vif0_pre_clk", lcdc0_dclkp_name };
PNAME(mux_vif1_src_p) = { "vif1_pre_clk", lcdc1_dclkp_name };
PNAME(mux_codec_src_p) = { "vif_pll_clk", "scaler_pll_clk", clkin_name };
/* Two PLL, one for dual datarate input logic, the other for scaler */
static const struct clk_pll_data rk618_clk_plls[] = {
PLL(VIF_PLL_CLK, "vif_pll_clk", "vif_pllin_clk",
RK618_CRU_PLL0_CON0,
0),
PLL(SCALER_PLL_CLK, "scaler_pll_clk", "scaler_pllin_clk",
RK618_CRU_PLL1_CON0,
0),
};
static const struct clk_mux_data rk618_clk_muxes[] = {
MUX(VIF_PLLIN_CLK, "vif_pllin_clk", mux_pll_in_p,
RK618_CRU_CLKSEL0, 6, 2,
0),
MUX(SCALER_PLLIN_CLK, "scaler_pllin_clk", mux_pll_in_p,
RK618_CRU_CLKSEL0, 8, 2,
0),
MUX(SCALER_IN_CLK, "scaler_in_clk", mux_scaler_in_src_p,
RK618_CRU_CLKSEL3, 15, 1,
0),
MUX(DITHER_CLK, "dither_clk", mux_dither_src_p,
RK618_CRU_CLKSEL3, 14, 1,
0),
MUX(VIF0_CLK, "vif0_clk", mux_vif0_src_p,
RK618_CRU_CLKSEL3, 1, 1,
CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
MUX(VIF1_CLK, "vif1_clk", mux_vif1_src_p,
RK618_CRU_CLKSEL3, 7, 1,
CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
};
static const struct clk_divider_data rk618_clk_dividers[] = {
DIV(LCDC0_CLK, "lcdc0_clk", lcdc0_dclkp_name,
RK618_CRU_CLKSEL0, 0, 3,
0),
DIV(LCDC1_CLK, "lcdc1_clk", lcdc1_dclkp_name,
RK618_CRU_CLKSEL0, 3, 3,
0),
};
static const struct clk_gate_data rk618_clk_gates[] = {
GATE(MIPI_CLK, "mipi_clk", "dither_clk",
RK618_CRU_CLKSEL1, 10,
0),
GATE(LVDS_CLK, "lvds_clk", "dither_clk",
RK618_CRU_CLKSEL1, 9,
0),
GATE(LVTTL_CLK, "lvttl_clk", "dither_clk",
RK618_CRU_CLKSEL1, 12,
0),
GATE(RGB_CLK, "rgb_clk", "dither_clk",
RK618_CRU_CLKSEL1, 11,
0),
};
static const struct clk_composite_data rk618_clk_composites[] = {
COMPOSITE(SCALER_CLK, "scaler_clk", mux_pll_src_p,
RK618_CRU_CLKSEL1, 3, 1,
RK618_CRU_CLKSEL1, 5, 3,
RK618_CRU_CLKSEL1, 4,
CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
COMPOSITE_NODIV(HDMI_CLK, "hdmi_clk", mux_hdmi_src_p,
RK618_CRU_CLKSEL3, 12, 2,
RK618_CRU_CLKSEL1, 8,
0),
COMPOSITE(VIF0_PRE_CLK, "vif0_pre_clk", mux_pll_src_p,
RK618_CRU_CLKSEL3, 0, 1,
RK618_CRU_CLKSEL3, 3, 3,
RK618_CRU_CLKSEL3, 2,
CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
COMPOSITE(VIF1_PRE_CLK, "vif1_pre_clk", mux_pll_src_p,
RK618_CRU_CLKSEL3, 6, 1,
RK618_CRU_CLKSEL3, 9, 3,
RK618_CRU_CLKSEL3, 8,
CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
COMPOSITE_NODIV(CODEC_CLK, "codec_clk", mux_codec_src_p,
RK618_CRU_CLKSEL1, 0, 2,
RK618_CRU_CLKSEL1, 2,
0),
};
static void rk618_clk_add_lookup(struct rk618_cru *cru, struct clk *clk,
unsigned int id)
{
if (cru->clk_data.clks && id)
cru->clk_data.clks[id] = clk;
}
static void rk618_clk_register_muxes(struct rk618_cru *cru)
{
struct clk *clk;
int i;
for (i = 0; i < ARRAY_SIZE(rk618_clk_muxes); i++) {
const struct clk_mux_data *data = &rk618_clk_muxes[i];
clk = devm_clk_regmap_register_mux(cru->dev, data->name,
data->parent_names,
data->num_parents,
cru->regmap, data->reg,
data->shift, data->width,
data->flags);
if (IS_ERR(clk)) {
dev_err(cru->dev, "failed to register clock %s\n",
data->name);
continue;
}
rk618_clk_add_lookup(cru, clk, data->id);
}
}
static void rk618_clk_register_dividers(struct rk618_cru *cru)
{
struct clk *clk;
int i;
for (i = 0; i < ARRAY_SIZE(rk618_clk_dividers); i++) {
const struct clk_divider_data *data = &rk618_clk_dividers[i];
clk = devm_clk_regmap_register_divider(cru->dev, data->name,
data->parent_name,
cru->regmap, data->reg,
data->shift, data->width,
data->flags);
if (IS_ERR(clk)) {
dev_err(cru->dev, "failed to register clock %s\n",
data->name);
continue;
}
rk618_clk_add_lookup(cru, clk, data->id);
}
}
static void rk618_clk_register_gates(struct rk618_cru *cru)
{
struct clk *clk;
int i;
for (i = 0; i < ARRAY_SIZE(rk618_clk_gates); i++) {
const struct clk_gate_data *data = &rk618_clk_gates[i];
clk = devm_clk_regmap_register_gate(cru->dev, data->name,
data->parent_name,
cru->regmap,
data->reg, data->shift,
data->flags);
if (IS_ERR(clk)) {
dev_err(cru->dev, "failed to register clock %s\n",
data->name);
continue;
}
rk618_clk_add_lookup(cru, clk, data->id);
}
}
static void rk618_clk_register_composites(struct rk618_cru *cru)
{
struct clk *clk;
int i;
for (i = 0; i < ARRAY_SIZE(rk618_clk_composites); i++) {
const struct clk_composite_data *data =
&rk618_clk_composites[i];
clk = devm_clk_regmap_register_composite(cru->dev, data->name,
data->parent_names,
data->num_parents,
cru->regmap,
data->mux_reg,
data->mux_shift,
data->mux_width,
data->div_reg,
data->div_shift,
data->div_width,
data->gate_reg,
data->gate_shift,
data->flags);
if (IS_ERR(clk)) {
dev_err(cru->dev, "failed to register clock %s\n",
data->name);
continue;
}
rk618_clk_add_lookup(cru, clk, data->id);
}
}
static void rk618_clk_register_plls(struct rk618_cru *cru)
{
struct clk *clk;
int i;
for (i = 0; i < ARRAY_SIZE(rk618_clk_plls); i++) {
const struct clk_pll_data *data = &rk618_clk_plls[i];
clk = devm_clk_regmap_register_pll(cru->dev, data->name,
data->parent_name,
cru->regmap, data->reg,
data->flags);
if (IS_ERR(clk)) {
dev_err(cru->dev, "failed to register clock %s\n",
data->name);
continue;
}
rk618_clk_add_lookup(cru, clk, data->id);
}
}
static int rk618_cru_probe(struct platform_device *pdev)
{
struct rk618 *rk618 = dev_get_drvdata(pdev->dev.parent);
struct device *dev = &pdev->dev;
struct rk618_cru *cru;
struct clk **clk_table;
const char *parent_name;
struct clk *clk;
int ret, i;
if (!of_device_is_available(dev->of_node))
return -ENODEV;
cru = devm_kzalloc(dev, sizeof(*cru), GFP_KERNEL);
if (!cru)
return -ENOMEM;
clk_table = devm_kcalloc(dev, NR_CLKS, sizeof(struct clk *),
GFP_KERNEL);
if (!clk_table)
return -ENOMEM;
for (i = 0; i < NR_CLKS; i++)
clk_table[i] = ERR_PTR(-ENOENT);
cru->dev = dev;
cru->parent = rk618;
cru->regmap = rk618->regmap;
cru->clk_data.clks = clk_table;
cru->clk_data.clk_num = NR_CLKS;
platform_set_drvdata(pdev, cru);
clk = devm_clk_get(dev, "clkin");
if (IS_ERR(clk)) {
ret = PTR_ERR(clk);
dev_err(dev, "failed to get clkin: %d\n", ret);
return ret;
}
strlcpy(clkin_name, __clk_get_name(clk), sizeof(clkin_name));
clk = devm_clk_get(dev, "lcdc0_dclkp");
if (IS_ERR(clk)) {
if (PTR_ERR(clk) != -ENOENT) {
ret = PTR_ERR(clk);
dev_err(dev, "failed to get lcdc0_dclkp: %d\n", ret);
return ret;
}
clk = NULL;
}
parent_name = __clk_get_name(clk);
if (parent_name)
strlcpy(lcdc0_dclkp_name, parent_name,
sizeof(lcdc0_dclkp_name));
clk = devm_clk_get(dev, "lcdc1_dclkp");
if (IS_ERR(clk)) {
if (PTR_ERR(clk) != -ENOENT) {
ret = PTR_ERR(clk);
dev_err(dev, "failed to get lcdc1_dclkp: %d\n", ret);
return ret;
}
clk = NULL;
}
parent_name = __clk_get_name(clk);
if (parent_name)
strlcpy(lcdc1_dclkp_name, parent_name,
sizeof(lcdc1_dclkp_name));
rk618_clk_register_plls(cru);
rk618_clk_register_muxes(cru);
rk618_clk_register_dividers(cru);
rk618_clk_register_gates(cru);
rk618_clk_register_composites(cru);
return of_clk_add_provider(dev->of_node, of_clk_src_onecell_get,
&cru->clk_data);
}
static int rk618_cru_remove(struct platform_device *pdev)
{
of_clk_del_provider(pdev->dev.of_node);
return 0;
}
static const struct of_device_id rk618_cru_of_match[] = {
{ .compatible = "rockchip,rk618-cru", },
{},
};
MODULE_DEVICE_TABLE(of, rk618_cru_of_match);
static struct platform_driver rk618_cru_driver = {
.driver = {
.name = "rk618-cru",
.of_match_table = of_match_ptr(rk618_cru_of_match),
},
.probe = rk618_cru_probe,
.remove = rk618_cru_remove,
};
module_platform_driver(rk618_cru_driver);
MODULE_AUTHOR("Wyon Bi <bivvy.bi@rock-chips.com>");
MODULE_DESCRIPTION("Rockchip rk618 CRU driver");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2017 Rockchip Electronics Co. Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This 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.
*/
#ifndef _DT_BINDINGS_CLK_RK618_CRU_H
#define _DT_BINDINGS_CLK_RK618_CRU_H
#define LCDC0_CLK 1
#define LCDC1_CLK 2
#define VIF_PLLIN_CLK 3
#define SCALER_PLLIN_CLK 4
#define VIF_PLL_CLK 5
#define SCALER_PLL_CLK 6
#define VIF0_CLK 7
#define VIF1_CLK 8
#define SCALER_IN_CLK 9
#define SCALER_CLK 10
#define DITHER_CLK 11
#define HDMI_CLK 12
#define MIPI_CLK 13
#define LVDS_CLK 14
#define LVTTL_CLK 15
#define RGB_CLK 16
#define VIF0_PRE_CLK 17
#define VIF1_PRE_CLK 18
#define CODEC_CLK 19
#endif