深度解析linux时钟子系统

描述

一、clk框架简介

linux内核中实现了一个CLK子系统,用于对上层提供各模块(例如需要时钟信号的外设,USB等)的时钟驱动接口,对下层提供具体SOC的时钟操作细节:

内核

一般情况下,在可运行linux的处理器平台中,都存在非常复杂的时钟树(clock tree)关系,也一定会有一个非常庞大和复杂的树状图,用于描述与时钟相关的器件,以及这些器件输出的clock关系。查看手册都会存在类似下列的内容:

内核

一款处理器中与时钟相关的器件主要包括有:

用于产生 CLOCK 的 Oscillator(有源振荡器,也称作谐振荡器)或者Crystal(无源振荡器,也称晶振)。

用于倍频的 PLL(锁相环,Phase Locked Loop)。

用于分频的Divider。

用于多路选择的 MUX。

用于CLOCK ENABLE控制的与门。

使用 CLOCK 的硬件模块(也可称为CONSUMER)。内核

linux内核中与clk框架相关源文件如下:

 

/include/linux/clk.h
/include/linux/clkdev.h
/include/linux/clk-provider.h
/include/linux/clk-conf.h
------------------------------------------------------
/drivers/clk/clk-devres.c
/drivers/clk-bulk.c
/drivers/clkdev.c
/drivers/clk.c
/drivers/clk-divider.c
/drivers/clk-fixed-factor.c
/drivers/clk-fixed-rate.c
/drivers/clk-gate.c
/drivers/clk-multiplier.c
/drivers/clk-mux.c
/drivers/clk-composite.c
/drivers/clk-fractional-divider.c
/drivers/clk-gpio.c
/drivers/clk-conf.c

 

二、clk框架接口

1、基于linux时钟子系统对接底层时钟操作的API

linux时钟子系统对接底层,也就是具体硬件常用的API可视为clk provider常用的接口函数,定义在linux/include/linux/clk-provider.h文件中。不同版本linux内核中对于clk-probider.h实现的而接口存在出入,参见源码更进一步获取接口和使用方法。

注册/注销时钟

 

//注册一个新的时钟。通常在设备驱动程序中使用,以向时钟框架添加新的时钟源。
struct clk *clk_register(struct device *dev, struct clk_hw *hw);

//带资源管理注册时钟
struct clk *devm_clk_register(struct device *dev, struct clk_hw *hw);

//卸载时钟
void clk_unregister(struct clk *clk);
//带资源管理卸载时钟
void devm_clk_unregister(struct device *dev, struct clk *clk);

 

2、驱动中常使用的API

芯片厂家会根据clk框架,对下层(即底层硬件)设计出接口,以供上层驱动接口调用,在内核中,提供的接口主要由/include/linux/clk.h文件导出,使用这些API接口时,需包含linux/clk.h头文件:

 

#include 

 

获取struct clk指针:

 

struct clk *devm_clk_get(struct device *dev, const char *id)(推荐使用,可以自动释放)
struct clk *clk_get(struct device *dev, const char *id)
static inline struct clk *devm_clk_get_optional(struct device *dev, constchar *id)

//(推荐使用,整组获取,整组开关)
static inline int __must_check devm_clk_bulk_get(struct device *dev, intnum_clks, struct clk_bulk_data *clks)
static inline int __must_check devm_clk_bulk_get_optional(struct device *dev,int num_clks, struct clk_bulk_data *clks)
static inline int __must_check devm_clk_bulk_get_all(struct device *dev,struct clk_bulk_data **clks)

 

获取/设置时钟频率

 

//根据给定的目标频率计算最接近的可实现频率。这个函数通常在设置时钟频率之前调用,以确保设置的频率是硬件支持的频率之一。
long clk_round_rate(struct clk *clk, unsigned long rate)

//获取时钟频率
unsigned long clk_get_rate(struct clk *clk)

//设置时钟频率
int clk_set_rate(struct clk *clk, unsigned long rate)

 

准备/使能clk:

 

/* 开时钟前调用,可能会造成休眠,所以把休眠部分放到这里,可以原子操作的放到enable里 */
int clk_prepare(struct clk *clk)

