Linux下时钟框架实践---一款芯片的时钟树配置

关键词:时钟、PLL、Mux、Divider、Gate、clk_summary等。

时钟和电源是各种设备的基础设施,整个时钟框架可以抽象为几种基本的元器件:负责提供晶振

Linux内核提供了良好的CCF(Common Clock Framework),框架的两端一个是provider,一个是consumer。

provider指的是提供时钟模块,包括晶振、PLL、Mux、Divider、Gate等,consumer指的是使用这些时钟的模块。

1. Linux时钟框架基础

相关文档对时钟框架做了详细的介绍:《Linux common clock framework(1)_概述》、《Linux common clock framework(2)_clock provider》、《Linux common clock framework(3)_实现逻辑分析》以及《Common Clock Framework系统结构》。

这里简单罗列一下相关知识。

1.1 编写时钟provider驱动

provider包含基本硬件元素:Oscillator/Crystal-提供时钟晶振、PLL-倍频、Mux-多路选择、Divider-分频器、Gate-控制开关,还有Fixed-Divider-固定分频器。

这些硬件都可以抽象成一种类型的时钟,所有类型的时钟都可以通过struct clk_hw描述。

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

struct clk_core {
    const char        *name;
    const struct clk_ops    *ops;
    struct clk_hw        *hw;
    struct module        *owner;
    struct clk_core        *parent;
    const char        **parent_names;
    struct clk_core        **parents;
    u8            num_parents;
    u8            new_parent_index;
    unsigned long        rate;
    unsigned long        req_rate;
    unsigned long        new_rate;
    struct clk_core        *new_parent;
    struct clk_core        *new_child;
    unsigned long        flags;
    bool            orphan;
    unsigned int        enable_count;
    unsigned int        prepare_count;
    unsigned long        min_rate;
    unsigned long        max_rate;
    unsigned long        accuracy;
    int            phase;
    struct hlist_head    children;
    struct hlist_node    child_node;
    struct hlist_head    clks;
    unsigned int        notifier_count;
#ifdef CONFIG_DEBUG_FS
    struct dentry        *dentry;
    struct hlist_node    debug_node;
#endif
    struct kref        ref;
};
struct clk_init_data {
    const char        *name;
    const struct clk_ops    *ops;
    const char        * const *parent_names;
    u8            num_parents;
    unsigned long        flags;
};
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);
};

clk_register()将描述时钟的struct clk_hw注册,转化成strcut clk变量。

但在实际使用中,对不同类型的时钟往往调用其对应的封装函数。

对于上面提到的硬件在下面都能找到对应的注册函数,其中包括一个composite设备作为一个组合注册。

struct clk *clk_register(struct device *dev, struct clk_hw *hw)

int clk_hw_register(struct device *dev, struct clk_hw *hw)
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);
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_fixed_factor(struct device *dev, const char *name,
        const char *parent_name, unsigned long flags,
        unsigned int mult, unsigned int div);
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);
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);

最后调用of_clk_add_provider()将注册的时钟加入到OF框架中。

int of_clk_add_provider(struct device_node *np,
            struct clk *(*clk_src_get)(struct of_phandle_args *args,
                           void *data),
            void *data);

1.2 consumer使用时钟

其他设备需要使用时钟,可以再驱动中后去时钟也可以在设备DTS中引用时钟。

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);
void devm_clk_put(struct device *dev, struct clk *clk);
long clk_round_rate(struct clk *clk, unsigned long rate);
int clk_set_rate(struct clk *clk, unsigned long rate);
bool clk_has_parent(struct clk *clk, struct clk *parent);
int clk_set_rate_range(struct clk *clk, unsigned long min, unsigned long max);
int clk_set_min_rate(struct clk *clk, unsigned long rate);
int clk_set_max_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);
struct clk *clk_get_sys(const char *dev_id, const char *con_id);

