一,基于总线的编程
platform_bus 总线:是一条虚拟总线,外设通过总线与cpu相连。所有片内外设设备,通过smc与cpu相连的设备可以使用platfrom_bus总线。
platform_bus_type :
platform_driver:驱动的一个结构体代表驱动(去寻找
platform_device)eg:driver.name="a"
platform_device:描述一个设备携带所有硬件相关信息eg:.name="a"
平台代码:用来描述一个板子,
platform_device放在这里。现在使用设备树
设备树描述一个板子以/{}开头 node为一级节点代表连接每个总线上的设备(控制器节点),二级节点child-Node
/{
node{
child-Node{
};
};
nodes;
}
基于platform总线的设备驱动:
struct platform_device
{
const char *name; 设备的名字
int id; 设备id
struct resource *resource; 资源列表
- num_resource; 资源个数
....
}
name 是platform_device和platform_driver的匹配的依据
id 是用来区分是第几个设备,如果只有一个设备则为-1
resource 描述设备所使用的硬件资源
struct resource {
resource_size_t start; 资源的开始
resource_size_t end; 资源结束
unsigned long flags; 资源的类型
...
}
资源类型:
IORESOURCE_MEM 内存资源
IORESOURCE_IRQ 中断资源
IORESOURCE_DMA DMA资源
num_resource 表示resource结构体数组的长度一般用ARRAY_SIZE取长度
注册platform_device
int platform_device_register(struct platform_device *)
struct platform_driver
{
struct device_driver driver;
int (*probe)(struct platform_device *);
int (*revmoe)(struct platofrm_device *);
}
driver.name 驱动名字,是platform_device和platform_driver的匹配的依据
probe
remove
注册platformm_drvier
int platform_driver_register(struct platform_driver *);
probe 当platform_device和platform_driver匹配后会被执行,且platform_device以形参的形式传递到probe函数中,
着这个函数中完成设备的初始化
‘remove 当注销platform_driver的时候调用,前提是platform_device和platform_driver匹配了
与probe 相反
Linux内核中断机制:
<linux/interrupt.h>
int __must_check request_irq(
unsigned int irq,
irq_handler_t handler,
unsigned long flag,
const char *name,
void *dev
);
参数:
irq : 中断号---软件概念
使用平台代码时:设备号定义在arch/arm/mach-xxxx/include/mach/irqs.h
使用设备树时:由设备树中节点的属性interrupts转换而来,通过platfrom_device获得
handler:中断处理函数
例:
irqreturn_t handler(int irq, void *dev)
{
/*处理中断*/
return IRQ_HANDLED;
}
IRQ_NONE:?
flag:中断类型 | 触发方式
中断类型:
IRQF_DISABLE :当中断处理函数执行期间,屏蔽全局中断
IRQF_SHARED:允许多个设备驱动注册同一个中断
触发方式:只针对外部中断
IRQF_TRIGGER_FALLING
IRQF_TRIGGER_RISING
IRQF_TRIGGER_HIGH
IRQF_TRIGGER_LOW
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING
name: 驱动名或设备名
dev:1、传参,这个变量在中断处理函数执行时被以形参的形式传递到中断处理函数中,不传则为NULL
2、共享中断中,这个参数必须有,并且需要具备唯一性,用来做身份识别,为free_irq服务
返回值:必须校验
0:成功
-errno:失败
void free_irq(unsigned int irq, void *dev);
irq: 中断号
dev:必须与reqeust_irq所传的第五个参数一直,尤其是在共享中断时
按键驱动:
1、原理图
KEY2==>GPX1_1==>EINT9
KEY3==>GPX1_2==>EINT10
下降沿触发
2、修改设备树
arch/arm/boot/dts/exynos4412-fs4412.dts
添加如下内容:
fs4412-key {
compatible = "fs4412,key";
interrupt-parent = <&gpx1>;
interrupts
= <1 2>, <2 2>;
};
在内核启动过程中被转换为==>
struct resource fs4412_key_resource[] = {
[0] = {
.start = EINT9中断号,
.end = EINT9中断号,
.flags = IORESOUCE_IRQ,
},
[1] = {
.start = EINT10中断号,
.end = EINT10中断号,
.flags = IORESOUCE_IRQ,
},
};
struct platform_device fs4412_key_device = {
.name = "fs4412-key",
.id = -1,
.resrouce = fs4412_key_resource,
.num_resoruce = ARRAY_SIZE(fs4412_key_resource);
};
3、写驱动
许可证声明
构建platform_driver
static const struct of_device_id test_of_match[] = {
{.compatible = "fs4412,key"},
{/*noting to done!*/},
};
MODULE_DEVICE_TABLE(of, test_of_match);
struct platform_driver test_driver = {
.probe = driver_probe,
.remove = driver_remove,
.driver = {
.name = "fs4412-key",
.of_match_table = of_match_ptr(test_of_match),
},
};
加载函数
注册platform_driver---->platform_driver_register
卸载函数
注销platform_driver---->platform_driver_unregiser
实现platform_driver中的probe方法
static int driver_probe(struct platform_device *pdev)
{
申请设备号
注册设备
初始化设备
获取中断号 pdev->resrouce[0].start X
pdev->resource[1].start X
一般做法:
struct resource *platform_get_resource(struct platform_device *pdev, unsigned int type, unsigned int num);
作用:从pdev中获得第num个类型为type的资源
struct resource *key1_resouce = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
struct resource *key2_resouce = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
中断号分别为key1_resouce->start, key2_resource->start
request_irq(key1_resource->start, handler, IRQF_DISABLED|IRQF_TRIGGER_FALLING, "key", NULL);
}
driver_remove
与probe相反
写中断处理函数
irqreturn_t handler(int irq, void *dev)
{
/*处理中断*/
printk("irq = %d\n", irq);
return IRQ_HANDLED;
}
为应用程序提供接口
read -- 阻塞读
工作队列的实现
内核定时器
获取当前时间
下半部机制属于延迟机制
Linux内核下半部实现机制:
内核线程
软中断
tasklet
workq ueue
tasklet
1、定义tasklet结构
struct tastlet_struct my_struct;
2、写下半部函数
void func(unsigned long)
3、初始化---将tasklet结构与下半部函数关联
tasklet_init(struct tasklet_struct *, void (*func)(unsigned long), unsigned long);
4、调度下半部函数---在上半部最后阶段执行,下半部函数在上半部函数返回后,会被执行
tasklet_schedule(struct tasklet_struct *);
workqueue
1、定义workqueue结构
struct work_struct my_struct;
2、写下半部函数
void func(struct work_struct *)
3、初始化---将tasklet结构与下半部函数关联
INIT_WORK(struct work_struct *, void (*func)(struct work_struct *));
4、调度下半部函数---在上半部最后阶段执行,下半部函数在上半部函数返回后,会被执行
schedule_work(struct work_struct *);
tasklet 下半部代码执行在中断上下文
workqueue 下半部代码执行在进程上下文
内核定时器:
1、定义
struct timer_list list;
2、初始化
a) init_timer(struct timer_list *);==> init_timer(&list);
b) list.function = myfunc;
- st.expires = jiffies + 5 * HZ;
3、激活定时器
add_timer(struct timer_list *); ==> add_timer(&list);
4、删除定时器
del_timer(struct timer_list *); ==> del_timer(&list);
ADC驱动
1、原理图
adcAIN3
2、芯片手册
- ADCCON 0x126C0000
- 1<< 0 | 0xff << 6 | 0x1 << 14 | 0x1 << 16
ADCDAT 0x126C000C 用来存放转换后的数据-----注意:读出数据后必须清掉高位
CLR 0x126C0018 写任意值清中断
ADCMUX 0x126C001C 通道选择寄存器
2、修改设备树
arch/arm/boot/dts/exynos4412-fs4412.dts
添加如下内容:
fs4412-adc {
compatible="fs4412,adc";
reg=<0x126C0000 0x20>;
interrupt-parent=<&combiner>;
interrupts=<10 3>;
interrupts=<10 3>;
};
在内核启动过程中被转换为==>
struct resource fs4412_adc_resource[] = {
[0] = {
.start = 0x126C0000,
.end= 0x126C0000 + 0x20,
.flags = IORESOUCE_MEM,
},
[1] = {
.start = ADC中断号,
.end = ADC中断号,
.flags = IORESOUCE_IRQ,
},
};
struct platform_device fs4412_adc_device = {
.name = "fs4412-adc",
.id = -1,
.resrouce = fs4412_adc_resource,
.num_resoruce = ARRAY_SIZE(fs4412_adc_resource);
};
3、写驱动
许可证声明
构建platform_driver
static const struct of_device_id test_of_match[] = {
{.compatible = "fs4412,adc"},
{/*noting to done!*/},
};
MODULE_DEVICE_TABLE(of, test_of_match);
struct platform_driver test_driver = {
.probe = driver_probe,
.remove = driver_remove,
.driver = {
.name = "fs4412-adc",
.of_match_table = of_match_ptr(test_of_match),
},
};
加载函数
注册platform_driver---->platform_driver_register
卸载函数
注销platform_driver---->platform_driver_unregiser
实现platform_driver中的probe方法
static int driver_probe(struct platform_device *pdev)
{
申请设备号
注册设备
初始化设备
获得资源
一般做法:
struct resource *platform_get_resource(struct platform_device *pdev, unsigned int type, unsigned int num);
作用:从pdev中获得第num个类型为type的资源
struct resource *irq_resouce = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
struct resource *mem_resouce = platform_get_resource(pdev, IORESOURCE_MEM, 0);
申请中断
寄存器映射及初始化
}
driver_remove
与probe相反
写中断处理函数
irqreturn_t handler(int irq, void *dev)
{
唤醒read
/*处理中断*/
清中断
printk("irq = %d\n", irq);
return IRQ_HANDLED;
}
为应用程序提供接口
read -- 阻塞读
开始转换---ADCCON、ADCMUX
等待转换结束--睡眠
读数据---ADCDAT
将数据传递给上层
Linux下I2C驱动的编写:
i2c_msg构建:
1、例 写一个数据到i2c设备中
设备寄存器地址为 0x20
- 0x00 0x11
消息个数由一个完整时序中开始为的个数决定
unsigned char txbuf[3] = {0x20, 0x00, 0x11};
struct i2c_msg msg[] = {
{
client->addr, 0/*表示写操作*/, 3, txbuf,
},
};
i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
2、例 从i2c设备的0x20地址读出一个数据,数据长度为16bit
unsigned char txbuf[1] = {0x20};
unsigned char rxbuf[2] = {0};
struct i2c_msg msg[] = {
{client->addr, 0, 1, txbuf},
{client->addr, I2C_M_RD, 2, rxbuf},
};
i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));