第十二章 时间管理

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 menuconfig和Kconfig共同作用得带的。

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, //--->uart的SFR物理地址

.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)。

问题: jiffes和HZ之间有什么关系?

jiffies的值在1秒钟之内增加HZ,linux系统启动到现在运行的"秒"数: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)+ 1s(HZ)

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)+ 1s(HZ)

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)+ 120ms(HZ)

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的转换通道,10选1

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设备驱动分成device和driver,这样使设备驱动的设计思路更加清晰

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

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

1.3 注意:

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

2 platform模型的组成

2.1 platform bus

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

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

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

2.2 platform device

2.2.1什么是platform device

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

物理地址 ---->例如,控制led灯,使用GPJ2CON和GPJ2DAT ,这两个寄存器的物理地址: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), //3个resource

.resource   = s3c_adc_resource,

};

2.2.3 platform device的使用

2.2.3.1定义一个platform device

2.2.3.2定义该platform device的resource

2.2.3.3将platform device安装到platform bus上

int platform_device_register(struct platform_device *);

2.2.3.4将platform device从platform 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 device和platform 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);

如:

在adc的driver的probe()中,获取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 driver的probe()

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.4将platform driver安装到platform bus上

int platform_driver_register(struct platform_driver *);

2.3.4.5从platform 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,  //ADC的platform 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 driver的probe函数---s3c_adc_probe()

static int __devinit s3c_adc_probe(struct platform_device *pdev)

{

//3.8.1从platform 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.5从platform 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");

}

}

//向ADC的platform device的platform 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

在adc的probe函数中

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)

linux 驱动笔记(六)相关推荐

  1. 嵌入式Linux驱动笔记--转自风筝丶

    为了阅读学习方便,将系列博客的网址进行粘贴,感谢原博客的分享. 嵌入式Linux驱动笔记(一)------第一个LED驱动程序 嵌入式Linux驱动笔记(二)------定时器 嵌入式Linux驱动笔 ...

  2. 嵌入式Linux驱动笔记(十六)------设备驱动模型(kobject、kset、ktype)

    ###你好!这里是风筝的博客, ###欢迎和我一起交流. 前几天去面试,被问到Linux设备驱动模型这个问题,没答好,回来后恶补知识,找了些资料,希望下次能答出个满意答案. Linux早期时候,一个驱 ...

  3. 嵌入式Linux驱动笔记(五)------学习platform设备驱动

    你好!这里是风筝的博客, 欢迎和我一起交流. 设备是设备,驱动是驱动. 如果把两个糅合写一起,当设备发生变化时,势必要改写整个文件,这是非常愚蠢的做法.如果把他们分开来,当设备发生变化时,只要改写设备 ...

  4. 嵌入式Linux驱动笔记(二十四)------framebuffer之使用spi-tft屏幕(上)

    你好!这里是风筝的博客, 欢迎和我一起交流. 最近入手了一块spi接口的tft彩屏,想着在我的h3板子上使用framebuffer驱动起来. 我们知道: Linux抽象出FrameBuffer这个设备 ...

  5. 嵌入式Linux驱动笔记(十八)------浅析V4L2框架之ioctl【转】

    转自:https://blog.csdn.net/Guet_Kite/article/details/78574781 权声明:本文为 风筝 博主原创文章,未经博主允许不得转载!!!!!!谢谢合作 h ...

  6. 嵌入式Linux驱动笔记(十一)------i2c设备之mpu6050驱动

    ###你好!这里是风筝的博客, ###欢迎和我一起交流. 上一节讲了i2c框架: 嵌入式Linux驱动笔记(十)------通俗易懂式了解i2c框架 这次就来写一写真正的i2c设备驱动: mpu605 ...

  7. linux 驱动笔记(四)

    第六章 GPIO的标准接口函数 1 什么是GPIO的标准接口函数 思考: 1.1设计GPIO驱动的方法??? 1.1.1 找到配置/控制GPIO的寄存器,得到了访问该寄存器的物理地址 1.1.2 申请 ...

  8. linux 驱动笔记(一)

    第一章 驱动概述 1 为什么要学linux驱动? linux分成内核空间和用户空间,这样对linux内核是一个保护,应用程序不能随便的访问内核,进而访问硬件. 应用程序(linuxIO编程 多进程 多 ...

  9. Linux驱动笔记-TNYCL

    0.小计 IRQ:中断 RST:存储 FP:寄存器 backlight:linux背光子系统: SOC:系统及芯片,即片上系统: UART:通用异步收发传输号,串行异步收发协议,二进制按位为单位传输: ...

最新文章

  1. JavaWeb上传图片到服务器,存储到数据库,并在页面显示
  2. driver: linux2.6 内核模块导出函数实例(EXPORT_SYMBOL)
  3. 2.3单链表的基本使用及其cpp示例
  4. JS兼容各个浏览器的本地图片上传即时预览效果
  5. 135_Power Query M语言快捷输入之输入法设置自定义短语
  6. CentosX64使用yum快速搭建xen虚拟化环境
  7. python学习笔记16--javascript总结
  8. NPAPI:JS的Number,在接口中可能是int32,也可能是double
  9. 全渠道零售中台与数字化转型(1)-中台的前世今身
  10. bat文件转换为exe文件
  11. 2021年饶州中学高考成绩查询,2019鄱阳饶州中学录取分数线
  12. 超详细教程:YOLO_V3(yolov3)训练自己的数据
  13. 利用树莓派4搭建私有云盘
  14. Feed Ratios_usaco3.2_暴力
  15. 用vue3.0.1如何搭建仿京东的电商H5项目呢?本文实战教你
  16. 4.文本分类——textRNN模型
  17. putty连接虚拟机服务器,SSH:putty通过SSH连接固定IP的虚拟机
  18. java中英文切换_中英文切换
  19. 域名解析协议-DNS
  20. Java-- Maps

热门文章

  1. 武林风云之linux单用户
  2. Python中的安全密码处理,非常重要!知道这些hei客也奈何不了你
  3. linux创建用户命令代码,useradd命令 – 创建用户
  4. 首创证券将在上交所上市:募资约19亿元,规模不及信达证券
  5. 计算机视觉中的图像标注工具总结
  6. Set实现数组对象去重
  7. 【Microsoft COCO数据集介绍以及简单使用】
  8. python函数测验题_python函数相关练习题
  9. 一本好书《AdvancED flex4》
  10. 低耦合高内聚 原则的应用