2410下clock源码分析
Author:aaron
这篇文章主要使用2.6.22下2410方面关于clock的源码来进行简单的分析, 希望通过这篇文档能对系统中的clock的使用问题有个了解.
写这篇文档除了参考了源码外, 还要参考2410的data sheet, 毕竟代码都是按照spec来写的嘛. 我们先来看下2410下各种clock是如何产生的:
我们可以看到, 2410的时钟源可以有两种: 由晶振产生或由外部CLOCK直接提供, 这可以由OM[3:2]来选择, 一般都是硬件定死的, 图中FCLK为CPU使用, HCLK为在AHB总线一侧的设备使用, PCLK为APB总线一侧的设备使用. 时钟源进来后要通过MPLL电路来产生FCLK,HCLK,PCLK, 因此很明显, 我们要通过设置MPLL方面的寄存器来控制输出的时钟频率.
更详细的关于clock方面的描述请参考2410的data sheet. 下面我们就开始来分析源码了
分析代码当然从初始化开始了, s3c24xx_init_clocks 就是它的初始化函数了,
Arch/arm/plat-s3c24xx/cpu.c:
/* 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) //运行到这里, 我们已经确定了我们板子上的cpu.
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); //调用具体型号cpu的初始化函数
}
这个函数是在smdk2410_map_io里被调用的, 传进来的参数是0, 可以看到主要的初始化由具体cpu来完成. 对于2410来说就是调用s3c2410_init_clocks()
arch/arm/mach-s3c2410/s3c2410.c:
void __init s3c2410_init_clocks(int xtal)
{
unsigned long tmp;
unsigned long fclk;
unsigned long hclk;
unsigned long pclk;
/* now we've got our machine bits initialised, work out what
* clocks we've got */
fclk = s3c2410_get_pll(__raw_readl(S3C2410_MPLLCON), xtal); //得到fclk,
tmp = __raw_readl(S3C2410_CLKDIVN); //获取fclk, hclk, plk间的比例参数
/* work out clock scalings */
hclk = fclk / ((tmp & S3C2410_CLKDIVN_HDIVN) ? 2 : 1); //计算出hclk
pclk = hclk / ((tmp & S3C2410_CLKDIVN_PDIVN) ? 2 : 1); //计算出pclk
/* print brieft summary of clocks, etc */
printk("S3C2410: core %ld.%03ld MHz, memory %ld.%03ld MHz, peripheral %ld.%03ld MHz/n",
print_mhz(fclk), print_mhz(hclk), print_mhz(pclk));
/* initialise the clocks here, to allow other things like the
* console to use them
*/
s3c24xx_setup_clocks(xtal, fclk, hclk, pclk); //把clock注册到系统中去
s3c2410_baseclk_add(); //把外设用到的clock也注册进系统中去
}
通过设置寄存器S3C2410_CLKDIVN的值可以设置fclk, hclk, pclk之间的比例, 因此由其中的一个值就可以计算出其他两个值了, 而这个比值的设置实在bootloader阶段设置好的, 而fclk的获取也是通过一个计算表达式得到的,
Mpll就是我们的FCLK, 根据这个公式, 函数s3c2410_get_pll就很好理解了, 这里就不贴出来了.
我们来看s3c24xx_setup_clocks函数
arch/arm/plat-s3c24xx/clock.c:
/* initalise all the clocks */
int __init s3c24xx_setup_clocks(unsigned long xtal,
unsigned long fclk,
unsigned long hclk,
unsigned long pclk)
{
printk(KERN_INFO "S3C24XX Clocks, (c) 2004 Simtec Electronics/n");
/* initialise the main system clocks */
clk_xtal.rate = xtal; //时钟源的频率
clk_upll.rate = s3c2410_get_pll(__raw_readl(S3C2410_UPLLCON), xtal);
clk_mpll.rate = fclk;
clk_h.rate = hclk;
clk_p.rate = pclk;
clk_f.rate = fclk;
/* 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_mpll) < 0) //注册mpll
printk(KERN_ERR "failed to register mpll clock/n");
if (s3c24xx_register_clock(&clk_upll) < 0) //注册upll
printk(KERN_ERR "failed to register upll clock/n");
if (s3c24xx_register_clock(&clk_f) < 0) //注册fclk
printk(KERN_ERR "failed to register cpu fclk/n");
if (s3c24xx_register_clock(&clk_h) < 0) //注册hclk
printk(KERN_ERR "failed to register cpu hclk/n");
if (s3c24xx_register_clock(&clk_p) < 0) //注册pclk
printk(KERN_ERR "failed to register cpu pclk/n");
return 0;
}
这个函数把所有的clock都注册进系统, 以备以后使用.
arch/arm/plat-s3c24xx/clock.c:
/* initialise the clock system */
int s3c24xx_register_clock(struct clk *clk)
{
clk->owner = THIS_MODULE;
if (clk->enable == NULL)
clk->enable = clk_null_enable; //enable函数, 以后会用到
/* add to the list of available clocks */
mutex_lock(&clocks_mutex);
list_add(&clk->list, &clocks); //把clock注册到clocks列表中去
mutex_unlock(&clocks_mutex);
return 0;
}
系统中存在一个clocks的列表, 系统中的所有用到的时钟都会被注册到该列表中去
arch/arm/plat-s3c24xx/clock.c:
static LIST_HEAD(clocks);
接着我们来看s3c2410_baseclk_add().
arch/arm/plat-s3c24xx/clock.c:
/* s3c2410_baseclk_add()
*
* Add all the clocks used by the s3c2410 or compatible CPUs
* such as the S3C2440 and S3C2442.
*
* We cannot use a system device as we are needed before any
* of the init-calls that initialise the devices are actually
* done.
*/
//上面的注释很明了了吧
int __init s3c2410_baseclk_add(void)
{
unsigned long clkslow = __raw_readl(S3C2410_CLKSLOW); //快,慢时钟寄存器
unsigned long clkcon = __raw_readl(S3C2410_CLKCON); //clock使能禁止寄存器
struct clk *clkp;
struct clk *xtal;
int ret;
int ptr;
clk_upll.enable = s3c2410_upll_enable; //登记使能函数
if (s3c24xx_register_clock(&clk_usb_bus) < 0) //注册usb clock
printk(KERN_ERR "failed to register usb bus clock/n");
/* register clocks from clock array */
clkp = init_clocks;//初始化要注册的clock 列表
for (ptr = 0; ptr < ARRAY_SIZE(init_clocks); ptr++, clkp++) {
/* ensure that we note the clock state */
clkp->usage = clkcon & clkp->ctrlbit ? 1 : 0; //该clock当前是否使能着,
ret = s3c24xx_register_clock(clkp); //注册该clock到系统中去
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 subsytems 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; //又是一个clock列表
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); //禁止该clock.
}
/* 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");
return 0;
}
该函数把外设要使用的clock也都注册进了系统, 便于以后设备驱动使用clock的时候获取.
那我们来看看这些外设的clock是如何定义的吧
arch/arm/plat-s3c24xx/clock.c:
struct clk clk_usb_bus = {
.name = "usb-bus",
.id = -1,
.rate = 0,
.parent = &clk_upll, //父节点,
};
Usb的clock. 它使用upll提供的clock
arch/arm/plat-s3c24xx/clock.c:
static struct clk init_clocks[] = {
{
.name = "lcd", //lcd控制器的clock
.id = -1,
.parent = &clk_h, //父节点为hclk, 即该控制器挂在了AHB总线上
.enable = s3c2410_clkcon_enable, //使能函数
.ctrlbit = S3C2410_CLKCON_LCDC,
}, {
.name = "gpio", //gpio的clock
.id = -1,
.parent = &clk_p, //父节点为pclk, 挂在APB上
.enable = s3c2410_clkcon_enable,
.ctrlbit = S3C2410_CLKCON_GPIO,
}, {
.name = "usb-host", //usb-host控制器
.id = -1,
.parent = &clk_h,
.enable = s3c2410_clkcon_enable,
.ctrlbit = S3C2410_CLKCON_USBH,
}, {
.name = "usb-device", //usb-device控制器
.id = -1,
.parent = &clk_h,
.enable = s3c2410_clkcon_enable,
.ctrlbit = S3C2410_CLKCON_USBD,
}, {
.name = "timers", //timers
.id = -1,
.parent = &clk_p,
.enable = s3c2410_clkcon_enable,
.ctrlbit = S3C2410_CLKCON_PWMT,
}, {
.name = "uart", //uart
.id = 0,
.parent = &clk_p,
.enable = s3c2410_clkcon_enable,
.ctrlbit = S3C2410_CLKCON_UART0,
}, {
.name = "uart",
.id = 1,
.parent = &clk_p,
.enable = s3c2410_clkcon_enable,
.ctrlbit = S3C2410_CLKCON_UART1,
}, {
.name = "uart",
.id = 2,
.parent = &clk_p,
.enable = s3c2410_clkcon_enable,
.ctrlbit = S3C2410_CLKCON_UART2,
}, {
.name = "rtc",
.id = -1,
.parent = &clk_p,
.enable = s3c2410_clkcon_enable,
.ctrlbit = S3C2410_CLKCON_RTC,
}, {
.name = "watchdog",
.id = -1,
.parent = &clk_p,
.ctrlbit = 0,
}, {
.name = "usb-bus-host",
.id = -1,
.parent = &clk_usb_bus,
}, {
.name = "usb-bus-gadget",
.id = -1,
.parent = &clk_usb_bus,
},
};
arch/arm/plat-s3c24xx/clock.c:
/* standard clock definitions */
static struct clk init_clocks_disable[] = {
{
.name = "nand",
.id = -1,
.parent = &clk_h,
.enable = s3c2410_clkcon_enable,
.ctrlbit = S3C2410_CLKCON_NAND,
}, {
.name = "sdi",
.id = -1,
.parent = &clk_p,
.enable = s3c2410_clkcon_enable,
.ctrlbit = S3C2410_CLKCON_SDI,
}, {
.name = "adc",
.id = -1,
.parent = &clk_p,
.enable = s3c2410_clkcon_enable,
.ctrlbit = S3C2410_CLKCON_ADC,
}, {
.name = "i2c",
.id = -1,
.parent = &clk_p,
.enable = s3c2410_clkcon_enable,
.ctrlbit = S3C2410_CLKCON_IIC,
}, {
.name = "iis",
.id = -1,
.parent = &clk_p,
.enable = s3c2410_clkcon_enable,
.ctrlbit = S3C2410_CLKCON_IIS,
}, {
.name = "spi",
.id = -1,
.parent = &clk_p,
.enable = s3c2410_clkcon_enable,
.ctrlbit = S3C2410_CLKCON_SPI,
}
};
所有这些clock都被注册进了系统, 接着我们来看使能函数.
arch/arm/plat-s3c24xx/clock.c:
int s3c2410_clkcon_enable(struct clk *clk, int enable)
{
unsigned int clocks = clk->ctrlbit; //该位就是该clock在寄存器S3C2410_CLKCON中的使能位
unsigned long clkcon;
//接下来就是使能该位
clkcon = __raw_readl(S3C2410_CLKCON);
if (enable)
clkcon |= clocks;
else
clkcon &= ~clocks;
/* ensure none of the special function bits set */
clkcon &= ~(S3C2410_CLKCON_IDLE|S3C2410_CLKCON_POWER);
__raw_writel(clkcon, S3C2410_CLKCON); //写回寄存器,即使能该clock.
return 0;
}
该函数主要是对寄存器S3C2410_CLKCON的操作, 可以参考2410的data sheet.
实际上写到这里clock的初始化基本完成了, 不过通过前面的代码我们可以看到, 外设的clock都还没使能了, 那在什么时候被使能的呢? 呵呵当然是在设备驱动里了, 我们以nand为例来看下.
Drivers/mtd/nand/s3c2410.c:
static int s3c24xx_nand_probe(struct platform_device *pdev,
enum s3c_cpu_type cpu_type)
{
…..
info->clk = clk_get(&pdev->dev, "nand"); //从系统中获取nand的clock
if (IS_ERR(info->clk)) {
dev_err(&pdev->dev, "failed to get clock");
err = -ENOENT;
goto exit_error;
}
clk_enable(info->clk); //使能该clock.
…..
}
看到了吧, 在nand的驱动里通过clk_get来从初始化时注册的clock列表中获取nand的clock, 然后通过clk_enable()来使能该clock.
arch/arm/plat-s3c24xx/clock.c:
int clk_enable(struct clk *clk)
{
if (IS_ERR(clk) || clk == NULL)
return -EINVAL;
clk_enable(clk->parent); //先使能父clock
mutex_lock(&clocks_mutex);
if ((clk->usage++) == 0)
(clk->enable)(clk, 1); //使能自己, 这个enable函数就是初始化时登记的函数.
mutex_unlock(&clocks_mutex);
return 0;
}
我们可以看到要使能某个clock时必须要先使能父clock, 然后才能使能自己,
Clock的disable工作跟enable差不多, 不分析了.
通过这篇文章的分析, 我们至少知道了每个设备的clock是如何而来的, 又是如何被使能的这么一个流程, 希望这篇文档对大家有所帮助.