linux 驱动笔记(六)

来源:互联网 发布:java的编译命令是哪个 编辑:程序博客网 时间:2024/05/16 07:10

第十二章 时间管理

 

1 什么是HZ

    linux内核的时钟频率,linux操作系统在工作的过程中,也需要一个时钟,这个时钟一般叫内核时钟 滴答时钟。进程的调度 时间片的轮转都是以这个时钟为基础的。

    内核时钟使用一个硬件的时钟模块产生的,该时钟模块的工作频率就是HZ

    一般HZ的值范围10~1000之间,HZ的值与CPU的性能有关,HZ的大,操作系统的时间精度越高 但是系统的负担就会重。

    内核定时器的周期是1/HZ

1.1 如何改变HZ的值?

1.1.1查看HZ的值

#vi .config

CONFIG_HZ=256

注意:

    .config是用来存放配置后的结果,但是我们不能直接修改.config文件。.config的内容是make menuconfigKconfig共同作用得带的。

 

1.1.2修改HZ的值

#vi arch/arm/Kconfig

config HZ

int

default 128 if ARCH_L7200

default 256 if S5P_HIGH_RES_TIMERS  --->OK

default 200 if ARCH_EBSA110 || ARCH_S3C2410 || ARCH_S5P6440 || ARCH_S5P6442 || ARCH_S5PV210

default OMAP_32K_TIMER_HZ if ARCH_OMAP && OMAP_32K_TIMER

default AT91_TIMER_HZ if ARCH_AT91

default 100

 

注意:修改完HZ的值,需要重新编译内核源码。

      HZ是内核中的一个全局的常量。printk("HZ=%d\n",HZ);

      所有的操作系统,都需要内核时钟。

 

1.2 内核时钟的来源

linux内核时钟,也是由硬件的定时器模块产生,在内核启动初始化过程,就会初始化内核的时钟。

linux内核启动过程中,初始化了一个硬件的时钟模块,并将该模块作为linux的内核时钟。

 

kernel的启动输出信息:

[0.000000] HZ[256]

 

1.3内核时钟初始化的入口???????

 

1.3.1 linux内核源码针对某个硬件平台的主初始化源文件

  linux/arch/arm/mach-s5pv210/mach-smdkc110.c

  linux/arch/arm/mach-s5pv210/mach-gec210.c

 

1.3.2找到主初始化源文件中的机器宏

MACHINE_START(GEC210, "GEC210")

.phys_io = S3C_PA_UART & 0xfff00000, //--->uartSFR物理地址

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

.boot_params = S5P_PA_SDRAM + 0x100, //uboot传递给zImage的启动参数存放的位置。

.init_irq = s5pv210_init_irq, //中断初始化

.map_io = smdkc110_map_io, //IO内存映射

.init_machine = smdkc110_machine_init, //主初始化函数

.timer = &s5p_systimer, //硬件定时器

MACHINE_END

 

linux内核在启动过程中会调用宏中定义的信息。

 

1.3.3硬件定时器--s5p_systimer

 

s5p_systimer的定义:

 

/arch/arm/plat-s5p/systimer-s5p.c

struct sys_timer s5p_systimer = {

.init = s5p_systimer_init,

.offset = s5p_gettimeoffset,

.resume = s5p_systimer_setup

};

 

/arch/arm/plat-s5p/hr-time-rtc.c

struct sys_timer s5p_systimer = {

.init = s5p_timer_init,

};

 

1.4如何判断使用哪个源文件中的结构体s5p_systimer???

1.4.1/arch/arm/plat-s5p/目录,查看哪个源文件已经编译成了目标文件

/arch/arm/plat-s5p$ ls hr-time-rtc.*

hr-time-rtc.c  hr-time-rtc.o

 

1.4.2正规的方法应该查看/arch/arm/plat-s5p/目录的Makefile

 

ifndef CONFIG_S5P_HIGH_RES_TIMERS

obj-$(CONFIG_SYSTIMER_S5P) += systimer-s5p.o

else

ifdef CONFIG_HRT_RTC

obj-y += hr-time-rtc.o

endif

endif

 

确定三个条件编译选项的值:

CONFIG_S5P_HIGH_RES_TIMERS

CONFIG_SYSTIMER_S5P

CONFIG_HRT_RTC   

 

.config --->针对具体的硬件平台所配置好的条件编译选项。make menuconfig + Kconfig

 

1.4.3 /arch/arm/plat-s5p/hr-time-rtc.c

这个源文件初始化了RTC TICK时钟,使用该TICK时钟作为linux的系统时钟。

 

思考:

1 什么是HZ

2 如何设置HZ的值?

3 HZ是哪里来的?

4 HZ有什么影响?

 

2 什么是jiffies

 

jiffies也是内核中的一个全局的变量,该变量记录的内核从启动到现在,内核时钟中断的次数。jiffies是一个32bits的无符号整性值,在GEC210的系统中,多长时间,jiffies的值会产生“回绕”?

天数 = 2**32/256/60/60/24

在一秒内,jiffies的值增加是HZ=256)。

 

问题: jiffesHZ之间有什么关系?

jiffies的值在1秒钟之内增加HZlinux系统启动到现在运行的""数:jiffies/HZ

jiffies的值每1/HZ秒增加一次。1/HZ =4ms

 

printk("jiffies = %d\n",jiffies);

 

3 内核中延时函数

 

3.1 应用程序的延时函数---睡眠延时

#include <unistd.h>

unsigned int sleep(unsigned int seconds);

int usleep(useconds_t usec);

 

3.2 内核中的忙等待延时

void ndelay(unsigned long x) //纳秒级的忙等待延时

void udelay(unsigned long x) //微秒级的忙等待延时

void mdelay(unsigned long x) //毫秒级的忙等待延时

适合于短延时,ms以下

 

3.3 内核中的睡眠延时

#include <linux/delay.h>

void ssleep(unsigned int seconds)

void msleep(unsigned int msecs)

 

适合于长延时,ms以上

 

思考:

忙等待延时和睡眠延时的区别??

 

注意:

忙等待延时的精度还是比较高的

睡眠延时的精度与HZ有关系,HZ越大,睡眠延时的精度越高

 

void msleep(unsigned int msecs)

{

unsigned long timeout = msecs_to_jiffies(msecs) + 1; //ms转换成jiffies

 

while (timeout)

timeout = schedule_timeout_uninterruptible(timeout);

}

 

毫秒级的延时,以1/HZ为单位的。

msleep(1)  --->延时4ms

msleep(4)  --->延时4ms

msleep(5)  --->延时8ms

 

4 内核的动态定时器

内核动态定时器以内核时钟为基础,可以设定一个超时的时间点,当系统超时了以后,会执行一个超时的处理函数。其中:

超时的时间点和超时处理函数都是可以自己设定的。

 

应用:

linux内核中,收到数据后,我想2秒钟之后,再处理这个数据,这个时候可以使用内核定时器

linux内核中,如果8ms产生一次定时器中断,利用该中断的处理函数来处理轮训的数据。

 

注意的问题:

内核动态定时器的超时时间必须是内核时钟周期的整数倍。???

 

