我这里用的是mini210开发板,在其内核源码中,采用了Linux设备驱动模型中的platform虚拟总线来管理ADC设备。首先看S5PV210提供的ADC驱动接口。在plat-samsung/dev-adc.c中定义了s3c_device_adc,它是一个platform_device结构体,描述adc这个设备。
/* plat-samsung/dev-adc.c */
static struct resource s3c_adc_resource[] = {
[0] = {
.start = SAMSUNG_PA_ADC,
.end = SAMSUNG_PA_ADC + SZ_256 - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_TC,
.end = IRQ_TC,
.flags = IORESOURCE_IRQ,
},
[2] = {
.start = IRQ_ADC,
.end = IRQ_ADC,
.flags = IORESOURCE_IRQ,
},
};
struct platform_device s3c_device_adc = {
.name = "samsung-adc",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_adc_resource),
.resource = s3c_adc_resource,
};
从中可以看出,s3c_adc_resource中定义了ADC模数转换器所需要的内存地址空间、中断等资源,然后将其作为成员放入到s3c_device_adc结构体中,接下来需要做的是将s3c_device_adc加入到Platform虚拟总线管理的设备链表中去。
在s5pv210_map_io函数中,首先将s3c_device_adc的name属性设置成了”s5pv210-adc”。
/* mach-s5pv210/cpu.c */
void __init s5pv210_map_io(void)
{
...
s3c_adc_setname("s5pv210-adc");
...
}
/* re-define device name depending on support. */
static inline void s3c_adc_setname(char *name)
{
**#ifdef CONFIG_SAMSUNG_DEV_ADC**
s3c_device_adc.name = name;
**#endif**
}
然后,s3c_device_adc结构体指针被添加到mini210_devices[]这个platform_device*结构体指针数组中。
static struct platform_device *mini210_devices[] __initdata = {
&s3c_device_adc,
...
}
最后,通过platform_add_devices(mini210_devices, ARRAY_SIZE(mini210_devices));
添加s3c_device_adc到platform虚拟总线管理的设备链表中去。
在Linux的设备驱动模型中,总线、设备和驱动这3个结构体非常重要,总线将设备和驱动绑定。系统每注册一个设备的时候,就会寻找与之匹配的驱动;相反的,系统每注册一个驱动的时候,会寻找与之匹配的设备,匹配工作由总线完成。
这里有了ADC的platform device设备,则需要对应的platform driver与其匹配,在plat-samsung/adc.c中,可以找到s3c_adc_init的定义。
static struct platform_device_id s3c_adc_driver_ids[] = {
{
.name = "s3c24xx-adc",
.driver_data = TYPE_S3C24XX,
}, {
.name = "s3c64xx-adc",
.driver_data = TYPE_S3C64XX,
}, {
.name = "s5pv210-adc",
.driver_data = TYPE_S5PV210,
},
{ }
};
MODULE_DEVICE_TABLE(platform, s3c_adc_driver_ids);
static struct platform_driver s3c_adc_driver = {
.id_table = s3c_adc_driver_ids,
.driver = {
.name = "s3c-adc",
.owner = THIS_MODULE,
},
.probe = s3c_adc_probe,
.remove = __devexit_p(s3c_adc_remove),
.suspend = s3c_adc_suspend,
.resume = s3c_adc_resume,
};
static int __init adc_init(void)
{
int ret;
ret = platform_driver_register(&s3c_adc_driver);
if (ret)
printk(KERN_ERR "%s: failed to add adc driver\n", __func__);
return ret;
}
arch_initcall(adc_init);
platform_bus_type总线的math函数依靠platform_device的name属性与platform_driver的driver.name或id_table.name是否一致来完成ADC设备与ADC驱动程序的匹配,匹配成功后,则调用platform driver的probe指向的函数,来对设备进行初始化。
s3c_adc_probe()完成了S5PV210的ADC基地址映射、分频值设置、数据位设置等,s3c_adc_remove()函数则实现了相反的功能。
static int s3c_adc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct adc_device *adc;
struct resource *regs;
int ret;
unsigned tmp;
// 为adc_device结构体分配内存
adc