一、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实现的而接口存在出入,参见源码更进一步获取接口和使用方法。
注册/注销时钟
//注册一个新的时钟。通常在设备驱动程序中使用,以向时钟框架添加新的时钟源。 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:
参数说明:
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核心数据结构如下图所示:
四、CLK调试
参见debugfs文件系统下的文件可推知目前系统中存在的clk情况,使用如下命令:
cat/sys/debug/kernel/clk/clk_summary
查看目前系统的时钟树(clk_tree)。例如:
可以在用户空间通过/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信息,例如:
每个目录代表一个clk信息,其目录下包含如下信息:
从内核源码角度,创建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时钟描述语句:
上述语句中无论是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); }
在上述代码中,与时钟相关的操作有四个地方:
获取了时钟,在这个通用uart驱动中的其他相关的回调中,则会依托于时钟实现这些回调函数,例如imx_startup():
综上所述,可见时钟框架在linux设备驱动的实现中非常重要,只要与外设相关的驱动实现几乎都需要使用到时钟框架。
-
内核
+关注
关注
3文章
1341浏览量
40109 -
Linux
+关注
关注
87文章
11136浏览量
208095 -
时钟
+关注
关注
10文章
1685浏览量
131029
原文标题:聊聊linux时钟子系统
文章出处:【微信号:嵌入式小生,微信公众号:嵌入式小生】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论