Linux Common Clock Framework (2)
[复制链接]
1.clock provider的DTS 我们知道,DTS(Device Tree Source)是用来描述设备信息的,那系统的clock资源,是什么设备呢?换句话,用什么设备表示呢?这决定了clock provider的DTS怎么写。 通常有两种方式: 方式1,将系统所有的clock,抽象为一个虚拟的设备,用一个DTS node表示。这个虚拟的设备称作clock controller,参考如下例子: /* arch/arm/boot/dts/exynos4210.dtsi */ clock: clock-controller@0x10030000 { compatible= "samsung,exynos4210-clock"; reg =<0x10030000 0x20000>; #clock-cells = <1>; }; clock,该clock设备的名称,clock consumer可以根据该名称引用clock; #clock-cells,该clock的cells,1表示该clock有多个输出,clock consumer需要通过ID值指定所要使用的clock(很好理解,系统那么多clock,被抽象为1个设备,因而需要额外的ID标识)。 方式2,每一个可输出clock的器件,如Oscillator、PLL、Mux等等,都是一个设备,用一个DTS node表示。每一个器件,即是clock provider,也是clock consumer(根节点除外,如OSC),因为它需要接受clock输入,经过处理后,输出clock。参考如下例子(如果能拿到对应的datasheet会更容易理解): /*arch/arm/boot/dts/sun4i-a10.dtsi */ clocks { #address-cells = <1>; #size-cells = <1>; ranges; /*This is a dummy clock, to be used as placeholder on * other mux clocks when a specific parentclock is not * yet implemented. It should be droppedwhen the driver * is complete. */ dummy: dummy { #clock-cells = <0>; compatible = "fixed-clock"; clock-frequency = <0>; }; osc24M: osc24M@01c20050 { #clock-cells = <0>; compatible = "allwinner,sun4i-osc-clk"; reg = <0x01c20050 0x4>; clock-frequency = <24000000>; }; osc32k:osc32k { #clock-cells = <0>; compatible = "fixed-clock"; clock-frequency = <32768>; }; pll1: pll1@01c20000 { #clock-cells = <0>; compatible = "allwinner,sun4i-pll1-clk"; reg = <0x01c20000 0x4>; clocks= <&osc24M>; }; /*dummy is 200M */ cpu: cpu@01c20054 { #clock-cells = <0>; compatible = "allwinner,sun4i-cpu-clk"; reg = <0x01c20054 0x4>; clocks = <&osc32k>,<&osc24M>, <&pll1>, <&dummy>; }; axi: axi@01c20054 { #clock-cells = <0>; compatible = "allwinner,sun4i-axi-clk"; reg = <0x01c20054 0x4>; clocks = <&cpu>; }; axi_gates: axi_gates@01c2005c { #clock-cells = <1>; compatible = "allwinner,sun4i-axi-gates-clk"; reg = <0x01c2005c 0x4>; clocks = <&axi>; clock-output-names = "axi_dram"; }; ahb: ahb@01c20054 { #clock-cells = <0>; compatible = "allwinner,sun4i-ahb-clk"; reg = <0x01c20054 0x4>; clocks = <&axi>; }; ahb_gates: ahb_gates@01c20060 { #clock-cells = <1>; compatible = "allwinner,sun4i-ahb-gates-clk"; reg = <0x01c20060 0x8>; clocks = <&ahb>; clock-output-names = "ahb_usb0", "ahb_ehci0", "ahb_ohci0", "ahb_ehci1", "ahb_ohci1", "ahb_ss", "ahb_dma", "ahb_bist", "ahb_mmc0", "ahb_mmc1", "ahb_mmc2", "ahb_mmc3", "ahb_ms", "ahb_nand", "ahb_sdram", "ahb_ace", "ahb_emac", "ahb_ts", "ahb_spi0", "ahb_spi1", "ahb_spi2", "ahb_spi3", "ahb_pata", "ahb_sata", "ahb_gps", "ahb_ve", "ahb_tvd", "ahb_tve0", "ahb_tve1", "ahb_lcd0", "ahb_lcd1", "ahb_csi0", "ahb_csi1", "ahb_hdmi", "ahb_de_be0", "ahb_de_be1", "ahb_de_fe0", "ahb_de_fe1", "ahb_mp", "ahb_mali400"; }; }; osc24M和osc32k是两个rootclock,因此只做clockprovider功能。它们的cells均为0,因为直接使用名字即可引用。另外,增加了“clock-frequency”自定义关键字,这样在板子使用的OSC频率改变时,如变为12M,不需要重新编译代码,只需更改DTS的频率即可(这不正是DeviceTree的核心思想吗!)。话说回来了,osc24M的命名不是很好,如果频率改变,名称也得改吧,clockconsumer的引用也得改吧; pll1即是clockprovider(cell为0,直接用名字引用),也是clockconsumer(clocks关键字,指定输入clock为“osc24M”); 再看一个复杂一点的,ahb_gates,它是clockprovidsr(cell为1),通过clock-output-names关键字,描述所有的输出时钟。同时它也是clockconsumer(由clocks关键字可知输入clock为“ahb”)。需要注意的是,clock-output-names关键字只为了方便clockprovider编程方便(后面会讲),clockconsumer不能使用(或者可理解为不可见);
2.clock consumer的DTS 例子1(对应方式1): /*arch/arm/boot/dts/exynos4210.dtsi */ mct@10050000 { compatible = "samsung,exynos4210-mct"; ... clocks = <&clock3>, <&clock 344>; clock-names = "fin_pll", "mct"; ... }; clocks,指明该设备的clock列表,clk_get时,会以它为关键字,去device_node中搜索,以得到对应的structclk指针; clocks需要指明的信息,由clockprovider的“#clock-cells”规定:为0时,只需要提供一个clockprovider name(称作phandle);为1时,表示phandle有多个输出,则需要额外提供一个ID,指明具体需要使用那个输出。这个例子直接用立即数表示,更好的做法是,将系统所有clock的ID,定义在一个头文件中,而DTS可以包含这个头文件,如“clocks= <&clock CLK_SPI0>”; clock-names,为clocks指定的那些clock分配一些易于使用的名字,driver可以直接以名字为参数,getclock的句柄(具体可参考Linuxcommon clock framework(1)中clk_get相关的接口描述)。 例子2,如果clockprovider的“#clock-cells”为0,可直接引用该clockprovider的名字,具体可参考方式2。 例子3,方式2有一个clockprovider的名字为apb0_gates,它的“#clock-cells”为1,并通过clock-output-names指定了所有的输出clock,那么,clockconsumer怎么引用呢?如下(2和.1中的方式2,来自同一个DTS文件): /*arch/arm/boot/dts/sun4i-a10.dtsi */ soc@01c20000 { compatible = "simple-bus"; ... pio: pinctrl@01c20800 { compatible = "allwinner,sun4i-a10-pinctrl"; reg = <0x01c208000x400>; clocks =<&apb0_gates 5>; ... } } 和例子1一样,指定phandle为“aph0_gates”,ID为5。 3. 使用clock framework编写clock驱动的步骤 编写clockdriver的步骤大概如下: 1)分析硬件的clocktree,按照上面所描述的分类,讲这些clock分类。 2)将clocktree在DTS中描述出来,需要注意以下几2点: a)对于fixedrate clocks,.compatible固定填充"fixed-clock",并提供"clock-frequency"和"clock-output-names"关键字。之后不需要再driver中做任何处理,clockframework core会帮我们搞定一切。 b)同样,对于fixedfactor clock,.compatible为"fixed-factor-clock",并提供"clock-div"、"clock-mult"和"clock-output-names"关键字。clockframework core会帮我们搞定一切。 切记,尽量利用kernel已有资源,不要多写一行代码,简洁的就是美的! 3)对于不能由clockframework core处理的clock,需要在driver中使用structof_device_id进行匹配,并在初始化时,调用OF模块,查找所有的DTS匹配项,并执行合适的regitser接口,注册clock。 4)注册clock的同时,将返回的structclk指针,保存在一个数组中,并调用of_clk_add_provider接口,告知clockframework core。 5)最后,也是最重要的一点,多看kernel源代码,多模仿,多抄几遍,什么都熟悉了!
|