/* 停止clock后的善后工作,可能会睡眠。*/
void clk_unprepare(struct clk *clk)

/* 原子操作,打开时钟,这个函数必须在产生实际可用的时钟信号后才能返回,不会睡眠 */
int clk_enable(struct clk *clk)

/* 原子操作,关闭时钟,不会睡眠 */
void clk_disable(struct clk *clk)

 

上述两套API的本质,是把CLOCK的启动/停止分为Atomic和Non-atomic两个阶段,以方便实现和调用。因此上面所说的“不会睡眠/可能会睡眠”,有两个角度的含义:

一是告诉底层的CLOCK Driver,需把可能引起睡眠的操作,放到Prepare()/Unprepare()中实现,一定不能放到Enable()/Disable()中;

二是提醒上层使用CLOCK的Driver,调用Prepare/Unprepare 接口时可能会睡眠,千万不能在Atomic上下文(例如内部包含Mutex 锁、中断关闭、Spinlock 锁保护的区域)调用,而调用Enable()/Disable()接口则可放心。

另外,CLOCK的Enable()/Disable()为什么需要睡眠呢?例如Enable PLL CLOCK,在启动PLL后,需要等待它稳定,然而PLL的稳定时间是很长的,因此这段时间要需要把CPU让出(进程睡眠),不然就会浪费CPU了。

最后,为什么会实现合在一起的clk_prepare_enable()/clk_disable_unprepare()接口呢?如果调用者能确保是在Non-atomic上下文中调用,就可以顺序调用prepare()/enable()、disable()/unprepared(),为了方便Colck框架就封装了这两个接口。

备注:使用clk_prepare_enable / clk_disable_unprepare,clk_prepare_enable / clk_disable_unprepare(或者clk_enable / clk_disable) 必须成对,以使引用计数正确。

三、CLK核心的数据结构和API

1、struct clk_notifier

stuct clk_notifier用于将CLK与通知器进行关联,也就是定义clk的通知器,基于srcu实现。该结构实现如下(/linux/include/linux/clk.h):

 

struct clk_notifier {
 struct clk   *clk;  //与该通知器关联的clk。
 struct srcu_notifier_head notifier_head; //用于这个CLK的blocking_notifier_head通知器。
 struct list_head  node;
};

 

常用API:

 

//注册一个通知块(notifier block),以便在指定时钟发生事件(例如频率变化)时接收通知。
int clk_notifier_register(struct clk *clk, struct notifier_block *nb);

//注销一个通知块
int clk_notifier_unregister(struct clk *clk, struct notifier_block *nb);

//带资源管理注册一个通知块(notifier block),以便在指定时钟发生事件(如频率变化)时接收通知。确保在设备驱动程序卸载时自动注销通知块。
int devm_clk_notifier_register(struct device *dev, struct clk *clk,struct notifier_block *nb);

 

2、struct clk_core

struct clk_core为clk框架的私有结构,定义如下:

 

struct clk_core {
 const char  *name;   //clk核心名称。
 const struct clk_ops *ops;  //该clk核心对应的ops。
 struct clk_hw  *hw;
 struct module  *owner;
 struct device  *dev;
 struct device_node *of_node;
 struct clk_core  *parent;
 struct clk_parent_map *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;
 bool   rpm_enabled;
 unsigned int  enable_count;
 unsigned int  prepare_count;
 unsigned int  protect_count;
 unsigned long  min_rate;
 unsigned long  max_rate;
 unsigned long  accuracy;
 int   phase;
 struct clk_duty  duty;
 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_core是struct device的子类,因为一款SOC的时钟关系一般以“树状”进行组织,在struct clk_core中提供描述父clk_core和子clk_core的组成元素。

3、struct clk_ops

struct clk_ops描述硬件时钟的回调操作,并由驱动程序通过clk_*API调用。该结构定义如下:

 

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);
};

 

prepare:准备启动时钟。该回调直到时钟完全准备好才会返回,调用clk_enable是安全的。这个回调的目的是允许时钟实现执行任何可能休眠的初始化。在prepare_lock被持有的情况下调用。

unprepare:将时钟从准备状态中释放出来。该函数通常会撤销在.prepare回调中完成的工作。在prepare_lock持有的情况下调用。

