0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

深度解析linux时钟子系统

嵌入式小生 来源:嵌入式小生 2024-09-29 16:46 次阅读

一、clk框架简介

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

wKgZoma8gtGAF233AAEuUivEFwE050.jpg

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

wKgZoma8gtGAXRgmAAIKxHdlzq0254.jpg

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

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

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

用于分频的Divider。

用于多路选择的 MUX。

用于CLOCK ENABLE控制的与门。

使用 CLOCK 的硬件模块(也可称为CONSUMER)。wKgZoma8guSAcNGKAAEU-84XAUM859.png

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实现的而接口存在出入,参见源码更进一步获取接口和使用方法。

注册/注销时钟

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

//带资源管理注册时钟
structclk*devm_clk_register(structdevice*dev,structclk_hw*hw);

//卸载时钟
voidclk_unregister(structclk*clk);
//带资源管理卸载时钟
voiddevm_clk_unregister(structdevice*dev,structclk*clk);

2、驱动中常使用的API

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

#include

获取struct clk指针:

structclk*devm_clk_get(structdevice*dev,constchar*id)(推荐使用,可以自动释放)
structclk*clk_get(structdevice*dev,constchar*id)
staticinlinestructclk*devm_clk_get_optional(structdevice*dev,constchar*id)

//(推荐使用,整组获取,整组开关)
staticinlineint__must_checkdevm_clk_bulk_get(structdevice*dev,intnum_clks,structclk_bulk_data*clks)
staticinlineint__must_checkdevm_clk_bulk_get_optional(structdevice*dev,intnum_clks,structclk_bulk_data*clks)
staticinlineint__must_checkdevm_clk_bulk_get_all(structdevice*dev,structclk_bulk_data**clks)

获取/设置时钟频率

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

//获取时钟频率
unsignedlongclk_get_rate(structclk*clk)

//设置时钟频率
intclk_set_rate(structclk*clk,unsignedlongrate)

准备/使能clk:

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

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

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

/*原子操作,关闭时钟,不会睡眠*/
voidclk_disable(structclk*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):

structclk_notifier{
structclk*clk;//与该通知器关联的clk。
structsrcu_notifier_headnotifier_head;//用于这个CLK的blocking_notifier_head通知器。
structlist_headnode;
};

常用API:

//注册一个通知块(notifierblock),以便在指定时钟发生事件(例如频率变化)时接收通知。
intclk_notifier_register(structclk*clk,structnotifier_block*nb);

//注销一个通知块
intclk_notifier_unregister(structclk*clk,structnotifier_block*nb);

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

2、struct clk_core

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

structclk_core{
constchar*name;//clk核心名称。
conststructclk_ops*ops;//该clk核心对应的ops。
structclk_hw*hw;
structmodule*owner;
structdevice*dev;
structdevice_node*of_node;
structclk_core*parent;
structclk_parent_map*parents;
u8num_parents;
u8new_parent_index;
unsignedlongrate;
unsignedlongreq_rate;
unsignedlongnew_rate;
structclk_core*new_parent;
structclk_core*new_child;
unsignedlongflags;
boolorphan;
boolrpm_enabled;
unsignedintenable_count;
unsignedintprepare_count;
unsignedintprotect_count;
unsignedlongmin_rate;
unsignedlongmax_rate;
unsignedlongaccuracy;
intphase;
structclk_dutyduty;
structhlist_headchildren;
structhlist_nodechild_node;
structhlist_headclks;
unsignedintnotifier_count;
#ifdefCONFIG_DEBUG_FS
structdentry*dentry;
structhlist_nodedebug_node;
#endif
structkrefref;
};

从上述结构的组成元素可知,struct clk_core是struct device的子类,因为一款SOC的时钟关系一般以“树状”进行组织,在struct clk_core中提供描述父clk_core和子clk_core的组成元素。

3、struct clk_ops

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

