时钟框图
先来看看S3C2440时钟的硬件框图:
将该图简化如下:
我们只想作为消费者怎么去使用这些时钟,并不关心“提供者”内部的层级结构,只要知道“直接提供者”,也不关系“直接提供者”的实现,我们只需要发出请求就可以了。
晶振设备树描述
我们看看在2440的设备树里怎么描述这提供者和消费者。先来看看晶振:
xti: xti_clock { compatible = "fixed-clock"; clock-frequency = <12000000>; clock-output-names = "xti"; #clock-cells = <0>; };
根据compatible
可以找到对应的驱动,驱动程序将晶振的频率记录下来,以后作为计算的基准。
然后再是PLL的设备节点:
clocks: clock-controller@4c000000 { compatible = "samsung,s3c2440-clock"; reg = <0x4c000000 0x20>; #clock-cells = <1>; };
设备节点本身非常简单,复杂的是它对应的驱动程序。在驱动程序里面,肯定会根据reg
获得寄存器的地址,然后设置各种内容。
大部分的芯片为了省电,它的外部模块时钟平时都是关闭的,只有在使用某个模块时,才设置相应的寄存器开启对应的时钟。
这些使用者各有不同,要怎么描述这些使用者呢?
我们可以为它们配上一个ID。在设备树中的#clock-cells = <1>;
表示 用多少个u32位来描述消费者。在本例中使用一个u32来描述。
这些ID值由谁提供的?
是由驱动程序提供的,该节点会对应一个驱动程序,驱动程序给硬件(消费者)都分配了一个ID,所以说复杂的操作都留给驱动程序来做。
LCD时钟设备树描述
消费者想使用时钟时,首先要找到时钟的直接提供者,向它发出申请。以LCD为例:
fb0: fb@4d000000{ compatible = "jz2440,lcd"; reg = <0x4D000000 0x60>; interrupts = <0 0 16 3>; clocks = <&clocks HCLK_LCD>; clock-names = "lcd"; …… }
在clock
属性里,首先要确定向谁发出时钟申请,这里是向clocks
发出申请,然后确定想要时钟提供者提供哪一路时钟,这里是HCLK_LCD
,在驱动程序里定义了该宏,每种宏对应了一个时钟ID。
定义如下:
…… /* hclk-gates */ #define HCLK_LCD 32 #define HCLK_USBH 33 #define HCLK_USBD 34 #define HCLK_NAND 35 #define HCLK_CAM 36 ……
因此,我们只需要在设备节点定义clocks
这个属性,这个属性确定时钟提供者,然后确定时钟ID,也就是向时钟提供者申请哪一路时钟。
对应的内核文档可以参考这两个文件:
Documentation/devicetree/bindings/clock/clock-bindings.txt
Documentation/devicetree/bindings/clock/samsung,s3c2410-clock.txt
那么我这个设备驱动程序,怎么去使用这些时钟呢? 以前的驱动程序:clk_get(NULL, "name");
clk_prepare_enable(clk);
现在的驱动程序:of_clk_get(node, 0);
clk_prepare_enable(clk);
总结
a. 设备树中定义了各种时钟, 在文档中称之为"Clock providers", 比如:
clocks: clock-controller@4c000000 { compatible = "samsung,s3c2440-clock"; reg = <0x4c000000 0x20>; #clock-cells = <1>; // 想使用这个clocks时要提供1个u32来指定它, 比如选择这个clocks中发出的LCD时钟、PWM时钟 };
b. 设备需要时钟时, 它是"Clock consumers", 它描述了使用哪一个"Clock providers"中的哪一个时钟(id), 比如:
fb0: fb@4d000000{ compatible = "jz2440,lcd"; reg = <0x4D000000 0x60>; interrupts = <0 0 16 3>; clocks = <&clocks HCLK_LCD>; // 使用clocks即clock-controller@4c000000中的HCLK_LCD };
c. 驱动中获得/使能时钟:
// 确定时钟个数 int nr_pclks = of_count_phandle_with_args(dev->of_node, "clocks", "#clock-cells"); // 获得时钟 for (i = 0; i < nr_pclks; i++) { struct clk *clk = of_clk_get(dev->of_node, i); } // 使能时钟 clk_prepare_enable(clk); // 禁止时钟 clk_disable_unprepare(clk);