is_prepared:查询硬件以确定时钟是否准备好。允许此函数休眠,如果此操作不是设置后,将使用prepare计数。(可选的)

unprepare_unused:自动取消时钟准备。只从clk_disable_unused调用,用于特殊需要的时钟准备。在持有prepare互斥锁的情况下调用。这个函数可能会休眠。

enable:自动启用时钟。该函数直到时钟正在生成一个有效的时钟信号之前不能返回,供消费者设备驱动使用。在enable_lock持有情况下调用,该函数必须不能睡眠。

disable:自动禁用时钟。在enable_lock持有情况下调用,该函数必须不能睡眠。

is_enabled:查询硬件以确定时钟是否开启。这个函数不能休眠。如果此操作不是设置,则enable计数将被使用,该函数可选。

disable_unused:自动禁用时钟。只从clk_disable_unused调用用于特殊需要的gate时钟。在enable_lock持有的情况下调用,这个函数不能睡眠。

save_context:保存时钟上下文,为断电做准备。

restore_context:在电源恢复后恢复时钟上下文。

recalc_rate:通过查询硬件重新计算该时钟的速率。如果驱动程序不能计算出这个时钟的速率,它必须返回0。如果此callback未设置,则时钟速率将初始化为0(可选的)。

round_rate:给定一个目标速率作为输入,实际上返回由时钟支持最接近的速率,父速率是一个input/output参数。

determine_rate:给定目标速率作为输入,返回实际上是由时钟支撑的最接近的速率。

set_parent:改变这个时钟的输入源,设置父时钟。

get_parent:查询硬件以确定时钟的父时钟。

set_rate:改变这个时钟的速率。

set_rate_and_parent:更改此时钟的速率和父时钟。

recalc_accuracy:重新计算一下这个钟的精度。时钟的准确性以PPB(十亿分之一)表示。父精度为输入参数。

get_phase:查询硬件以获取时钟的当前相位。

set_phase:将时钟信号的相位按指定的度数移位。

get_duty_cycle:查询硬件,获取时钟当前占空比。

set_duty_cycle:将占空比应用于由分子(第二个参数)和分母(第三个参数)指定的时钟信号。

init:执行特定于平台的初始化魔术。

terminate:释放由init分配的资源。

debug_init:为这个时钟设置特定类型的debugfs条目,在为这个时钟创建了debugfs目录条目之后,调用它一次。上述结构中的回调函数的实现需根据具体情况而定,每个callback的具体含义根据名称可以知道,CLK时钟框架对上层开放的API都会间接调用到这些callback,下表是一个clock硬件矩阵表,用于描述特定应用场景下需要实现的callback:

内核

参数说明:

y表示强制,必须实现。

n表示包含该回调无效或没有必要实现。

空单元格表示是可选的,或者必须根据具体情况评估实现。

4、struct clk_gate

struct clk_gate用于描述门控时钟,该结构定义如下:

 

struct clk_gate {
 struct clk_hw hw;     //处理公共接口和特定于硬件的接口。
 void __iomem *reg; //寄存器控制门。
 u8  bit_idx;      //单比特控制门。
 u8  flags;        //特定硬件的falg标志。
 spinlock_t *lock;    //自旋锁。
};

 

常用API:

 

to_clk_gate()

clk_register_gate()/clk_unregister_gate()

 

5、struct clk

struct clk用于描述一个clk设备,该结构定义如下:

 

struct clk {
 struct clk_core *core;          //表示clk核心。
 struct device *dev;             //clk设备的父设备。
 const char *dev_id;             //设备id。
 const char *con_id;        
 unsigned long min_rate;         //最小频率。
 unsigned long max_rate;         //最大频率。
 unsigned int exclusive_count;   //独占计数。
 struct hlist_node clks_node;    //clk链表。
};

 

6、struct clk_hw

struct clk_hw用于描述特定硬件实列的结构,该结构定义如下:

 

struct clk_hw {
 struct clk_core *core;            //clk核心。
 struct clk *clk;                  //clk设备。       
 const struct clk_init_data *init; //描述clk初始化数据
};

 

