linuxspi总线和设备驱动架构之三:spi控制器驱动

源辰布道师【刘经欢】 源辰布道师【刘经欢】     2022-08-03     245

关键词:

通过第一篇文章,我们已经知道,整个SPI驱动架构可以分为协议驱动、通用接口层和控制器驱动三大部分。其中,控制器驱动负责最底层的数据收发工作,为了完成数据的收发工作,控制器驱动需要完成以下这些功能:
1.    申请必要的硬件资源,例如中断,DMA通道,DMA内存缓冲区等等;
2.    配置SPI控制器的工作模式和参数,使之可以和相应的设备进行正确的数据交换工作;

3.    向通用接口层提供接口,使得上层的协议驱动可以通过通用接口层访问控制器驱动;

4.    配合通用接口层,完成数据消息队列的排队和处理,直到消息队列变空为止;

/*****************************************************************************************************/
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/*****************************************************************************************************/

定义控制器设备


SPI控制器遵循linux的设备模型框架,所以,一个SPI控制器在代码中对应一个device结构,对于嵌入式系统,我们通常把SPI控制器作为一个平台设备来对待,所以,对于我们来说,只要在板级的代码中为SPI控制器定义一个platform_device结构即可。下面以Samsung的SOC芯片:S3C6410,做为例子,看看如何定义这个platform_device。以下的代码来自:/arch/arm/plat-samsung/devs.c中:

 

static struct resource s3c64xx_spi0_resource[] = {
	[0] = DEFINE_RES_MEM(S3C_PA_SPI0, SZ_256),
	[1] = DEFINE_RES_DMA(DMACH_SPI0_TX),
	[2] = DEFINE_RES_DMA(DMACH_SPI0_RX),
	[3] = DEFINE_RES_IRQ(IRQ_SPI0),
};

struct platform_device s3c64xx_device_spi0 = {
	.name		= "s3c6410-spi",
	.id		= 0,
	.num_resources	= ARRAY_SIZE(s3c64xx_spi0_resource),
	.resource	= s3c64xx_spi0_resource,
	.dev = {
		.dma_mask		= &samsung_device_dma_mask,
		.coherent_dma_mask	= DMA_BIT_MASK(32),
	},
};

由此可见,在这个platform_device中,我们定义了控制器所需的寄存器地址、DMA通道资源和IRQ编号,设备的名字定义为:s3c64xx-spi,这个名字用于后续和相应的控制器驱动相匹配。在machine的初始化代码中,我们需要注册这个代表SPI控制器的平台设备,另外,也会通过s3c64xx_spi0_set_platdata函数设置平台相关的参数供后续的控制器驱动使用:

 

 

static struct platform_device *crag6410_devices[] __initdata = {
        ......
        &s3c64xx_device_spi0,
        ......
};

static void __init xxxx_machine_init(void)
{

        s3c64xx_spi0_set_platdata(NULL, 0, 2);
        //注册平台设备
        platform_add_devices(crag6410_devices, ARRAY_SIZE(crag6410_devices));
}

s3c64xx_spi0_set_platdata函数的定义如下:

 

 

void __init s3c64xx_spi0_set_platdata(int (*cfg_gpio)(void), int src_clk_nr,
						int num_cs)
{
	struct s3c64xx_spi_info pd;
	......
	pd.num_cs = num_cs;
	pd.src_clk_nr = src_clk_nr;
	pd.cfg_gpio = (cfg_gpio) ? cfg_gpio : s3c64xx_spi0_cfg_gpio;
        ......
	s3c_set_platdata(&pd, sizeof(pd), &s3c64xx_device_spi0);
}

上述函数主要是指定了控制器使用到的gpio配置、片选引脚个数和时钟配置等信息。这些信息在后面的控制器驱动中要使用到。

注册SPI控制器的platform_driver


 

上一节中,我们把SPI控制器注册为一个platform_device,相应地,对应的驱动就应该是一个平台驱动:platform_driver,它们通过platform bus进行相互匹配。以下的代码来自:/drivers/spi/spi-s3c64xx.c

static struct platform_driver s3c64xx_spi_driver = {
        .driver = {
                .name   = "s3c64xx-spi",
                .owner = THIS_MODULE,
                .pm = &s3c64xx_spi_pm,
                .of_match_table = of_match_ptr(s3c64xx_spi_dt_match),
        },
        .remove = s3c64xx_spi_remove,
        .id_table = s3c64xx_spi_driver_ids,
};
MODULE_ALIAS("platform:s3c64xx-spi");

static int __init s3c64xx_spi_init(void)
{
        return platform_driver_probe(&s3c64xx_spi_driver, s3c64xx_spi_probe);
}
subsys_initcall(s3c64xx_spi_init);