int clk_prepare(struct clk *clk);
void clk_unprepare(struct clk *clk);
static inline int clk_prepare_enable(struct clk *clk)
static inline void clk_disable_unprepare(struct clk *clk)
struct clk *of_clk_get(struct device_node *np, int index);
struct clk *of_clk_get_by_name(struct device_node *np, const char *name);
struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec);

2. 如何实现一款芯片的时钟框架

对一款芯片配置时钟框架,首先拿到时钟框架图,上面会有详细的Mux关系、是否有Divider、是否是Fixed Divider、是否有gate等等。

将这些器件找到对应的Linux时钟框架抽象,将整张时钟框架图抽象成Linux时钟框架识别的属性结构。

然后还需要每一个器件的寄存器解释。

在有了这些准备工作之后,工作氛围两部分:编写器件抽象驱动,比如Fixed clock、Gate、Divider等;按照时钟框架图编写DTS文件,寄存器参照规格书,compatible和驱动对应。

2.1 编写类型时钟驱动

首先通过CLK_OF_DECLARE()将字符串和xx2000_divider_setup()进行关联,然后在xx2000_divider_setup进行时钟的注册。

static void xx2000_divider_setup(struct device_node *node)
{
    void __iomem *reg;
    struct resource res;
    struct clk *clk;
    unsigned int bit_shift = 0, bit_width = 0;
    const char *clk_name = NULL;
    const char *parent_name;
    int ret = 0;

    if(!node)
        return;

    reg = of_io_request_and_map(node, 0, of_node_full_name(node));-----------------------------------将寄存器映射,后续对divider的设置以及读取都需要此寄存器。
    if(IS_ERR(reg)) {
        pr_err("%s <%s> must have a reg property.\n", __func__, node->name);
        return;
    }

    if(of_property_read_u32(node, "bit-shift", &bit_shift)) {----------------------------------------操作divider需要知道配置divider的位偏移及位宽。然后根据频率选择divider的值,设置到寄存器中。获取时钟频率也通过读取寄存器值进行计算。
        pr_err("%s <%s> must have a bit-shift property.\n", __func__, node->name);
        goto err_unmap;
    }
    if(of_property_read_u32(node, "bit-width", &bit_width)) {
        pr_err("%s <%s> must have a bit-width property.\n", __func__, node->name);
        goto err_unmap;
    }

    parent_name = of_clk_get_parent_name(node, 0);----------------------------------------------------获取父时钟名称。
    if(!parent_name)
    {
        pr_err("%s <%s> must have a parent.\n", __func__, node->name);
        goto err_unmap;
    }

    of_property_read_string(node, "clock-output-names", &clk_name);

    clk = clk_register_divider(NULL, clk_name, parent_name, 0, reg, bit_shift, bit_width, 0, NULL);---注册divider时钟,必须要有的参数有reg、bit_shift、bit_width,以及本身的名称。
    if(IS_ERR(clk))
    {
        pr_err("%s Failed to register <%s>.\n", __func__, node->name);
        goto err_unmap;
    }

    ret = of_clk_add_provider(node, of_clk_src_simple_get, clk);--------------------------------------将注册的时钟加入到OF框架。
    if(ret)
    {
        pr_err("%s Failed to add <%s>.\n", __func__, node->name);
        goto err_unregister;
    }

    return;

    err_unregister:
        clk_unregister_divider(clk);

    err_unmap:
        iounmap(reg);
        of_address_to_resource(node, 0, &res);
        release_mem_region(res.start, resource_size(&res));
    return;
}

CLK_OF_DECLARE(xx2000_clk_divider, "xx2000,clk-divider", xx2000_divider_setup);

2.2 编写DTS文件

有了上面的时钟框架图、时钟寄存器规格书和驱动,就可以按部就班的按照时钟框架图一步一步编写DTS。

  • 编写fixed clock的晶振、PLL等;
  • 编写多路复用Mux和分频器Divider,需要配置寄存器以及寄存器的bit-shift和bit-width。

