通过第一篇文章,我们已经知道,整个SPI驱动架构可以分为协议驱动。通用接口层和控制器驱动三大部分。其中,控制器驱动负责最底层的数据收发工作,为了完成数据收发工作,控制器驱动需要完成以下这些功能:
1.申请必要的硬件资源,例如中断,DMA通道,DMA内存缓冲区等等;
2.配置SPI控制器的工作模式和参数,使之可以和相应的设备进行正确的数据交换工作;
3.向通用接口层提供接口,使得上层的协议驱动可以通过通用接口层访问控制器驱动;
4.配合通用接口层,完成数据消息队列的排队和处理,知道消息队列变为空为止;
定义控制器设备
SPI控制器遵循linux的设备模型框架,所以,一个SPI控制器在代码中对应一个device结构,对于嵌入式系统,我们通常把SPI控制器作为一个平台设备来对待,一般在设备树文件中会定义SPI控制器设备。这部分跟具体的SOC密切相关,这部分代码一般也有芯片厂商实现。
spi0: spi@e0300000 {
compatible = "actions,s700-spi";
reg = <0 0xe0300000 0 0x1000>;
interrupts = <GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>;
#address-cells = <1>;
#size-cells = <0>;
clocks = <&clock CLK_SPI0>;
clock-names = "spi0";
status = "disabled";
};
指定了SPI控制器的IO内存地址,中断线及中断类型,指定了clock及name
在内核解析设备树文件的时候,会把spi0注册为平台设备。根据linux设备驱动模型,有设备就会有与之对应的平台驱动,
注册SPI控制器的platform_driver
上一节中,我们知道设备树最终会把把SPI控制器注册为一个platform_device,相应的,对应的驱动就是一个平台驱动:platform_driver,他们通过platform bus进行相互匹配,以下代码来自drivers/spi/spi-owl.c中
static const struct of_device_id owl_spi_dt_ids[] = {
{ .compatible = "actions,s900-spi" },
{ .compatible = "actions,s700-spi" },
{ .compatible = "actions,ats3605-spi" },
};
static struct platform_driver owl_spi_driver = {
.probe = owl_spi_probe,
.remove = owl_spi_remove,
.driver = {
.name = "spi-owl",
.of_match_table = of_match_ptr(owl_spi_dt_ids),
},
};
static int __init owl_spi_init(void)
{
pr_info("[OWL] SPI controller initialization\n");
return platform_driver_register(&owl_spi_driver);
}
subsys_initcall(owl_spi_init);
显然,在系统初始化阶段(subsys_initcall阶段)通过owl_spi_init(),注册了一个平台驱动,该驱动的名字正好也是owl_spi_driver,自然地,平台总线会把它和上一节中通过设备树注册的platform_device匹配,匹配上之后就会触发proble回调函数被调用,当然,这里的匹配是通过compatible字段进行匹配的。
注册spi_master
在linux设备模型看来,代表spi控制器的是第一节所定义的platform_device结构,但是对于SPI通用接口层来说,代表控制器的是spi_master结构。我们知道设备和驱动匹配上之后,驱动的probe回调函数就会被调用,而probe回调函数正是对驱动程序和设备进行初始化的合适时机。
static int owl_spi_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct spi_master *master;
struct owl_spi_data *aspi;
struct resource *res;
int ret, num_cs;
master = spi_alloc_master(&pdev->dev, sizeof(*aspi));//分配spi_master结构
if (!master)
return -ENOMEM;
aspi = spi_master_get_devdata(master);
master->dev.of_node = pdev->dev.of_node;
platform_set_drvdata(pdev, master);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//获取resource
if (!res) {
ret = -ENODEV;
goto free_master;
}
aspi->phys = res->start;
aspi->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(aspi->base)) {
ret = PTR_ERR(aspi->base);
goto free_master;
}
aspi->clk = devm_clk_get(&pdev->dev, NULL);//获取时钟
if (IS_ERR(aspi->clk)) {
dev_err(&pdev->dev, "spi clock not found.\n");
ret = PTR_ERR(aspi->clk);
goto free_master;
}
ret = clk_prepare_enable(aspi->clk);
if (ret) {
dev_err(&pdev->dev, "Unable to enable APB clock.\n");
goto free_master;
}
aspi->dev = &pdev->dev;
/* SPI controller initializations */
owl_spi_init_hw(aspi);
ret = of_property_read_u32(np, "num-cs", &num_cs);
if (ret < 0)
master->num_chipselect = 1;
else
master->num_chipselect = num_cs;
master->setup = owl_spi_setup; //设置spi_master
master->cleanup = owl_spi_cleanup;
master->transfer_one_message = owl_spi_transfer_one_message;
master->bus_num = pdev->id;
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
master->flags = 0;
master->bits_per_word_mask = BIT(32 - 1) | BIT(16 - 1) | BIT(8 - 1);
master->dev.of_node = np;
aspi->master = master;
init_completion(&aspi->xfer_completion);
ret = spi_register_master(master);//向通用接口层注册spi_master结构
if (ret) {
dev_err(&pdev->dev, "cannot register SPI master\n");
goto disable_clk;
}
return 0;
}
在上述函数中,除了完成必要的硬件资源初始化工作以外,最重要的工作就是通过spi_alloc_master函数分配了一个spi_master结构,初始化该结构,最终通过spi_register_master函数完成了对控制器的注册工作,从代码中我们可以看出,spi_master结构中的几个重要回调函数已经被赋值,这几个回调函数由通用接口层在合适的时机被调用,以便完成控制器和设备之间的数据交换。
实现spi_master结构的回调函数
事实上,SPI控制器驱动程序的主要工作,就是实现spi_master结构中的几个回调函数,其他的工作逻辑,均由通用接口层帮我完成,通用接口层会在合适时机调用这几个回调函数,这里简单介绍一下各个回调函数的作用,具体实现例子,请各位自行阅读代码树中的各个平台的例子(代码位于/drivers/spi/)
int (*setup)(struct spi_device *spi);
当协议驱动希望修改控制器的工作模式或参数时,会调用通用接口层提供的API:spi_setup().该函数最后会调用setup回调函数来完成设置工作。
int (*transfer)(struct spi_device *spi, struct spi_message *mesg);
目前已经可以不用我们自己实现该回调函数,初始化时直接设为NULL即可,目前的通用接口层已经实现了消息队列化,注册spi_master时,通用接口层会提供实现好的通用函数,现在只有一些老的驱动还在使用该回调函数,新的驱动已经停止使用该回调函数,而是应该使用队列化的transfer_one_message回调,需要注意的是,我们只能选择其中一种方式设置了transfer_one_message回调,就不能设置transfer回调,反之亦然。
void (*cleanup)(struct spi_device *spi);
当一个spi从设备(spi_device)被释放时,该回调函数会被调用,以便释放该从设备所占用的硬件资源。
int (*prepare_transfer_hardware)(struct spi_master *master);
int (*unprepare_transfer_hardware)(struct spi_master *master);
这两个回调函数用于在发起一个数据传送过程前和后,给控制器驱动一个机会,申请或释放某些必要的硬件资源,例如DMA资源和内存资源等等。
int (*transfer_one_message)(struct spi_master *master,struct spi_message *mesg);
当通用接口层发现master的队列中有消息要传送数据时,会调用该回调函数,所以该回调函数是真正完成一个消息传送的工作函数,当传送完成时,应该调用spi_finalize_current_message函数,以便通知通用接口层,发起队列中的下一个消息的传送工作。