第20章 Linux芯片级移植及底层驱动之时钟驱动

20.8 时钟驱动

    在一个SoC中,晶振、PLL、驱动和门等会形成一个时钟树形结构,在Linux 2.6中,也存有clk_get_rate()、clk_set_rate()、clk_get_parent()、clk_set_parent()等通用API,但是这些API由每个SoC单独实现,而且各个SoC供应商在实现方面的差异很大,于是内核增加了一个新的通用时钟框架以解决这个碎片化问题。之所以称为通用时钟,是因为这个通用主要体现在:

    1)统一的clk结构体,统一的定义于clk.h中的clk API,这些API会调用统一的clk_ops中的回调函数;这个统一的clk结构体的定义如代码清单20.22所示。

代码清单20.22 clk结构体

struct clk {
         const char *name;
         const struct clk_ops *ops;// 关于时钟使能、禁止、计算频率等的操作集
         struct clk_hw *hw;
         char **parent_names;
         struct clk **parents;
         struct clk *parent;
         struct hlist_head children;
         struct hlist_node child_node;
         ...

};

代码清单20.23 clk_ops结构体

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);
        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);
        void            (*init)(struct clk_hw *hw);
        int             (*debug_init)(struct clk_hw *hw, struct dentry *dentry);
};

2)对具体的SoC如何去实现针对自己SoC的clk驱动,如何提供硬件特定的回调函数的方法也进行了统一。

struct clk_hw 是联系clk_ops中回调函数和具体硬件细节的纽带,clk_hw中只包含通用时钟结构体的指针以及具体硬件的init数据,如代码清单20.24所示。

代码清单20.24 clk_hw结构体

include/linux/clk-provider.h

struct clk_hw {
        struct clk_core *core;
        struct clk *clk;
        const struct clk_init_data *init;

};

    其中的clk_init_data包含了具体时钟的名称可能的父级时钟的名称列表parent_names可能的父级时钟数

量num_parents等,这些名称的匹配对建立时钟间的父子关系功不可没,如代码清单20.25所示。

代码清单20.25 clk_init_data结构体

include/linux/clk-provider.h

struct clk_init_data {
         const char *name;
         const struct clk_ops *ops;
         const char **parent_names;
         u8 num_parents;
         unsigned long flags;
};

从clk核心层到具体芯片clk驱动的调用顺序为:

clk_enable(clk);-------> clk->ops->enable(clk->hw);

通用的clk API(如clk_enable)在调用底层clk结构体的clk_ops成员函数(如clk->ops->enable)时,会将

clk->hw传递过去。

一般在具体的驱动中会定义针对特定clk的结构体,该结构体中包含clk_hw成员以及硬件私有数据
struct clk_foo {
          struct clk_hw hw;
          ... hardware specific data goes here ...
}

并定义to_clk_foo()宏,以便通过clk_hw获取clk_foo:

#define to_clk_foo(_hw) container_of(_hw, struct clk_foo, hw)

    在针对clk_foo的clk_ops的回调函数中,可以通过clk_hw和to_clk_foo最终获得硬件私有数据,并访问硬件读写寄存器以改变时钟的状态:

struct clk_ops clk_foo_ops {
        .enable = &clk_foo_enable;
        .disable = &clk_foo_disable;
};
int clk_foo_enable(struct clk_hw *hw)
{
        struct clk_foo *foo;
        foo = to_clk_foo(hw);
        /* 访问硬件读写寄存器以改变时钟的状态 */
        …
        return 0;
};

    在具体的clk驱动中,需要通过clk_register()以及它的变体注册硬件上所有的clk,通过

clk_register_clkdev()注册clk的一个lookup,这两个函数的原型为:

struct clk *clk_register(struct device *dev, struct clk_hw *hw);
int clk_register_clkdev(struct clk *clk, const char *con_id, const char *dev_fmt, ...);

    另外,针对不同的clk类型(如固定频率的clk、clk门、clk驱动等),clk子系统又提供了几个快捷函数以完成clk_register()的过程:

struct clk *clk_register_fixed_rate(struct device *dev, const char *name,
            const char *parent_name, unsigned long flags, unsigned long fixed_rate);
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);
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);

以drivers/clk/sirf/clk-prima2.c为例,与该驱动对应的芯片SiRFprimaII的外围接了一个26MHz的晶振和一个
32.768kHz的RTC晶振,在26MHz晶振的后面又有3个PLL,PLL后面又接了更多的clk节点,相关驱动代码如清单20.26所示。
代码清单20.26 clk驱动案例