#include <linux/timer.h>

4.1 定义内核动态定时器

struct timer_list {

struct list_head entry;

unsigned long expires;

struct tvec_base *base;

 

void (*function)(unsigned long);

unsigned long data;

 

int slack;

 

};

成员说明:

unsigned long expires; --->内核动态定时器的超时时间

void (*function)(unsigned long); --->内核动态定时器的操作处理函数

unsigned long data; ---> 向超时处理函数传递的参数

 

例:

static struct timer_list gec210_timer;

 

4.2 内核动态定时器的初始化

void init_timer(struct timer_list *timer)

 

例:

init_timer(&gec210_timer)

 

//动态定时器的超时处理函数

void gec210_timer_test(unsigned long data)

{

printk("data =%lu\n",data); //20

printk("jiffies=%d\n",jiffies);

printk("HZ = %d\n",HZ);

}

 

 

gec210_timer.expires = jiffies + HZ; //超时的时间=现在的时间(jiffis+ 1sHZ

gec210_timer.function=gec210_timer_test;

gec210_timer.data = 20;

 

4.3 将内核动态定时器加入内核,并启动内核动态定时器

void add_timer(struct timer_list *timer)

 

4.4 修改动态定时器的下一次超时时间,并启动内核动态定时器

int mod_timer(struct timer_list *timer, unsigned long expires)

 

4.5 删除内核动态定时器

int del_timer(struct timer_list *timer)

 

A作业:

定义一个动态定时器,定时器的超时时间是500ms。该定时器可以产生周期定的计时,每次计时时间到。使用printk输出字符串。

 

思考:

1 什么是内核动态定时器

2 如何在内核中设计一个动态定时器

3 如何利用内核动态定时器实现按键去抖

 

 

 

 

 

 

B 代码一:

1. Filename: led_drv.c

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/interrupt.h>

#include <linux/timer.h>

 

static struct timer_list gec210_timer;

 

//动态定时器的超时处理函数

static void gec210_timer_test(unsigned long data)

{

printk("data =%lu\n",data); //20

printk("jiffies=%lu\n",jiffies);

printk("HZ = %d\n",HZ);

mod_timer(&gec210_timer, jiffies + 3*HZ);

}

 

 

static int __init gec210_key_init(void) //驱动的初始化及安装函数

{

init_timer(&gec210_timer);

gec210_timer.expires = jiffies + HZ; //超时的时间=现在的时间(jiffis+ 1sHZ

gec210_timer.function = gec210_timer_test;

gec210_timer.data = 20;

add_timer(&gec210_timer);

printk("hello gec210\n"); //替代printf()

return 0;

}

 

static void __exit gec210_key_exit(void)

{

del_timer(&gec210_timer);

printk("good bye gec210\n");

}

 

module_init(gec210_key_init); //驱动的入口

module_exit(gec210_key_exit); //驱动的出口

 

//内核模块的描述

MODULE_AUTHOR("bobeyfeng@163.com");

MODULE_DESCRIPTION("the first demo of module");

MODULE_LICENSE("GPL"); //符合GPL协议

MODULE_VERSION("V1.0");

//--------------------------------------------------------------------------

2. Filename: Makefile

obj-m += led_drv.o

#KERNELDIR := /lib/modules/$(shell uname -r)/build

KERNELDIR := /home/gec/linux-2.6.35.7-gec-v3.0-gt110

PWD:=$(shell pwd)

 

default:

$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:

rm -rf *.o *.mod.c *.mod.o *.ko

 

B 代码二:

1. Filename: led_drv.c

 

//当有按键按下,我们就可以读按键的状态;没有按键按下,进程就睡眠在驱动的read函数中。#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/gpio.h>

#include <linux/fs.h>

#include <linux/io.h>

#include <linux/ioport.h>

#include <linux/cdev.h>

#include <linux/uaccess.h>

#include <linux/miscdevice.h>

#include <linux/interrupt.h>

#include <linux/wait.h>

#include <linux/timer.h>

 

static struct timer_list gec210_timer;

 

//1.定义个等待队列头

static wait_queue_head_t gec210_key_wq;

//3.定义一个等待队列的条件

static int gec210_key_flag = 0; //有按键按下为真(1),没有按键按下为假(0)

 

 

//定义一个buffer,存放按键的状态

static char key_buf[3] = {0,0,0}; //k2 K3 K4 没有按下

 

struct irq_info

{

char irq_name[10];

int irq_num;

};

 

struct irq_info key_irq_info[3] = {

{

.irq_name = "key2_irq",

.irq_num = IRQ_EINT(16),

},

{

.irq_name = "key3_irq",

.irq_num = IRQ_EINT(17),

},

{

.irq_name = "key4_irq",

.irq_num = IRQ_EINT(18),

},

};

 

ssize_t gec210_key_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos)

{

int ret,i;

//4.判断等待条件,有按键按下,就继续读;没有按键按下,就阻塞,进程进入等待队列睡眠。

wait_event_interruptible(gec210_key_wq, gec210_key_flag);

if(len != 3)

return -EINVAL;

ret = copy_to_user(buf,key_buf,len);

if(ret < 0)

return -EFAULT;

gec210_key_flag = 0; //重置等待条件

for(i=0;i<3;i++)

key_buf[i] = 0;

return len;

}

 

 

static struct file_operations gec210_key_fops = {

.owner = THIS_MODULE,

.read  = gec210_key_read,

};

 

 

 

static struct miscdevice gec210_key_miscdev = {

.minor = MISC_DYNAMIC_MINOR, //混杂设备的次设备号,系统自动分配:MISC_DYNAMIC_MINOR

.name = "key_drv",  //misc device的名字,也是设备文件的名字

.fops = &gec210_key_fops,  //文件操作集

};

 

//内核动态定时器的超时处理函数

static void gec210_timer_test(unsigned long data)

{

int irq;

irq = data;

if(irq == key_irq_info[0].irq_num)

{

key_buf[0] = 1;//KEY2按下

}

else if(irq == key_irq_info[1].irq_num)

{

key_buf[1] = 1;//KEY3按下

}

else if(irq == key_irq_info[2].irq_num)

{

key_buf[2] = 1;//KEY4按下

}

//5.当条件满足后,唤醒等待队列中的进程。

gec210_key_flag = 1; //当有按键按下的时候,条件设置成真

wake_up_interruptible(&gec210_key_wq);

}

 

 

//外部中断的中断服务程序

static irqreturn_t gec210_key_isr(int irq, void *dev)

{

printk("<0>""---------irq: %d isr -------- \n", irq);

gec210_timer.data = irq;

mod_timer(&gec210_timer,jiffies + 30); //改变超时时间,并打开动态定时器

return IRQ_HANDLED; // 表示isr已经正常执行了

}

 

 

static int __init gec210_key_irq_init(void)

