Linux驱动修炼之道-clock框架

本文详细解析了S3C2440处理器中时钟系统的初始化流程,从内核启动到具体时钟配置的过程,并介绍了如何通过内核函数注册和获取时钟。
AI助手已提取文章相关产品:

努力成为linux kernel hacker的人李万鹏原创作品,为梦而战。转载请标明出处

http://blog.youkuaiyun.com/woshixingaaa/archive/2011/05/17/6426203.aspx

内核启动时,会调用s3c24xx_register_clock函数注册很多时钟,所谓注册,就是在一个链表中保存各种"struct clk*"结构指针,这些"struct clk"结构有:clk_f(表示FCLK),clk_h(表示HCLK),clk_p(表示PCLK)等。然后可以通过clk_get_rate函数获得获得某类时钟频率。下面到内核中分析一下源码,这里跟踪内核启动时clock system的初始化过程:

asmlinkage void __init start_kernel(void) { ............ setup_arch(&command_line); ............ }

start_kernel调用了setup_arch(&command_line):

void __init setup_arch(char **cmdline_p) { .......... paging_init(mdesc); .......... }

setup_arch调用了paging_init(mdesc):

void __init paging_init(struct machine_desc *mdesc){ ............. devicemaps_init(mdesc); ............. }

paging_init调用了devicemaps_init(mdesc):

static void __init devicemaps_init(struct machine_desc *mdesc) { .................... /*这里调用具体体系结构的map_io函数*/ if (mdesc->map_io) mdesc->map_io(); ................... }

在我们板子的文件中查到这个map_io函数:

static void __init smdk2440_map_io(void) { s3c24xx_init_io(smdk2440_iodesc, ARRAY_SIZE(smdk2440_iodesc)); s3c24xx_init_clocks(12000000); s3c24xx_init_uarts(smdk2440_uartcfgs, ARRAY_SIZE(smdk2440_uartcfgs)); }

这个函数有一句s3c24xx_init_clocks(12000000);跟踪进去:

/* s3c24xx_init_clocks * * Initialise the clock subsystem and associated information from the * given master crystal value. * * xtal = 0 -> use default PLL crystal value (normally 12MHz) * != 0 -> PLL crystal value in Hz */ void __init s3c24xx_init_clocks(int xtal) { if (xtal == 0) xtal = 12*1000*1000; if (cpu == NULL) panic("s3c24xx_init_clocks: no cpu setup?\n"); if (cpu->init_clocks == NULL) panic("s3c24xx_init_clocks: cpu has no clock init\n"); else (cpu->init_clocks)(xtal); }

这个函数是设置晶振的频率为12M,也就是我板子上的晶振12M。注意这里最后一句:(cpu->init_clocks)(xtal);我们要查看cpu_table了。

static struct cpu_table cpu_ids[] __initdata = { ...................... { .idcode = 0x32440001, .idmask = 0xffffffff, .map_io = s3c244x_map_io, .init_clocks = s3c244x_init_clocks, .init_uarts = s3c244x_init_uarts, .init = s3c2440_init, .name = name_s3c2440a }, ..................... };

在cpu_table中可以找到这个init_clocks函数,也就是我们苦苦寻觅的clock system初始化函数了,真是众里寻他千百度,那人却在灯火阑珊处。

void __init s3c244x_init_clocks(int xtal) { /* initialise the clocks here, to allow other things like the * console to use them, and to add new ones after the initialisation */ s3c24xx_register_baseclocks(xtal); s3c244x_setup_clocks(); s3c2410_baseclk_add(); }

这个s3c244x_init_clocks完成了clock system全部的初始化工作。现在一个一个来分析里边的3个函数。s3c24xx_register_baseclocks()函数在arch/arm/plat-s3c/clock.c中实现如下:这里对基本的时钟clk_xtal,clk_mpll,clk_upll,clk_f,clk_h,clk_p进行了注册。

int __init s3c24xx_register_baseclocks(unsigned long xtal) { printk(KERN_INFO "S3C24XX Clocks, (c) 2004 Simtec Electronics\n"); clk_xtal.rate = xtal; /* register our clocks */ if (s3c24xx_register_clock(&clk_xtal) < 0) printk(KERN_ERR "failed to register master xtal\n"); if (s3c24xx_register_clock(&clk_mpll) < 0) printk(KERN_ERR "failed to register mpll clock\n"); if (s3c24xx_register_clock(&clk_upll) < 0) printk(KERN_ERR "failed to register upll clock\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"); return 0; }

下边看一下这个注册函数,主要任务就是把struct clk结构添加到clocks链表中。

/* clock information */ static LIST_HEAD(clocks);

这个是链表的头的注册函数。注册的struct clk结构体都要添加到这个clocks链表中。

/* initialise the clock system */ int s3c24xx_register_clock(struct clk *clk) { ......... list_add(&clk->list, &clocks); ......... }

现在来看第二个函数:它的任务就是设置fclk,hclk,pclk,相信如果认真写过arm裸机程序的人一定很容易看懂下边的代码了,可以对照s3c2440的手册来看的。

void __init_or_cpufreq s3c244x_setup_clocks(void) { ................. s3c24xx_setup_clocks(fclk, hclk, pclk); }

这里调用了一个s3c24xx_setup_clocks函数,下面看它的实现:

/* initalise all the clocks */ void __init_or_cpufreq s3c24xx_setup_clocks(unsigned long fclk, unsigned long hclk, unsigned long pclk) { clk_upll.rate = s3c24xx_get_pll(__raw_readl(S3C2410_UPLLCON), clk_xtal.rate); clk_mpll.rate = fclk; clk_h.rate = hclk; clk_p.rate = pclk; clk_f.rate = fclk; }

