本文详细分析TI Davinci DM368 SPI接口的平台设备注册、外部设备注册、平台驱动注册、外部驱动程序注册。应该注意以下四点:
- 平台设备描述DM368芯片集成的SPI接口硬件描述
- 平台设备驱动描述如何控制DM368芯片的SPI接口,从而能够收发数据
- 外部设备描述的是挂在SPI接口上的设备(以下称为spi设备)
- 外部驱动程序描述的是如何控制SPI接口上的外部设备(以下称为spi驱动)
如下图所示平台设备与平台驱动通过平台总线连接在一起,类似的spi设备与spi驱动通过spi总线连接在一起
描述spi有三个数据结构:
- spi_master 描述SPI主机控制器,其实就是platform driver
- spi_driver 描述spi驱动
- spi_device 描述spi设备
spi_master嵌入在spi_device之中,也就是说spi_device中已经包含了spi控制器,更进一步讲,可以认为spi_device就是连接在spi接口上的spi设备,这样就将平台设备和平台设备驱动相关的知识给隐藏了。从而只专注于spi_driver、spi_device,他们两个也通过总线连接在一起。这样就和平台设备与平台设备驱动通过平台总线连接具有相似性,好处就是,可以实现内核代码公用。
本文对代码的追踪是按照调用顺序进行的
平台设备注册、平台设备驱动注册与连接
在…\arch\arm\mach-davinci\dm365.c之下
static struct platform_device dm365_spi0_device = {
.name = "spi_davinci",
.id = 0,
.dev = {
.dma_mask = &dm365_spi0_dma_mask,
.coherent_dma_mask = DMA_BIT_MASK(32),
.platform_data = &dm365_spi0_pdata,
},
.num_resources = ARRAY_SIZE(dm365_spi0_resources),
.resource = dm365_spi0_resources,
};//定义了spi平台设备,name为“spi_davinci”,因此必然存在一个spi平台驱动,其名字为“spi_davinci”,注册在平台总线的设备通过这个名字进行匹配
void __init dm365_init_spi0(unsigned chipselect_mask,
const struct spi_board_info *info, unsigned len)
{
davinci_cfg_reg(DM365_SPI0_SCLK);
davinci_cfg_reg(DM365_SPI0_SDI);
davinci_cfg_reg(DM365_SPI0_SDO);
/* not all slaves will be wired up */
if (chipselect_mask & BIT(0))
davinci_cfg_reg(DM365_SPI0_SDENA0);
if (chipselect_mask & BIT(1))
davinci_cfg_reg(DM365_SPI0_SDENA1);
spi_register_board_info(info, len);
platform_device_register(&dm365_spi0_device);//注册spi平台设备,因此在平台设备总线上挂着了一个spi平台设备
}
//先初始化设备,然后向platform bus上添加设备
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);//初始化
return platform_device_add(pdev);//添加设备
}
int platform_device_add(struct platform_device *pdev)
{
int i, ret = 0;
if (!pdev)
return -EINVAL;
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus;//可以看到是在platform_bus上注册了一个设备,此处的platform_bus是一个全局变量
pdev->dev.bus = &platform_bus_type;
if (pdev->id != -1)
dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);
else
dev_set_name(&pdev->dev, "%s", pdev->name);
for (i = 0; i < pdev->num_resources; i++) {
struct resource *p, *r = &pdev->resource[i];
if (r->name == NULL)
r->name = dev_name(&pdev->dev);
p = r->parent;
if (!p) {
if (resource_type(r) == IORESOURCE_MEM)
p = &iomem_resource;
else if (resource_type(r) == IORESOURCE_IO)
p = &ioport_resource;
}
if (p && insert_resource(p, r)) {
printk(KERN_ERR
"%s: failed to claim resource %d\n",
dev_name(&pdev->dev), i);
ret = -EBUSY;
goto failed;
}
}
pr_debug("Registering platform device '%s'. Parent at %s\n",
dev_name(&pdev->dev), dev_name(pdev->dev.parent));
ret = device_add(&pdev->dev);//添加设备
if (ret == 0)
return ret;
failed:
while (--i >= 0) {
struct resource *r = &pdev->resource[i];
unsigned long type = resource_type(r);
if (type == IORESOURCE_MEM || type == IORESOURCE_IO)
release_resource(r);
}
return ret;
}
//只选取了与我们分析最为密切的一些函数
int device_add(struct device *dev)
{
//...
error = bus_add_device(dev);//在总线上添加平台设备
//...
bus_probe_device(dev);//在总线上寻找与该平台设备相关的平台驱动
//...
}
void bus_probe_device(struct device *dev)
{
struct bus_type *bus = dev->bus;
int ret;
if (bus && bus->p->drivers_autoprobe) {
ret = device_attach(dev);
WARN_ON(ret < 0);
}
}
//如果找到了平台驱动就将两者连接,没有则返回
//假如有,因为后面平台驱动的注册会有类似的匹配过程:平台驱动定义、平台驱动注册到平台总线上、在平台总线上寻找name匹配的平台驱动,下面我们忽略了平台设备驱动的代码分析过程,读者可以自行分析
int device_attach(struct device *dev)
{
int ret = 0;
down(&dev