Linux内核时钟框架

本文发布时间: 2019-Mar-22
1: //节电,给多个设备重用时钟。一般情况下,固件启动的过程会输出一个描述板子上所有存在设备的表。如果没有这个表,系统启动代码建立正确的设备的唯一方法就是为一个特定的板子编译一个kernel。这种board-specific kernel广泛用在嵌入式和一般系统的开发上。//即,通用的启动过程时,由固件引导初始化硬件,并把一个描述板子上所有设备的表传给系统。然后系统就可以进行对应的加载,实现PC机的通用化,启动。//但对于嵌入式来说,一般是根据特定的板,生成特定的内核。在大部分情况下,设备的memory和IRQ资源不足够让驱动正常工作。board setup code会用device的platform_data 域来为设备提供一些额外的资源。/*嵌入式系统上的设备会频繁地使用一个或者多个时钟,这些时钟因为节电的原因只有在真正使用的时候才会被打开,系统在启动过程中会为设备分配时钟*/,可以通过clk_get(&pdev->dev, clock_name)来获得需要的时钟。/*clk初始化,采用一个链表把所有到时钟结构体链接在一起,该链表的头是clocks。davinci_clk_init()例程由io.c中的davinci_map_common_io()例程调用,后者被davinci_map_io()例程调用,而其又被注册到board_evm.c中的机器描述符中(struct machine_desc),故具体调用过程是:start_kernel()-->setup_arch()-->paging_init()--> mdesc->map_io()(其就是 davinci_map_io())-->davinci_map_common_io()-->davinci_clk_init()。*/2:arch/arm/plat_xx: 特定平台的通用共用的代码。 平台arch/arm/mach_xx: 完全针对单个板的底层代码。 单个板3:从一个时钟产生器(CLOCK generator)中产生的时钟分两类:interfaces clocks: 供给接口电路(总线?) xx_ICLKfunctional clocks: 供给功能模块。 xx_FCLKclk结构是根据平台自定的结构。clk模型是平台特定的,/arch/arm/xx平台/clock.h: 提供clk结构,及操作接口,宏定义。/arch/arm/xx平台/clock.c: 提供操作于该平台的时钟的操作。PS:下面使用的struct clk结构有些老,新的结构体定义到文件中查找adc_clock = clk_get(NULL, "adc"); if (!adc_clock) { printk(KERN_ERR "failed to get adc clock source\n"); return -ENOENT; } clk_use(adc_clock); clk_enable(adc_clock);上面的这段代码是touchscreen的驱动中的一段,我不清楚,所以去学学系统各个模块时钟的使用方式。在系统的初始化的时候,看见过,但是忘了,再回顾一下。 那是在paging_init()中调用了 mdesc->map_io(),void __init sbc2440_map_io(void){ s3c24xx_init_io(sbc2440_iodesc, ARRAY_SIZE(sbc2440_iodesc)); s3c24xx_init_clocks(12000000); //这个是系统各个部分始终初始化的起点 s3c24xx_init_uarts(sbc2440_uartcfgs, ARRAY_SIZE(sbc2440_uartcfgs)); s3c24xx_set_board(&sbc2440_board); s3c_device_nand.dev.platform_data = &bit_nand_info;}跟 cpu_table 有关,拷贝过来/* table of supported CPUs */static const char name_s3c2410[] = "S3C2410";static const char name_s3c2440[] = "S3C2440";static const char name_s3c2410a[] = "S3C2410A";static const char name_s3c2440a[] = "S3C2440A";static struct cpu_table cpu_ids[] __initdata ={ { .idcode = 0x32410000, .idmask = 0xffffffff, .map_io = s3c2410_map_io, .init_clocks = s3c2410_init_clocks, .init_uarts = s3c2410_init_uarts, .init = s3c2410_init, .name = name_s3c2410 }, { .idcode = 0x32410002, .idmask = 0xffffffff, .map_io = s3c2410_map_io, .init_clocks = s3c2410_init_clocks, .init_uarts = s3c2410_init_uarts, .init = s3c2410_init, .name = name_s3c2410a }, { .idcode = 0x32440000, .idmask = 0xffffffff, .map_io = s3c2440_map_io, .init_clocks = s3c2440_init_clocks, .init_uarts = s3c2440_init_uarts, .init = s3c2440_init, .name = name_s3c2440 }, { .idcode = 0x32440001, .idmask = 0xffffffff, .map_io = s3c2440_map_io, .init_clocks = s3c2440_init_clocks, .init_uarts = s3c2440_init_uarts, .init = s3c2440_init, .name = name_s3c2440a }};和时钟相关的调用路径: 在 s3c24xx_init_clocks() -> (cpu->init_clocks)(xtal)-> s3c24xx_setup_clocks()这个s3c24xx_setup_clocks()注册了系统的所有时钟,仔细看看它。 在这个函数被调用之前,代码已经根据3C2410_MPLLCON,S3C2410_CLKDIVN寄存器和晶振的频率计算出了fclk,hclk,pclk,他们应该分别是400M,100M,50M。struct clk{ struct list_head list; struct module *owner; struct clk *parent; const char *name; int id; atomic_t used; unsigned long rate; unsigned long ctrlbit; int (*enable)(struct clk *, int enable);};clk数据结构是系统中时钟的抽象,它用list串成一个双向链表,在这个clocks链表里的clk结构,说明是系统中已经注册的,parent 表示他的来源,f,h,p之一,name是寻找到某个clk的唯一标识。enable是面向对象的思想的体现,不过,这里没有用到,只是全部被填充为 s3c24xx_clkcon_enable()。/* clock information */static LIST_HEAD(clocks);static DECLARE_MUTEX(clocks_sem);/* clock definitions */static struct clk init_clocks[] ={ { .name = "nand", .id = -1, .parent = &clk_h, .enable = s3c24xx_clkcon_enable, .ctrlbit = S3C2410_CLKCON_NAND }, { .name = "lcd", .id = -1, .parent = &clk_h, .enable = s3c24xx_clkcon_enable, .ctrlbit = S3C2410_CLKCON_LCDC }, { .name = "usb-host", .id = -1, .parent = &clk_h, .enable = s3c24xx_clkcon_enable, .ctrlbit = S3C2410_CLKCON_USBH }, { .name = "usb-device", .id = -1, /*.parent = &clk_h, */ .parent = &clk_xtal, .enable = s3c24xx_clkcon_enable, .ctrlbit = S3C2410_CLKCON_USBD }, { .name = "timers", .id = -1, .parent = &clk_p, .enable = s3c24xx_clkcon_enable, .ctrlbit = S3C2410_CLKCON_PWMT }, { .name = "sdi", .id = -1, .parent = &clk_p, .enable = s3c24xx_clkcon_enable, .ctrlbit = S3C2410_CLKCON_SDI }, { .name = "uart", .id = 0, .parent = &clk_p, .enable = s3c24xx_clkcon_enable, .ctrlbit = S3C2410_CLKCON_UART0 }, { .name = "uart", .id = 1, .parent = &clk_p, .enable = s3c24xx_clkcon_enable, .ctrlbit = S3C2410_CLKCON_UART1 }, { .name = "uart", .id = 2, .parent = &clk_p, .enable = s3c24xx_clkcon_enable, .ctrlbit = S3C2410_CLKCON_UART2 }, { .name = "gpio", .id = -1, .parent = &clk_p, .enable = s3c24xx_clkcon_enable, .ctrlbit = S3C2410_CLKCON_GPIO }, { .name = "rtc", .id = -1, .parent = &clk_p, .enable = s3c24xx_clkcon_enable, .ctrlbit = S3C2410_CLKCON_RTC }, { .name = "adc", .id = -1, .parent = &clk_p, .enable = s3c24xx_clkcon_enable, .ctrlbit = S3C2410_CLKCON_ADC }, { .name = "i2c", .id = -1, .parent = &clk_p, .enable = s3c24xx_clkcon_enable, .ctrlbit = S3C2410_CLKCON_IIC }, { .name = "iis", .id = -1, .parent = &clk_p, .enable = s3c24xx_clkcon_enable, .ctrlbit = S3C2410_CLKCON_IIS }, { .name = "spi", .id = -1, .parent = &clk_p, .enable = s3c24xx_clkcon_enable, .ctrlbit = S3C2410_CLKCON_SPI }, { .name = "watchdog", .id = -1, .parent = &clk_p, .ctrlbit = 0 }};仔细看,usb-device 的parent有些特别,watchdog没有enable,只有uart才有id,其他的id都是-1。 下面可以看 s3c24xx_setup_clocks()了,像所注视的那样,它初始化了所有的时钟,其实是注册到clocks链表里面,以后可以从clocks链表中找到。/* initalise all the clocks */int __init s3c24xx_setup_clocks(unsigned long xtal, unsigned long fclk, unsigned long hclk, unsigned long pclk){ struct clk *clkp = init_clocks; int ptr; int ret; printk(KERN_INFO "S3C2410 Clocks, (c) 2004 Simtec Electronics\n"); /* initialise the main system clocks */ clk_xtal.rate = xtal; clk_h.rate = hclk; clk_p.rate = pclk; clk_f.rate = fclk;上面的时钟是祖宗级别的,他们的频率已经被确定了。分别代表晶震12Mhz,arm核400M,h总线100M,p总线50M。 /* it looks like just setting the register here is not good * enough, and causes the odd hang at initial boot time, so * do all of them indivdually. * * I think disabling the LCD clock if the LCD is active is * very dangerous, and therefore the bootloader should be * careful to not enable the LCD clock if it is not needed. * * and of course, this looks neater */ s3c24xx_clk_enable(S3C2410_CLKCON_NAND, 0); // ghcstop: disable? ==> enable s3c24xx_clk_enable(S3C2410_CLKCON_USBH, 0); s3c24xx_clk_enable(S3C2410_CLKCON_USBD, 0); s3c24xx_clk_enable(S3C2410_CLKCON_ADC, 0); s3c24xx_clk_enable(S3C2410_CLKCON_IIC, 0); s3c24xx_clk_enable(S3C2410_CLKCON_SPI, 0); //s3c24xx_clk_enable(S3C2410_CLKCON_IIS, 1); // default value is 1 ==> enables3c24xx_clk_enable用来使能/禁止系统对某个模块供应时钟,他操作的对象是CLKCON,这个寄存器的bit[4~20]每位代表了系统中的一个模块的时钟供应情况,要么使能,要么禁止。bit[2~3]分别代表idle和sleep模式,所以 s3c24xx_clk_enable总是去擦出这两个bit位。然后根据第2个参数去打开(1)/禁止(0)对模个模块的时钟供应。显然,上面的操作都是禁止时钟供应的,包括nand,usbhost,usbdevice,adc,iic,spi。 /* assume uart clocks are correctly setup */ /* register our clocks */ if (s3c24xx_register_clock(&clk_xtal) < 0) printk(KERN_ERR "failed to register master xtal\n"); if (s3c24xx_register_clock(&clk_f) < 0) printk(KERN_ERR "failed to register cpu fclk\n"); if (s3c24xx_register_clock(&clk_h) < 0) printk(KERN_ERR "failed to register cpu hclk\n"); if (s3c24xx_register_clock(&clk_p) < 0) printk(KERN_ERR "failed to register cpu pclk\n");s3c24xx_register_clock用于注册这个时钟到clocks链表,他还设置clk的owner成员为内核模块所拥有,并且设置 clk->used原子型结构为没有被使用(0),然后根据clk->enable有无初始值,为没有初始值的设置一个哑 clk_null_enable,上面的四个base clock都是不能被关闭的,所以他们的clk->enable成员都是clk_null_enable /* register clocks from clock array */ for (ptr = 0; ptr < ARRAY_SIZE(init_clocks); ptr++, clkp++) { ret = s3c24xx_register_clock(clkp); if (ret < 0) { printk(KERN_ERR "Failed to register clock %s (%d)\n", clkp->name, ret); } }上面完成了系统其他部分时钟初始化,当然这部分才是我们关心的内容,这些模块的时钟源都来自base clock。其中watchdog没有enable成员,不能被关闭。 return 0;}//s3c24xx_setup_clocks()end下面是四个系统的基本时钟,clk_xtal代表晶震。他们的rate都被上面的函数确定了,而其他部分的时钟还没有rate呢。/* base clocks */static struct clk clk_xtal ={ .name = "xtal", .id = -1, .rate = 0, .parent = NULL, .ctrlbit = 0,};static struct clk clk_f ={ .name = "fclk", .id = -1, .rate = 0, .parent = NULL, .ctrlbit = 0,};static struct clk clk_h ={ .name = "hclk", .id = -1, .rate = 0, .parent = NULL, .ctrlbit = 0,};static struct clk clk_p ={ .name = "pclk", .id = -1, .rate = 0, .parent = NULL, .ctrlbit = 0,};宏THIS_MODULE,它的定义如下是#define THIS_MODULE (&__this_module),__this_module是一个struct module变量,代表当前模块,跟current有几分相似。可以通过THIS_MODULE宏来引用模块的struct module结构。好了,回头看看让我晕的函数。 adc_clock = clk_get(NULL, "adc"); if (!adc_clock) { printk(KERN_ERR "failed to get adc clock source\n"); return -ENOENT; } clk_use(adc_clock); clk_enable(adc_clock);上面涉及到3个函数,分别是clk_get,clk_use,clk_enable()。其中clk_get()的主要代码如下: list_for_each_entry(p, &clocks, list) { if (p->id == -1 && strcmp(id, p->name) == 0 && try_module_get(p->owner)) { clk = p; break; } }看到了吧,不再clocks这个时钟链表里的时钟配置是不会被看到的,这都是s3c24xx_register_clock()函数的功劳,然后他根据名字,找到对应的时钟结构,比如根据"adc"找到adc的clk结构,然后增加对这个模块的使用计数,最后返回这个找到的clk指针。 clk_use()很简单,只是单纯的增加本时钟的使用int clk_use(struct clk *clk){ atomic_inc(&clk->used); return 0;}在看时钟打开函数,clk_enable(adc_clock)int clk_enable(struct clk *clk){ if (IS_ERR(clk)) return -EINVAL; return (clk->enable)(clk, 1);}这里就体现出了面向对象的思想了,其中watchdog,四个基本的时钟是没有打开关闭的。当然这个函数也是最主要的操作,他包含了对寄存器CLKCON的操作。


(以上内容不代表本站观点。)
---------------------------------
本网站以及域名有仲裁协议。
本網站以及域名有仲裁協議。

2024-Mar-04 02:11pm
栏目列表