就是把得到的fclk,hclk,pclk赋值相应结构体。
下面来看第三个函数,这个主要就是对外设的struct clk进行注册。这个函数一共分两部分,有两个数组,一个是init_clocks,也就是在boot时需要提供时钟的,一个是init_clocks_disable,这里的每个成员都是在boot的时候需要disable时钟的。这两个数组分别进行注册,但是注册init_clocks_disable数组中成员的for循环中调用了s3c2410_clkcon_enable(clkp, 0);也就是将相应的clk disable掉。

int __init s3c2410_baseclk_add(void) { unsigned long clkslow = __raw_readl(S3C2410_CLKSLOW); unsigned long clkcon = __raw_readl(S3C2410_CLKCON); struct clk *clkp; struct clk *xtal; int ret; int ptr; clk_upll.enable = s3c2410_upll_enable; if (s3c24xx_register_clock(&clk_usb_bus) < 0) printk(KERN_ERR "failed to register usb bus clock\n"); /* register clocks from clock array */ clkp = init_clocks; for (ptr = 0; ptr < ARRAY_SIZE(init_clocks); ptr++, clkp++) { /* ensure that we note the clock state */ clkp->usage = clkcon & clkp->ctrlbit ? 1 : 0; ret = s3c24xx_register_clock(clkp); if (ret < 0) { printk(KERN_ERR "Failed to register clock %s (%d)\n", clkp->name, ret); } } /* We must be careful disabling the clocks we are not intending to * be using at boot time, as subsystems such as the LCD which do * their own DMA requests to the bus can cause the system to lockup * if they where in the middle of requesting bus access. * * 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. */ /* install (and disable) the clocks we do not need immediately */ clkp = init_clocks_disable; for (ptr = 0; ptr < ARRAY_SIZE(init_clocks_disable); ptr++, clkp++) { ret = s3c24xx_register_clock(clkp); if (ret < 0) { printk(KERN_ERR "Failed to register clock %s (%d)\n", clkp->name, ret); } s3c2410_clkcon_enable(clkp, 0); } /* show the clock-slow value */ xtal = clk_get(NULL, "xtal"); printk("CLOCK: Slow mode (%ld.%ld MHz), %s, MPLL %s, UPLL %s\n", print_mhz(clk_get_rate(xtal) / ( 2 * S3C2410_CLKSLOW_GET_SLOWVAL(clkslow))), (clkslow & S3C2410_CLKSLOW_SLOW) ? "slow" : "fast", (clkslow & S3C2410_CLKSLOW_MPLL_OFF) ? "off" : "on", (clkslow & S3C2410_CLKSLOW_UCLK_OFF) ? "off" : "on"); s3c_pwmclk_init(); return 0; }

在arch/arm/plat-s3c/clock.c中实现了clock system对外提供的接口:

/*则加模块的引用计数*/ struct clk *clk_get(struct device *dev, const char *id); /*减少模块的引用计数*/ void clk_put(struct clk *clk); /*使能某个模块的时钟,比如ADC模块等*/ int clk_enable(struct clk *clk); /*禁止模块的时钟*/ void clk_disable(struct clk *clk); /*获得某类时钟频率*/ unsigned long clk_get_rate(struct clk *clk); /*设置某类部件的时钟(比如设置CAM接口时钟)*/ int clk_set_rate(struct clk *clk, unsigned long rate); /*获得父clk*/ struct clk *clk_get_parent(struct clk *clk); /*设置父clk*/ int clk_set_parent(struct clk *clk, struct clk *parent);

您可能感兴趣的与本文相关内容

Linux常见驱动源码分析(kernel hacker修炼)--李万鹏 李万鹏 IBM Linux Technology Center kernel team 驱动资料清单内容如下: Linux设备模型(中)之上层容器.pdf Linux设备模型(上)之底层模型.pdf Linux驱动修炼-驱动中一些常见的宏.pdf Linux驱动修炼-内存映射.pdf Linux驱动修炼-看门狗框架源码分析.pdf Linux驱动修炼-触摸屏驱动之s3c2410_ts源码分析.pdf Linux驱动修炼-SPI驱动框架源码分析(中).pdf Linux驱动修炼-SPI驱动框架源码分析(下).pdf Linux驱动修炼-SPI驱动框架源码分析(上).pdf Linux驱动修炼-RTC子系统框架与源码分析.pdf Linux驱动修炼-platform.pdf Linux驱动修炼-LCD背光与gpio控制.pdf Linux驱动修炼-INPUT子系统(下).pdf Linux驱动修炼-INPUT子系统(上).pdf Linux驱动修炼-framebuffer(中).pdf Linux驱动修炼-framebuffer(下).pdf Linux驱动修炼-framebuffer(上).pdf Linux驱动修炼-DMA框架源码分析(下).pdf Linux驱动修炼-DMA框架源码分析(上).pdf Linux驱动修炼-DM9000A网卡驱动框架源码分析(中).pdf Linux驱动修炼-DM9000A网卡驱动框架源码分析(下).pdf Linux驱动修炼-DM9000A网卡驱动框架源码分析(上).pdf Linux驱动修炼-clock框架.pdf Linux驱动修炼-ADC驱动.pdf Linux内核访问外设I O资源的方式.pdf LINUX内核USB子系统学习笔记之初识USB.pdf kernel hacker修炼驱动-流水灯.pdf kernel hacker修炼驱动-混杂设备.pdf kernel hacker修炼驱动-按键.pdf kernel hacker修炼之PCI subsystem(五).pdf kernel hacker修炼之PCI subsystem(四).pdf kernel hacker修炼之PCI subsystem(三).pdf kernel hacker修炼之PCI subsystem(六).pdf kernel hacker修炼之PCI subsystem(二).pdf
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值