{

int ret, i;

//2.初始化等待队列头

init_waitqueue_head(&gec210_key_wq);

for(i=0; i<3; i++)

{

ret = request_irq(key_irq_info[i].irq_num, gec210_key_isr,

IRQF_TRIGGER_RISING, key_irq_info[i].irq_name, NULL);

if(ret)

{

printk("request irq error \n");

goto failed_request_irq;

}

}

ret = misc_register(&gec210_key_miscdev);

if(ret < 0)

{

printk("misc register error\n");

goto failed_misc_register;

}

init_timer(&gec210_timer);

gec210_timer.expires = jiffies + 30; //超时的时间=现在的时间(jiffis+ 120msHZ

gec210_timer.function = gec210_timer_test;

printk("gec210 key irq init success ! \n");

 

return 0;

failed_misc_register:

failed_request_irq:

while(i--)

free_irq(key_irq_info[i].irq_num, NULL);

return ret;

}

 

 

static void __exit gec210_key_irq_exit(void)

{

int i;

for(i=0; i<3; i++)

{

free_irq(key_irq_info[i].irq_num, NULL);

}

misc_deregister(&gec210_key_miscdev);

del_timer(&gec210_timer);

printk("gec210 key irq exit success ! \n");

}

 

 

module_init(gec210_key_irq_init);

module_exit(gec210_key_irq_exit);

 

 

MODULE_AUTHOR("lllllssssssssjjjjjjjjjjjjjjj");

MODULE_DESCRIPTION("the first demo of irq device");

MODULE_LICENSE("GPL"); //符合GPL协议

MODULE_VERSION("V1.0");

 

//---------------------------------------

2. Filename: test.c

#include <stdio.h>

#include <fcntl.h>

 

char key_status[3] = {0,0,0};

 

 

int main(void)

{

int fd,i,ret;

fd = open("/dev/key_drv", O_RDONLY);

if(fd < 0)

{

perror("open /dev/key_drv");

return -1;

}

while(1)

{

ret = read(fd, key_status, 3);

if(ret != 3)

{

perror("read");

return -1;

}

for(i=0;i<3;i++)

{

printf("key_status[%d] = %d\n",i,key_status[i]);

}

}

 

close(fd);

return 0;

}

 

//---------------------------------------

 

3. Filename: Makefile

 

obj-m += key_drv.o

KERNELDIR := /home/gec/linux-2.6.35.7-gec-v3.0-gt110

PWD:=$(shell pwd)

 

default:

$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

 

clean:

rm -rf *.o *.mod.c *.mod.o *.ko

 

 

 

第十三章  linux内核中的同步互斥

 

1原子变量与原子操作

 

对一个变量的操作过程是一个原子过程,该过程不能被打断,是一个连续的过程。

当一个变量的访问过程被两个或两个以上的线程共享。当线程A正在访问该变量,而被线程B打断,线程B也访问该变量,则变量就是一个临界资源,要需要保护,否则该变量的访问会出错。见图1.

解决该问题的方法:

将该变量定义成一个原子变量,该变量的访问过程是一个原子过程,这个就不会出错。

 

1.1 原子变量

当多线程共享一个变量的时候,我们需要将该变量定义成一个原子变量,多线程对该变量的访问是一个互斥的过程。原子变量实际上是一个多进程共享的计数值。

 

typedef struct {

int counter;   //计数值

} atomic_t;

 

例:

static atomic_t gec210_cnt; //将多线程共享的计数值定义成一个原子变量

 

1.2 原子操作

使用内核提供的函数,来访问原子变量,这个过程是一个原子过程---原子操作

void atomic_inc(atomic_t *v) //counter=counter+1

void atomic_dec(atomic_t *v) //counter=counter-1

void atomic_sub(int i, atomic_t *v) //counter=counter-i

void atomic_add(int i, atomic_t *v)//counter=counter+i

void atomic_set(atomic_t *v, int i) //counter=i

 

1.3 思考:

1)为什么引入原子变量

2)什么是原子变量

3)什么是原子操作

4)原子变量和原子操作的应用场合

 

2 原子位操作

 

将一个位操作的过程设计成一个原子过程,保证该过程不会被打断。

 

2.1 C语言的位操作:

unsigned long  a

a |= (1<<20);

a &= ~(1<<30);

a ^= (1<<10); //a的第10位取反,其他位保持不变

 

以上都是对一个变量的位操作,但是该位操作过程不是一个原子过程,当多线程共享该过程的时候,变量的访问会出错。

 

2.2需要使用原子位操作:

 

unsigned long a

 

clear_bit(30,&a);

set_bit(20,&a);

change_bit(10,&a);

 

2.3函数原型

#define set_bit(nr, p) ATOMIC_BITOP_LE(set_bit,nr,p)

#define clear_bit(nr, p) ATOMIC_BITOP_LE(clear_bit,nr,p)

#define change_bit(nr, p) ATOMIC_BITOP_LE(change_bit,nr,p)

#define test_and_set_bit(nr, p) ATOMIC_BITOP_LE(test_and_set_bit,nr,p)

#define test_and_clear_bit(nr, p) ATOMIC_BITOP_LE(test_and_clear_bit,nr,p)

#define test_and_change_bit(nr, p) ATOMIC_BITOP_LE(test_and_change_bit,nr,p)

后面三个函数应该先返回变量某个位的值,然后再对变量的某个进行位操作。

 

le   --- little endian   ---ARM默认小端格式

be --- big endian

 

2.4例子程序:

linux/drivers/char/watchdog/s3c2410_wdt.c

 

static unsigned long open_lock=0; //定义一个全局变量

 

static int s3c2410wdt_open(struct inode *inode, struct file *file)

{

//第一次打开驱动的时候,返回0,然后再将open_lock的第0位置1

if (test_and_set_bit(0, &open_lock)) //原子位操作

return -EBUSY;

 

expect_close = 0;

 

/* start the timer */

s3c2410wdt_start();

}

 

static int s3c2410wdt_release(struct inode *inode, struct file *file)

{

if (expect_close == 42)

s3c2410wdt_stop();

else {

dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n");

s3c2410wdt_keepalive();

}

expect_close = 0;

//open_lock的第0位清0

clear_bit(0, &open_lock); //原子位操作

return 0;

}

 

2.5思考:

file_operations中,open()release()函数中,原子位操作的作用?????

 

防止看门狗驱动被打开多次。

 

3 自旋锁

自旋锁也是对临界资源的一种保护。当一个进程获得了自旋锁,然后访问自旋锁保护的临界资源,第二个进程也想,获得该自旋锁访问临界资源,但是第二个进程在原地“自旋”(忙等待),直到第一个进程释放该自旋锁。

 

3.1 例:

linux/drivers/char/watchdog/s3c2410_wdt.c

 

static DEFINE_SPINLOCK(wdt_lock); //1.静态定义并初始化一个自旋锁,该自旋锁是处于可用状态。

 

//看门狗喂狗-->给看门狗的计数寄存器写入一个初始值,防止计数值减到0.

static void s3c2410wdt_keepalive(void)

{

spin_lock(&wdt_lock); //2.自旋锁上锁

writel(wdt_count, wdt_base + S3C2410_WTCNT); //临界资源--WTCNT

spin_unlock(&wdt_lock); //3.自旋锁解锁

}

 

思考:

什么是看门狗,看门狗定时器的工作过程?

 

3.2 自旋锁的使用场合

