在linux 2.6版本的设备驱动中,主要关心总线、设备和驱动者三个实体,总线将设备和驱动绑定。在系统没注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。
一个现实的linux设备和驱动通常都需要接在一种总线上,对于本身依附于PCI、USB、I2C、SPI等设备而言,这自然不是问题,但是在嵌入式系统里面,SoC系统集成的独立外设控制器、挂接在SoC内存空间的外设等,却不依附于此类总线。基于这一背景,Linux发明了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动成为platform_driver。
由以上可知,我们需要基于platform编写LED驱动,那么我们主要关注两个部分即可:第一、platform 设备部分;第二、platform驱动部分,下面就分别进行介绍
在介绍之前,首先明确一个观点,编写platform总线驱动platform设备名字和platform总线名字必须一致,否则驱动无法找到设备,设备无法找到驱动
1、platform 设备部分
设备部分主要包括了一个平台设备数据结构struct platform_device,然后剩下的就是platform的register和unregister了,具体代码如下所示
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <asm/arch/regs-gpio.h>
#include <asm/arch/leds-gpio.h>
static void led_dev_release(struct device *dev)
{
printk("<kernel> release\n");
}
static struct s3c24xx_led_platdata s3c2416_pdata_led4 = { //LED控制数据
.gpio = S3C2410_GPK5,
.flags = S3C24XX_LEDF_ACTLOW,
.name = "led4"
};
static struct platform_device s3c_led_dev = { //定义一个LED平台设备
.name = "s3c2416_led", //平台设备名字,此名字很重要
.id = 0,
.dev = {
.platform_data = &s3c2416_pdata_led4,
.release = led_dev_release, //平台设备撤销的时候会调用,如果没有则会出错,可以试试
},
};
static int __init led_device_init(void) //模块初始化,主要注册该设备
{
int ret;
ret = platform_device_register(&s3c_led_dev);
if (ret) {
printk("device register failed\n");
return ret;
}
printk("led device init\n");
return 0;
}
static void __exit led_device_exit(void)
{
platform_device_unregister(&s3c_led_dev);
printk("led device bye!\n");
}
module_init(led_device_init);
module_exit(led_device_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("laneyu<yulane.lang@gmail.com>");
2、platform 驱动部分
平台驱动部分主要包括了一个驱动数据结构strut platform_driver,以及平台驱动注册和撤销,还有就是最重要的probe和remove了,具体代码如下所示
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <asm/arch/regs-gpio.h>
#include <asm/arch/leds-gpio.h>
int led_driver_probe(struct platform_device *pdev)
{
struct s3c24xx_led_platdata *pdata = pdev->dev.platform_data;
s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_OUTPUT);
s3c2410_gpio_setpin(pdata->gpio, 0);
printk("led on\n");
return 0;
}
int led_driver_remove(struct platform_device *pdev)
{
struct s3c24xx_led_platdata *pdata = pdev->dev.platform_data;
s3c2410_gpio_setpin(pdata->gpio, 1);
printk("led off\n");
return 0;
}
struct platform_driver s3c_led_drv = {
.probe = led_driver_probe,
.remove = led_driver_remove,
.driver = {
.name = "s3c2416_led", //平台驱动名字,注意此名字必须和平台设备名字一致
},
};
static int __init plat_led_init(void)
{
int ret;
ret = platform_driver_register(&s3c_led_drv);
if (ret) {
printk("led register failed!\n");
return ret;
}
printk("led dirver init\n");
return 0;
}
static void __exit plat_led_exit(void)
{
platform_driver_unregister(&s3c_led_drv);
printk("led driver exit");
}
module_init(plat_led_init);
module_exit(plat_led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("laneyu<yulane.lang@gmail.com>");
3、编译
Makefile如下所示
.PHONY: all clean
obj-m += device.o driver.o
KDIR := /home/kernel2416
all:
make ARCH=arm CROSS_COMPILE=arm-linux- -C $(KDIR) SUBDIRS=$(PWD) modules
clean:
make ARCH=arm CROSS_COMPILE=arm-linux- -C $(KDIR) SUBDIRS=$(PWD) clean
4、测试
将编写好的driver.ko device.ko加载到内核里面去,设备和设备驱动加载没有先后顺序要求,此处,我们先加载设备,然后加载驱动,如下所示
加载设备 insmod device.ko,如下图所示
此处,我们可以看到执行完了insmod device.ko以后,打印出了一句led device init以及查看/sys/devices/platform下面多了一个s3c2416_led.0的设备名字
接下来,我们才用同样的方法注册平台驱动 ,即insmod,如下图所示
此处,我们可以看到打印出来我们想打印的两句话,并且可以看到开发板的一个LED灯以及亮了
接下来我们开始撤销设备和设备驱动,首先撤销设备驱动,如下所示
rmmod driver.ko,结果如下
注意:撤销设备驱动以后,我们可以发现开发板上面的LED灯灭了
接下来撤销设备,操作结果如下
rmmod device.ko,结果如下
5、platform调试技巧
platform总线驱动调试最重要的技巧就是,查看probe函数有没有执行,如果probe函数没有执行那么问题肯定就是平台设备名和平台驱动名不一致导致的,因此,只需要记住这一点就可以编写好platform驱动了。
此处,是动态加载设备,后面还可以修改为静态加载设备