static unsigned long pll_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
{
        unsigned long fin = parent_rate;
        struct clk_pll *clk = to_pllclk(hw);
         …
}

static long pll_clk_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate)
{
             …
}

static int pll_clk_set_rate(struct clk_hw *hw, unsigned long rate,unsigned long parent_rate)
{
             …

}

static struct clk_ops std_pll_ops = {
        .recalc_rate = pll_clk_recalc_rate,
        .round_rate = pll_clk_round_rate,
        .set_rate = pll_clk_set_rate,
};

static const char *pll_clk_parents[] = {
        "osc",
};

static struct clk_init_data clk_pll1_init = {
        .name = "pll1",
        .ops = &std_pll_ops,
        .parent_names = pll_clk_parents,
        .num_parents = ARRAY_SIZE(pll_clk_parents),
};

static struct clk_init_data clk_pll2_init = {
        .name = "pll2",
        .ops = &std_pll_ops,
        .parent_names = pll_clk_parents,
        .num_parents = ARRAY_SIZE(pll_clk_parents),
};

static struct clk_init_data clk_pll3_init = {
        .name = "pll3",
        .ops = &std_pll_ops,
        .parent_names = pll_clk_parents,
        .num_parents = ARRAY_SIZE(pll_clk_parents),
};

static struct clk_pll clk_pll1 = {
        .regofs = SIRFSOC_CLKC_PLL1_CFG0,
        .hw = {
                  .init = &clk_pll1_init,
        },
};

static struct clk_pll clk_pll2 = {
        .regofs = SIRFSOC_CLKC_PLL2_CFG0,
        .hw = {
                  .init = &clk_pll2_init,
        },
};

static struct clk_pll clk_pll3 = {
        .regofs = SIRFSOC_CLKC_PLL3_CFG0,
        .hw = {
                  .init = &clk_pll3_init,
        },
};

void __init sirfsoc_of_clk_init(void)
{
         …

        /* These are always available (RTC and 26MHz OSC)*/
        clk = clk_register_fixed_rate(NULL, "rtc", NULL, CLK_IS_ROOT, 32768);
        BUG_ON(!clk);
        clk = clk_register_fixed_rate(NULL, "osc", NULL, CLK_IS_ROOT, 26000000);
        BUG_ON(!clk);

        clk = clk_register(NULL, &clk_pll1.hw);
        BUG_ON(!clk);
        clk = clk_register(NULL, &clk_pll2.hw);
        BUG_ON(!clk);
        clk = clk_register(NULL, &clk_pll3.hw);
        BUG_ON(!clk);
         …

}

    目前内核更加倡导的方法是通过设备树来描述电路板上的时钟树,以及时钟和设备之间的绑定关系。通常需要在clk控制器的节点中定义#clock-cells属性,并且在clk驱动中通过of_clk_add_provider()注册时钟控制器为一个时钟树的提供者(Provider),并建立系统中各个时钟和索引的映射表,如:


        在每个具体的设备中,对应的.dts节点上的clocks=<&clks index>属性指向其引用的clk控制器节点以及

使用的时钟的索引,如:

gps@a8010000 {
         compatible = "sirf,prima2-gps";
         reg = <0xa8010000 0x10000>;
         interrupts = <7>;
         clocks = <&clks 9>;
};

    要特别强调的是,在具体的设备驱动中,一定要通过通用clk API来操作所有的时钟,而不要直接通过读写clk控制器的寄存器来进行,这些API包括:

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);
int clk_prepare(struct clk *clk);
void clk_unprepare(struct clk *clk);
void clk_disable(struct clk *clk);
static inline int clk_prepare_enable(struct clk *clk);
static inline void clk_disable_unprepare(struct clk *clk);
unsigned long clk_get_rate(struct clk *clk);
int clk_set_rate(struct clk *clk, unsigned long rate);
struct clk *clk_get_parent(struct clk *clk);
int clk_set_parent(struct clk *clk, struct clk *parent);

备注:

     名称中含有prepare、unprepare字符串的API是内核后来才加入的,过去只有clk_enable()和clk_disable()。只有clk_enable()和clk_disable()带来的问题是,有时候,某些硬件使能/禁止时钟可能会引起睡眠以使得使能/禁止不能在原子上下文进行。加上prepare后,把过去的clk_enable()分解成不可在原子上下文调用的clk_prepare()(该函数可能睡眠)和可以在原子上下文调用的clk_enable()。而clk_prepare_enable()则同时完成准备和使能的工作,当然也只能在可能睡眠的上下文调用该API。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值