临界资源的访问时间比较短,如果一个进程在原地等待的时间比较短,要比进程进入睡眠再从睡眠状态唤醒更加有效率,这个时候使用自旋锁。

 

3.3 自旋锁的使用方法:

 

3.3.1定义并初始化一个自旋锁

3.3.1.1静态的定义及初始化

DEFINE_SPINLOCK(name);

 

3.3.1.2动态定义并初始化一个自旋锁

typedef struct spinlock {

union {

struct raw_spinlock rlock;

};

} spinlock_t;

 

void spin_lock_init(spinlock_t *lock)

 

例:

static spinlock_t wdt_lock;

spin_lock_init(&wdt_lock);

 

3.3.2自旋锁上锁

void spin_lock(spinlock_t *lock) //自旋锁上锁

void spin_lock_irq(spinlock_t *lock) //自旋锁上锁,同时关闭中断

void spin_lock_irqsave(spinlock_t *lock, unsigned int flags)  //自旋锁上锁,同时关闭中断,并保存中断的状态

 

3.3.3自旋锁解锁

void spin_unlock(spinlock_t *lock)

void spin_unlock_irq(spinlock_t *lock)

void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)

 

3.4 自旋锁使用的注意事项

3.4.1临界资源的访问时间不能太长,如果太长用自旋锁效率不高,这个时候可以考虑使用互斥锁或信号量

3.4.2自旋锁一个忙等待锁,不是阻塞锁,所以在中断服务程序中(中断上下文中)可以使用自旋锁,但是不能使用互斥锁或信号量

3.4.3自旋锁一般是使用在单处理器抢占式内核或多处理器的环境下。如果是在单处理器非抢占式内核,可以不使用自旋锁,直接关闭中断就ok了。

 

3.4.4自旋锁不能递归调用,如:进程A已经得到了自旋锁X,然后进程A还想再次获得自旋锁X,则进程A就会一直原地“自旋”,产生了死锁。

3.4.5自旋锁保护的临界区不能使用可能产生阻塞的函数(ssleep(),获取信号量,获得互斥锁....),否则可能产生死锁。见图

3.4.6当一个进程得到自旋锁的时候,抢占器是被关闭的。

 

4 信号量

4.1 什么是信号量

信号量是一个阻塞锁,当一个进程得不到信号量的时候,该进程会产生阻塞,然后改进程进入睡眠状态。当有进程释放该信号量的时候,睡眠的进程就会被唤醒。

信号量是一个多值的锁。

例子:假如:114期的教室有1个讲师的位置和50个学生的位置?

 

可以用信号量来保护50个学生的座位,则信号量的值是50.当有一个同学进入课室,信号量的值就会减1,当第50个同学进入课室,信号量的值减为0.51个同学获得信号量,则信号量的值为-1,代表有一个同学在等待座位。当前面有一个同学离开教室,则第51个同学就得到了座位。

 

可以使用互斥锁来保护讲师的座位,互斥锁是信号量的特例,是一个二值的信号量,只有上锁和解锁两个状态。

 

4.2 信号量的使用

#include <linux/semaphore.h>

4.2.1定义一个信号量

struct semaphore {

spinlock_t lock;

unsigned int count;

struct list_head wait_list;

};

 

例:

static struct semaphore gec210_sema;

 

4.2.2信号量的初始化

sema_init(struct semaphore *sem, int val)

例:

sema_init(&gec210_sema, 50)

 

4.2.3获得一个信号量

void down(struct semaphore *sem); //不可中断的睡眠

int  down_interruptible(struct semaphore *sem); //可中断的睡眠

 

有什么区别???

 

4.2.4释放一个信号量

void up(struct semaphore *sem);

 

5 互斥锁(互斥量 互斥体)

互斥锁是一个二值的信号量,只有上锁和解锁两个状态。互斥锁是一种睡眠锁。

 

#include <linux/mutex.h>

5.1 互斥锁的应用举例

/arch/arm/mach-s5pv210/adc.c

 

//定义并初始化一个互斥锁adc_mutex

static DEFINE_MUTEX(adc_mutex); //静态定义的方法

 

//读取ADC转换后数字量

int s3c_adc_get_adc_data(int channel)

{

int adc_value = 0;

int cur_adc_port = 0;

 

 

mutex_lock(&adc_mutex); //互斥锁上锁

 

cur_adc_port = adc_port;

adc_port = channel; //得到ADC的转换通道,101

 

adc_value = s3c_adc_convert(); //开始ADC转换,得到转换后的数字量

 

adc_port = cur_adc_port;

 

mutex_unlock(&adc_mutex); //互斥锁解锁

 

return adc_value;

}

 

ADC作为一个临界资源,对ADC的转换过程进行保护,使用了互斥锁。

 

5.2 互斥锁的使用方法

 

5.2.1定义并初始化一个互斥锁

 

5.2.1.1静态定义并初始化

DEFINE_MUTEX(mutexname)

 

5.2.1.2动态的定义及初始化