具体的DTS配置,参考如下:

            cpu_core_clk: cpu-core-clk {---------------------------------cpu_core_clk是在其他设备中clocks指向的名称。
                #clock-cells = <0>;--------------------------------------0表示只有一个输出,1表示多余一个输出。
                compatible = "xx2000,clk-divider";-----------------------如果有特殊需求,还需要编写自己的驱动。这里通过此字符串进行匹配。
                reg = <CPU_CLK_DIV 0x4>;---------------------------------配置此事中的寄存器地址以及大小。
                bit-shift = <0>;-----------------------------------------对于divider类型需要知道配置bit在寄存器中的偏移以及bit位宽。
                bit-width = <5>;
                clocks = <&cpu_mux 0>;-----------------------------------clocks指向父时钟。
                clock-output-names = "cpu_core_clk";---------------------本时钟输出名称,在consumer时钟中可以使用此名称来获得该时钟的struct clk结构体。
            };

3. 对时钟框架进行验证

3.1 clk_summary验证时钟树

通过读取/sys/kernel/debug/clk/clk_summary信息,和时钟框图对照,可以验证DTS配置正确与否。

   clock                             enable  prepare_cnt        rate   accuracy   phase
---------------------------------------------------------------------------------------- ddr_pll                                  0            0  1200000000          0 0
 nn_pll                                   0            0   750000000          0 0
 video_pll                                0            0  1100000000          0 0
    sdio0_mux                             0            0  1100000000          0 0
       sdio0_cclk_divider                 0            0    39285715          0 0
          sdio0_cclk                      0            0    39285715          0 0...
 cpu_pll                                  0            0  1000000000          0 0
    cpu_mux                               0            0  1000000000          0 0
       cpu_core_clk                       0            0  1000000000          0 0
          cpu_bus_clk                     0            0   500000000          0 0
             cpu_apb_clk                  0            0   250000000          0 0
             ddr_cpu_port_clk             0            0   500000000          0 0
 rtc_clk                                  0            0       32768          0 0
    tsen_mux                              0            0       32768          0 0
       tsen_clk                           0            0       32768          0 0
 ref_clk                                  0            0    24000000          0 0
    wdt_clk                               0            0    24000000          0 0
    timer3_clk                            0            0    24000000          0 0
    timer2_clk                            0            0    24000000          0 0
    timer1_clk                            0            0    24000000          0 0
    timer0_clk                            0            0    24000000          0 0
    ref_clk_750_fixed_factor              0            0       32000          0 0
       usb_suspend_clk                    0            0       32000          0 0  

3.2 验证时钟实际输出

在/sys/kernel/debug/clk目录下,每个时钟都有自己的目录。

在clk_debug_create_one()函数中,对divider和gate类型时钟创建相应的节点用于控制硬件。

 static int clk_debug_create_one(struct clk_core *core, struct dentry *pdentry)
 {
        struct dentry *d;
@@ -2182,6 +2290,7 @@ static int clk_debug_create_one(struct clk_core *core, struct dentry *pdentry)
                if (ret)
                        goto err_out;
        }
+xx2000_clk_create(core);

        ret = 0;
        goto out;

下面根据struct clk_core所对应的struct clk_ops来判断时钟的类型,gate创建xx2000_gate,divider创建xx2000_rate节点。

static ssize_t xx2000_gate_read(struct file *filp, char __user *buffer,
                    size_t count, loff_t *ppos)
{
    struct clk_core *pdata = filp->private_data;
    unsigned int value;
    char tmp[32];
    size_t size;

    value = __clk_is_enabled(pdata->hw->clk);
    size = sprintf(tmp, "%u\n", value);
    printk("%s value=%u\n", __func__, value);

    return simple_read_from_buffer(buffer, count, ppos, tmp, size);
}

static ssize_t xx2000_gate_write(struct file *filp,
                     const char __user *buffer,
                     size_t count, loff_t *ppos)
{
    struct clk_core *pdata = filp->private_data;
    unsigned int value;
    int ret = 0;

    ret = kstrtouint_from_user(buffer, count, 0, &value);
    if (ret)
        return -EFAULT;

    printk("%s name=%s value=%u\n", __func__, pdata->name, value);

    if(value)
        clk_prepare_enable(pdata->hw->clk);
    else
        clk_disable_unprepare(pdata->hw->clk);
    return count;
}

