S3C2410的时钟驱动分析

来源:互联网 发布:网络上下其手的意思 编辑:程序博客网 时间:2024/05/22 08:06

S3C2410的时钟驱动分析


1起点

我们以QT2410这个机器配置为例来说明.

在文件.../arch/arm/mach-s3c2410/mach-qt2410.c中我们定义一个机器配置

MACHINE_START(QT2410,"QT2410")

.phys_io=S3C2410_PA_UART,

.io_pg_offst=(((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,

.boot_params=S3C2410_SDRAM_PA + 0x100,

.map_io=qt2410_map_io,

.init_irq=s3c24xx_init_irq,

.init_machine=qt2410_machine_init,

.timer=&s3c24xx_timer,

MACHINE_END


当内核启动时以QT2410的配置启动时,start_kernel(...)中会调用map_io()qt2410_map_io()函数.

//start_kernel(...)中被调用.

staticvoid __init qt2410_map_io(void)

{

//添加IO地址映射.

s3c24xx_init_io(qt2410_iodesc,ARRAY_SIZE(qt2410_iodesc));

//初始化各种时钟源.

s3c24xx_init_clocks(12*1000*1000);

//初始化UART

s3c24xx_init_uarts(smdk2410_uartcfgs,ARRAY_SIZE(smdk2410_uartcfgs));

}

在说s3c24xx_init_clocks(12*1000*1000);之前我们需要先看一下前面那个函数.

其中在s3c24xx_init_io(...)

void__init s3c24xx_init_io(struct map_desc *mach_desc, int size)

{

unsignedlong idcode = 0x0;


/*initialise the io descriptors we need for initialisation */

//初始化我们需要的IO描述符,

iotable_init(s3c_iodesc,ARRAY_SIZE(s3c_iodesc));


//CPU的结构代码,当前为0x32410000

if(cpu_architecture() >= CPU_ARCH_ARMv5) {

idcode= s3c24xx_read_idcode_v5();

}else {

idcode= s3c24xx_read_idcode_v4();

}

//根据当前CPUchipid查找到当前是的cpu_table结构.

cpu= s3c_lookup_cpu(idcode);


if(cpu == NULL) {

printk(KERN_ERR"Unknown CPU type 0x%08lx/n", idcode);

panic("UnknownS3C24XX CPU");

}


printk("CPU%s (id 0x%08lx)/n", cpu->name, idcode);


if(cpu->map_io == NULL || cpu->init == NULL) {

printk(KERN_ERR"CPU %s support not enabled/n", cpu->name);

panic("UnsupportedS3C24XX CPU");

}


//赋值机器重启调用函数.

arm_pm_restart= s3c24xx_pm_restart;


//调用当前cpuIO映射函数.

(cpu->map_io)(mach_desc,size);

}

其中对于s3c2410芯片来说读到的ID0x32410000,则对应的cpu_table结构为:

staticstruct 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

},

终于说到了这个cpu_table结构,这个结构在s3c24xx_init_clocks(12*1000*1000)中会用到

2初始化

.../arch/arm/plat-s3c24xx/cpu.c

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);//初始化各种时钟源。

}

这里的(cpu->init_clocks)(xtal)实际为:

s3c2410_init_clocks(12* 1000 * 1000 )

.../arch/arm/mach-s3c2410/s3c2410.c

void__init s3c2410_init_clocks(int xtal)

{

unsignedlong tmp;

unsignedlong fclk;

unsignedlong hclk;

unsignedlong 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);


//读取时钟分频寄存器内容

tmp= __raw_readl(S3C2410_CLKDIVN);


/*work out clock scalings */

//根据核心时钟计算/内存时钟/外围时钟.

hclk= fclk / ((tmp & S3C2410_CLKDIVN_HDIVN) ? 2 : 1);

pclk= hclk / ((tmp & S3C2410_CLKDIVN_PDIVN) ? 2 : 1);


/*print brieft summary of clocks, etc */


printk("S3C2410:core %ld.%03ld MHz, memory %ld.%03ld MHz, peripheral %ld.%03ldMHz/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);


//注册所有初始化的,未初始化的时钟.

s3c2410_baseclk_add();

}



/*initalise all the clocks */

//初始化所有时钟.

//xtal: 基础时钟,

//fclk:内核时钟

//hclk: 内存时钟

//pclk: 外围

int__init s3c24xx_setup_clocks(unsigned long xtal,

unsignedlong fclk,

unsignedlong hclk,

unsignedlong 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)

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");


return0;

}

注册了6个时钟,所谓注册就是将这些时钟加入到了clocks的链表中.

时钟的结构如下:


structclk {

//属性

structlist_head list;// 链表头

structmodule *owner;// 所属模块

structclk *parent;// 父节点

constchar *name;// 名称

int id;// ID

int usage;// 使用计数,>0,便enable,<=0,disable

unsignedlong rate;// 速率

unsignedlong ctrlbit;// 控制位.

//操作函数

int (*enable)(struct clk *, int enable);// 使能

int (*set_rate)(struct clk *c, unsigned long rate);// 设置速率

unsignedlong (*get_rate)(struct clk *c);// 读取速率

unsignedlong (*round_rate)(struct clk *c, unsigned long rate);//

int (*set_parent)(struct clk *c, struct clk *parent);// 设置父节点.

};


//添加所有s3c2410使用的时钟.

//在每一个用来初始化设备的调用没有实际完成前,我们不能使用这个系统设备.

int__init s3c2410_baseclk_add(void)

{

//读慢时钟控制寄存器内容

unsignedlong clkslow = __raw_readl(S3C2410_CLKSLOW);

//读时钟控制寄存器内容.

unsignedlong clkcon = __raw_readl(S3C2410_CLKCON);

structclk *clkp;

structclk *xtal;

intret;

intptr;

//设置时钟clk_upll的使能函数,在这个函数中通过写慢速时钟控制寄存器的来使能UCLK

clk_upll.enable= s3c2410_upll_enable;


//注册 USB-BUS时钟,

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);

}

}



//我们必须小心去disable那些我们在启动时不想打开使用的时钟.例如,LCD子系统,他们向总线发出DMA请求可以导致系统去查询

//disableLCD时钟在LCD正在使用的时候是危险的。所以bootloader应该十分小心,如果不需要打开LCD,就不要儾使能它。

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");


return0;

}

初始化后需要关闭的时钟:


/*standard clock definitions */


staticstruct 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,

}

};

初始化后需要根据控制寄存器的状态来判断是否工作的时钟:

staticstruct 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,

},

};


3时钟的使用

比如在s3c2410nandflash驱动的使用中

获取时钟:在驱动加载时

//获得给nandflash使用的时钟控制结构.并便能该时钟.

staticint s3c24xx_nand_probe(struct platform_device *pdev,

enum s3c_cpu_type cpu_type)

{

..............

info->clk= clk_get(&pdev->dev, "nand");

if(IS_ERR(info->clk)) {

dev_err(&pdev->dev,"failed to get clock/n");

err= -ENOENT;

gotoexit_error;

}

clk_enable(info->clk);

................

}

  • 首先调用clk_get(...)通过比较名称”nand”找到时钟结构:

{

.name="nand",

.id=-1,

.parent=&clk_h,

.enable=s3c2410_clkcon_enable,

.ctrlbit=S3C2410_CLKCON_NAND,

},

  • 然后调用clk_enable(info->clk);即调用:

ints3c2410_clkcon_enable(struct clk *clk, int enable)

{

unsignedint clocks = clk->ctrlbit;

unsignedlong 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);


return0;

}

通过写时钟控制寄存器将nand时钟使能.同样也可以通过这个函数将时钟关闭.

同样在电源管理的suspend函数中将时钟关闭,resume中将时钟打开.


原创粉丝点击