linux 时钟源初步分析linux kernel 时钟框架详细介绍
初步概念: 看datasheet的关于时钟与定时器的部分,
FCLK供给cpu,
HCLK供给AHB总线设备(存储器控制器,中断控制器、LCD控制器、DMA、USB主机控制器等),
PCLK供给APB总线上的设备(watchdog、IIS、i2c、 pwm、定时器、ADC、uart、gpio、rtc、spi)
上电时 fclk的时钟等于外部时钟fin, 然后等待LOCKTIME后, 依照MPLLCON寄存器的设置,倍频到高频。
UPLLCON专用于USB同于MPLLCON。
关于分频: CLKDIVN寄存器设置fclk、hclk、pclk的分频比。
时钟控制寄存器CLOCK CONTROL REGISTER (CLKCON)
寄存器的每一位大部分控制一个硬件模块的时钟源,这个功能将被用于内核控制硬件模块的方式,首先内核定义了
static struct clk init_clocks[] = {
{
  .name  = "dma",
  .id  = 0,
  .parent  = &clk_h,
  .enable  = s3c2412_clkcon_enable,
  .ctrlbit = S3C2412_CLKCON_DMA0,
 }, {
  .name  = "dma",
  .id  = 1,
  .parent  = &clk_h,
  .enable  = s3c2412_clkcon_enable,
  .ctrlbit = S3C2412_CLKCON_DMA1,
 },
......
......
{ .name    = "adc",
      .id       = -1,
      .parent  = &clk_p,
      .enable  = s3c24xx_clkcon_enable,
      .ctrlbit = S3C2410_CLKCON_ADC
    },
}(其中的每一项为clock结构体)
结构体,其中ctrlbit项对应于CLKCON的每一位, 其中每一项的编号是通过 name来识别, 操作函数都为s3c24xx_clkcon_enable, 其中parent项表示该模块的时钟来源。
在内核里面有时候我们需要自己手动的开启硬件模块,方法如下:
static struct clk *adc_clock;
定义一个clock时钟,这个结构体代表一个时钟的抽象。
adc_clock = clk_get(NULL, "adc");
 if (!adc_clock) {
  printk(KERN_ERR "failed to get adc clock source/n");
  return -ENOENT;
 }
获取相应模块所对应的clock结构体(存于static struct clk init_clocks中), clk_get函数是通过 name去 init_clocks结构体中遍历找到相应的clock,进而获得了硬件模块相应的clock结构体。
clk_enable(adc_clock);
使能clock,即使能CLKCON中硬件模块相对应的位,从而实现了硬件模块的时钟源使能。clk_enable函数使用递归方式使能相应的CLKCON位,这个意思也就是说这个函数调用同时也会将这个时钟源的上一级时钟源同样使能,一直往上推保证这条时钟线ok,这个是主要的意思,具体涉及到上级时钟源的设置还需要另外的机制控制,可以参照源码分析。
①.时钟源选择
在系统复位时检测引脚OM3:OM2,若是0:0,则主时钟源选择外部晶振,usb时钟源选择外部晶振
②.MPLLCON main pll control 主时钟寄存器控制
用于设定FCLK和Fin的倍数。

Mpll(FCLK) = ( 2 × m × Fin ) / ( p × 2^s )
其中m=MDIV+8, p=PDIV+2, s=SDIV,Fin晶振频率
比如
Fin=12MHz
MDIV=0x7F=127,m=135
PDIV=2,p=4
SDIV=1,s=1
则FCLK=405MHz
注意:系统复位时,必须写一次MPLLCON UPLLCON ,这样系统才能正常工作。即使不改变其值,即使复位后MPLL UPLL都是使能的,也要写一次,另外还有如下
③.CLKDIVN clock divider control 时钟分频控制寄存器
用于设置 FCLK HCLK PCLK三者的比例
而CAMDIVN如下
比如
CAMDIVN[8]=0
CAMDIVN[9]=0
HDIVN=2,则HCLK=FCLK / 4
PDIVN=1,,则PCLK=HCLK / 2
有以下示例,摘自嵌入式linux开发完全手册
2011-12-18
2440的片内外设时钟使能控制

linux下将各个片内外设时钟统一管理,组成时钟队列。
arch/arm/plat-s3c24xx/s3c2410-clock.c
- 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,
 - }
 - };
 - static struct clk init_clocks[] = {
 - {
 - .name = "lcd",
 - .id = -1,
 - .parent = &clk_h,
 - .enable = s3c2410_clkcon_enable,
 - .ctrlbit = S3C2410_CLKCON_LCDC,
 - }, {
 - .name = "gpio",
 - .id = -1,
 - .parent = &clk_p,
 - .enable = s3c2410_clkcon_enable,
 - .ctrlbit = S3C2410_CLKCON_GPIO,
 - }, {
 - .name = "usb-host",
 - .id = -1,
 - .parent = &clk_h,
 - .enable = s3c2410_clkcon_enable,
 - .ctrlbit = S3C2410_CLKCON_USBH,
 - }, {
 - .name = "usb-device",
 - .id = -1,
 - .parent = &clk_h,
 - .enable = s3c2410_clkcon_enable,
 - .ctrlbit = S3C2410_CLKCON_USBD,
 - }, {
 - .name = "timers",
 - .id = -1,
 - .parent = &clk_p,
 - .enable = s3c2410_clkcon_enable,
 - .ctrlbit = S3C2410_CLKCON_PWMT,
 - }, {
 - .name = "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,
 - },
 - };
 
