Linuxclock子系统及驱动实例
2023-06-06 14:16:41 面包芯语

consumer:


(资料图片仅供参考)

时钟的使用者,clock子系统向consumer的提供通用的时钟API接口,使其可以屏蔽底层硬件差异。提供给consumer操作的API如下:

structclk*clk_get(structdevice*dev,constchar*id);structclk*devm_clk_get(structdevice*dev,constchar*id);intclk_enable(structclk*clk);//使能时钟,不会睡眠voidclk_disable(structclk*clk);//使能时钟,不会睡眠unsignedlongclk_get_rate(structclk*clk);voidclk_put(structclk*clk);longclk_round_rate(structclk*clk,unsignedlongrate);intclk_set_rate(structclk*clk,unsignedlongrate);intclk_set_parent(structclk*clk,structclk*parent);structclk*clk_get_parent(structclk*clk);intclk_prepare(structclk*clk);voidclk_unprepare(structclk*clk);intclk_prepare_enable(structclk*clk)//使能时钟,可能会睡眠voidclk_disable_unprepare(structclk*clk)//禁止时钟,可能会睡眠unsignedlongclk_get_rate(structclk*clk)//获取时钟频率

consumer在使用这些API时,必须先调用devm_clk_get()clk_get()获取一个struct clk *指针句柄,后续都通过传入该句柄来操作,struct clk相当于实例化一个时钟。

ccf:

clock子系统的核心,用一个struct clk_core结构体表示,每个注册设备都对应一个struct clk_core

provider(时钟的提供者):

struct clk_hw:表示一个具体的硬件时钟。

struct clk_init_data:struct clk_hw结构体成员,用于表示该时钟下的初始化数据,如时钟名字name、操作函数ops等。

//include/linux/clk-provider.hstructclk_hw{structclk_core*core;structclk*clk;conststructclk_init_data*init;}structclk_init_data{constchar*name;//时钟名字conststructclk_ops*ops;//时钟硬件操作函数集合constchar*const*parent_names;//父时钟名字conststructclk_parent_data*parent_data;conststructclk_hw**parent_hws;u8num_parents;unsignedlongflags;}

struct clk_ops:时钟硬件操作的函数集合,定义了操作硬件的回调函数,consumer在调用clk_set_rate()等API时会调用到struct clk_ops具体指向的函数,这个需要芯片厂商开发clock驱动时去实现。

//include/linux/clk-provider.hstructclk_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);};

3、时钟API的使用

对于一般的驱动开发(非clock驱动),我们只需要在dts中配置时钟,然后在驱动调用通用的时钟API接口即可。

1、设备树中配置时钟