structclk_ops{
int(*prepare)(structclk_hw*hw);
void(*unprepare)(structclk_hw*hw);
int(*is_prepared)(structclk_hw*hw);
void(*unprepare_unused)(structclk_hw*hw);
int(*enable)(structclk_hw*hw);
void(*disable)(structclk_hw*hw);
int(*is_enabled)(structclk_hw*hw);
void(*disable_unused)(structclk_hw*hw);
int(*save_context)(structclk_hw*hw);
void(*restore_context)(structclk_hw*hw);
unsignedlong(*recalc_rate)(structclk_hw*hw,
unsignedlongparent_rate);
long(*round_rate)(structclk_hw*hw,unsignedlongrate,
unsignedlong*parent_rate);
int(*determine_rate)(structclk_hw*hw,
structclk_rate_request*req);
int(*set_parent)(structclk_hw*hw,u8index);
u8(*get_parent)(structclk_hw*hw);
int(*set_rate)(structclk_hw*hw,unsignedlongrate,
unsignedlongparent_rate);
int(*set_rate_and_parent)(structclk_hw*hw,
unsignedlongrate,
unsignedlongparent_rate,u8index);
unsignedlong(*recalc_accuracy)(structclk_hw*hw,
unsignedlongparent_accuracy);
int(*get_phase)(structclk_hw*hw);
int(*set_phase)(structclk_hw*hw,intdegrees);
int(*get_duty_cycle)(structclk_hw*hw,
structclk_duty*duty);
int(*set_duty_cycle)(structclk_hw*hw,
structclk_duty*duty);
int(*init)(structclk_hw*hw);
void(*terminate)(structclk_hw*hw);
void(*debug_init)(structclk_hw*hw,structdentry*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:

wKgZoma8gvWAG7_QAAE0yaH1VE0799.png

参数说明:

y表示强制,必须实现。

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

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

4、struct clk_gate

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

structclk_gate{
structclk_hwhw;//处理公共接口和特定于硬件的接口。
void__iomem*reg;//寄存器控制门。
u8bit_idx;//单比特控制门。
u8flags;//特定硬件的falg标志。
spinlock_t*lock;//自旋锁。
};

常用API:

to_clk_gate()

clk_register_gate()/clk_unregister_gate()

5、struct clk

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

structclk{
structclk_core*core;//表示clk核心。
structdevice*dev;//clk设备的父设备。
constchar*dev_id;//设备id。
constchar*con_id;
unsignedlongmin_rate;//最小频率。
unsignedlongmax_rate;//最大频率。
unsignedintexclusive_count;//独占计数。
structhlist_nodeclks_node;//clk链表。
};

6、struct clk_hw

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

structclk_hw{
structclk_core*core;//clk核心。
structclk*clk;//clk设备。
conststructclk_init_data*init;//描述clk初始化数据
};

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

7、struct clk_divider

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

structclk_divider{
structclk_hwhw;//处理公共接口和特定硬件的接口
void__iomem*reg;//分频器的寄存器
u8shift;//分频位域的偏移量
u8width;//分频位域的宽度
u8flags;//标志
conststructclk_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用于描述多路复用器的时钟,该结构定义如下:

structclk_mux{
structclk_hwhw;
void__iomem*reg;
constu32*table;
u32mask;
u8shift;
u8flags;
spinlock_t*lock;
};

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

常用API:

voidclk_unregister_mux(structclk*clk);
voidclk_hw_unregister_mux(structclk_hw*hw);

9、struct clk_fixed_factor

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

structclk_fixed_factor{
structclk_hwhw;//处理公共接口和特定硬件的接口。
unsignedintmult;//倍频器
unsignedintdiv;//分频器
};

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

10、struct clk_fractional_divider

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

structclk_fractional_divider{
structclk_hwhw;//处理公共接口和特定硬件的接口
void__iomem*reg;//用于分频器的寄存器
u8mshift;//分频位域分子的偏移量
u8mwidth;//分频位域分子的宽度
u8nshift;//分频位域分母的偏移量
u8nwidth;//分频位域分母的宽度
u8flags;//标志位
void(*approximation)(structclk_hw*hw,//近似方法的callback
unsignedlongrate,unsignedlong*parent_rate,
unsignedlong*m,unsignedlong*n);
spinlock_t*lock;//注册锁
};

11、struct clk_multiplier

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

structclk_multiplier{
structclk_hwhw;//处理公共接口和特定硬件的接口
void__iomem*reg;//倍频器的寄存器
u8shift;//乘法位域的偏移量
u8width;//乘法位域的宽度
u8flags;//标志
spinlock_t*lock;//注册锁
};

12、struct clk_composite

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

structclk_composite{
structclk_hwhw;//处理公共接口和特定硬件的接口
structclk_opsops;//clk对应的ops的callback

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

conststructclk_ops*mux_ops;//对mux的时钟ops
conststructclk_ops*rate_ops;//对rate的时钟ops
conststructclk_ops*gate_ops;//对gate的时钟ops
};

常用API:

to_clk_composite()

clk_register_composite()/clk_unregister_composite()

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

wKgaoma8gwOABKaMAAEX0TwU8g0922.png

四、CLK调试

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

cat/sys/debug/kernel/clk/clk_summary

查看目前系统的时钟树(clk_tree)。例如:wKgZoma8gxCATfrpAAItCtHu2dE808.png

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

//getrate:
cat/sys/kernel/debug/aclk_gmac0/clk_rate

//setrate:
echo24000000>/sys/kernel/debug/aclk_gmac0/clk_rate

//打开clk:
echo1>/sys/kernel/debug/aclk_gmac0/clk_enable_count

//关闭clk:
echo0>/sys/kernel/debug/aclk_gmac0/clk_enable_count

五、CLK信息导出

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

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

wKgZoma8gx2ARW_3AAO5S-iOTWE257.png

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

wKgZomb5FouAUo8EAABj7IrREB4451.png

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

staticint__initclk_debug_init(void)
{
structclk_core*core;

#ifdefCLOCK_ALLOW_WRITE_DEBUGFS
pr_warn("
");
pr_warn("********************************************************************
");
pr_warn("**NOTICENOTICENOTICENOTICENOTICENOTICENOTICE**
");
pr_warn("****
");
pr_warn("**WRITEABLEclkDebugFSSUPPORTHASBEENENABLEDINTHISKERNEL**
");
pr_warn("****
");
pr_warn("**Thismeansthatthiskernelisbuilttoexposeclkoperations**
");
pr_warn("**suchasparentorratesetting,enabling,disabling,etc.**
");
pr_warn("**touserspace,whichmaycompromisesecurityonyoursystem.**
");
pr_warn("****
");
pr_warn("**Ifyouseethismessageandyouarenotdebuggingthe**
");
pr_warn("**kernel,reportthisimmediatelytoyourvendor!**
");
pr_warn("****
");
pr_warn("**NOTICENOTICENOTICENOTICENOTICENOTICENOTICE**
");
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);

return0;
}

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时钟描述语句:

wKgZoma8gyqAPVRyAANMrtVtnM0887.png

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

2、驱动层clk

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

常用API有:

//查找并获取对时钟产生器的引用
structclk*clk_get(structdevice*dev,constchar*id);
structclk*devm_clk_get(structdevice*dev,constchar*id);

//当时钟源处于运行状态时,通知系统
intclk_enable(structclk*clk);

//当时钟源不再使用时,通知系统
voidclk_disable(structclk*clk);

clk_prepare_enable()

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

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

staticintserial_imx_probe(structplatform_device*pdev)
{
structimx_port*sport;
void__iomem*base;
intret=0;
structresource*res;
inttxirq,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);
elseif(ret< 0)
  return ret;

 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 base = devm_ioremap_resource(&pdev->dev,res);
if(IS_ERR(base))
returnPTR_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=(unsignedlong)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,"failedtogetipgclk:%d
",ret);
returnret;
}

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,"failedtogetperclk:%d
",ret);
returnret;
}

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
");
returnret;
}
}
sport->port.uartclk=clk_get_rate(sport->clk_per);

/*
*AllocatetheIRQ(s)i.MX1hasthreeinterruptswhereaslater
*chipsonlyhaveoneinterrupt.
*/
if(txirq>0){
ret=devm_request_irq(&pdev->dev,rxirq,imx_rxint,0,
dev_name(&pdev->dev),sport);
if(ret)
returnret;

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

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

platform_set_drvdata(pdev,sport);

returnuart_add_one_port(&imx_reg,&sport->port);
}

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

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

wKgaoma8g0qAY3-YAAFro0UVpQs184.png

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

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

    关注

    3

    文章

    1341

    浏览量

    40109
  • Linux
    +关注

    关注

    87

    文章

    11136

    浏览量

    208095
  • 时钟
    +关注

    关注

    10

    文章

    1685

    浏览量

    131029

原文标题:聊聊linux时钟子系统

文章出处:【微信号:嵌入式小生,微信公众号:嵌入式小生】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Linux下输入子系统上报触摸屏坐标

      在 Linux 中,输入子系统是由输入子系统设备驱动层、输入子系统核心层(Input Core)和输入子系统事件处理层(Event Ha
    的头像 发表于 09-25 08:56 2266次阅读
    <b class='flag-5'>Linux</b>下输入<b class='flag-5'>子系统</b>上报触摸屏坐标

    Linux LED子系统详解

    Linux LED子系统详解
    的头像 发表于 06-10 10:37 1422次阅读
    <b class='flag-5'>Linux</b> LED<b class='flag-5'>子系统</b>详解

    深度搜索Linux操作系统系统构建和原理解析

    深度搜索Linux操作系统系统构建和原理解析!比较好的一本Linux内核书籍,从另一个角度去解
    发表于 09-16 16:40

    如何使用Linux内核中的input子系统

    的 input 子系统下提供的 API 函数接口,完成设备的注册即可。在本章节中我们来学习一下如何使用 Linux内核中的 input 子系统
    发表于 12-29 07:20

    基于Linux内核输入子系统的驱动研究

    Linux因其完全开放的特性和稳定优良的性能深受欢迎,当推出了内核输入子系统后,更方便了嵌入式领域的驱动开放。介绍了Linux的设备驱动基础,详细阐述了基于Linux内核输入
    发表于 09-12 16:38 23次下载

    Linux内核输入子系统的驱动研究

    Linux内核输入子系统的驱动研究
    发表于 10-31 14:41 14次下载
    <b class='flag-5'>Linux</b>内核输入<b class='flag-5'>子系统</b>的驱动研究

    详细了解Linux设备模型中的input子系统

    linux输入子系统linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler)、输入
    发表于 05-12 09:04 1007次阅读
    详细了解<b class='flag-5'>Linux</b>设备模型中的input<b class='flag-5'>子系统</b>

    解析MSP430系统时钟资源

    解析MSP430系统时钟资源
    发表于 09-26 11:39 1次下载

    Windows 子系统助力 Linux 2.0

    Windows 子系统助力 Linux 2.0
    的头像 发表于 01-04 11:17 578次阅读

    Linux系统中NFC子系统架构分析

    目前在Linux系统中,每个厂家都使用不同的方式实现NFC驱动,然后自己在应用层上面做适配。但是Linux也已经推出NFC子系统,很多厂家也逐步在统一。
    发表于 01-04 14:01 1793次阅读

    linux-usb子系统的核心描述

    本文将描述linux-usb子系统的核心,主要分析其核心的初始化流程,文中源码基于内核版本:4.1.15。
    的头像 发表于 01-14 09:37 2438次阅读

    Linux内核之LED子系统(一)

    Linux内核的LED子系统是一种重要的框架,用于管理和控制设备上的LED指示灯。在嵌入式系统和物联网设备中,LED子系统发挥着关键作用,为开发者提供了一种统一的方式来控制和定制LED
    发表于 10-02 16:53 916次阅读
    <b class='flag-5'>Linux</b>内核之LED<b class='flag-5'>子系统</b>(一)

    Linux reset子系统有什么功能

    Linux reset子系统 reset子系统非常简单,与clock子系统非常类似,但在驱动实现上,reset驱动更简单。 因为clock驱动主要是
    的头像 发表于 09-27 14:06 645次阅读
    <b class='flag-5'>Linux</b> reset<b class='flag-5'>子系统</b>有什么功能

    Linux clock子系统是什么

    clock子系统 Linux时钟子系统由CCF(common clock framework)框架管理, CCF向上给用户提供了通用的时钟
    的头像 发表于 09-27 14:25 703次阅读
    <b class='flag-5'>Linux</b> clock<b class='flag-5'>子系统</b>是什么

    时钟子系统中clock驱动实例

    clock驱动实例 clock驱动在时钟子系统中属于provider,provider是时钟的提供者,即具体的clock驱动。 clock驱动在Linux刚启动的时候就要完成,比 in
    的头像 发表于 09-27 14:39 652次阅读
    <b class='flag-5'>时钟</b><b class='flag-5'>子系统</b>中clock驱动实例