struct clk_hw中包含了struct clk_core和struct clk。可以看成是clk框架中对clk核心和clk设备的封装。

7、struct clk_divider

struct clk_divider描述可调的分频时钟,该结构定义如下:

 

struct clk_divider {
 struct clk_hw hw;    //处理公共接口和特定硬件的接口
 void __iomem *reg;   //分频器的寄存器
 u8  shift;    //分频位域的偏移量
 u8  width;    //分频位域的宽度
 u8  flags;    //标志
 const struct clk_div_table *table; //数组的值/除数对,最后一项div = 0。
 spinlock_t *lock;   //注册锁
};

 

具有影响其输出频率的可调分压器的时钟。实现.recalc_rate,.set_rate和.round_rate。

常用API:

 

clk_register_divider()/clk_unregister_divider()

clk_hw_register_divider()/clk_hw_unregister_divider()

 

8、struct clk_mux

struct clk_mux用于描述多路复用器的时钟,该结构定义如下:

 

struct clk_mux {
 struct clk_hw hw;
 void __iomem *reg;
 const u32 *table;
 u32  mask;
 u8  shift;
 u8  flags;
 spinlock_t *lock;
};

 

上述结构中组成元素几乎与struct clk_divider一样。

常用API:

 

void clk_unregister_mux(struct clk *clk);
void clk_hw_unregister_mux(struct clk_hw *hw);

 

9、struct clk_fixed_factor

struct clk_fixed_factor用于倍频和分频时钟。该结构定义如下:

 

struct clk_fixed_factor {
 struct clk_hw hw;   //处理公共接口和特定硬件的接口。
 unsigned int mult; //倍频器
 unsigned int div;  //分频器
};

 

具有固定乘法器和除法器的时钟。输出频率为父时钟速率除以div再乘以mult。在.recalc_rate,.set_rate和.round_rate中实现。

10、struct clk_fractional_divider

struct clk_fractional_divider用于描述可调分数的分频时钟,该结构定义如下:

 

struct clk_fractional_divider {
 struct clk_hw hw;                          //处理公共接口和特定硬件的接口
 void __iomem *reg;                        //用于分频器的寄存器
 u8  mshift;                              //分频位域分子的偏移量
 u8  mwidth;                              //分频位域分子的宽度
 u8  nshift;                              //分频位域分母的偏移量
 u8  nwidth;                              //分频位域分母的宽度
 u8  flags;                               //标志位
 void  (*approximation)(struct clk_hw *hw,   //近似方法的callback
    unsigned long rate, unsigned long *parent_rate,
    unsigned long *m, unsigned long *n);    
 spinlock_t *lock;                            //注册锁
};

 

11、struct clk_multiplier

struct clk_multiplier结构用于描述可调的倍频时钟,该结构定义如下:

 

struct clk_multiplier {
 struct clk_hw hw;          //处理公共接口和特定硬件的接口
 void __iomem *reg;        //倍频器的寄存器
 u8  shift;               //乘法位域的偏移量
 u8  width;               //乘法位域的宽度
 u8  flags;               //标志
 spinlock_t *lock;           //注册锁
};

 

12、struct clk_composite

struct clk_composite结构用于描述多路复用器、分频器和门控时钟的组合时钟。该结构定义如下:

 

struct clk_composite {
 struct clk_hw hw;            //处理公共接口和特定硬件的接口
 struct clk_ops ops;           //clk对应的ops的callback

 struct clk_hw *mux_hw;       //处理复合和硬件特定多路复用时钟
 struct clk_hw *rate_hw;      //处理复合和硬件特定的频率时钟
 struct clk_hw *gate_hw;      //处理之间的组合和硬件特定的门控时钟

 const struct clk_ops *mux_ops;    //对mux的时钟ops
 const struct clk_ops *rate_ops;   //对rate的时钟ops
 const struct clk_ops *gate_ops;   //对gate的时钟ops
};

 

常用API:

 

to_clk_composite()

clk_register_composite()/clk_unregister_composite()

 

clk核心数据结构如下图所示:

内核

四、CLK调试

参见debugfs文件系统下的文件可推知目前系统中存在的clk情况,使用如下命令:

 