void mutex_init(struct mutex *lock

例:

static struct mutex gec210_led_mutex;

mutex_init(&gec210_led_mutex)

 

5.2.2互斥锁上锁

void mutex_lock(struct mutex *lock)

void mutex_lock_interruptible(struct mutex *lock)

 

5.2.3互斥锁解锁

void mutex_unlock(struct mutex *lock);

 

6 自旋锁与信号量(互斥锁的区别)

6.1 自旋锁是一个忙等待锁,而互斥锁一个睡眠锁

6.2 如果访问临界资源的时间比较短,应该使用自旋锁;如果临界区的访问时间比较长,可以使用互斥锁

6.3 在中断上下文中,我们应该使用自旋锁,而不能互斥锁

6.4 如果在临界区代码中,使用了可能产生阻塞的函数,这个时候,只能使用互斥锁。

 

A作业

1. 将自旋锁 互斥锁加到led灯的驱动中。

 

第十四章 platform模型

 

注意:platform模型还是比较关键的。在linux内核中,内核源码所自带的驱动程序都是以platform模型的方式来设计的。

 

1 什么是platform模型

1.1 platform提出的原因

 

linux设备驱动我们可以将其分成两个部分,一部分是用来描述硬件信息,即device;另外一部分用来描述软件信息,即driver

 

这样软件和硬件做了分离,当硬件信息有改变得时候,我们只需要修改linux设备驱动中的device,而不需要修改driver,这样方便驱动的移植。

 

1.2 platform模型的优点

1.2.1 platform模型将linux设备驱动分成devicedriver,这样使设备驱动的设计思路更加清晰

1.2.2 linux设备驱动分成devicedriver可以方便linux内核来管理设备驱动

1.2.3当硬件设计发生了改变(例:DM9000有一个控制平台改变到另外控制平台)的时候,但是硬件设备是没有变的(DM9000),在这种情况下,我们只需要修改device就可以完成DM9000的驱动的移植,而不需要改driver。方便linux设备驱动的移植。

 

1.3 注意:

platform模型不是用来简化一个linux设备驱动程序的设计,而是将linux的设备驱动分成了两个部分:devicedriver,有以上的优点。

 

2 platform模型的组成

2.1 platform bus

    平台总线,platform bus不需要我们来设计,在linux内核的初始化过程中,建立一个platform bus总线,platform bus总线是一个虚拟总线。

    platform deviceplatform driver都是安装到platform bus上,由platform bus总线来管理platform deviceplatform driver

    platform bus根据platform deviceplatform driver的“名字”,来完成二者的匹配,当匹配成功,就会执行platform driver下的probe函数,这个函数就是driver的初始化函数。

 

2.2 platform device

 

2.2.1什么是platform device

linux设备驱动的硬件信息,“死”的信息。主要内容是:

物理地址 ---->例如,控制led,使用GPJ2CONGPJ2DAT,这两个寄存器的物理地址:0xe0200280~0xe0200287

GPIO口号 ---->例如,控制led,使用GPIO口号:S5PV210_GPJ2(0)~S5PV210_GPJ2(3)

中断号 ---->IRQ_EINT(16)

控制时序 ---->LCD驱动:行的回扫时间,帧的回扫时间 液晶屏的分辨率bpp 像素时钟频率。

 

将这些描述硬件资源的信息可以叫做resource

 

2.2.2 platform device的举例

例:

linux/arch/arm/plat-s5p/devs.c

ADC驱动的platform device

 

static struct resource s3c_adc_resource[] = {

[0] = {

.start = S3C_PA_ADC,   //ADC SFR的物理地址0xe1700000

.end   = S3C_PA_ADC + SZ_4K - 1,

.flags = IORESOURCE_MEM, //是一段IO内存

},

[1] = {

.start = IRQ_PENDN, //触摸笔按下的中断号

.end   = IRQ_PENDN,

.flags = IORESOURCE_IRQ, //是一个中断号

},

[2] = {

.start = IRQ_ADC, //ADC转换完成后产生的中断

.end   = IRQ_ADC,

.flags = IORESOURCE_IRQ,

}

};

 

struct platform_device s3c_device_adc = {

.name   = "s3c-adc",

.id               = -1,

.num_resources   = ARRAY_SIZE(s3c_adc_resource), //3resource

.resource   = s3c_adc_resource,

};

 

2.2.3 platform device的使用

2.2.3.1定义一个platform device

2.2.3.2定义该platform deviceresource

2.2.3.3platform device安装到platform bus

int platform_device_register(struct platform_device *);

2.2.3.4platform deviceplatform bus上卸载

void platform_device_unregister(struct platform_device *);

 

2.3 platform driver

2.3.1 platform driver举例

例:ADC驱动的driver

linux/arch/arm/mach-s5pv210/adc.c

 

static struct platform_driver s3c_adc_driver = {

       .probe          = s3c_adc_probe,   //驱动的安装及初始化函数,当platform deviceplatform driver匹配成功,就调用probe()

       .remove         = s3c_adc_remove, //驱动的卸载函数

       .suspend        = s3c_adc_suspend, //驱动的暂停工作函数,较少使用

       .resume         = s3c_adc_resume, //驱动继续工作,较少使用

       .driver = {

.owner = THIS_MODULE,

.name = "s3c-adc", //与对应platform device的名字保持一致。

},

};

 

2.3.2分析platform driver中的probe函数

分析probe函数,做了哪些工作?

2.3.2.1要从platform device中获得resource,假如该资源是SFR的物理地址

2.3.2.2 request_mem_region(),申请物理内存区

2.3.2.3 ioremap()得到虚拟地址

2.3.2.4定义并初始化文件操作集

2.3.2.5注册一个混杂设备

 

思考:

使用platform模型设计驱动与不用platform模型设计驱动有什么差异????

 

2.3.3 driver如何获取device的资源

struct resource *platform_get_resource(struct platform_device *, unsigned int, unsigned int);

 

如:

adcdriverprobe()中,获取IOMEM类型的资源,第0个。

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

int platform_get_irq(struct platform_device *, unsigned int);

 

2.3.4 platform driver的用法

2.3.4.1定义一个platform driver

2.3.4.2定义platform driverprobe()

    a.要从platform device中获得resource,假如该资源是SFR的物理地址

    b.request_mem_region(),申请物理内存区

    c.ioremap()得到虚拟地址

    d.定义并初始化文件操作集

    e.注册一个混杂设备

2.3.4.3定义platform driver中的remove()

    a 资源释放

    b 设备的卸载

 

2.3.4.4platform driver安装到platform bus

int platform_driver_register(struct platform_driver *);

2.3.4.5platform bus上卸载platform driver

void platform_driver_unregister(struct platform_driver *);

 

作业:

    GPIO号为资源,设计beep的设备驱动程序,使用platform模型

 

3 如何分析linux内核源码中的驱动程序

linux内核源码包自带了一个硬件的驱动,我们如何找到这些驱动,如何分析这些驱动??

 

GEC210平台为例,ADC的设备驱动:

 

3.1 linux内核针对GEC210平台的主初始化源文件

linux/arch/arm/mach-s5pv210/mach-smdkc110.c --->samsung

linux/arch/arm/mach-s5pv210/mach-gec210.c     --->gec

主初始化源文件一般是硬件的板子有关系。

 

3.2 找到一个机器宏

MACHINE_START(GEC210, "GEC210")

/* Maintainer: Kukjin Kim <kgene.kim@samsung.com> */

.phys_io = S3C_PA_UART & 0xfff00000,

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

.boot_params = S5P_PA_SDRAM + 0x100,

.init_irq = s5pv210_init_irq,

.map_io = smdkc110_map_io,

.init_machine = smdkc110_machine_init,

.timer = &s5p_systimer, //内核时钟

MACHINE_END

 

相当于一个宏定义的结构体,内核启动过程中,会调用机器宏中的数据和接口函数。

 

3.3 针对GEC210平台的主初始化函数-->smdkc110_machine_init

 

static void __init smdkc110_machine_init(void)

{

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

platform_add_devices(smdkc110_devices, ARRAY_SIZE(smdkc110_devices));

 

........

gec210_dm9000_set(); //初始化控制网卡的存储器控制器

 

------------------------

s3cfb_set_platdata(&gec210_fb_data);  //设置LCD的特定参数--->platform_device

 

s3c_adc_set_platdata(&s3c_adc_platform); //设置ADC的特定参数--->platform_device

}

 

3.4 添加平台设备---platform_add_devices()

int platform_add_devices(struct platform_device **devs, int num)

{

int i, ret = 0;

 

for (i = 0; i < num; i++) {

ret = platform_device_register(devs[i]); //注册platform device

if (ret) {

while (--i >= 0)

platform_device_unregister(devs[i]);

break;

}

}

 

return ret;

}

 

3.5 platform device的总表

一般,在内核源码自带的驱动中,platform device是集中放在一起的,而platform driver是放在不同的源文件中。

 

现在,需要找到放在一起的platform device

 

针对gec210平台全部的platform device

static struct platform_device *smdkc110_devices[] __initdata = {

#ifdef CONFIG_FIQ_DEBUGGER

&s5pv210_device_fiqdbg_uart2,

#endif

#ifdef CONFIG_MTD_ONENAND  ---->条件编译选项--->去哪里查看.config

&s5pc110_device_onenand,

#endif

#ifdef CONFIG_MTD_NAND

&s3c_device_nand, //nand flash设备驱动的platform device

#endif

&s5p_device_rtc,

#ifdef CONFIG_SND_S3C64XX_SOC_I2S_V4

&s5pv210_device_iis0,

#endif

#ifdef CONFIG_SND_S3C_SOC_AC97

&s5pv210_device_ac97,

#endif

#ifdef CONFIG_SND_S3C_SOC_PCM

&s5pv210_device_pcm0,

#endif

#ifdef CONFIG_SND_SOC_SPDIF

&s5pv210_device_spdif,

#endif

&s3c_device_wdt, //看门狗的platform device

 

#ifdef CONFIG_FB_S3C

&s3c_device_fb,

#endif

#ifdef CONFIG_DM9000

#if 0

&s5p_device_dm9000,

#else

&gec210_device_dm9000,

#endif

#endif

 

#ifdef CONFIG_VIDEO_MFC50

&s3c_device_mfc,

#endif

#ifdef CONFIG_TOUCHSCREEN_S3C

&s3c_device_ts,

#endif

&s3c_device_keypad,

 

#ifdef CONFIG_S5P_ADC --->条件编译选项

&s3c_device_adc,  //ADCplatform device

#endif

 

#ifdef CONFIG_VIDEO_FIMC

&s3c_device_fimc0,

&s3c_device_fimc1,

&s3c_device_fimc2,

#endif

#ifdef CONFIG_VIDEO_FIMC_MIPI

&s3c_device_csis,

#endif

#ifdef CONFIG_VIDEO_JPEG_V2

&s3c_device_jpeg,

#endif

#ifdef CONFIG_VIDEO_G2D

&s3c_device_g2d,

#endif

#ifdef CONFIG_VIDEO_TV20

&s5p_device_tvout,

&s5p_device_cec,

&s5p_device_hpd,

#endif

 

&s3c_device_g3d,

&s3c_device_lcd,

 

&s3c_device_i2c0,

#ifdef CONFIG_S3C_DEV_I2C1

&s3c_device_i2c1,

#endif

#ifdef CONFIG_S3C_DEV_I2C2

&s3c_device_i2c2,

#endif

 

#ifdef CONFIG_USB_EHCI_HCD

&s3c_device_usb_ehci,

#endif

#ifdef CONFIG_USB_OHCI_HCD

&s3c_device_usb_ohci,

#endif

 

#ifdef CONFIG_USB_GADGET

&s3c_device_usbgadget,

#endif

#ifdef CONFIG_USB_ANDROID

&s3c_device_android_usb,

#ifdef CONFIG_USB_ANDROID_MASS_STORAGE

&s3c_device_usb_mass_storage,

#endif

#ifdef CONFIG_USB_ANDROID_RNDIS

&s3c_device_rndis,

#endif

#endif

#ifdef CONFIG_BATTERY_S3C

&sec_device_battery,

#endif

#ifdef CONFIG_S3C_DEV_HSMMC

&s3c_device_hsmmc0,

#endif

#ifdef CONFIG_S3C_DEV_HSMMC1

&s3c_device_hsmmc1,

#endif

#ifdef CONFIG_S3C_DEV_HSMMC2

&s3c_device_hsmmc2,

#endif

#ifdef CONFIG_S3C_DEV_HSMMC3

&s3c_device_hsmmc3,

#endif

 

#ifdef CONFIG_S3C64XX_DEV_SPI

&s5pv210_device_spi0,

&s5pv210_device_spi1,

#endif

#ifdef CONFIG_S5PV210_POWER_DOMAIN

&s5pv210_pd_audio,

&s5pv210_pd_cam,

&s5pv210_pd_tv,

&s5pv210_pd_lcd,

&s5pv210_pd_g3d,

&s5pv210_pd_mfc,

#endif

 

#ifdef CONFIG_HAVE_PWM

&s3c_device_timer[0],

&s3c_device_timer[1],

&s3c_device_timer[2],

&s3c_device_timer[3],

#endif

};

 

通过这个总表可以很容易的找到platform device

 

3.6 找到了ADC设备驱动的platform device

#ifdef CONFIG_S5P_ADC

/* ADCTS */

static struct resource s3c_adc_resource[] = {

[0] = {

.start = S3C_PA_ADC,

.end   = S3C_PA_ADC + SZ_4K - 1,

.flags = IORESOURCE_MEM,

},

[1] = {

.start = IRQ_PENDN,

.end   = IRQ_PENDN,

.flags = IORESOURCE_IRQ,

},

[2] = {

.start = IRQ_ADC,

.end   = IRQ_ADC,

.flags = IORESOURCE_IRQ,

}

};

 

struct platform_device s3c_device_adc = {

.name   = "s3c-adc",

.id               = -1,

.num_resources   = ARRAY_SIZE(s3c_adc_resource),

.resource   = s3c_adc_resource,

};

 

void __init s3c_adc_set_platdata(struct s3c_adc_mach_info *pd)

{

struct s3c_adc_mach_info *npd;

 

npd = kmalloc(sizeof(*npd), GFP_KERNEL);

if (npd) {

memcpy(npd, pd, sizeof(*npd));

s3c_device_adc.dev.platform_data = npd;

} else {

printk(KERN_ERR "no memory for ADC platform data\n");

}

}

#endif /* CONFIG_S5P_ADC */

 

3.7 通过platform device找到platform driver

linux/arch/arm/mach-s5pv210/adc.c

 

static struct platform_driver s3c_adc_driver = {

       .probe          = s3c_adc_probe,

       .remove         = s3c_adc_remove,

       .suspend        = s3c_adc_suspend,

       .resume         = s3c_adc_resume,

       .driver = {

.owner = THIS_MODULE,

.name = "s3c-adc",

},

};

 

 

3.8 分析platform driverprobe函数---s3c_adc_probe()

 

static int __devinit s3c_adc_probe(struct platform_device *pdev)

{

 

//3.8.1platform device中得到resource

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

dev = &pdev->dev;

 

//3.8.2申请物理内存区

adc_mem = request_mem_region(res->start, size, pdev->name);

 

//3.8.3ioremap()得到虚拟地址

static void __iomem *base_addr;

base_addr = ioremap(res->start, size);

 

//3.8.4获得该模块的系统时钟,并打开该时钟

adc_clock = clk_get(&pdev->dev, "adc"); //"adc"是系统时钟的名字

 

if (IS_ERR(adc_clock)) {

dev_err(dev, "failed to fine ADC clock source\n");

ret = PTR_ERR(adc_clock);

goto err_clk;

}

 

clk_enable(adc_clock); //打开这个时钟

/*

linux内核中,有一个“时钟树”,这个时钟树用来管理各个外设模块的使用,如:ADC PWM WDT LCD ...

我们都学要把这个时钟打开,否则这个模块就不能工作。

 

时钟树---->下一个笔记。

注意:

使用的时钟的名字"adc"和“时钟树”中的名字保持一致。

*/

//3.8.5platform device中得到硬件的配置参数,并使用这些配置参数来配置ADC的寄存器

 

/* read platform data from device struct */

plat_data = s3c_adc_get_platdata(&pdev->dev);

 

if ((plat_data->presc & 0xff) > 0) //plat_data->presc = 49

writel(S3C_ADCCON_PRSCEN |

       S3C_ADCCON_PRSCVL(plat_data->presc & 0xff),

       base_addr + S3C_ADCCON); //打开分频器,并设置分频值

else

writel(0, base_addr + S3C_ADCCON);

 

/* Initialise registers */

if ((plat_data->delay & 0xffff) > 0) //plat_data->delay = 10000

writel(plat_data->delay & 0xffff, base_addr + S3C_ADCDLY);

 

if (plat_data->resolution == 12) //配置12bits的转换模式

writel(readl(base_addr + S3C_ADCCON) |

       S3C_ADCCON_RESSEL_12BIT, base_addr + S3C_ADCCON);

writel((readl(base_addr + S3C_ADCCON) | S3C_ADCCON_STDBM) & ~S3C_ADCCON_PRSCEN,

base_addr + S3C_ADCCON);

/*

分析:

 

void __init smdkc110_machine_init(void)

{

........

s3c_adc_set_platdata(&s3c_adc_platform);

 

......

}

该函数的功能:*/

s3c_adc_set_platdata()

 

void __init s3c_adc_set_platdata(struct s3c_adc_mach_info *pd)

{

struct s3c_adc_mach_info *npd;

 

npd = kmalloc(sizeof(*npd), GFP_KERNEL);

if (npd) {

memcpy(npd, pd, sizeof(*npd));

s3c_device_adc.dev.platform_data = npd;

} else {

printk(KERN_ERR "no memory for ADC platform data\n");

}

}

 

//ADCplatform deviceplatform data上安装一个数据---s3c_adc_platform

s3c_device_adc.dev.platform_data = s3c_adc_platform

 

这个数据是:s3c_adc_platform什么?

 

#ifdef CONFIG_S5P_ADC

static struct s3c_adc_mach_info s3c_adc_platform __initdata = {

/* s5pc110 support 12-bit resolution */

.delay  = 10000, //转换延时

.presc  = 49, //ADC分频器的分频值

.resolution = 12, //12bits转换模式选择  10/12

};

#endif

 

adcprobe函数中

s3c_adc_get_platdata(&pdev->dev);  --->获得platform device中的platform data

 

 

//3.8.6注册一个混杂设备

ret = misc_register(&s3c_adc_miscdev);

 

 

//3.8.7混杂设备

static struct miscdevice s3c_adc_miscdev = {

.minor = ADC_MINOR,

.name = "adc",

.fops = &s3c_adc_fops,

};

 

//3.8.8文件操作集

static const struct file_operations s3c_adc_fops = {

.owner = THIS_MODULE,

.read = s3c_adc_read,

.open = s3c_adc_open,

.ioctl = s3c_adc_ioctl,

};

 

第十五章  linux内核的时钟树

1 内核时钟的应用举例

1.1 ADC的时钟---"adc"

linux/arch/arm/mach-s5pv210/adc.c

 

static struct clk *adc_clock;

安装驱动:

adc_clock = clk_get(&pdev->dev, "adc");

 

if (IS_ERR(adc_clock)) {

dev_err(dev, "failed to fine ADC clock source\n");

ret = PTR_ERR(adc_clock);

goto err_clk;

}

 

clk_enable(adc_clock);

卸载驱动:

clk_disable(adc_clock);

clk_put(adc_clock);

&pdev->dev --->platform device的设备,如果驱动不使用platform模型,&pdev->dev使用NULL替换

 

1.2 WDT的时钟----"watchdog"

 linux/drivers/watchdog/s3c2410_wdt.c

static struct clk *wdt_clock;

 

安装驱动:

wdt_clock = clk_get(&pdev->dev, "watchdog");

if (IS_ERR(wdt_clock)) {

dev_err(dev, "failed to find watchdog clock source\n");

ret = PTR_ERR(wdt_clock);

goto err_irq;

}

 

clk_enable(wdt_clock);

卸载驱动:

clk_disable(wdt_clock);

clk_put(wdt_clock);

 

2 内核时钟树

linux内核的时钟树,应该是和CPU对应的。

 

linux/arch/arm/mach-s5pv210/clock.c

static struct clk init_clocks_disable[] = {

{

.name = "rot", //IMAGE ROTATOR

.id = -1,

.parent = &clk_hclk_dsys.clk,

.enable = s5pv210_clk_ip0_ctrl,

.ctrlbit = (1<<29),

}, {

.name = "otg", //USB OTG

.id = -1,

.parent = &clk_hclk_psys.clk,

.enable = s5pv210_clk_ip1_ctrl,

.ctrlbit = (1<<16),

}, {

.name = "usb-host", //USB2.0

.id = -1,

.parent = &clk_hclk_psys.clk,

.enable = s5pv210_clk_ip1_ctrl,

.ctrlbit = (1<<17),

}, {

.name = "jpeg", //jpeg的硬解码器

.id = -1,

.parent = &clk_hclk_dsys.clk,

.enable = s5pv210_clk_ip5_ctrl,

.ctrlbit = S5P_CLKGATE_IP5_JPEG,

}, {

.name = "mfc",

.id = -1,

.parent = &clk_hclk_msys.clk,

.enable = s5pv210_clk_ip0_ctrl,

.ctrlbit = (1<<16),

}, {

.name = "sclk_fimc_lclk",

.id = 0,

.parent = &clk_hclk_dsys.clk,

.enable = s5pv210_clk_ip0_ctrl,

.ctrlbit = (1<<24),

}, {

.name = "sclk_fimc_lclk",

.id = 1,

.parent = &clk_hclk_dsys.clk,

.enable = s5pv210_clk_ip0_ctrl,

.ctrlbit = (1<<25),

}, {

.name = "sclk_fimc_lclk",

.id = 2,

.parent = &clk_hclk_dsys.clk,

.enable = s5pv210_clk_ip0_ctrl,

.ctrlbit = (1<<26),

}, {

.name = "otg",

.id = -1,

.parent = &clk_hclk_psys.clk,

.enable = s5pv210_clk_ip1_ctrl,

.ctrlbit = (1<<16),

}, {

.name = "usb-host",

.id = -1,

.parent = &clk_hclk_psys.clk,

.enable = s5pv210_clk_ip1_ctrl,

.ctrlbit = (1<<17),

}, {

.name = "dsim",

.id = -1,

.parent = &clk_hclk_dsys.clk,

.enable = s5pv210_clk_ip1_ctrl,

.ctrlbit = (1<<2),

}, {

.name = "onenand",

.id = -1,

.parent = &clk_hclk_psys.clk,

.enable = s5pv210_clk_ip1_ctrl,

.ctrlbit = (1 << 24),

.dev = &s5pc110_device_onenand.dev,

}, {

.name = "nand",

.id = -1,

.parent = &clk_hclk_psys.clk,

.enable = s5pv210_clk_ip1_ctrl,

.ctrlbit = ((1 << 28) | (1 << 24)),

.dev = &s3c_device_nand.dev,

}, {

.name = "cfcon",

.id = 0,

.parent = &clk_hclk_psys.clk,

.enable = s5pv210_clk_ip1_ctrl,

.ctrlbit = (1<<25),

}, {

.name = "hsmmc",

.id = 0,

.parent = &clk_hclk_psys.clk,

.enable = s5pv210_clk_ip2_ctrl,

.ctrlbit = (1<<16),

}, {

.name = "hsmmc",

.id = 1,

.parent = &clk_hclk_psys.clk,

.enable = s5pv210_clk_ip2_ctrl,

.ctrlbit = (1<<17),

}, {

.name = "hsmmc",

.id = 2,

.parent = &clk_hclk_psys.clk,

.enable = s5pv210_clk_ip2_ctrl,

.ctrlbit = (1<<18),

}, {

.name = "hsmmc",

.id = 3,

.parent = &clk_hclk_psys.clk,

.enable = s5pv210_clk_ip2_ctrl,

.ctrlbit = (1<<19),

}, {

.name = "systimer",

.id = -1,

.parent = &clk_pclk_psys.clk,

.enable = s5pv210_clk_ip3_ctrl,

.ctrlbit = (1<<16),

}, {

.name = "rtc",

.id = -1,

.parent = &clk_pclk_psys.clk,

.enable = s5pv210_clk_ip3_ctrl,

.ctrlbit = (1<<15),

}, {

.name = "i2c",

.id = 0,

.parent = &clk_pclk_psys.clk,

.enable = s5pv210_clk_ip3_ctrl,

.ctrlbit = (1<<7),

}, {

.name = "i2c",

.id = 1,

.parent = &clk_pclk_psys.clk,

.enable = s5pv210_clk_ip3_ctrl,

.ctrlbit = (1<<10),

}, {

.name = "i2c",

.id = 2,

.parent = &clk_pclk_psys.clk,

.enable = s5pv210_clk_ip3_ctrl,

.ctrlbit = (1<<9),

}, {

.name = "spi",

.id = 0,

.parent = &clk_pclk_psys.clk,

.enable = s5pv210_clk_ip3_ctrl,

.ctrlbit = (1<<12),

}, {

.name = "spi",

.id = 1,

.parent = &clk_pclk_psys.clk,

.enable = s5pv210_clk_ip3_ctrl,

.ctrlbit = (1<<13),

}, {

.name = "spi",

.id = 2,

.parent = &clk_pclk_psys.clk,

.enable = s5pv210_clk_ip3_ctrl,

.ctrlbit = (1<<14),

}, {

.name = "timers",

.id = -1,

.parent = &clk_pclk_psys.clk,

.enable = s5pv210_clk_ip3_ctrl,

.ctrlbit = (1<<23),

}, {

.name = "adc",

.id = -1,

.parent = &clk_pclk_psys.clk,

.enable = s5pv210_clk_ip3_ctrl,

.ctrlbit = (1<<24),

}, {

.name = "keypad",

.id = -1,

.parent = &clk_pclk_psys.clk,

.enable = s5pv210_clk_ip3_ctrl,

.ctrlbit = (1<<21),

}, {

.name = "i2s_v50",

.id = 0,

.parent = &clk_p,

.enable = s5pv210_clk_ip3_ctrl,

.ctrlbit = (1<<4),

}, {

.name = "i2s_v32",

.id = 0,

.parent = &clk_p,

.enable = s5pv210_clk_ip3_ctrl,

.ctrlbit = (1 << 5),

}, {

.name = "i2s_v32",

.id = 1,

.parent = &clk_p,

.enable = s5pv210_clk_ip3_ctrl,

.ctrlbit = (1 << 6),

}, {

.name = "ac97",

.id = -1,

.parent = &clk_pclk_psys.clk,

.enable = s5pv210_clk_ip3_ctrl,

.ctrlbit = (1 << 1),

}, {

.name = "spdif",

.id = -1,

.parent = &clk_pclk_psys.clk,

.enable = s5pv210_clk_ip3_ctrl,

.ctrlbit = (1 << 0),

}, {

.name = "pcm",

.id = 2,

.parent = &clk_pclk_psys.clk,

.enable = s5pv210_clk_ip3_ctrl,

.ctrlbit = S5P_CLKGATE_IP3_PCM2,

}, {

.name = "pcm",

.id = 1,

.parent = &clk_pclk_psys.clk,

.enable = s5pv210_clk_ip3_ctrl,

.ctrlbit = S5P_CLKGATE_IP3_PCM1 | S5P_CLKGATE_IP3_I2S1 ,

}, {

.name = "pcm",

.id = 0,

.parent = &clk_pclk_psys.clk,

.enable = s5pv210_clk_ip3_ctrl,

.ctrlbit = S5P_CLKGATE_IP3_PCM0,

}, {

.name = "i2c-hdmiphy",

.id = -1,

.parent = &clk_pclk_psys.clk,

.enable = s5pv210_clk_ip3_ctrl,

.ctrlbit = (1 << 11),

}, {

.name = "hdmi",

.id = -1,

.parent = &clk_hclk_dsys.clk,

.enable = s5pv210_clk_ip1_ctrl,

.ctrlbit = (1 << 11),

}, {

.name = "tvenc",

.id = -1,

.parent = &clk_hclk_dsys.clk,

.enable = s5pv210_clk_ip1_ctrl,

.ctrlbit = (1 << 10),

}, {

.name = "mixer",

.id = -1,

.parent = &clk_hclk_dsys.clk,

.enable = s5pv210_clk_ip1_ctrl,

.ctrlbit = (1 << 9),

}, {

.name = "vp",

.id = -1,

.parent = &clk_hclk_dsys.clk,

.enable = s5pv210_clk_ip1_ctrl,

.ctrlbit = (1 << 8),

}, {

.name = "rotator",

.id = -1,

.parent = &clk_hclk_dsys.clk,

.enable = s5pv210_clk_ip0_ctrl,

.ctrlbit = (1 << 29),

}, {

.name = "g2d",

.id = -1,

.parent = &clk_hclk_dsys.clk,

.enable = s5pv210_clk_ip0_ctrl,

.ctrlbit = (1 << 12),

}, {

.name = "usb_osc",

.id = -1,

.enable = s5pv210_usbosc_enable,

.ctrlbit = (1 << 1),

}, {

.name = "secss",

.id = -1,

.parent = &clk_hclk_psys.clk,

.enable = s5pv210_clk_ip2_ctrl,

.ctrlbit = (1 << 0),

}, {

.name = "seckey",

.id = -1,

.parent = &clk_pclk_psys.clk,

.enable = s5pv210_clk_ip4_ctrl,

.ctrlbit = (1 << 3),

},

};

 

找到ADC的时钟

{

.name = "adc",

.id = -1,

.parent = &clk_pclk_psys.clk,

.enable = s5pv210_clk_ip3_ctrl, //控制这个时钟的寄存器

.ctrlbit = (1<<24), //这个寄存器的位

},

CLK_GATE_IP3[24] Gating all clocks for TSADC

(0: mask, 1: pass)


0 0