Linux时钟管理clk

硬件资源越来越庞大和复杂,内核的另一个挑战就是要便捷的管理这些资源。同时,面对如此之多的平台不同的CPU,管理机制需要统一适用,这就需要对资源的管理抽象到更加通用的层次。CPU中各个模块都需要时钟驱动,内核需要一种机制能通用所有的平台,方便的管理CPU上所有的clk资源。这里分析Linux对clk的管理。
1. clk通用接口

内核定义了一套标准的接口(include/linux/clk.h),用于所有的平台之上。每个时钟源对象使用一个struct clk结构来表示。而struct clk结构的具体内容由各平台自己定义。clk.h头文件定义了操作一个clk对象的所有接口。内核的其他地方可以也只能使用clk.h中提供的这些接口函数来操作clk。

  1. struct clk *clk_get(struct device *dev, const char *id);  
  2. int clk_enable(struct clk *clk);  
  3. void clk_disable(struct clk *clk);  
  4. unsigned long clk_get_rate(struct clk *clk);  
  5. void clk_put(struct clk *clk);  
  6. long clk_round_rate(struct clk *clk, unsigned long rate);  
  7. int clk_set_rate(struct clk *clk, unsigned long rate);  
  8. int clk_set_parent(struct clk *clk, struct clk *parent);  
  9. struct clk *clk_get_parent(struct clk *clk);  
  10. struct clk *clk_get_sys(const char *dev_id, const char *con_id);  
  11. int clk_add_alias(const char *alias, const char *alias_dev_name, char *id,  
  12.             struct device *dev);  


struct clk

clk结构体是平台相关的。在arch/arm/mach-mx5/clock.c中会预先描述CPU中所有的clk对象。

  • parent - clk是由parent分出来的。那么如果parent关闭了,当前clk也就没有了。
  • secondary - 第二时钟源,用于enable/disable当前clk。
  • usecount - 引用计数。
  • get_rate, set_rate, enable, disable, set_parent - 很显然,这些函数指针指到实际操作的函数。clk.h中的各接口函数最后都会调用到这里的函数指针。函数指针是隔离变化的最好办法,在这里一下就把层次抽象出来了。

2. clocks链表

arch/arm/mach-mx5/clock.c中不仅定义了所有的clk对象,而且每个clk对象还要对应一个struct clk_lookup结构。在初始化时,会将所有的clk_loopup结构添加进入clocks链表中。

  1. struct clk_lookup {  
  2.     struct list_head    node;  
  3.     const char      *dev_id;  
  4.     const char      *con_id;  
  5.     struct clk      *clk;  
  6. };  

clk_lookup,顾名思义就知道它是用来查找struct clk结构的。有了它,就可以通过设备名或时钟源的名字来找到相应的struct clk结构。链表操作位于drivers/clk/clkdev.c


3. clk平台通用操作

arch/arm/plat-mxc/clock.c源文件中定义了mxc平台clock的通用操作接口。

enable/disable函数中可以看到引用计数usecount的作用。一个clk只有当其usecount为0的时候才会做实际的打开动作,也只有usecount为0时才能确认没有被任何其他设备使用,可以禁止了。层次关系被递归调用和引用计数巧妙的实现。

  1. static void __clk_disable(struct clk *clk)  
  2. {  
  3.     if (clk == NULL || IS_ERR(clk))  
  4.         return;  
  5.     WARN_ON(!clk->usecount);  
  6.   
  7.     if (!(--clk->usecount)) {  
  8.         if (clk->disable)  
  9.             clk->disable(clk);  
  10.   
  11.         __clk_disable(clk->secondary);  
  12.         __clk_disable(clk->parent);  
  13.     }  
  14. }  
  15.   
  16. static int __clk_enable(struct clk *clk)  
  17. {  
  18.     if (clk == NULL || IS_ERR(clk))  
  19.         return -EINVAL;  
  20.   
  21.     if (clk->usecount++ == 0) {  
  22.         __clk_enable(clk->parent);  
  23.         __clk_enable(clk->secondary);  
  24.   
  25.         if (clk->enable)  
  26.             clk->enable(clk);  
  27.     }  
  28.     return 0;  
  29. }  

4. clk与pm

为了省电,当不需要clk时将其关闭,上面的clk_enable/clk_disable实现了此功能。除了关闭clk省电,还可以降低clk频率以达到省电的目的。当系统当前负载较轻,不需要clk跑在那么高的频率时,就可以对该clk降频了。从这些关系可以看到,clk与电源管理,cpufreq等都可能有关联。

5.使用

1.定义struct clk *clk;

2.获取需要操作的clock结构体 clk=clk_get(&pdev->dev, "pclk"); /* 第一个参数一般取NULL */
3.设置频率 clk_set_rate(clk); /* 返回时钟频率 */
4.产生时钟 clk_enable(clk);

5.停止时钟clk_disable(clk);

clk_get从一个时钟list链表中以字符id名称来查找一个时钟clk结构体并且返回,最后调用clk.enable(),来时能对应的外设时钟源。

clk_get_rate函数可以从clk_get得到的某设备结构体中获得该设备的时钟频率。

诸如watchdog等设备可以在类似arch/arm/plat-s3c24xx/S3c2410-clock.c 中找到。

static struct clk init_clocks[] = {
	{
		.name		= "lcd",
		.parent		= &clk_h,
		.enable		= s3c2410_clkcon_enable,
		.ctrlbit	= S3C2410_CLKCON_LCDC,
	}, {
		.name		= "gpio",
		.parent		= &clk_p,
		.enable		= s3c2410_clkcon_enable,
		.ctrlbit	= S3C2410_CLKCON_GPIO,
	}, {
		.name		= "usb-host",
		.parent		= &clk_h,
		.enable		= s3c2410_clkcon_enable,
		.ctrlbit	= S3C2410_CLKCON_USBH,
	}, {
		.name		= "usb-device",
		.parent		= &clk_h,
		.enable		= s3c2410_clkcon_enable,
		.ctrlbit	= S3C2410_CLKCON_USBD,
	}, {
		.name		= "timers",
		.parent		= &clk_p,
		.enable		= s3c2410_clkcon_enable,
		.ctrlbit	= S3C2410_CLKCON_PWMT,
	}, {
		.name		= "uart",
		.devname	= "s3c2410-uart.0",
		.parent		= &clk_p,
		.enable		= s3c2410_clkcon_enable,
		.ctrlbit	= S3C2410_CLKCON_UART0,
	}, {
		.name		= "uart",
		.devname	= "s3c2410-uart.1",
		.parent		= &clk_p,
		.enable		= s3c2410_clkcon_enable,
		.ctrlbit	= S3C2410_CLKCON_UART1,
	}, {
		.name		= "uart",
		.devname	= "s3c2410-uart.2",
		.parent		= &clk_p,
		.enable		= s3c2410_clkcon_enable,
		.ctrlbit	= S3C2410_CLKCON_UART2,
	}, {
		.name		= "rtc",
		.parent		= &clk_p,
		.enable		= s3c2410_clkcon_enable,
		.ctrlbit	= S3C2410_CLKCON_RTC,
	}, {
		.name		= "watchdog",
		.parent		= &clk_p,
		.ctrlbit	= 0,
	}, {
		.name		= "usb-bus-host",
		.parent		= &clk_usb_bus,
	}, {
		.name		= "usb-bus-gadget",
		.parent		= &clk_usb_bus,
	},
};





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值