显然,系统初始化阶段(subsys_initcall阶段),通过s3c64xx_spi_init(),注册了一个平台驱动,该驱动的名字正好也是:s3c64xx-spi,自然地,平台总线会把它和上一节定义的platform_device匹配上,并且触发probe回调被调用(就是s3c64xx_spi_probe函数)。当然,这里的匹配是通过id_table字段完成的:

 

static struct platform_device_id s3c64xx_spi_driver_ids[] = {
        {
                .name           = "s3c2443-spi",
                .driver_data    = (kernel_ulong_t)&s3c2443_spi_port_config,
        }, {
                .name           = "s3c6410-spi",
                .driver_data    = (kernel_ulong_t)&s3c6410_spi_port_config,
        }, 
        ......
        { },
};

注册spi_master


在linux设备模型看来,代表SPI控制器的是第一节所定义的platform_device结构,但是对于SPI通用接口层来说,代表控制器的是spi_master结构,关于spi_master结构的描述,请参看第二篇文章:Linux SPI总线和设备驱动架构之二:SPI通用接口层。我们知道,设备和驱动匹配上后,驱动的probe回调函数就会被调用,而probe回调函数正是对驱动程序和设备进行初始化的合适时机,本例中,对应的probe回调是:s3c64xx_spi_probe:

 