cat /sys/debug/kernel/clk/clk_summary

 

查看目前系统的时钟树(clk_tree)。例如:内核

可以在用户空间通过/sys设置时钟节点:

 

//get rate:
cat /sys/kernel/debug/aclk_gmac0/clk_rate

//set rate:
echo 24000000 > /sys/kernel/debug/aclk_gmac0/clk_rate

//打开clk:
echo 1 > /sys/kernel/debug/aclk_gmac0/clk_enable_count

//关闭clk:
echo 0 > /sys/kernel/debug/aclk_gmac0/clk_enable_count

 

五、CLK信息导出

1、与debugfs调试信息相关的初始化

当内核支持debugfs且开启对clk的调试支持,我们可以在/sys文件系统路径中的clk目录下查看关于系统中所有注册的clk信息,例如:

内核

每个目录代表一个clk信息,其目录下包含如下信息:

内核

从内核源码角度,创建debugfs调试目录或文件由clk_debug_init()完成:

 

static int __init clk_debug_init(void)
{
 struct clk_core *core;

#ifdef CLOCK_ALLOW_WRITE_DEBUGFS
 pr_warn("
");
 pr_warn("********************************************************************
");
 pr_warn("**     NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE           **
");
 pr_warn("**                                                                **
");
 pr_warn("**  WRITEABLE clk DebugFS SUPPORT HAS BEEN ENABLED IN THIS KERNEL **
");
 pr_warn("**                                                                **
");
 pr_warn("** This means that this kernel is built to expose clk operations  **
");
 pr_warn("** such as parent or rate setting, enabling, disabling, etc.      **
");
 pr_warn("** to userspace, which may compromise security on your system.    **
");
 pr_warn("**                                                                **
");
 pr_warn("** If you see this message and you are not debugging the          **
");
 pr_warn("** kernel, report this immediately to your vendor!                **
");
 pr_warn("**                                                                **
");
 pr_warn("**     NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE           **
");
 pr_warn("********************************************************************
");
#endif

 rootdir = debugfs_create_dir("clk", NULL);

 debugfs_create_file("clk_summary", 0444, rootdir, &all_lists,
       &clk_summary_fops);
 debugfs_create_file("clk_dump", 0444, rootdir, &all_lists,
       &clk_dump_fops);
 debugfs_create_file("clk_orphan_summary", 0444, rootdir, &orphan_list,
       &clk_summary_fops);
 debugfs_create_file("clk_orphan_dump", 0444, rootdir, &orphan_list,
       &clk_dump_fops);

 mutex_lock(&clk_debug_lock);
 hlist_for_each_entry(core, &clk_debug_list, debug_node)
  clk_debug_create_one(core, rootdir);

 inited = 1;
 mutex_unlock(&clk_debug_lock);

 return 0;
}

 

clk_debug_init()函数由late_initcall()(/drivers/clk.c)导出。

六、clk驱动设计

1、底层驱动(clk-provider)

对于一款SOC,特定厂家都会针对时钟编写对应的驱动。包括用于以下功能的文件:

用于倍频的 PLL(锁相环,Phase Locked Loop)。

用于分频的Divider。

用于多路选择的 MUX。

用于CLOCK ENABLE控制的与门。

使用 CLOCK 的硬件模块(也可称为CONSUMER)。

在设计这些clk驱动时,本质上是实现对应struct clk_ops下的callback后,调用clk_register注册进linux内核的时钟框架。不同的时钟器件在内核中都存在与之对应的数据结构,且开放有对应的API接口,将其注册到内核中。

例如Nxp的Imx6ul这款SOC,在/arch/arm/mach-imx/clk-imx6ull.c中则实现了对应时钟框架的底层驱动,由imx6ul_clocks_init()实现:

 

CLK_OF_DECLARE(imx6ul, "fsl,imx6ul-ccm", imx6ul_clocks_init);

 

存在下图类似的SOC时钟描述语句:

内核

上述语句中无论是imx_clk_mux()还是imx_clk_pllv3()都会调用clk_register()向内核注册时钟资源。

2、驱动层clk

当底层(clk-provider)设计完成后,在驱动层(也称为消费者(Consumer))则可以很方便的获取对应的clk句柄,并可以进行enable/disable时钟等操作了。

常用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);

//当时钟源不再使用时,通知系统
void clk_disable(struct clk *clk);

clk_prepare_enable()

 

在内核源码中,可以发现很多的驱动设计都会使用到时钟子系统,用于对外设的控制和开启/停止。

例如,在Nxp提供的一个名为imx.c(/drivers/tty/serial)的通用uart驱动中,在.probe中则会首先进行时钟相关的操作:

 

static int serial_imx_probe(struct platform_device *pdev)
{
 struct imx_port *sport;
 void __iomem *base;
 int ret = 0;
 struct resource *res;
 int txirq, rxirq, rtsirq;

 sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);
 if (!sport)
  return -ENOMEM;

 ret = serial_imx_probe_dt(sport, pdev);
 if (ret > 0)
  serial_imx_probe_pdata(sport, pdev);
 else if (ret < 0)
  return ret;

 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 base = devm_ioremap_resource(&pdev->dev, res);
 if (IS_ERR(base))
  return PTR_ERR(base);

 rxirq = platform_get_irq(pdev, 0);
 txirq = platform_get_irq(pdev, 1);
 rtsirq = platform_get_irq(pdev, 2);

 sport->port.dev = &pdev->dev;
 sport->port.mapbase = res->start;
 sport->port.membase = base;
 sport->port.type = PORT_IMX,
 sport->port.iotype = UPIO_MEM;
 sport->port.irq = rxirq;
 sport->port.fifosize = 32;
 sport->port.ops = &imx_pops;
 sport->port.rs485_config = imx_rs485_config;
 sport->port.rs485.flags =
  SER_RS485_RTS_ON_SEND | SER_RS485_RX_DURING_TX;
 sport->port.flags = UPF_BOOT_AUTOCONF;
 init_timer(&sport->timer);
 sport->timer.function = imx_timeout;
 sport->timer.data     = (unsigned long)sport;

 sport->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
 if (IS_ERR(sport->clk_ipg)) {
  ret = PTR_ERR(sport->clk_ipg);
  dev_err(&pdev->dev, "failed to get ipg clk: %d
", ret);
  return ret;
 }

 sport->clk_per = devm_clk_get(&pdev->dev, "per");
 if (IS_ERR(sport->clk_per)) {
  ret = PTR_ERR(sport->clk_per);
  dev_err(&pdev->dev, "failed to get per clk: %d
", ret);
  return ret;
 }

 sport->port.uartclk = clk_get_rate(sport->clk_per);
 if (sport->port.uartclk > IMX_MODULE_MAX_CLK_RATE) {
  ret = clk_set_rate(sport->clk_per, IMX_MODULE_MAX_CLK_RATE);
  if (ret < 0) {
   dev_err(&pdev->dev, "clk_set_rate() failed
");
   return ret;
  }
 }
 sport->port.uartclk = clk_get_rate(sport->clk_per);

 /*
  * Allocate the IRQ(s) i.MX1 has three interrupts whereas later
  * chips only have one interrupt.
  */
 if (txirq > 0) {
  ret = devm_request_irq(&pdev->dev, rxirq, imx_rxint, 0,
           dev_name(&pdev->dev), sport);
  if (ret)
   return ret;

  ret = devm_request_irq(&pdev->dev, txirq, imx_txint, 0,
           dev_name(&pdev->dev), sport);
  if (ret)
   return ret;
 } else {
  ret = devm_request_irq(&pdev->dev, rxirq, imx_int, 0,
           dev_name(&pdev->dev), sport);
  if (ret)
   return ret;
 }

 imx_ports[sport->port.line] = sport;

 platform_set_drvdata(pdev, sport);

 return uart_add_one_port(&imx_reg, &sport->port);
}

 

在上述代码中,与时钟相关的操作有四个地方:内核

获取了时钟,在这个通用uart驱动中的其他相关的回调中,则会依托于时钟实现这些回调函数,例如imx_startup():

内核

综上所述,可见时钟框架在linux设备驱动的实现中非常重要,只要与外设相关的驱动实现几乎都需要使用到时钟框架。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分