linux 时钟架构

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  5
RW 0x0
i2s1_mclk_sel
i2s1 mclk to GPIO source slection
1'b0: i2s1_mclk_rx
1'b1: i2s1_mclk_tx
3)muxpmugrf时钟,3568 soc厂商mux时钟组件
例:pmugrf模块mux时钟输出,填充id,name,父name,grf寄存器offset,bit位置,bit位宽

    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_CON0
Address: Operational Base + offset (0x0598)
Bit Attr Reset Value
Description
31:16 RW 0x0000
write_enable
Write enable for lower 16 bits, each bit is individual.
1'b0: Write access disable
1'b1: Write access enable
15:12 RO 0x0
reserved
11
RW 0x0
drv_sel
drv_sel
10:3 RW 0x00
drv_delaynum
drv_delaynum
2:1
RW 0x2
drv_degree
drv_degree
0
RW 0x0
init_state
init_state
CRU_EMMC_CON1
Address: Operational Base + offset (0x059C)
Bit Attr Reset Value
Description
31:16 RW 0x0000
write_enable
Write enable for lower 16 bits, each bit is individual.
1'b0: Write access disable
1'b1: Write access enable
15:11 RO 0x00
reserved
10
RW 0x0
sample_sel
sample_sel
9:2
RW 0x00
sample_delaynum
sample_delaynum
1:0
RW 0x0
sample_degree
sample_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等设置

  • 10
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值