一、开发环境
1、内核:Linux 2.6.22.6;
2、JZ2440
3、ubuntu 9.10
二、概念
一个现实的linux设备驱动通常需要挂接在一种总线上,对于本身依附于PCI,USB,IIC,SPI等的设备而言,这自然不是问题,但是在嵌入式系统里面,SOC系统中集成的独立的外设控制器,挂接在SOC内存空间的外设等确不依附于此类总线。基于这一背景,linux发明了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动成为platform_driver.Platform总线是linux2.6内核加的一种虚拟总线。Platform驱动与传统的设备驱动模型相比,优势在于Platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序使用这些资源时使用统一的接口,提高了程序的可移植性。Linux 2.6的设备驱动模型中,把I2C、RTC、LCD等都归纳为platform_device。总线将设备和驱动分别注册进各自的链表,在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。
三、过程
1、device层
(1)定义platform_device 结构体类型的变量,如建立led_device 。
static struct platform_device led_device = {
.name = "myled",
.id = -1,
.dev = {
.release = led_release,
},
.resource = led_resources,
.num_resources= ARRAY_SIZE(led_resources),
};
其中有个重要的成员是resource,是设备的资源信息,如IO地址,中断号等。
(2)定义resource结构体类型的变量。硬件资源就是在这确定的。如定义led_resources
static struct resource led_resources[] = {
[0]={
.start = 0x56000050,
.end = 0x56000050 + 8 - 1,
.flags = IORESOURCE_MEM,
},
[1]={
.start = 5,
.end = 5,
.flags = IORESOURCE_IRQ,
}
};
(3)在入口函数注册、注销平台设备。使用函数platform_device_register、platform_device_unregister。如:
static int led_device_init(void)
{
platform_device_register(&led_device);
return 0;
}
static void led_device_exit(void)
{
platform_device_unregister(&led_device);
}
(1)定义platform_driver 结构体类型的变量,如定义led_driver :
struct platform_driver led_driver = {
.probe = led_driver_probe,
.remove = __devexit_p(led_driver_remove),
.driver = {
.name = "myled",
}
};
注意.name,每当注册platform_driver,或上面platform_device 时,在bus层的platform_match函数首先判断是否有id_table,如果有则使用id_table来进行匹配,否则,判断platform_device和platform_driver成员里的name,如果二者的name字段相同则匹配,如果匹配则调用platform_driver的probe函数。
(2)在入口函数注册、注销平台设备驱动。使用函数platform_driver_register、platform_driver_unregister。如:
static int led_driver_init(void)
{
platform_driver_register(&led_driver);
return 0;
}
static void led_driver_exit(void)
{
platform_driver_unregister(&led_driver);
}
(3)编写probe函数。
这里主要可以进行一些初始化操作。如例子中注册了字符设备驱动,设置了IO功能并进行了ioremap。需要注意的是,对于platform_device 里的资源resource 是使用platform_get_resource函数得到的,再对这些资源做具体的操作。
(4)编写remove函数。对probe函数中加载的资源进行释放。
(5)具体的驱动编写。如例子里实现了open、write。
3、bus层
这层不需要编写。韦老师架构图
在device层或driver层注册平台设备或平台设备驱动时,bus会调用device_add或driver_register分别加入各自的链表,名字为各自结构体变量的.name项。接着调用platform_match函数先判断是否有id_table,如果有则使用id_table来进行匹配,否则,判断platform_device和platform_driver成员里的name,如果二者的name字段相同则匹配成功,然后调用platform_driver的probe函数。
四、程序
device.c
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <linux/gpio_keys.h>
#include <asm/gpio.h>
static void led_release(struct device * dev)
{
}
static struct resource led_resources[] = {
[0]={
.start = 0x56000050,
.end = 0x56000050 + 8 - 1,
.flags = IORESOURCE_MEM,
},
[1]={
.start = 5,
.end = 5,
.flags = IORESOURCE_IRQ,
}
};
static struct platform_device led_device = {
.name = "myled",
.id = -1,
.dev = {
// .platform_data = &nand_data,
.release = led_release,
},
.resource = led_resources,
.num_resources = ARRAY_SIZE(led_resources),
};
static int led_device_init(void)
{
platform_device_register(&led_device);
return 0;
}
static void led_device_exit(void)
{
platform_device_unregister(&led_device);
}
module_init(led_device_init);
module_exit(led_device_exit);
MODULE_LICENSE("GPL");
driver.c
/* 分配/设置/注册一个platform_driver */
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/io.h>
static struct class *leds_class;
static struct class_device *leds_class_devs;
static int pin;
static volatile unsigned long *gpio_con;
static volatile unsigned long *gpio_dat;
static int ret;
static int led_open(struct inode *inode, struct file *file)
{
printk("led_open\n");
/* 配置为输出 */
*gpio_con &= ~(0x3<<(pin*2));
*gpio_con |= (0x1<<(pin*2));
return 0;
}
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val;
printk("led_write\n");
copy_from_user(&val, buf, count); // copy_to_user();
if (val == 1)
{
// 点灯
*gpio_dat &= ~(1<<pin);
}
else
{
// 灭灯
*gpio_dat |= (1<<pin);
}
return 0;
}
static struct file_operations leds_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = led_open,
.write = led_write,
};
static int led_driver_probe(struct platform_device *pdev)
{
struct resource *res;
printk("led_driver_probe\n");
/* get a pointer to the register memory */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
gpio_con = ioremap(res->start, res->end - res->start + 1);
gpio_dat = gpio_con + 1;
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
pin = res->start;
ret = register_chrdev(0,"myled",&leds_fops);
leds_class = class_create(THIS_MODULE, "myled");
class_device_create(leds_class, NULL, MKDEV(ret, 0), NULL, "myled");/* /dev/leds */
return 0;
}
static int led_driver_remove(struct platform_device *pdev)
{
printk("led_driver_remove\n");
class_device_destroy(leds_class, MKDEV(ret, 0));
class_destroy(leds_class);
unregister_chrdev(ret, "myled");
iounmap(gpio_con);
return 0;
}
struct platform_driver led_driver = {
.probe = led_driver_probe,
.remove = __devexit_p(led_driver_remove),
.driver = {
.name = "myled",
}
};
static int led_driver_init(void)
{
platform_driver_register(&led_driver);
return 0;
}
static void led_driver_exit(void)
{
platform_driver_unregister(&led_driver);
}
module_init(led_driver_init);
module_exit(led_driver_exit);
MODULE_LICENSE("GPL");
测试程序test.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc,char **argv)
{
int fd;
int val=1;
fd=open("/dev/myled",O_RDWR);
if(fd<0)
{
printf("can't open!");
}
if(argc!=2)
{
printf("Usage :\n");
printf("%s <on|off>\n",argv[0]);
return 0;
}
if(strcmp(argv[1],"on")==0)
{
val =1;
}
else
{
val = 0;
}
write(fd,&val,4);
return 0;
}