CCF框架简介
Common Clock Framewrok(CCF)
此图为网上摘抄
时钟提供者(provider):
此图为网上摘抄
时钟种类
1)提供基础时钟的时钟源,时钟振荡器 (时钟输入,compatible = "fixed-clock")
2)用于倍频的锁相环(PLL) (厂商结构体)
3)用于多路选择的选择器(MUX)(struct clk_mux)
4)用于分频的分频器(DIV)(struct clk_factor,struct clk_divider, struct clk_fractional_divider)
5)用于时钟使能的门电路(GATE)(struct clk_gate)
6)用于模块的时钟(composite)//综合mux, gate, divider等功能的clock
provider注册时钟模块
1.soc cru模块设备树节点
如3568 pmucru(0xfdd00000)/pmuscru(0xfdd30000),cru(0xfdd20000)/scru(0xfdd10000)
2.fixed-clock
如soc输入时钟,设备树节点配置为compatible = "fixed-clock";
3.其他模块设备节点输出时钟
如3568设备树节点
usb2phy0: usb2-phy@fe8a0000 {
compatible = "rockchip,rk3568-usb2phy";
reg = <0x0 0xfe8a0000 0x0 0x10000>;
interrupts = <GIC_SPI 135 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&pmucru CLK_USBPHY0_REF>;
clock-names = "phyclk";
#clock-cells = <0>;
assigned-clocks = <&cru USB480M>;
assigned-clock-parents = <&usb2phy0>;
clock-output-names = "usb480m_phy";
rockchip,usbgrf = <&usb2phy0_grf>;......
驱动注册
if (rphy->clk) {
clk_name = __clk_get_name(rphy->clk);
init.parent_names = &clk_name;
init.num_parents = 1;
} else {
init.parent_names = NULL;
init.num_parents = 0;
}
rphy->clk480m_hw.init = &init;
/* register the clock */
rphy->clk480m = clk_register(rphy->dev, &rphy->clk480m_hw);
if (IS_ERR(rphy->clk480m)) {
ret = PTR_ERR(rphy->clk480m);
goto err_ret;
}
ret = of_clk_add_provider(node, of_clk_src_simple_get, rphy->clk480m);
时钟子树:
xin_osc0_usbphy0_g 1 1 0 24000000 0 0 50000
clk_usbphy0_ref 1 1 0 24000000 0 0 50000
usb480m_phy 0 0 0 480000000 0 0 50000
usb480m 0 0 0 480000000 0 0 50000
provider注册时钟使用接口
// include/linux/clk-provider.h
struct clk_hw{
struct clk_core *core;
struct clk *clk;
const struct clk_init_data *init;
}struct clk_init_data{
const char *name; //时钟名字
const struct clk_ops *ops; //时钟硬件操作函数集合
const char *const *parent_names; //父时钟名字
const struct clk_parent_data *parent_data;
const struct clk_hw **parent_hws;
u8 num_parents;
unsigned long flags;
}//include/linux/clk-provider.h
struct clk_ops {
int (*prepare)(struct clk_hw *hw);
void (*unprepare)(struct clk_hw *hw);
int (*is_prepared)(struct clk_hw *hw);
void (*unprepare_unused)(struct clk_hw *hw);
int (*enable)(struct clk_hw *hw);
void (*disable)(struct clk_hw *hw);
int (*is_enabled)(struct clk_hw *hw);
void (*disable_unused)(struct clk_hw *hw);
int (*save_context)(struct clk_hw *hw);
void (*restore_context)(struct clk_hw *hw);
unsigned long (*recalc_rate)(struct clk_hw *hw,
unsigned long parent_rate);
long (*round_rate)(struct clk_hw *hw, unsigned long rate,
unsigned long *parent_rate);
int (*determine_rate)(struct clk_hw *hw,
struct clk_rate_request *req);
int (*set_parent)(struct clk_hw *hw, u8 index);
u8 (*get_parent)(struct clk_hw *hw);
int (*set_rate)(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate);
int (*set_rate_and_parent)(struct clk_hw *hw,
unsigned long rate,
unsigned long parent_rate, u8 index);
unsigned long (*recalc_accuracy)(struct clk_hw *hw,
unsigned long parent_accuracy);
int (*get_phase)(struct clk_hw *hw);
int (*set_phase)(struct clk_hw *hw, int degrees);
int (*get_duty_cycle)(struct clk_hw *hw,
struct clk_duty *duty);
int (*set_duty_cycle)(struct clk_hw *hw,
struct clk_duty *duty);
int (*init)(struct clk_hw *hw);
void (*terminate)(struct clk_hw *hw);
void (*debug_init)(struct clk_hw *hw, struct dentry *dentry);
};
最终注册接口
int clk_hw_register(struct device *dev, struct clk_hw *hw)
struct clk *clk_register(struct device *dev, struct clk_hw *hw)时钟注册过程:
struct clk_core *core;kzalloc core,赋值core->name,core->ops,core->parent_names[i]------>clk_hw_create_clk------>struct clk,clk->devid, clk->conid赋值,clk->clks_node插入hw->core->clks hash链表------>static int __clk_core_init(struct clk_core *core)------>clk_core_lookup,查找时钟名是否已经注册,从clk_root_list和clk_orphan_list两个hlist链表查找------>static struct clk_core *__clk_init_parent(struct clk_core *core),调用core->ops->get_parent获取默认parent,赋值core->parent,如果parent找到了则该时钟加入parent children哈希链表:
hlist_add_head(&core->child_node,
&core->parent->children);
core->orphan = core->parent->orphan;
未找到加入孤儿链表:
hlist_add_head(&core->child_node, &clk_orphan_list);
core->orphan = true;
本身就不存在parent加入root哈希链表:
hlist_add_head(&core->child_node, &clk_orphan_list);
core->orphan = true;
------>core->ops->init(core->hw)------>赋值core->accuracy,core->phase,core->duty,core->rate,core->req_rate,core->boot_enabled------>CLK_IS_CRITICAL时钟prepare&enable,clk_core_prepare:设置电压,调用core->ops->prepare,clk_core_enable:lockdep_assert_held(&enable_lock),core->enable_cout==0递归调用clk_core_enable(core->parent)对父时钟enable,调用core->ops->enable,core->enable_count++------->clk_core_reparent_orphans_nolock:重新轮询hlist_head* clk_orphan_list,重新__clk_init_parent 孤儿链表的child_node 的parent,若找到parent------>__clk_set_parent_before:
if (core->prepare_count) {
clk_core_prepare_enable(parent);
clk_core_enable_lock(core);
}
/* update the clk tree topology */
flags = clk_enable_lock();
clk_reparent(core, parent);
clk_enable_unlock(flags);
------>__clk_set_parent_after:
/*
* Finish the migration of prepare state and undo the changes done
* for preventing a race with clk_enable().
*/
if (core->prepare_count) {
clk_core_disable_lock(core);
clk_core_disable_unprepare(old_parent);
}
/* re-balance ref counting if CLK_OPS_PARENT_ENABLE is set */
if (core->flags & CLK_OPS_PARENT_ENABLE) {
clk_core_disable_unprepare(parent);
clk_core_disable_unprepare(old_parent);
}
------>__clk_recalc_accuracies:重新设置orphan时钟子树的core->accuracy------>__clk_recalc_rates:重新设置orphan时钟子树的core->rate
------>clk_debug_register:将时钟注册到debugfs
provider链表
provider时钟模块加入of_clk_providers链表
/**
* of_clk_add_provider() - Register a clock provider for a node
* @np: Device node pointer associated with clock provider
* @clk_src_get: callback for decoding clock
* @data: context pointer for @clk_src_get callback.
*/
int of_clk_add_provider(struct device_node *np,
struct clk *(*clk_src_get)(struct of_phandle_args *clkspec,
void *data),
void *data)
{
struct of_clk_provider *cp;
int ret;
cp = kzalloc(sizeof(*cp), GFP_KERNEL);
if (!cp)
return -ENOMEM;
cp->node = of_node_get(np);
cp->data = data;
cp->get = clk_src_get;
mutex_lock(&of_clk_mutex);
list_add(&cp->link, &of_clk_providers);
mutex_unlock(&of_clk_mutex);
pr_debug("Added clock from %pOF\n", np);
ret = of_clk_set_defaults(np, true);
if (ret < 0)
of_clk_del_provider(np);
return ret;
}
EXPORT_SYMBOL_GPL(of_clk_add_provider);
clk_src_get------>of_clk_src_onecell_get
struct clk *of_clk_src_onecell_get(struct of_phandle_args *clkspec, void *data)
{
struct clk_onecell_data *clk_data = data;
unsigned int idx = clkspec->args[0];
if (idx >= clk_data->clk_num) {
pr_err("%s: invalid clock index %u\n", __func__, idx);
return ERR_PTR(-EINVAL);
}
return clk_data->clks[idx];
}
时钟驱动会将注册的时钟存放在clk_data中
设备树节点指定rate,parent
平台驱动probe会调用of_clk_set_defaults
of_clk_set_defaults:设定父时钟,设定速率
int of_clk_set_defaults(struct device_node *node, bool clk_supplier)
{
int rc;
if (!node)
return 0;
rc = __set_clk_parents(node, clk_supplier);
if (rc < 0)
return rc;
return __set_clk_rates(node, clk_supplier);
}
EXPORT_SYMBOL_GPL(of_clk_set_defaults);
__set_clk_parents:
获取parent属性"assigned-clock-parents"数量,依次轮询parent:
1.获取父时钟
rc = of_parse_phandle_with_args(node, "assigned-clock-parents", "#clock-cells", index, &clkspec):获取时钟参数
pclk = of_clk_get_from_provider_with_orphans(&clkspec):
获取provider
list_for_each_entry(provider, &of_clk_providers, link) {
if (provider->node == clkspec->np)
............
通过provider,clk id获取时钟clk = provider->get(clkspec, provider->data);获取hw __clk_get_hw(clk);创建clk = __clk_create_clk(hw, dev_id, con_id,with_orphans);
2. 获取时钟
rc = of_parse_phandle_with_args(node, "assigned-clocks", "#clock-cells", index, &clkspec);
clk = of_clk_get_from_provider_with_orphans(&clkspec);
3.设定pck为clk parent
rc = clk_set_parent(clk, pclk);------>core->ops->set_parent(core->hw, p_index);
static int platform_drv_probe(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
int ret;
ret = of_clk_set_defaults(_dev->of_node, false);
if (ret < 0)
return ret;
ret = dev_pm_domain_attach(_dev, true);
if (ret)
goto out;
if (drv->probe) {
ret = drv->probe(dev);
if (ret)
dev_pm_domain_detach(_dev, true);
}
out:
if (drv->prevent_deferred_probe && ret == -EPROBE_DEFER) {
dev_warn(_dev, "probe deferral not supported\n");
ret = -ENXIO;
}
return ret;
}
int of_clk_set_defaults(struct device_node *node, bool clk_supplier)
{
int rc;
if (!node)
return 0;
rc = __set_clk_parents(node, clk_supplier);
if (rc < 0)
return rc;
return __set_clk_rates(node, clk_supplier);
}
__set_clk_rates:
同clk_set_parent
设备树节点
i2s3_mclkin: i2s3-mclkin {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <12288000>;
clock-output-names = "i2s3_mclkin";
};mpll: mpll {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <800000000>;
clock-output-names = "mpll";
};xin24m: xin24m {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <24000000>;
clock-output-names = "xin24m";
};时钟模块设备树节点
cru: clock-controller@fdd20000 {
compatible = "rockchip,rk3568-cru";
reg = <0x0 0xfdd20000 0x0 0x1000>;
rockchip,grf = <&grf>;
#clock-cells = <1>;
#reset-cells = <1>;assigned-clocks =
<&pmucru CLK_RTC_32K>, <&cru ACLK_RKVDEC_PRE>,
<&cru CLK_RKVDEC_CORE>, <&pmucru PLL_PPLL>,
<&pmucru PCLK_PMU>, <&cru PLL_CPLL>,
<&cru CPLL_500M>, <&cru CPLL_333M>,
<&cru CPLL_250M>, <&cru CPLL_125M>,
<&cru CPLL_100M>, <&cru CPLL_62P5M>,
<&cru CPLL_50M>, <&cru CPLL_25M>,
<&cru PLL_GPLL>,
<&cru ACLK_BUS>, <&cru PCLK_BUS>,
<&cru ACLK_TOP_HIGH>, <&cru ACLK_TOP_LOW>,
<&cru HCLK_TOP>, <&cru PCLK_TOP>,
<&cru ACLK_PERIMID>, <&cru HCLK_PERIMID>,
<&cru PLL_NPLL>, <&cru ACLK_PIPE>,
<&cru PCLK_PIPE>, <&cru CLK_I2S0_8CH_TX_SRC>,
<&cru CLK_I2S0_8CH_RX_SRC>, <&cru CLK_I2S1_8CH_TX_SRC>,
<&cru CLK_I2S1_8CH_RX_SRC>, <&cru CLK_I2S2_2CH_SRC>,
<&cru CLK_I2S2_2CH_SRC>, <&cru CLK_I2S3_2CH_RX_SRC>,
<&cru CLK_I2S3_2CH_TX_SRC>, <&cru MCLK_SPDIF_8CH_SRC>,
<&cru ACLK_VOP>;
assigned-clock-rates =
<32768>, <300000000>,
<300000000>, <200000000>,
<100000000>, <1000000000>,
<500000000>, <333000000>,
<250000000>, <125000000>,
<100000000>, <62500000>,
<50000000>, <25000000>,
<1188000000>,
<150000000>, <100000000>,
<500000000>, <400000000>,
<150000000>, <100000000>,
<300000000>, <150000000>,
<1200000000>, <400000000>,
<100000000>, <1188000000>,
<1188000000>, <1188000000>,
<1188000000>, <1188000000>,
<1188000000>, <1188000000>,
<1188000000>, <1188000000>,
<500000000>;
assigned-clock-parents =
<&pmucru CLK_RTC32K_FRAC>, <&cru PLL_GPLL>,
<&cru PLL_GPLL>;
};
时钟消费者(consumer):
使用ccf提供的通用api使能时钟,设置模块工作频率:
devm_clk_get()
或clk_get()
获取一个struct clk_hw *,调用 clk = __clk_create_clk(hw, dev_id, con_id,with_orphans)创建struct clk * clk
指针句柄,加入链表hlist_add_head(&clk->clks_node, &hw->core->clks);后续都通过传入该句柄来操作,struct clk相当于实例化一个时钟。
struct clk *clk_get(struct device *dev, const char *id);
struct clk *devm_clk_get(struct device *dev, const char *id);
int clk_enable(struct clk *clk);//使能时钟,不会睡眠
void clk_disable(struct clk *clk);//使能时钟,不会睡眠
unsigned long clk_get_rate(struct clk *clk);
void clk_put(struct clk *clk);//释放clk
long clk_round_rate(struct clk *clk, unsigned long rate);
int clk_set_rate(struct clk *clk, unsigned long rate);
int clk_set_parent(struct clk *clk, struct clk *parent);
struct clk *clk_get_parent(struct clk *clk);
int clk_prepare(struct clk *clk);
void clk_unprepare(struct clk *clk);
int clk_prepare_enable(struct clk *clk) //使能时钟,可能会睡眠
void clk_disable_unprepare(struct clk *clk) //禁止时钟,可能会睡眠
unsigned long clk_get_rate(struct clk *clk) //获取时钟频率
设备树节点:
uart0: serial@fdd50000 {
compatible = "rockchip,rk3568-uart", "snps,dw-apb-uart";
reg = <0x0 0xfdd50000 0x0 0x100>;
interrupts = <GIC_SPI 116 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&pmucru SCLK_UART0>, <&pmucru PCLK_UART0>;
clock-names = "baudclk", "apb_pclk";
reg-shift = <2>;
reg-io-width = <4>;
dmas = <&dmac0 0>, <&dmac0 1>;
pinctrl-names = "default";
pinctrl-0 = <&uart0_xfer>;
status = "disabled";
};uart1: serial@fe650000 {
compatible = "rockchip,rk3568-uart", "snps,dw-apb-uart";
reg = <0x0 0xfe650000 0x0 0x100>;
interrupts = <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru SCLK_UART1>, <&cru PCLK_UART1>;
clock-names = "baudclk", "apb_pclk";
reg-shift = <2>;
reg-io-width = <4>;
dmas = <&dmac0 2>, <&dmac0 3>;
pinctrl-names = "default";
pinctrl-0 = <&uart1m0_xfer>;
status = "disabled";
};usb2phy0: usb2-phy@fe8a0000 {
compatible = "rockchip,rk3568-usb2phy";
reg = <0x0 0xfe8a0000 0x0 0x10000>;
interrupts = <GIC_SPI 135 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&pmucru CLK_USBPHY0_REF>;
clock-names = "phyclk";
#clock-cells = <0>;
assigned-clocks = <&cru USB480M>;
assigned-clock-parents = <&usb2phy0>;
clock-output-names = "usb480m_phy";//模块时钟输出,驱动模块会进行时钟注册
rockchip,usbgrf = <&usb2phy0_grf>;
status = "disabled";u2phy0_host: host-port {
#phy-cells = <0>;
status = "disabled";
};u2phy0_otg: otg-port {
#phy-cells = <0>;
status = "disabled";
};
};
实际使用例子
用3568来举例:
时钟注册过程(时钟提供者):
3568时钟模块(Clock & Reset Unit (CRU)):pmucru(0xfdd00000)/pmuscru(0xfdd30000),cru(0xfdd20000)/scru(0xfdd10000)
cru设备树节点:
pmucru: clock-controller@fdd00000 {
compatible = "rockchip,rk3568-pmucru";
reg = <0x0 0xfdd00000 0x0 0x1000>;
rockchip,grf = <&grf>;
rockchip,pmugrf = <&pmugrf>;
#clock-cells = <1>;
#reset-cells = <1>;assigned-clocks = <&pmucru SCLK_32K_IOE>;
assigned-clock-parents = <&pmucru CLK_RTC_32K>;
};cru: clock-controller@fdd20000 {
compatible = "rockchip,rk3568-cru";
reg = <0x0 0xfdd20000 0x0 0x1000>;
rockchip,grf = <&grf>;
#clock-cells = <1>;
#reset-cells = <1>;assigned-clocks =
<&pmucru CLK_RTC_32K>, <&cru ACLK_RKVDEC_PRE>,
<&cru CLK_RKVDEC_CORE>, <&pmucru PLL_PPLL>,
<&pmucru PCLK_PMU>, <&cru PLL_CPLL>,
<&cru CPLL_500M>, <&cru CPLL_333M>,
<&cru CPLL_250M>, <&cru CPLL_125M>,
<&cru CPLL_100M>, <&cru CPLL_62P5M>,
<&cru CPLL_50M>, <&cru CPLL_25M>,
<&cru PLL_GPLL>,
<&cru ACLK_BUS>, <&cru PCLK_BUS>,
<&cru ACLK_TOP_HIGH>, <&cru ACLK_TOP_LOW>,
<&cru HCLK_TOP>, <&cru PCLK_TOP>,
<&cru ACLK_PERIMID>, <&cru HCLK_PERIMID>,
<&cru PLL_NPLL>, <&cru ACLK_PIPE>,
<&cru PCLK_PIPE>, <&cru CLK_I2S0_8CH_TX_SRC>,
<&cru CLK_I2S0_8CH_RX_SRC>, <&cru CLK_I2S1_8CH_TX_SRC>,
<&cru CLK_I2S1_8CH_RX_SRC>, <&cru CLK_I2S2_2CH_SRC>,
<&cru CLK_I2S2_2CH_SRC>, <&cru CLK_I2S3_2CH_RX_SRC>,
<&cru CLK_I2S3_2CH_TX_SRC>, <&cru MCLK_SPDIF_8CH_SRC>,
<&cru ACLK_VOP>;
assigned-clock-rates =
<32768>, <300000000>,
<300000000>, <200000000>,
<100000000>, <1000000000>,
<500000000>, <333000000>,
<250000000>, <125000000>,
<100000000>, <62500000>,
<50000000>, <25000000>,
<1188000000>,
<150000000>, <100000000>,
<500000000>, <400000000>,
<150000000>, <100000000>,
<300000000>, <150000000>,
<1200000000>, <400000000>,
<100000000>, <1188000000>,
<1188000000>, <1188000000>,
<1188000000>, <1188000000>,
<1188000000>, <1188000000>,
<1188000000>, <1188000000>,
<500000000>;
assigned-clock-parents =
<&pmucru CLK_RTC32K_FRAC>, <&cru PLL_GPLL>,
<&cru PLL_GPLL>;
};
cru模块驱动代码路径:drivers/clk/rockchip/clk-rk3568.c
static void __init rk3568_clk_init(struct device_node *np)
1.注册pll时钟,rockchip_clk_register_pll
pll时钟
struct rockchip_clk_pll *pll;
pll mux时钟
struct clk_mux *pll_mux;
#define PNAME(x) static const char *const x[] __initconst
PNAME(mux_pll_p) = { "xin24m" };
#define RK2928_PLL_CON(x) ((x) * 0x4)
#define PLL(_type, _id, _name, _pnames, _flags, _con, _mode, _mshift, \
_lshift, _pflags, _rtable) \
{ \
.id = _id, \
.type = _type, \
.name = _name, \
.parent_names = _pnames, \
.num_parents = ARRAY_SIZE(_pnames), \
.flags = CLK_GET_RATE_NOCACHE | _flags, \
.con_offset = _con, \
.mode_offset = _mode, \
.mode_shift = _mshift, \
.lock_shift = _lshift, \
.pll_flags = _pflags, \
.rate_table = _rtable, \
}static struct rockchip_pll_clock rk3568_pll_clks[] __initdata = {
[apll] = PLL(pll_rk3328, PLL_APLL, "apll", mux_pll_p,
0, RK3568_PLL_CON(0),
RK3568_MODE_CON0, 0, 0, 0, rk3568_pll_rates),
[dpll] = PLL(pll_rk3328, PLL_DPLL, "dpll", mux_pll_p,
0, RK3568_PLL_CON(8),
RK3568_MODE_CON0, 2, 1, 0, NULL),
[cpll] = PLL(pll_rk3328, PLL_CPLL, "cpll", mux_pll_p,
0, RK3568_PLL_CON(24),
RK3568_MODE_CON0, 4, 2, 0, rk3568_pll_rates),
[gpll] = PLL(pll_rk3328, PLL_GPLL, "gpll", mux_pll_p,
0, RK3568_PLL_CON(16),
RK3568_MODE_CON0, 6, 3, 0, rk3568_pll_rates),
[npll] = PLL(pll_rk3328, PLL_NPLL, "npll", mux_pll_p,
0, RK3568_PLL_CON(32),
RK3568_MODE_CON0, 10, 5, 0, rk3568_pll_rates),
[vpll] = PLL(pll_rk3328, PLL_VPLL, "vpll", mux_pll_p,
0, RK3568_PLL_CON(40),
RK3568_MODE_CON0, 12, 6, 0, rk3568_pll_rates),
};
con_offset:pll配置寄存器,mode_offset:寄存器CRU_MODE_CON00,mode_shift:寄存器CRU_MODE_CON00中相关配置位 ,lock_shift:寄存器GRF_SOC_STATUS0中相关pll是否lock指示位
,pll_flags:,rate_table:rk3568_pll_rates,pll时钟配置表,相关时钟配置如下
static struct rockchip_pll_rate_table rk3568_pll_rates[] = {
/* _mhz, _refdiv, _fbdiv, _postdiv1, _postdiv2, _dsmpd, _frac */
RK3036_PLL_RATE(2208000000, 1, 92, 1, 1, 1, 0),
RK3036_PLL_RATE(2184000000, 1, 91, 1, 1, 1, 0),
RK3036_PLL_RATE(2160000000, 1, 90, 1, 1, 1, 0),
RK3036_PLL_RATE(2088000000, 1, 87, 1, 1, 1, 0),
RK3036_PLL_RATE(2064000000, 1, 86, 1, 1, 1, 0),
RK3036_PLL_RATE(2040000000, 1, 85, 1, 1, 1, 0),
RK3036_PLL_RATE(2016000000, 1, 84, 1, 1, 1, 0),
RK3036_PLL_RATE(1992000000, 1, 83, 1, 1, 1, 0),
RK3036_PLL_RATE(1920000000, 1, 80, 1, 1, 1, 0),
RK3036_PLL_RATE(1896000000, 1, 79, 1, 1, 1, 0),
RK3036_PLL_RATE(1800000000, 1, 75, 1, 1, 1, 0),
RK3036_PLL_RATE(1704000000, 1, 71, 1, 1, 1, 0),
RK3036_PLL_RATE(1608000000, 1, 67, 1, 1, 1, 0),
RK3036_PLL_RATE(1600000000, 3, 200, 1, 1, 1, 0),
RK3036_PLL_RATE(1584000000, 1, 132, 2, 1, 1, 0),
RK3036_PLL_RATE(1560000000, 1, 130, 2, 1, 1, 0),
RK3036_PLL_RATE(1536000000, 1, 128, 2, 1, 1, 0),
RK3036_PLL_RATE(1512000000, 1, 126, 2, 1, 1, 0),
RK3036_PLL_RATE(1488000000, 1, 124, 2, 1, 1, 0),
RK3036_PLL_RATE(1464000000, 1, 122, 2, 1, 1, 0),
RK3036_PLL_RATE(1440000000, 1, 120, 2, 1, 1, 0),
RK3036_PLL_RATE(1416000000, 1, 118, 2, 1, 1, 0),
RK3036_PLL_RATE(1400000000, 3, 350, 2, 1, 1, 0),
RK3036_PLL_RATE(1392000000, 1, 116, 2, 1, 1, 0),
RK3036_PLL_RATE(1368000000, 1, 114, 2, 1, 1, 0),
RK3036_PLL_RATE(1344000000, 1, 112, 2, 1, 1, 0),
RK3036_PLL_RATE(1320000000, 1, 110, 2, 1, 1, 0),
RK3036_PLL_RATE(1296000000, 1, 108, 2, 1, 1, 0),
RK3036_PLL_RATE(1272000000, 1, 106, 2, 1, 1, 0),
RK3036_PLL_RATE(1248000000, 1, 104, 2, 1, 1, 0),
RK3036_PLL_RATE(1200000000, 1, 100, 2, 1, 1, 0),
RK3036_PLL_RATE(1188000000, 1, 99, 2, 1, 1, 0),
RK3036_PLL_RATE(1104000000, 1, 92, 2, 1, 1, 0),
RK3036_PLL_RATE(1100000000, 3, 275, 2, 1, 1, 0),
RK3036_PLL_RATE(1008000000, 1, 84, 2, 1, 1, 0),
RK3036_PLL_RATE(1000000000, 3, 250, 2, 1, 1, 0),
RK3036_PLL_RATE(912000000, 1, 76, 2, 1, 1, 0),
RK3036_PLL_RATE(816000000, 1, 68, 2, 1, 1, 0),
RK3036_PLL_RATE(800000000, 3, 200, 2, 1, 1, 0),
RK3036_PLL_RATE(700000000, 3, 350, 4, 1, 1, 0),
RK3036_PLL_RATE(696000000, 1, 116, 4, 1, 1, 0),
RK3036_PLL_RATE(600000000, 1, 100, 4, 1, 1, 0),
RK3036_PLL_RATE(594000000, 1, 99, 4, 1, 1, 0),
RK3036_PLL_RATE(500000000, 1, 125, 6, 1, 1, 0),
RK3036_PLL_RATE(408000000, 1, 68, 2, 2, 1, 0),
RK3036_PLL_RATE(312000000, 1, 78, 6, 1, 1, 0),
RK3036_PLL_RATE(216000000, 1, 72, 4, 2, 1, 0),
RK3036_PLL_RATE(200000000, 1, 100, 3, 4, 1, 0),
RK3036_PLL_RATE(148500000, 1, 99, 4, 4, 1, 0),
RK3036_PLL_RATE(100000000, 1, 150, 6, 6, 1, 0),
RK3036_PLL_RATE(96000000, 1, 96, 6, 4, 1, 0),
RK3036_PLL_RATE(74250000, 2, 99, 4, 4, 1, 0),
{ /* sentinel */ },
};
2.注册arm核时钟,rockchip_clk_register_armclk
cpu父时钟名:
PNAME(mux_armclk_p) = { "apll", "gpll" };
cpu时钟:
struct rockchip_cpuclk
3.其他分支时钟,rockchip_clk_register_branches
1)mux时钟,通用框架时钟组件
例:填充id,name,父name,mux寄存器,bit位置,bit位宽
#define MUX(_id, cname, pnames, f, o, s, w, mf) \
{ \
.id = _id, \
.branch_type = branch_mux, \
.name = cname, \
.parent_names = pnames, \
.num_parents = ARRAY_SIZE(pnames), \
.flags = f, \
.muxdiv_offset = o, \
.mux_shift = s, \
.mux_width = w, \
.mux_flags = mf, \
.gate_offset = -1, \
}MUX(USB480M, "usb480m", mux_usb480m_p, CLK_SET_RATE_PARENT,
RK3568_MODE_CON0, 14, 2, MFLAGS),
/**
* struct clk_mux - multiplexer clock
*
* @hw: handle between common and hardware-specific interfaces
* @reg: register controlling multiplexer
* @table: array of register values corresponding to the parent index
* @shift: shift to multiplexer bit field
* @mask: mask of mutliplexer bit field
* @flags: hardware-specific flags
* @lock: register lock
*
* Clock with multiple selectable parents. Implements .get_parent, .set_parent
* and .recalc_rate
*
* Flags:
* CLK_MUX_INDEX_ONE - register index starts at 1, not 0
* CLK_MUX_INDEX_BIT - register index is a single bit (power of two)
* CLK_MUX_HIWORD_MASK - The mux settings are only in lower 16-bit of this
* register, and mask of mux bits are in higher 16-bit of this register.
* While setting the mux bits, higher 16-bit should also be updated to
* indicate changing mux bits.
* CLK_MUX_ROUND_CLOSEST - Use the parent rate that is closest to the desired
* frequency.
*/
struct clk_mux {
struct clk_hw hw;
void __iomem *reg;
u32 *table;
u32 mask;
u8 shift;
u8 flags;
spinlock_t *lock;
};
const struct clk_ops clk_mux_ops = {
.get_parent = clk_mux_get_parent,
.set_parent = clk_mux_set_parent,
.determine_rate = clk_mux_determine_rate,
};
注册接口
struct clk *clk_register_mux(struct device *dev, const char *name,
const char * const *parent_names, u8 num_parents,
unsigned long flags,
void __iomem *reg, u8 shift, u8 width,
u8 clk_mux_flags, spinlock_t *lock)
struct clk *clk_register_mux_table(struct device *dev, const char *name,
const char * const *parent_names, u8 num_parents,
unsigned long flags,
void __iomem *reg, u8 shift, u32 mask,
u8 clk_mux_flags, u32 *table, spinlock_t *lock)
2)muxgrf时钟,3568 soc厂商mux时钟组件
例:grf模块mux时钟输出,填充id,name,父name,grf寄存器offset,bit位置,bit位宽
struct rockchip_muxgrf_clock {
struct clk_hw hw;
struct regmap *regmap;
u32 reg;
u32 shift;
u32 width;
int flags;
};
MUXGRF(I2S1_MCLKOUT, "i2s1_mclkout", i2s1_mclkout_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT,
RK3568_GRF_SOC_CON1, 5, 1, MFLAGS),
GRF_SOC_CON1, bit 5RW 0x0i2s1_mclk_seli2s1 mclk to GPIO source slection1'b0: i2s1_mclk_rx1'b1: i2s1_mclk_tx
MUXPMUGRF(SCLK_32K_IOE, "clk_32k_ioe", clk_32k_ioe_p, 0,
RK3568_PMU_GRF_SOC_CON0, 0, 1, MFLAGS)
4)divider时钟,通用框架时钟组件
例:分频时钟,填充id,name,父name,grf寄存器offset,bit位置,bit位宽
/**
* struct clk_divider - adjustable divider clock
*
* @hw: handle between common and hardware-specific interfaces
* @reg: register containing the divider
* @shift: shift to the divider bit field
* @width: width of the divider bit field
* @max_prate: the maximum frequency of the parent clock
* @table: array of value/divider pairs, last entry should have div = 0
* @lock: register lock
*
* Clock with an adjustable divider affecting its output frequency. Implements
* .recalc_rate, .set_rate and .round_rate
*
* Flags:
* CLK_DIVIDER_ONE_BASED - by default the divisor is the value read from the
* register plus one. If CLK_DIVIDER_ONE_BASED is set then the divider is
* the raw value read from the register, with the value of zero considered
* invalid, unless CLK_DIVIDER_ALLOW_ZERO is set.
* CLK_DIVIDER_POWER_OF_TWO - clock divisor is 2 raised to the value read from
* the hardware register
* CLK_DIVIDER_ALLOW_ZERO - Allow zero divisors. For dividers which have
* CLK_DIVIDER_ONE_BASED set, it is possible to end up with a zero divisor.
* Some hardware implementations gracefully handle this case and allow a
* zero divisor by not modifying their input clock
* (divide by one / bypass).
* CLK_DIVIDER_HIWORD_MASK - The divider settings are only in lower 16-bit
* of this register, and mask of divider bits are in higher 16-bit of this
* register. While setting the divider bits, higher 16-bit should also be
* updated to indicate changing divider bits.
* CLK_DIVIDER_ROUND_CLOSEST - Makes the best calculated divider to be rounded
* to the closest integer instead of the up one.
* CLK_DIVIDER_READ_ONLY - The divider settings are preconfigured and should
* not be changed by the clock framework.
* CLK_DIVIDER_MAX_AT_ZERO - For dividers which are like CLK_DIVIDER_ONE_BASED
* except when the value read from the register is zero, the divisor is
* 2^width of the field.
*/
struct clk_divider {
struct clk_hw hw;
void __iomem *reg;
u8 shift;
u8 width;
u8 flags;
unsigned long max_prate;
const struct clk_div_table *table;
spinlock_t *lock;
};
注册接口:
struct clk *clk_register_divider(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
void __iomem *reg, u8 shift, u8 width,
u8 clk_divider_flags, spinlock_t *lock)
struct clk *clk_register_divider_table(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
void __iomem *reg, u8 shift, u8 width,
u8 clk_divider_flags, const struct clk_div_table *table,
spinlock_t *lock)//div只能选表中的值
5)fraction_divider时钟,调用通用composite时钟组件与mux时钟组件
6)half_divider时钟,调用通用composite时钟组件
DIV_ROUND_UP_ULL(2*parent_rate,2*val+3)
7)gate时钟,通用框架时钟组件
例:开关时钟,填充id,name,父name,bit位置
GATE(ACLK_NPU, "aclk_npu", "aclk_npu_pre", 0,
RK3568_CLKGATE_CON(3), 7, GFLAGS),
/**
* struct clk_gate - gating clock
*
* @hw: handle between common and hardware-specific interfaces
* @reg: register controlling gate
* @bit_idx: single bit controlling gate
* @flags: hardware-specific flags
* @lock: register lock
*
* Clock which can gate its output. Implements .enable & .disable
*
* Flags:
* CLK_GATE_SET_TO_DISABLE - by default this clock sets the bit at bit_idx to
* enable the clock. Setting this flag does the opposite: setting the bit
* disable the clock and clearing it enables the clock
* CLK_GATE_HIWORD_MASK - The gate settings are only in lower 16-bit
* of this register, and mask of gate bits are in higher 16-bit of this
* register. While setting the gate bits, higher 16-bit should also be
* updated to indicate changing gate bits.
* CLK_GATE_NO_SET_RATE - The Gate not allowed to set rate.
* And not allowed to set parent rate.
*/
struct clk_gate {
struct clk_hw hw;
void __iomem *reg;
u8 bit_idx;
u8 flags;
spinlock_t *lock;
};
时钟注册接口:
struct clk *clk_register_gate(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
void __iomem *reg, u8 bit_idx,
u8 clk_gate_flags, spinlock_t *lock)
8)composite时钟,通用框架时钟组件
COMPOSITE(CLK_CAM0_OUT, "clk_cam0_out", gpll_usb480m_xin24m_p, 0,
RK3568_CLKSEL_CON(36), 6, 2, MFLAGS, 0, 6, DFLAGS,
RK3568_CLKGATE_CON(19), 9, GFLAGS),
/***
* struct clk_composite - aggregate clock of mux, divider and gate clocks
*
* @hw: handle between common and hardware-specific interfaces
* @mux_hw: handle between composite and hardware-specific mux clock
* @rate_hw: handle between composite and hardware-specific rate clock
* @gate_hw: handle between composite and hardware-specific gate clock
* @brother_hw: a member of clk_composite who has the common parent clocks
* with another clk_composite, and it's also a handle between
* common and hardware-specific interfaces
* @mux_ops: clock ops for mux
* @rate_ops: clock ops for rate
* @gate_ops: clock ops for gate
*/
struct clk_composite {
struct clk_hw hw;
struct clk_ops ops;
struct clk_hw *mux_hw;
struct clk_hw *rate_hw;
struct clk_hw *gate_hw;
struct clk_hw *brother_hw;
const struct clk_ops *mux_ops;
const struct clk_ops *rate_ops;
const struct clk_ops *gate_ops;
};
时钟注册接口:
struct clk *clk_register_composite(struct device *dev, const char *name,
const char * const *parent_names, int num_parents,
struct clk_hw *mux_hw, const struct clk_ops *mux_ops,
struct clk_hw *rate_hw, const struct clk_ops *rate_ops,
struct clk_hw *gate_hw, const struct clk_ops *gate_ops,
unsigned long flags)
9)composite_brother时钟,两个composite时钟组件,其中一个兄弟时钟是half_divider时钟
/* PD_NPU */
COMPOSITE_BROTHER(CLK_NPU_SRC, "clk_npu_src", npll_gpll_p, 0,
RK3568_CLKSEL_CON(7), 6, 1, MFLAGS, 0, 4, DFLAGS,
RK3568_CLKGATE_CON(3), 0, GFLAGS,
&rk3568_clk_npu_np5),
static struct rockchip_clk_branch rk3568_clk_npu_np5 __initdata =
COMPOSITE_HALFDIV(CLK_NPU_NP5, "clk_npu_np5", npll_gpll_p, 0,
RK3568_CLKSEL_CON(7), 7, 1, MFLAGS, 4, 2, DFLAGS,
RK3568_CLKGATE_CON(3), 1, GFLAGS);
10)fractional_divider时钟,分数分频器,通用框架时钟组件
/**
* struct clk_fractional_divider - adjustable fractional divider clock
*
* @hw: handle between common and hardware-specific interfaces
* @reg: register containing the divider
* @mshift: shift to the numerator bit field
* @mwidth: width of the numerator bit field
* @nshift: shift to the denominator bit field
* @nwidth: width of the denominator bit field
* @max_parent: the maximum frequency of fractional divider parent clock
* @lock: register lock
*
* Clock with adjustable fractional divider affecting its output frequency.
*
* Flags:
* CLK_FRAC_DIVIDER_NO_LIMIT - not need to follow the 20 times limit on
* fractional divider
*/
struct clk_fractional_divider {
struct clk_hw hw;
void __iomem *reg;
u8 mshift;
u8 mwidth;
u32 mmask;
u8 nshift;
u8 nwidth;
u32 nmask;
u8 flags;
unsigned long max_prate;
void (*approximation)(struct clk_hw *hw,
unsigned long rate, unsigned long *parent_rate,
unsigned long *m, unsigned long *n);
spinlock_t *lock;
};
时钟注册接口
struct clk *clk_register_fractional_divider(struct device *dev,
const char *name, const char *parent_name, unsigned long flags,
void __iomem *reg, u8 mshift, u8 mwidth, u8 nshift, u8 nwidth,
u8 clk_divider_flags, spinlock_t *lock)
12)factor时钟,固定倍数时钟,通用框架时钟,设置rate无效果,父时钟与本时钟倍数固定
#define FACTOR(_id, cname, pname, f, fm, fd) \
{ \
.id = _id, \
.branch_type = branch_factor, \
.name = cname, \
.parent_names = (const char *[]){ pname }, \
.num_parents = 1, \
.flags = f, \
.div_shift = fm, \
.div_width = fd, \
}
FACTOR(0, "clk_gmac0_tx_div5", "clk_gmac0", 0, 1, 5)
/**
* struct clk_fixed_factor - fixed multiplier and divider clock
*
* @hw: handle between common and hardware-specific interfaces
* @mult: multiplier
* @div: divider
*
* Clock with a fixed multiplier and divider. The output frequency is the
* parent clock rate divided by div and multiplied by mult.
* Implements .recalc_rate, .set_rate and .round_rate
*/
struct clk_fixed_factor {
struct clk_hw hw;
unsigned int mult;
unsigned int div;
};
时钟注册接口:
struct clk *clk_register_fixed_factor(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
unsigned int mult, unsigned int div)
{
struct clk_hw *hw;
hw = clk_hw_register_fixed_factor(dev, name, parent_name, flags, mult,
div);
if (IS_ERR(hw))
return ERR_CAST(hw);
return hw->clk;
}
13)mmc时钟,3568 soc厂商时钟组件,单独设置mmc模块时钟参数
#define MMC(_id, cname, pname, offset, shift) \
{ \
.id = _id, \
.branch_type = branch_mmc, \
.name = cname, \
.parent_names = (const char *[]){ pname }, \
.num_parents = 1, \
.muxdiv_offset = offset, \
.div_shift = shift, \
}
MMC(SCLK_EMMC_DRV, "emmc_drv", "cclk_emmc", RK3568_EMMC_CON0, 1),
MMC(SCLK_EMMC_SAMPLE, "emmc_sample", "cclk_emmc", RK3568_EMMC_CON1, 1),
CRU_EMMC_CON0Address: Operational Base + offset (0x0598)Bit Attr Reset ValueDescription31:16 RW 0x0000write_enableWrite enable for lower 16 bits, each bit is individual.1'b0: Write access disable1'b1: Write access enable15:12 RO 0x0reserved11RW 0x0drv_seldrv_sel10:3 RW 0x00drv_delaynumdrv_delaynum2:1RW 0x2drv_degreedrv_degree0RW 0x0init_stateinit_stateCRU_EMMC_CON1Address: Operational Base + offset (0x059C)Bit Attr Reset ValueDescription31:16 RW 0x0000write_enableWrite enable for lower 16 bits, each bit is individual.1'b0: Write access disable1'b1: Write access enable15:11 RO 0x00reserved10RW 0x0sample_selsample_sel9:2RW 0x00sample_delaynumsample_delaynum1:0RW 0x0sample_degreesample_degree
14)ddrclk时钟,调用composite时钟组件
#define COMPOSITE_DDRCLK(_id, cname, pnames, f, mo, ms, mw, \
ds, dw, df) \
{ \
.id = _id, \
.branch_type = branch_ddrclk, \
.name = cname, \
.parent_names = pnames, \
.num_parents = ARRAY_SIZE(pnames), \
.flags = f, \
.muxdiv_offset = mo, \
.mux_shift = ms, \
.mux_width = mw, \
.div_shift = ds, \
.div_width = dw, \
.div_flags = df, \
.gate_offset = -1, \
}
15)dclk_divider时钟
#define COMPOSITE_DCLK(_id, cname, pnames, f, mo, ms, mw, mf, ds, dw,\
df, go, gs, gf, prate) \
{ \
.id = _id, \
.branch_type = branch_dclk_divider, \
.name = cname, \
.parent_names = pnames, \
.num_parents = ARRAY_SIZE(pnames), \
.flags = f, \
.muxdiv_offset = mo, \
.mux_shift = ms, \
.mux_width = mw, \
.mux_flags = mf, \
.div_shift = ds, \
.div_width = dw, \
.div_flags = df, \
.gate_offset = go, \
.gate_shift = gs, \
.gate_flags = gf, \
.max_prate = prate, \
}
COMPOSITE_DCLK(DCLK_VOP1, "dclk_vop1", hpll_vpll_gpll_cpll_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT,
RK3568_CLKSEL_CON(40), 10, 2, MFLAGS, 0, 8, DFLAGS,
RK3568_CLKGATE_CON(20), 11, GFLAGS, RK3568_DCLK_PARENT_MAX_PRATE),
16)fixed-rate clock
/*
* DOC: Basic clock implementations common to many platforms
*
* Each basic clock hardware type is comprised of a structure describing the
* clock hardware, implementations of the relevant callbacks in struct clk_ops,
* unique flags for that hardware type, a registration function and an
* alternative macro for static initialization
*/
/**
* struct clk_fixed_rate - fixed-rate clock
* @hw: handle between common and hardware-specific interfaces
* @fixed_rate: constant frequency of clock
*/
struct clk_fixed_rate {
struct clk_hw hw;
unsigned long fixed_rate;
unsigned long fixed_accuracy;
u8 flags;
};
时钟注册接口:
struct clk *clk_register_fixed_rate(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
unsigned long fixed_rate)
/**
* clk_hw_register_fixed_rate_with_accuracy - register fixed-rate clock with
* the clock framework
* @dev: device that is registering this clock
* @name: name of this clock
* @parent_name: name of clock's parent
* @flags: framework-specific flags
* @fixed_rate: non-adjustable clock rate
* @fixed_accuracy: non-adjustable clock rate
*/
struct clk_hw *clk_hw_register_fixed_rate_with_accuracy(struct device *dev,
const char *name, const char *parent_name, unsigned long flags,
unsigned long fixed_rate, unsigned long fixed_accuracy)
时钟使用(时钟消费者)
1.设备树节点配置
tp2803: tp2803@44 {
status = "okay";
compatible = "techpoint,tp2803";
reg = <0x44>;
power-domains = <&power RK3568_PD_VI>;
reset-gpios = <&gpio1 RK_PA7 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
pinctrl-0 = <&cif_clk>;
clocks = <&cru CLK_CIF_OUT>;
clock-names = "tpclk";.....
2.代码中使用
tp2803->clkin = devm_clk_get(dev,"tpclk");
if(IS_ERR(tp2803->clkin)){
dev_err(dev, "Failed to get clk: %ld\n",
PTR_ERR(tp2803->clkin));
return PTR_ERR(tp2803->clkin);
}
ret = clk_set_rate(tp2803->clkin,TPCLK_IN);
if(ret){
dev_err(dev, "Failed to set cif clk rate\n");
return ret;
}
ret = clk_prepare_enable(tp2803->clkin);
......
调试方式
1.查看时钟树:cat /sys/kernel/debug/clk/clk_summary
2.可以在/sys/kernel/debug/clk目录下的各时钟进行enable, rate ,parent等设置