2410下clock源碼分析

 

2410clock源碼分析

Author:aaron

   這篇文章主要使用2.6.222410方面關於clock的源碼來進行簡單的分析, 希望通過這篇文檔能對系統中的clock的使用問題有個瞭解. 

   寫這篇文檔除了參考了源碼外, 還要參考2410data sheet, 畢竟代碼都是按照spec來寫的嘛. 我們先來看下2410下各種clock是如何產生的:

      

我們可以看到, 2410的時鐘源可以有兩種: 由晶振產生或由外部CLOCK直接提供, 這可以由OM[3:2]來選擇, 一般都是硬件定死的, 圖中FCLKCPU使用, HCLK爲在AHB總線一側的設備使用, PCLKAPB總線一側的設備使用.  時鐘源進來後要通過MPLL電路來產生FCLK,HCLK,PCLK, 因此很明顯, 我們要通過設置MPLL方面的寄存器來控制輸出的時鐘頻率. 

更詳細的關於clock方面的描述請參考2410data 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,   //父節點,

};

Usbclock. 它使用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",   //gpioclock

              .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的操作, 可以參考2410data 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");  //從系統中獲取nandclock

       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列表中獲取nandclock, 然後通過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,  然後才能使能自己,

 Clockdisable工作跟enable差不多, 不分析了.

 通過這篇文章的分析, 我們至少知道了每個設備的clock是如何而來的, 又是如何被使能的這麼一個流程, 希望這篇文檔對大家有所幫助.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章