static const struct file_operations xx2000_gate_ops = {
    .owner = THIS_MODULE,
    .open = simple_open,
    .read = xx2000_gate_read,
    .write = xx2000_gate_write,
    .release = single_release,
};

static ssize_t xx2000_rate_read(struct file *filp, char __user *buffer,
                    size_t count, loff_t *ppos)
{
    struct clk_core *pdata = filp->private_data;
    unsigned long rate;
    char tmp[32];
    size_t size;

    rate = clk_get_rate(pdata->hw->clk);
    size = sprintf(tmp, "%lu\n", rate);
    printk("%s value=%lu\n", __func__, rate);

    return simple_read_from_buffer(buffer, count, ppos, tmp, size);
}

static ssize_t xx2000_rate_write(struct file *filp,
                     const char __user *buffer,
                     size_t count, loff_t *ppos)
{
    struct clk_core *pdata = filp->private_data;
    unsigned int rate;
    int ret = 0;

    ret = kstrtouint_from_user(buffer, count, 0, &rate);
    if (ret)
        return -EFAULT;

    printk("%s value=%u\n", __func__, rate);

    if(rate)
        clk_set_rate(pdata->hw->clk, rate);
    return count;
}

static const struct file_operations xx2000_rate_ops = {
    .owner = THIS_MODULE,
    .open = simple_open,
    .read = xx2000_rate_read,
    .write = xx2000_rate_write,
    .release = single_release,
};

void xx2000_clk_create(struct clk_core *core)
{
    const struct clk_ops *clk_ops = core->ops;

    //printk("%s %s %p %p %p %p\n", __func__, core->name ,clk_ops, &clk_gate_ops, &clk_mux_ops, &clk_divider_ops);
    if(clk_ops == &clk_gate_ops)
    {
        debugfs_create_file("xx2000_gate", S_IRUSR | S_IWUSR, core->dentry, core, &xx2000_gate_ops);
    }
    else if(clk_ops == &clk_mux_ops)
    {
//        debugfs_create_file("xx2000_mux", S_IRUSR | S_IWUSR, core->dentry, core, &xx2000_mux_ops);
    }
    else if(clk_ops == &clk_divider_ops)
    {
        debugfs_create_file("xx2000_rate", S_IRUSR | S_IWUSR, core->dentry, core, &xx2000_rate_ops);
    }
}

选择合适的clk输出pin,对上面的不同时钟进行开关、频率选择。

可以通过clk_summary查看结果;还可以通过测量pin输出波形验证结果是否正确。

4. 小结

Linux提供了良好的时钟框架,wowotec.net对其进行了良好的总结。

在实际应用中,通过时钟框架图对时钟树进行抽象,结合时钟规格书配置时钟树;编写时钟驱动。

然后查看clk_summary,并进行验证;最后在相应的设备驱动中使用时钟。

原文地址:https://www.cnblogs.com/arnoldlu/p/10307827.html

时间: 2024-08-01 12:13:10

Linux下时钟框架实践---一款芯片的时钟树配置的相关文章

linux 通用时钟框架CCF

linux 通用时钟框架CCF 简介 这里讲的时钟是给soc各组件提供时钟的树状框架,并不是内核使用的时间,和其他模块一样,clk也有框架,用以适配不同的平台.适配层之上是客户代码和接口,也就是各模块(如需要时钟信号的外设,usb等)的驱动.适配层之下是具体的soc平台的时钟操作细节. 内核中另外一个具有类似树状框架特点的是regulator框架.对比regulator框架,clk框架不确定性更大,内核中仅仅提供了少数的适配规范,struct clk都是各平台自己的clk驱动实现.       

linux 时钟源初步分析linux kernel 时钟框架详细介绍