static int s3c64xx_spi_probe(struct platform_device *pdev)
{
        ......

        /* 分配一个spi_master结构 */
        master = spi_alloc_master(&pdev->dev,
                                sizeof(struct s3c64xx_spi_driver_data));
        ......

        platform_set_drvdata(pdev, master);
        ......
        master->dev.of_node = pdev->dev.of_node;
        master->bus_num = sdd->port_id;
        master->setup = s3c64xx_spi_setup;
        master->cleanup = s3c64xx_spi_cleanup;
        master->prepare_transfer_hardware = s3c64xx_spi_prepare_transfer;
        master->transfer_one_message = s3c64xx_spi_transfer_one_message;
        master->unprepare_transfer_hardware = s3c64xx_spi_unprepare_transfer;
        master->num_chipselect = sci->num_cs;
        master->dma_alignment = 8;
        master->bits_per_word_mask = SPI_BPW_MASK(32) | SPI_BPW_MASK(16) |
                                        SPI_BPW_MASK(8);
        /* the spi->mode bits understood by this driver: */
        master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
        master->auto_runtime_pm = true;

        ......
        /* 向通用接口层注册spi_master结构 */
        if (spi_register_master(master)) {
                dev_err(&pdev->dev, "cannot register SPI master
");
                ret = -EBUSY;
                goto err3;
        }

        ......
}

上述函数,除了完成必要的硬件资源初始化工作以外,最重要的工作就是通过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(),该API函数最后会调用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 (*prepare_message)(struct spi_master *master, struct spi_message *message)

int (*unprepare_message)(struct spi_master *master, struct spi_message *message)

这两个回调函数也是用于在发起一个数据传送过程前和后,给控制器驱动一个机会,对message进行必要的预处理或后处理,比如根据message需要交换数据的从设备,设定控制器的正确工作时钟、字长和工作模式等。

int (*transfer_one_message)(struct spi_master *master, struct spi_message *mesg)

当通用接口层发现master的队列中有消息需要传送时,会调用该回调函数,所以该函数是真正完成一个消息传送的工作函数,当传送工作完成时,应该调用spi_finalize_current_message函数,以便通知通用接口层,发起队列中的下一个消息的传送工作。

linuxspi总线和设备驱动架构之一:系统概述

SPI是"SerialPeripheralInterface"的缩写,是一种四线制的同步串行通信接口,用来连接微控制器、传感器、存储设备,SPI设备分为主设备和从设备两种,用于通信和控制的四根线分别是: CS  片选信号SCK 时钟信号MISO ... 查看详情

linuxspi总线和设备驱动架构之四:spi数据传输的队列化

我们知道,SPI数据传输可以有两种方式:同步方式和异步方式。所谓同步方式是指数据传输的发起者必须等待本次传输的结束,期间不能做其它事情,用代码来解释就是,调用传输的函数后,直到数据传输完成,函数才会返回。... 查看详情

linuxspi总线-spi控制器驱动层(代码片段)

1.简介        控制器驱动负责最底层的数据收发工作,为了完成数据的收发工作,控制器驱动需要完成以下这些功能:申请必要的硬件资源,例如中断,DMA通道,DMA内存缓冲区等等;配置SPI控制器... 查看详情

linuxspi总线-spi通用接口层(代码片段)

1.简介        由前面的博客可知,SPI通用接口层说白了就是一个中间层,承上启下,为协议驱动和控制器驱动提供一系列的标准接口API、标准数据结构,所以SPI通用接口层可以说是SPI总线的一个核心层了。  ... 查看详情

linuxspi总线-概述(代码片段)

1.定义        SPI是"SerialPeripheralInterface"的缩写,是一种四线制的同步串行通信接口,用来连接微控制器、传感器、存储设备,SPI设备分为主设备和从设备两种,用于通信和控制的四根线分别是:CS 片... 查看详情

linuxspi总线-spi协议驱动层(代码片段)

        以ymu836这款音频编解码芯片的驱动为例:staticstructspi_driverymu836_spi_driver=.driver=.name="ymu836",.owner=THIS_MODULE,.of_match_table=ymu836_of_match,,.probe=ymu836 查看详情

linuxspi通过设备树文件添加设备

参考技术A如上DTS文件片段,SPIDevice节点必须定义在SPIMaster节点下,其中compatible属性和reg属性,以上compatible属性用于匹配对应的Driver程序,reg属性用于指定使用的SPIMaster的编号,SPI相关设备树文件识别见下文讲解。匹配设备树文... 查看详情

sylixosspi总线框架浅析(代码片段)

修订历史版本日期原因V1.002018/8/18创建文档目录1SPI总线关键结构体11.1总线传输控制消息块11.2SPI总线适配器21.3SPI设备32SPI各个结构体之间的联系32.1总线链表32.2总线与设备33参考资料41SPI总线关键结构体1.1总线传输控制消息块传输... 查看详情

linuxspi驱动移植小结

2012-01-0722:21:29 效果图:  理论学习后,主要是linux中spi子系统设备框架的了解后,主控制器与设备分离的思想,那么我要开始动手了。1, makemenuconfig添加spi配置 2,platform_driver及platform_device数据结构都有了。于... 查看详情

arm总线架构(代码片段)

   S3C2440集成了丰富了外设控制器(LCD控制器、USBDevice控制器、USBHost控制器、NANDFLASH控制器、I2C控制器、SPI控制器等)。要控制这些外设就要设置相应控制器的寄存器以产生相应的驱动时序。学习S3C2440,主要是如何配置... 查看详情

spi和dmx区别

...而更有效地传输数据。参考技术A1.SPI是一种全双工的串行总线,用于在两个或多个设备之间传输数据,而DMX是一种专用的控制信号,用于控制灯光、音响等设备。2.SPI的传输速率可以达到50MHz,而DMX的传输速率为250Kbps。3.SPI使用4... 查看详情

linux驱动的软件架构

Linux驱动的软件架构Linux设备驱动非常重视软件的可用性和跨平台能力。1.Linux设备和驱动的分离把设备端的信息从驱动里剥离出来,让驱动以某种标准化的方法拿到这些信息。即Linux的总线、设备和驱动模型,总线负责匹... 查看详情

现场总线快速讲解之三

观点原因理解的实质角度485总线一些现场总线使用485接线一种电气标准通信CAN总线现场总线的一种具体实现一种网络局域网Modbus总线很多行业使用Modbus传输数据一种协议行业应用PROFIBUS系统使用了PROFIBUS技术一种控制系统控制系... 查看详情

stm32f412应用开发笔记之三:spi总线通讯与ad采集

本次我们在NUCLEO-F412ZG试验模拟量输入采集。我们的模拟量输入采用ADI公司的AD7705,是一片16位两路差分输入的AD采集芯片。具有SPI接口,我们将采用SPI接口与AD7705通讯。两路输入一路接氧气传感器,一路接氢气传感器。氧气传感... 查看详情

i2c和spi总线对比

1iic总线不是全双工,2根线SCLSDA。spi总线实现全双工,4根线SCKCSMOSIMISO2iic总线是多主机总线,通过SDA上的地址信息来锁定从设备。spi总线只有一个主设备,主设备通过CS片选来确定从设备3iic总线传输速度在100kbps-4Mbps。spi总线传输... 查看详情

spi总线协议理解

  1、什么是SPI:    是摩托罗拉公司设计的一种全双工通信、高速的、同步的串行外部设备通信协议。    2、SPI作用:    用于设备之间的数据交互。   3、SPI由什么构成:    1)MOSI:主设备输出从... 查看详情

linux内核设备信息集合

...A、B、I2C、SPI,soc外面接了外设E和F。IP外设有具体的总线,如I2C总线、SPI总线,对应的I2C设备和SPI设备就挂在各自的总线上,但是在soc内部只有系统总线,是没有具体总线的。第一节中讲了总线、设备和 查看详情

求ispspijtag三者区别详解?

...个引脚定义:SPI简述(SerialPeripheralInterface--串行外设接口)总线系统是一种同步串行外设接口,它可以使MCU与各种外围设备以串行方式进行通信以交换信息。外围设置FLASHRAM、网络控制器、LCD显示驱动器、A/D转换器和MCU等。SPI总线... 查看详情