mmc0:mmc0@0x12345678{compatible="xx,xx-mmc0";......clocks=<&periPERI_MCI0>;//指定mmc0的时钟来自PERI_MCI0,PERI_MCI0的父时钟是periclocks-names="mmc0";//时钟名,调用devm_clk_get获取时钟时,可以传入该名字......};

以mmc的设备节点为例,上述mmc0指定了时钟来自PERI_MCI0,PERI_MCI0的父时钟是peri,并将所指定的时钟给它命名为"mmc0"。

2、驱动中使用API接口

简单的使用:

/*1、获取时钟*/host->clk=devm_clk_get(&pdev->dev,NULL);//或者devm_clk_get(&pdev->dev,"mmc0")if(IS_ERR(host->clk)){dev_err(dev,"failedtofindclocksource\n");ret=PTR_ERR(host->clk);gotoprobe_out_free_dev;}/*2、使能时钟*/ret=clk_prepare_enable(host->clk);if(ret){dev_err(dev,"failedtoenableclocksource.\n");gotoprobe_out_free_dev;}probe_out_free_dev:kfree(host);

在驱动中操作时钟,第一步需要获取struct clk指针句柄,后续都通过该指针进行操作,例如:设置频率:

ret=clk_set_rate(host->clk,300000);

获得频率:

ret=clk_get_rate(host->clk);

注意:devm_clk_get()的两个参数是二选一,可以都传入,也可以只传入一个参数。

像i2c、mmc等这些外设驱动,通常只需要使能门控即可,因为这些外设并不是时钟源,它们只有开关。如果直接调用clk_ser_rate函数设置频率,clk_set_rate会向上传递,即设置它的父时钟频率。例如在该例子中直接调用clk_set_rate函数,最终设置的是时钟源peri的频率。

4、clock驱动实例

clock驱动在时钟子系统中属于provider,provider是时钟的提供者,即具体的clock驱动。

clock驱动在Linux刚启动的时候就要完成,比initcall都要早期,因此clock驱动是在内核中进行实现。在内核的drivers/clk目录下,可以看到各个芯片厂商对各自芯片clock驱动的实现:

下面以一个简单的时钟树,举例说明一个芯片的时钟驱动的大致实现过程:

1、时钟树

通常来说,一个芯片的时钟树是比较固定的,例如,以下时钟树:

时钟树的根节点一般是晶振时钟,上图根节点为24M晶振时钟。根节点下面是PLL,PLL用于提升频率。PPL0下又分频给PERI、DSP和ISP。PLL1分频给DDR和ENC。

对于PLL来说,PLL的频率可以通过寄存器设置,但通常是固定的,所以PLL属于固定时钟。

对PERI、DSP等模块来说,它们的频率来自于PLL的分频,因此这些模块的时钟属于分频时钟。

2、设备树

设备树中表示一个时钟源,应有如下属性,例如24M晶振时钟:

clocks{osc24M:osc24M{compatible="fixed-clock";#clock-cells=<0>;clock-output-name="osc24M";clock-frequency=<24000000>;};};

3、驱动实现

clock驱动编写的基本步骤:

fixed_clk固定时钟实现

fixed_clk针对像PLL这种具有固定频率的时钟,对于PLL,我们只需要实现.recalc_rate函数。

设备树:

#definePLL0_CLK0clocks{osc24M:osc24M{compatible="fixed-clock";#clock-cells=<0>;clock-output-names="osc24M";clock-frequency=<24000000>;};pll0:pll0{compatible="xx,choogle-fixed-clk";#clock-cells=<0>;clock-id=;clock-frequency=<1000000000>;clock-output-names="pll0";clocks=<&osc24M>;};};

驱动:

#include#include#include#include#include#include#include#include#include#defineCLOCK_BASE0X12340000#defineCLOCK_SIZE0X1000structxx_fixed_clk{void__iomem*reg;//保存映射后寄存器基址unsignedlongfixed_rate;//频率intid;//clockidstructclk_hw*;};staticunsignedlongxx_pll0_fixed_clk_recalc_rate(structclk_hw*hw,unsignedlongparent_rate){unsignedlongrecalc_rate;//硬件操作:查询寄存器,获得分频系数,计算频率然后返回returnrecalc_rate;}staticstructclk_opsxx_pll0_fixed_clk_ops={.recalc_rate=xx_pll0_fixed_clk_recalc_rate,};structclk_ops*xx_fixed_clk_ops[]={&xx_pll0_fixed_clk_ops,};structclk*__initxx_register_fixed_clk(constchar*name,constchar*parent_name,void__iomem*res_reg,u32fixed_rate,intid,conststructclk_ops*ops){structxx_fixed_clk*fixed_clk;structclk*clk;structclk_init_datainit={};fixed_clk=kzalloc(sizeof(*fixed_clk),GFP_KERNEL);if(!fixed_clk)returnERR_PTR(-ENOMEM);//初始化structclk_init_data数据init.name=name;init.flags=CLK_IS_BASIC;init.parent_names=parent_name?&parent_name:NULL;init.num_parents=parent_name?1:0;fixed_clk->reg=res_reg;//保存映射后的基址fixed_clk->fixed_rate=fixed_rate;//保存频率fixed_clk->id=id;//保存clockidfixed_clk->hw.init=&init;//时钟注册clk=clk_register(NULL,&fixed_clk->hw);if(IS_ERR(clk))kfree(fixed_clk);returnclk;}staticvoid__initof_xx_fixed_clk_init(structdevice_node*np){structclk_onecell_data*clk_data;constchar*clk_name=np->name;constchar*parent_name=of_clk_get_parent_name(np,0);void__iomem*res_reg=ioremap(CLOCK_BASE,CLOCK_SIZE);//寄存器基址映射u32rate=-1;intclock_id,index,number;clk_data=kmalloc(sizeof(structclk_onecell_data),GFP_KERNEL);if(!clk_data)return;number=of_property_count_u32_elems(np,"clock-id");clk_data->clks=kcalloc(number,sizeof(structclk*),GFP_KERNEL);if(!clk_data->clks)gotoerr_free_data;of_property_read_u32(np,"clock-frequency",&rate);/***操作寄存器:初始化PLL时钟频率*......*/for(index=0;indexclks[index]=xx_register_fixed_clk(clk_name,parent_name,res_reg,rate,clock_id,ak_fixed_clk_ops[pll_id]);if(IS_ERR(clk_data->clks[index])){pr_err("%sregisterfixedclkfailed:clk_name:%s,index=%d\n",__func__,clk_name,index);WARN_ON(true);continue;}clk_register_clkdev(clk_data->clks[index],clk_name,NULL);//注册时钟设备}clk_data->clk_num=number;if(number==1){of_clk_add_provider(np,of_clk_src_simple_get,clk_data->clks[0]);}else{of_clk_add_provider(np,of_clk_src_onecell_get,clk_data);}return;err_free_data:kfree(clk_data);}CLK_OF_DECLARE(xx_fixed_clk,"xx,xx-fixed-clk",of_xx_fixed_clk_init);

factor_clk分频时钟实现

peri的时钟来自于Pll的分频,对于这类时钟,需要实现.round_rate.set_rate.recalc_rate

设备树:

#definePLL0_CLK0#defeinePLL0_FACTOR_PERI0clocks{osc24M:osc24M{//晶振时钟compatible="fixed-clock";#clock-cells=<0>;clock-output-names="osc24M";clock-frequency=<24000000>;};pll0:pll0{//pll倍频时钟compatible="xx,xx-fixed-clk";#clock-cells=<0>;clock-id=;clock-frequency=<1000000000>;clock-output-names="pll0";clocks=<&osc24M>;//pll的父时钟为24M晶振};factor_pll0_clk:factor_pll0_clk{//pll分频时钟compatible="xx,xx-pll0-factor-clk";#clock-cells=<1>;clock-id=;clock-output-names="pll0_peri";clocks=<&pll0PLL0_CLK>;//PERI子系统的父时钟为pll0};};

驱动:

staticlongxx_factor_pll0_clk_round_rate(structclk_hw*hw,unsignedlongrate,unsignedlong*parent_rate){unsignedlonground_rate;//返回时钟实际支持的最接近速率returnround_rate;}staticintxx_factor_pll0_clk_set_rate(structclk_hw*hw,unsignedlongrate,unsignedlongparent_rate){intret=0;//操作寄存器,设置频率returnret;}staticunsignedlongxx_factor_pll0_clk_recalc_rate(structclk_hw*hw,unsignedlongparent_rate){unsignedlongrecalc_rate;//查询寄存器,获得分频系数,计算频率然后返回returnrecalc_rate;}conststructclk_opsxx_factor_clk_ops={.round_rate=xx_factor_pll0_clk_round_rate,//给定目标速率作为输入,返回时钟.set_rate=xx_factor_pll0_clk_set_rate,.recalc_rate=xx_factor_pll0_clk_recalc_rate,}staticvoid__initof_xx_factor_clk_init(structdevice_node*np){//驱动入口//参考上述pll的注册,唯一不同的就是structclk_ops的成员函数实现}CLK_OF_DECLARE(xx_factor_clk,"xx,xx-factor-clk",of_xx_facotr_clk_init);

gate_clk门控时钟实现

门控就是开关,对于门控而言,我们只需要实现struct clk_ops.enable.disable

设备树:

#definePLL0_CLK0#defeinePLL0_FACTOR_PERI0#definePERI_MCI00mmc0:mmc0@0x12345678{compatible="xx,xx-mmc0";......clocks=<&periPERI_MCI0>;clocks-names="mmc0";......};clocks{osc24M:osc24M{compatible="fixed-clock";#clock-cells=<0>;clock-output-names="osc24M";clock-frequency=<24000000>;};pll0:pll0{compatible="xx,xx-fixed-clk";#clock-cells=<0>;clock-id=;clock-frequency=<1000000000>;clock-output-names="pll0";clocks=<&osc24M>;};factor_pll0_clk:factor_pll0_clk{compatible="xx,xx-pll0-factor-clk";#clock-cells=<1>;clock-id=;clock-output-names="pll0_peri";clocks=<&pll0PLL0_CLK>;};peri:peri{compatible="xx,xx-gate-clk";#clock-cells=<1>;/*perigate*/clock-id=;clock-output-names="mci0_peri";clocks=<&factor_pll0_clkPLL0_FACTOR_PERI>;};};

驱动:

staticintxx_gate_clk_enable(structclk_hw*hw){//寄存器操作,打开门控return0;}staticintxx_gate_clk_disable(structclk_hw*hw){//寄存器操作,门控关return0;}conststructclk_opsak_gate_clk_ops={.enable=xx_gate_clk_enable,.disable=xx_gate_clk_disable,}staticvoid__initof_xx_gate_clk_init(structdevice_node*np){//参考上述fixed_clk的注册,几乎相同,只不过操作函数clk_ops的实现不一样}CLK_OF_DECLARE(xx_gate_clk,"xx,xx-gate-clk",of_xx_gate_clk_init);

每个芯片厂商在clock驱动的实现上都有很大的差异,上述只是对clock驱动实现的简单举例,作为参考。对于一般的驱动,只需要会简单的使用内核提供的时钟API接口即可。

相关新闻: