Linux SPI 总线 和设备驱动架构之三:SPI控制器驱动

通过第一篇文章,我们已经知道,整个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函数,以便通知通用接口层,发起队列中的下一个消息的传送工作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值