初步概念: 看datasheet的关于时钟与定时器的部分, FCLK供给cpu, HCLK供给AHB总线设备(存储器控制器,中断控制器.LCD控制器.DMA.USB主机控制器等), PCLK供给APB总线上的设备(watchdog.IIS.i2c. pwm.定时器.ADC.uart.gpio.rtc.spi) 上电时 fclk的时钟等于外部时钟fin, 然后等待LOCKTIME后, 依照MPLLCON寄存器的设置,倍频到高频. UPLLCON专用于USB同于MPLLCON. 关于分频: CLKD

linux下ejabberd框架搭建

ejabberd为erlang的IM的开源框架,一直想找个时间研究研究: 1.下载Ejabberd安装包 wget http://www.process-one.net/downloads/ejabberd/2.1.13/ejabberd-2.1.13-linux-x86_64-installer.run 2.下载完成后,给安装包加权限,否则无法安装 [[email protected] xmpp]# chmod +x ejabberd-2.1.13-linux-x86_64-installer

Windows和Linux下scrapy框架的安装

windows下安装: 1.安装Anaconda环境管理工具 也可以使用pip安装,值得注意的是如果你使用的是pip安装,你需要解决相应的包依赖(解决依赖一般会让你怀疑人生.怀疑scrapy,建议还是用Anaconda安装,少年!) Anaconda 下载地址:https://www.anaconda.com/download/    里面有不同的平台,不同的版本,根据您的平台进行下载即可!这里的话建议大家下载Anaconda全包,而不是Miniconda,因为后者虽然小巧,但会出现一些问题,如

Linux下Spark框架配置(Python)

简述  Spark是UC Berkeley AMP lab所开源的类Hadoop MapReduce的通用并行框架,Spark,拥有Hadoop MapReduce所具有的优点:但不同于MapReduce的是Job中间输出结果可以保存在内存中,从而不再需要读写HDFS,因此Spark能更好地适用于数据挖掘与机器学习等需要迭代的MapReduce的算法.由于spark带有Python的API,而本人比较专于Python语言.因此在此分享一下我在配置spark的方法以及心得. 配置过程 步骤一: 下

linux下时钟及时间同步机制

linux有两个时钟:系统时钟和硬件时钟 硬件时钟:hwclock    显示硬件时钟 系统时钟:data只修改系统时间 hwclock --help -s    硬件设为系统 -w    系统设为硬件 ntp:Network Time Protocol ntpdate --help ntpdate 127.0.0.1

[日常] Linux下的docker实践

1.Linux 发展出了另一种虚拟化技术:Linux 容器(Linux Containers,缩写为 LXC) 2.Linux 容器不是模拟一个完整的操作系统,而是对进程进行隔离 3.Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口 4.Docker 是服务器----客户端架构.命令行运行docker命令的时候,需要本机有 Docker 服务 curl -sSL https://get.docker.com/ | sh service docker start 5.Do

linux下Flask框架搭建简单网页

开始安装FLASK需要创建一个虚拟环境,虚拟环境可以不干扰正在使用的系统环境,避免影响,并且也不需要完全的root权限,更加安全可靠. 搭建环境 Python3.4 进入到microblog目录下创建一个虚拟环境 python -m venv flask 一些系统中可能需要使用命令Python3 Python2.7则需要安装虚拟环境 sudo apt-get install python-virtualenv 创建虚拟环境 virtualenv flask 进入flask目录下的bin然后安装f

linux下storm(0.9版本以上)的环境配置和小Demo

一.引言: 在storm发布到0.9.x以后,配置storm将会变得简单很多,也就是只需要配置zookeeper和storm即可,而不再需要配置zeromq和jzmq,由于网上面的storm配置绝大部分都是0.9以前的storm版本,所以有很多工作是不需要进行的,下面就storm的0.9.5版本在linux环境下进行配置进行详细解析. 由于配置storm只需要两个步骤,大大简化了配置,也是storm团队做了很大的努力,让程序员们专注于程序,让storm配置进行异常简单,好了,废话说了不少,下面正