2410下clock源码分析

这篇文章主要使用 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 是如何而来的 , 又是如何被使能的这么一个流程 , 希望这篇文档对大家有所帮助 .

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值