Linux内核时钟框架

ARM嵌入式系统时钟管理

逢山开路 遇水架桥,今天想自己写个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()注册了系统的所有时钟,仔细看看它。 在这个函数被调用之前,代码已经根据3C2410_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内核中断框架源码是一个复杂且核心的部分,以下从几个关键方面进行详细分析: ### 中断管理核心结构 `struct irq_desc` `struct irq_desc` 是 Linux 内核中断管理的核心枢纽,它连接了硬件中断控制器、中断处理函数和内核中断框架,通过统一的结构抽象不同硬件的差异,实现了中断的高效管理。理解该结构是掌握 Linux 中断机制的关键 [^2]。以下是 `struct irq_desc` 相关源码概念的体现,它描述了每个中断请求(IRQ): ```c // 示例代码示意,实际定义可能更复杂 struct irq_desc { // 可能包含指向硬件中断控制器操作函数集的指针 struct irq_chip *irq_chip; // 中断处理函数相关信息 struct irqaction *action; // 其他成员... }; ``` ### 中断请求队列与 `irq_desc_t` Linux 设计架构时,专门为每个中断请求 IRQ 设置一个完整队列,即中断请求队列。对于每个 IRQ,都用一个 `irq_desc_t` 数据结构来描述。中断线逻辑上对应一个中断请求号(中断号),第 x 个中断号(IRQx)的缺省中断向量为 x + 32。在 256 个中断向量中,除 32 个分配给异常以外,还有 244 个作为中断向量 [^3]。 ### 中断向量 Intel x86 系列微机共支持 256 种向量中断,为使处理器较容易地识别每种中断源,将它们从 0 - 255 编号,即赋予一个中断类型码 n,Intel 把这个 8 位的无符号整数叫做一个向量,因此也叫中断向量 [^5]。 ### 中断控制与原子性保证 CPU 对于任务切换是通过时钟中断来控制的,只要将中断屏蔽,就可以保证在当前 CPU 中的所有操作都不会被中断,从而保证了原子性。在单核 CPU 上,通过操作 EFLAGS 寄存器相当于保存 EFLAGS 表示中断 [^4]。以下是简单的中断屏蔽示例代码: ```c // 屏蔽中断 void disable_interrupts(void) { asm volatile("cli"); } // 开启中断 void enable_interrupts(void) { asm volatile("sti"); } ``` ### 整体中断处理流程 中断处理开始于硬件设备产生中断信号,信号通过中断线传输,对应到相应的中断请求号(IRQ)。内核根据 `irq_desc` 结构找到对应的中断处理函数,执行中断处理。在处理过程中,可能会涉及到中断屏蔽、上下文切换等操作以保证系统的稳定性和原子性。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值