您的位置:首页 > 其它

S3C2410的时钟驱动分析

2008-12-01 09:00 381 查看

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(...)中被调用
.
static
void __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)
{
unsigned
long 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();
}
//
根据当前CPU的chip
id查找到当前是的cpu_table结构.
cpu
= s3c_lookup_cpu(idcode);

if
(cpu == NULL) {
printk(KERN_ERR
"Unknown CPU type 0x%08lx/n", idcode);
panic("Unknown
S3C24XX 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("Unsupported
S3C24XX CPU");
}

//
赋值机器重启调用
函数.
arm_pm_restart
= s3c24xx_pm_restart;

//
调用
当前cpu的IO映射函数.
(cpu->map_io)(mach_desc,
size);
}
其中对于s3c2410芯片来说读到的ID为0x32410000,则对应的cpu_table结构为:
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
},
终于说到了这个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)
{
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);

//
读取时钟分频寄存器内容
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.%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);

//
注册所有
初始化的,未初始化的时钟.
s3c2410_baseclk_add();
}

/*
initalise all the clocks */
//
初始化所有时钟.
//
xtal: 基础时钟,
//
fclk:内核时钟
//
hclk: 内存时钟
//
pclk: 外围
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)
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");

return
0;
}
注册了6个时钟,
所谓注册就是将这些时钟加入到了clocks的链表中.
时钟的结构如下:

struct
clk {
//
属性
struct
list_head list; // 链表头
struct
module *owner; // 所属模块

struct
clk *parent; // 父节点
const
char *name; // 名称
int
id; // ID
int
usage; // 使用计数,当>0时,便
enable,<=0时,disable
unsigned
long rate; // 速率
unsigned
long ctrlbit; // 控制位.
//
操作函数
int
(*enable)(struct clk *, int enable); // 使能
int
(*set_rate)(struct clk *c, unsigned long rate); // 设置速率
unsigned
long (*get_rate)(struct clk *c); // 读取速率

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

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

//
添加所有s3c2410使用的时钟.
//
在每一个用来初始化设备的调用没有实际完成前,我们不能使用这个系统设备
.
int
__init s3c2410_baseclk_add(void)
{
//
读慢时钟控制寄存器内容
unsigned
long clkslow = __raw_readl(S3C2410_CLKSLOW);
//
读时钟控制寄存器内容.
unsigned
long clkcon = __raw_readl(S3C2410_CLKCON);
struct
clk *clkp;
struct
clk *xtal;
int
ret;
int
ptr;

//
设置时钟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");

return
0;
}
初始化后需要关闭的时钟:

/*
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,
}
};
初始化后需要根据控制寄存器的状态来判断是否工作的时钟:
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,
},
};

3
时钟的使用
比如在s3c2410的nand
flash驱动的使用中
获取时钟:
在驱动加载时
//
获得给nand
flash使用的时钟控制结构.并便能该时钟.
static
int 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;
goto
exit_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);即调用:

int
s3c2410_clkcon_enable(struct clk *clk, int enable)

{

unsigned
int clocks = clk->ctrlbit;

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

return
0;

}

通过写时钟控制寄存器将nand时钟使能.同样也可以通过这个函数将时钟关闭.
同样在电源管理的suspend函数中将时钟关闭,在resume中将时钟打开.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: