https://blog.csdn.net/qq_28992301/article/details/52385518
在设备驱动模型中,总线负责将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。
为什么要有这种匹配机制?难道不是很多余吗?原因详见Linux设备驱动模型与sysfs文末
一个platform总线驱动程序可以对应多个设备,并且设备的变化也不会影响驱动。这是如何实现的呢?
下面,以led驱动为实例,分析怎么使用platform来写驱动
/*sjh_add*/
/*第一步:创建一个适用于我们设备的platform_data类型*/
struct s5pv210_led_platdata {
unsigned int gpio;
unsigned int flags;
char *name;
char *def_trigger;
};
/*第二步:为一个具体设备实例化一个platform_data*/
static struct s5pv210_led_platdata x210_led1_pdata = {
.gpio = S5PV210_GPJ0(3),
.flags = NULL,
.name = "led1",
.def_trigger = NULL,
};
/*第三步:实例化一个platform_device,正式创建设备*/
static struct platform_device x210_led1 = {
.name = "s5pv210_led",//要和platform驱动中的名字对应
.id = 1,
.dev = {
.platform_data = &x210_led1_pdata,//底层信息
},
};
/* 第四步:把我们的platform_device添加进数组,开机时系统会注册数组中所有设备*/
static struct platform_device *smdkc110_devices[] __initdata = {
/*sjh_add*/
&x210_led1,
#ifdef CONFIG_FIQ_DEBUGGER
&s5pv210_device_fiqdbg_uart2,
#endif
...
/*该数组很长,后面就不贴了*/
/*自留地格式,提供给probe和release函数,让它们可以解析platdata*/
extern struct s5pv210_led_platdata {
unsigned int gpio;
unsigned int flags;
char *name;
char *def_trigger;
};
static int s5pv210_led_probe(struct platform_device *pdev)
{
/*导入自留地格式s5pv210_led_platdata,这样我们才能解析参数*/
struct s5pv210_led_platdata *pdata = pdev->dev.platform_data;
int ret = -1;
/*这是一个例子,我们如何通过传入的参数获得led的gpio编号信息*/
gpio_num = pdata->gpio;
/*各种注册、初始化操作*/
...
return 0;
}
/*卸载模块将触发remove函数*/
static int s5pv210_led_remove(struct platform_device *pdev)
{
struct s5pv210_led_platdata *pdata = pdev->dev.platform_data;
/*各种注销操作*/
return 0;
}
/*定义我们的platform_driver。注意name要和platform_device中相同*/
static struct platform_driver s5pv210_led_driver = {
.probe = s5pv210_led_probe,
.remove = s5pv210_led_remove,
.driver = {
.name = "s5pv210_led",
.owner = THIS_MODULE,
},
};
/*模块与卸载加载函数,在里面分别添加platform驱动的注册和卸载函数*/
static int __init s5pv210_led_init(void)
{
return platform_driver_register(&s5pv210_led_driver);
}
static void __exit s5pv210_led_exit(void)
{
platform_driver_unregister(&s5pv210_led_driver);
}
module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);
//模块描述信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("taurenking");
MODULE_DESCRIPTION("S5PV210 LED driver");
MODULE_ALIAS("S5PV210 LED driver");
struct s5pv210_led_platdata *pdata = pdev->dev.platform_data;
,这样编译器才能知道格式,我们才能解析参数gpio_num = pdata->gpio;
,硬件操作函数调用全局变量即可获得设备的底层信息,例如static void s5pv210_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
if (value == 0) {
gpio_set_value(gpio_num, 1);
}else{
gpio_set_value(gpio_num, 0);
}
}
有时,我们需要去分析一些由厂商实现的platform总线驱动,比如framebuffer。此类platform总线驱动本质上和我们之前分析的完全相同,只是在细节上略有区别罢了
s3c_device_fb
,它被定义在arch/arm/plat-xxx/devs.c内:struct platform_device s3c_device_fb = {
.name = "s3cfb",
.id = -1,
.num_resources = ARRAY_SIZE(s3cfb_resource),
.resource = s3cfb_resource,
.dev = {
.dma_mask = &fb_dma_mask,
.coherent_dma_mask = 0xffffffffUL
}
};
s3cfb_set_platdata
,该函数明显是用来为platform设备填充platform_data的 static struct s3c_platform_fb ek070tn93_fb_data __initdata = {
.hw_ver = 0x62,
.nr_wins = 5,
.default_win = CONFIG_FB_S3C_DEFAULT_WINDOW,
.swap = FB_SWAP_WORD | FB_SWAP_HWORD,
.lcd = &ek070tn93,
.cfg_gpio = ek070tn93_cfg_gpio,
.backlight_on = ek070tn93_backlight_on,
.backlight_onoff = ek070tn93_backlight_off,
.reset_lcd = ek070tn93_reset_lcd,
};
/ {
model = "SolidRun HummingBoard DL/Solo";
compatible = "solidrun,hummingboard", "fsl,imx6dl";
ir_recv: ir-receiver {
compatible = "gpio-ir-receiver";
gpios = <&gpio1 2 1>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hummingboard_gpio1_2>;
};
/*后面一堆代码就省略了*/
/*驱动中定义的of_match_table*/
static struct of_device_id gpio_ir_recv_of_match[] = {
{ .compatible = "gpio-ir-receiver", },
{ },
};
/*of_match_table被绑定到driver结构体内*/
static struct platform_driver gpio_ir_recv_driver = {
.probe = gpio_ir_recv_probe,
.remove = gpio_ir_recv_remove,
.driver = {
.name = GPIO_IR_DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(gpio_ir_recv_of_match),
},
};