- //在需要操作各个外设的时钟时,就调用用内核提供的各个函数即可。如摘自mini2440_adc.c
 - static struct clk *adc_clock;
 - adc_clock = clk_get(NULL, "adc");//获取时钟
 - if (!adc_clock) {
 - printk(KERN_ERR "failed to get adc clock source\n");
 - return -ENOENT;
 - }
 - clk_enable(adc_clock);//使能时钟
 - //在不需要时,禁止掉
 - if (adc_clock) {
 - clk_disable(adc_clock);//禁止时钟
 - clk_put(adc_clock);
 - adc_clock = NULL;//源码下是一个空操作,可能是预留
 - }
 
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()注册了系统的所有时钟,仔细看看它。
在这个函数被调用之前,代码已经根据 S3C2410_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 ==> enable
s3c24xx_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的操作。逢山开路 遇水架桥,今天想自己写个adc的驱动,发现不清楚系统各个模块的系统时钟如何使用。
总不能自己想怎么弄,就怎么弄吧,还是学学框架吧——使用时钟的框架。
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()注册了系统的所有时钟,仔细看看它。
在这个函数被调用之前,代码已经根据 S3C2410_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 ==> enable
s3c24xx_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的操作。
linux 时钟源初步分析linux kernel 时钟框架详细介绍的更多相关文章
- Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7)
		
http://blog.chinaunix.net/uid-20543672-id-3157283.html Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3 ...
 - Linux内核源码分析方法_转
		
Linux内核源码分析方法 转自:http://www.cnblogs.com/fanzhidongyzby/archive/2013/03/20/2970624.html 一.内核源码之我见 Lin ...
 - linux内存源码分析 - 零散知识点
		
本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 直接内存回收中的等待队列 内存回收详解见linux内存源码分析 - 内存回收(整体流程),在直接内存回收过程中, ...
 - linux内存源码分析 - SLAB分配器概述【转】
		
本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 之前说了管理区页框分配器,这里我们简称为页框分配器,在页框分配器中主要是管理物理内存,将物理内存的页框分配给申请 ...
 - linux内存源码分析 - SLAB分配器概述
		
本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 之前说了管理区页框分配器,这里我们简称为页框分配器,在页框分配器中主要是管理物理内存,将物理内存的页框分配给申请 ...
 - linux中断源码分析 - 中断发生(三)
		
本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 回顾 上篇文章linux中断源码分析 - 初始化(二)已经描述了中断描述符表和中断描述符数组的初始化,由于在初始 ...
 - Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7)【转】
		
原文地址:Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://bl ...
 - Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7)【转】
		
原文地址:Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.c ...
 - Linux内核源码分析之setup_arch (二)
		
1. 概述 接着上一篇<Linux内核源码分析之setup_arch (一)>继续分析,本文首先分析arm_memblock_init函数,然后分析内核启动阶段的是如何进行内存管理的. 2 ...
 
随机推荐
- ios-XML文档解析之SAX解析
			
首先SAX解析xml *xml文档的格式特点是节点,大体思路是把每个最小的子节点作为对象的属性,每个最小子节点的'父'节点作为对象,将节点转化为对象,输出. 每个节点都是成对存在的,有开始有结束.有始 ...
 - Struts2 用 s:if test 判断属性和字符串相等时 注意双引号和单引号的使用
			
字符串N一定要用“”双引号包含,从test的包含则用单引号 ‘ ’,如果相反,则不能正确判断该属性是否与该字符串相等. 正确:<s:if test='activityBean.searchFor ...
 - js 多选题选项内容显示在标题下
			
<body><div class="page-container"> <div class="view-container"> ...
 - 客户端 ios与android 的判断
			
<script> if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) { //alert(navigator.userAgen ...
 - Android Studio更新升级方法(转)
			
自从2013 Google I/O大会之后,笔者就将android ide开发工具从eclipse迁移到Android Studio了,android studio一直在更新完善,为了与时俱进,我们当 ...
 - (转)【ASP.NET开发】获取客户端IP地址 via C#
			
[ASP.NET开发]获取客户端IP地址 via C# 说明:本文中的内容是我综合博客园上的博文和MSDN讨论区的资料,再通过自己的实际测试而得来,属于自己原创的内容说实话很少,写这一篇是为了记录自己 ...
 - 解决maven编译spark1.5报错问题
			
spark1.5发布了,赶紧去下了源码尝鲜 git clone git://github.com/apache/spark.git -b branch-1.5 输入命令进行编译 ./make-dist ...
 - 夺命雷公狗---linux之centos的安装
			
由于要玩node.js了,所以还是来复习下linux系统才行,所以夺命雷公狗分享两套安装linux的方法,这是centos的安装方法,,, 管理员默认帐号为:root,密码则是刚才您输入的那个...
 - yii2中表单的字段标签名称
			
1.以登陆页面为例,默认是英文的,在loginForm.php中添加attributeLabels,可以变成中文 具体代码如下: public function attributeLabels(){ ...
 - Sublime怎样新建HTML文档
			
1.在右下角有个plain text的标志,点击,选择文件类型为HTML 2.保存为HTML文档 3.这时候输入"!"(注意一定要选择对输入法) 4.然后按"Tab&qu ...