Linux SPI Framework

  • 1.1 SPI 总线简介
  • 1.2 LINUX驱动分层
  • 1.3 LINUX设备模型
  • 1.4 LINUX SPI 核心层:
    • 1.4.1 SPI Host Master
    • 1.4.2 SPI Slave Device
  • 1.5 Linux SPI 控制器设备和驱动
    • 1.5.1 spi master 注册
    • 1.5.2 spi_register_controller
    • 1.5.3 spi_controller_initialize_queue
  • 1.6 LINUX SPI 外设驱动
    • 1.6.1 SPI Driver Register
      • 1.6.1.1 SPI 总线注册
      • 1.6.1.2 spi_match_device 介绍
      • 1.6.1.3 spi driver match
  • 1.7 SPI 数据传输
    • 1.7.1 spi 数据传输相关概念
      • 1.7.1.1 SPI 异步方式难点
      • 1.7.1.2 SPI 队列化传输方式
    • 1.7.2 spi_transfer 的队列化
    • 1.7.3 spi_message 发送请求
    • 1.7.4 spi_transfer 数据发送
      • 1.7.5 spi 信息统计
  • 1.8 linux spi 通用驱动spidev.c

1.1 SPI 总线简介

SPI (同步外设接口) 是由摩托罗拉公司开发的全双工同步串行总线,其接口由四种信号构成:

  1. MISO(串行数据输入),
  2. MOSI(串行数据输出),
  3. SCK(串行移位时钟),
  4. SS/CS(从使能信号)

现在芯片技术日新月异, SPI 模块的结构也在变化中, 像OMAP 系列中的 SPI 模块还支持 5 线的一种模式),SS /CS决定了唯一的与主设备通信的从设备,主设备通过产生移位时钟来发起通讯。通讯时,数据由 MOSI 输出,MISO 输入,数据在时钟的上升或下降沿由 MOSI 输出,在紧接着的下降或上升沿由 MISO 读入,这样经过 8/16 次时钟的改变,完成 8/16 位数据的传输。

SPI 模块为了和外设进行数据交换, 根据外设工作要求, 其输出串行同步时钟极性(CPOL) 和相位 (CPHA) 可以进行配置:

  • 如果 CPOL=0, 串行同步时钟的空闲状态为低电平,
  • 如果CPOL=1, 串行同步时钟的空闲状态为高电平,
  • 如果 CPHA=0, 在串行同步时钟的第一个跳变沿(上升或下降)数据被采样,
  • 如果 CPHA=1, 在串行同步时钟的第二个跳变沿(上升或下降)数据被采样,

1.2 LINUX驱动分层

在面向对象的程序设计中, 可以为某一类相似的事物定义一个基类, 而具体的事物可以继承这个基类中的函数。
Linux 内核中频繁使用到面向对象的设计思想。在设备驱动方面,往往为同类的设备设计了一个框架, 而框架中的核心层则实现了该设备通用的一些功能。而且具体的设备不想使用核心层的函数, 它可以重载之。这就是我们所说的在驱动设计中的分层思想。

此外, 在驱动的设计中, 还会使用分离的思想。如果一个设备的驱动和 host(Controller) 的驱动休戚相关, 那么, 这就意味着这个普通的设备如果用在不同的 host上, 会采用 n 个版本的驱动。

外设驱动与 host controller 的驱动不相关, host controller的驱动不关心外设, 而外设驱动也不关心主机, 外设只是访问核心层的通用 API 进行数据传输, 主机和外设之间可以进行任意的组合。相当于在控制器驱动和设备驱动之间增加一层核心层, 对内对外都隐藏了对端的不确定性。仔细通读 USB/SPI/PCI 的代码就会发现这种思想的体现。

1.3 LINUX设备模型

设备驱动模型中, 主要包含总线设备驱动三个实体, 总线将设备和驱动绑定, 在系统每注册一个设备的时候, 会寻找与之匹配的驱动,反之,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。

根据这个模型的需求, 一个现实的 linux 设备和驱动通常都需要挂接在一种总线上, 否则无法管他们的匹配。
注册驱动和注册设备都是由不同的 API 来完成的。对于本身依附于 PCI/USB/I2C/SPI 的设备而言, 这自然不是问题, 他们都有
对应的总线,但是在嵌入式系统里面,Controller 系统中集成的外设控制器, 挂载在内存空间的外设确不依附于此类总线。就像 SPI 控制器, PCI 控制器等等都是这种情况。那如何处理呢?基于这一背景, Linux 发明了一种虚拟的总线称为 platform 总线,相应的设备称为 platform_device, 而驱动称为 platform_driver

使用 platform 总线在驱动中大体有以下几个好处:

  1. 使得设备被挂接在一个总线上,使配套的 sysfs 节点、设备电源管理都成为可能。
  2. 隔离了 BSP 和驱动。BSP 中定义 platform 设备和设备使用的资源(可以使用platform_data 的形式来包括
    platform 设备的设备),设备的具体配置信息。而在驱动中,只需要通过通用 API 去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。

1.4 LINUX SPI 核心层:

核心层代码负责这个框架中通用的部分,满足分层的思想,位于drivers/spi/spi.c。 主要承担的工作包括:注册 spi总线,提供基本 SPI 总线操作 API:

int spi_register_driver(struct spi_driver *sdrv);
struct spi_device *spi_alloc_device(struct spi_master *master);
int spi_add_device(struct spi_device *spi);
struct spi_device *spi_new_device(struct spi_master *master, struct spi_board_info *chip);
struct spi_master *spi_alloc_master(struct device *dev, unsigned size);
int spi_register_master(struct spi_master *master);
void spi_unregister_master(struct spi_master *master);
int spi_sync(struct spi_device *spi, struct spi_message *message);
int spi_write_then_read(struct spi_device *spi, const u8 *txbuf, unsigned n_tx, u8 *rxbuf, unsigned n_rx);
spi_register_board_info(struct spi_board_info const *info, unsigned n);

可以参考 driver/spi/spi.c

1.4.1 SPI Host Master

在 Linux 中, 每一个类型的驱动都会有一个相应的结构体来描述, spi_master 就是用来描述 SPI host controller 驱动的,
其主要成员主要有以下几个:

  • bus_num;
  • cs;
  • spi 模式;
  • 时钟设置用到的函数;
  • 数据传输用到的函数等。

具体定义如下:

struct spi_master {struct device   dev;/* 表示是SPI主机控制器的编号。由平台代码决定 */s16              bus_num; /* 控制器支持的片选数量,即能支持多少个spi设备 */              u16             num_chipselect;        /* 针对设备设置SPI的工作时钟及数据传输模式等。在spi_add_device函数中调用 */int             (*setup)(struct spi_device *spi); /* 实现数据的双向传输,可能会睡眠 */int             (*transfer)(struct spi_device *spi, struct spi_message *mesg); /* 注销时调用 */void            (*cleanup)(struct spi_device *spi);  ...
};

SPI Host Conctroller 驱动中分配/注册/注销等操作,会调用 spi core layer API:

 spi_alloc_master();  spi_register_master();  spi_unregister_master();

实际上在 master 的驱动中实现的就是 spi_master 中的初始化以及注册,其中最重要的就是transfer(早期)和setup这两个操作的实现。spi 每次数据传输最终调用到的函数就是这里的transfer(早期)。该函数就对应着实际硬件上寄存器的控制。setup同理。

这里可能会有疑问, spi master controller 注册对应的驱动框架里的哪一层? 按说, SPI Host Controller 已经作为 platform 设备都注册过了。这里就需要用驱动设计里面的分层思想来解释。使用到 platform 总线 API 注册到 platform总线上的控制器设备和驱动, 都是 common 的部分。specific 的部分, 会在 platform driver 注册后, 在 probe 函数里面基于注册的 common 部分的资源信息来具体实现。称之为 spi_master 的注册部分。

1.4.2 SPI Slave Device

spi master 依附于platform bus 不同,spi device 依附于 spi bus,每个 spi deivice 对应的 driver 都会有一个spi_driver 结构体来进行描述,结构体中定义对应的操作函数指针:

struct spi_driver {int         (*probe)(struct spi_device *spi);int         (*remove)(struct spi_device *spi);void        (*shutdown)(struct spi_device *spi);int         (*suspend)(struct spi_device *spi, pm_message_t mesg);int         (*resume)(struct spi_device *spi);struct      device_driver    driver;
};

同时依附于SPI Bus 的设备都会有一个 spi_device 结构体来描述,结构中定义了设备的片选、模式、速率以及对应 Host 驱动的spi_master 等。

struct spi_device {struct device       dev;struct spi_master   *master;u32         max_speed_hz;u8          chip_select;u8          mode;
#define SPI_CPHA    0x01            /* clock phase */
#define SPI_CPOL    0x02            /* clock polarity */
#define SPI_MODE_0  (0|0)           /* (original MicroWire) */
#define SPI_MODE_1  (0|SPI_CPHA)
#define SPI_MODE_2  (SPI_CPOL|0)
#define SPI_MODE_3  (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH     0x04            /* chipselect active high? */
#define SPI_LSB_FIRST   0x08            /* per-word bits-on-wire */
#define SPI_3WIRE       0x10            /* SI/SO signals shared */
#define SPI_LOOP        0x20            /* loopback mode */u8         bits_per_word;int        irq;void       *controller_state;void       *controller_data;char       modalias[32];
};

依附于SPI 总线的设备驱动对应的总线类型为 spi_bus_type,在内核的 drivers/spi/spi.c 中定义。

struct bus_type spi_bus_type = { .name       = "spi",.dev_attrs  = spi_dev_attrs,.match      = spi_match_device,.uevent     = spi_uevent,.suspend    = spi_suspend,.resume     = spi_resume,
};
EXPORT_SYMBOL_GPL(spi_bus_type);

而 具体的 SPI 外设信息 linux spi 早期使用 struct spi_board_info 来描述,该结构体包含外设的片选号、总线号、模式以及传输速率等信息,在系统初始化时将会调用spi_register_board_info()来注册硬件信息;现在都通过dts 进行描述。

SPI 设备驱动将会调用 spi_register_driver(); 来注册一个spi_driver驱动,注册时最终会调用 spi_bus_type 中的 match 来使spi_driverspi_device相匹配。设备驱动 spi_driver 是在驱动里定义的,但是 spi_device 呢?事实上上文提到的spi_master的注册会在spi_register_board_info之后,spi_master注册的过程中会调用 scan_boardinfo 扫描 board_list,找到挂接在它上面的spi设备,然后创建并注册spi_device。

1.5 Linux SPI 控制器设备和驱动

在嵌入式系统中,一般处理器中都集成了一个或者多个SPI控制器。这些控制器一般都为Master。
由设备模型可知,SPI 控制器的设备和驱动是要挂在总线上的。所以 SPI 控制器驱动用到了platform 虚拟总线。
SPI 控制器驱动一般放在drivers/spi 下。

由于spi controller 也是作为设备注册到内核中,所以就需要有对应的device node对其进行描述,用户可以在其平台
对应的 dts 文件中添加 spi controller 设备的资源(若片上有n个spi控制器,可以在对应的dts中添加多个描述)。

以开源代码MTK平台为例:
arch/arm64/boot/dts/mediatek/mt8183.dtsi

                spi0: spi@1100a000 {compatible = "mediatek,mt8183-spi";#address-cells = <1>;#size-cells = <0>;reg = <0 0x1100a000 0 0x1000>;interrupts = <GIC_SPI 120 IRQ_TYPE_LEVEL_LOW>;clocks = <&topckgen CLK_TOP_SYSPLL_D5_D2>,<&topckgen CLK_TOP_MUX_SPI>,<&infracfg CLK_INFRA_SPI0>;clock-names = "parent-clk", "sel-clk", "spi-clk";status = "disabled";};

spi0 contorller 对应 spi controller 的驱动 drivers/spi/spi-mt65xx.c:

static struct platform_driver mtk_spi_driver = {.driver = {.name = "mtk-spi",.pm     = &mtk_spi_pm,.of_match_table = mtk_spi_of_match,},.probe = mtk_spi_probe,.remove = mtk_spi_remove,
};/** module_platform_driver是由module_driver封装而来,并填充* 了platform_driver_register、platform_driver_unregister , 见下面分析* /
module_platform_driver(mtk_spi_driver);

最后使用 platform_device_register 来进行注册,注册成功后可在 /sys/bus/platform/devices 看到相关信息

Linux 内核中会大量采用 module_platform_driver 来完成向 Linux 内核注册 platform 驱动的操作。module_platform_driver 定义在 include/linux/platform_device.h 文件中:

#define module_platform_driver(__platform_driver) \module_driver(__platform_driver, platform_driver_register, \platform_driver_unregister)

最终展开后就是如下形式:

static int __init xxx_init(void)
{return platform_driver_register(&xxx);
}
module_init(xxx_init);
static void __exit xxx_init(void)
{return platform_driver_unregister(&xxx);
}
module_exit(xxx_exit);

platform_driverplatform_device(上文dts中的内容) 匹配后,会调用驱动中的 mtk_spi_probe, 然后根据传入的 platform_device 参数,构建一个用于描述 SPI 控制器的结构体 spi_master,并注册。spi_register_master(master)
后续注册的spi_device 需要选定自己的spi_master,并利用spi_master提供的传输功能传输 spi 数据。

1.5.1 spi master 注册

上文介紹了 spi master dvice 的描述及 spi mater driver 的簡單注冊,接下來以 MTK SPI Master 驱动为例,進行簡單介紹,
在這部分内容中,我们主要关注 devm_spi_register_master

static int mtk_spi_probe(struct platform_device *pdev)
{struct spi_master *master;struct mtk_spi *mdata;const struct of_device_id *of_id;struct resource *res;int i, irq, ret, addr_bits;master = spi_alloc_master(&pdev->dev, sizeof(*mdata));if (!master) {dev_err(&pdev->dev, "failed to alloc spi master\n");return -ENOMEM;}master->auto_runtime_pm = true;master->dev.of_node = pdev->dev.of_node;master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST;/* SPI客户端驱动程序请求SPI主控制器来配置设备特定的CS设置,保持和非活动的时间要求 */master->set_cs = mtk_spi_set_cs;master->prepare_message = mtk_spi_prepare_message;  // 来传输单个传输,同时对同时到达的传输进行排队master->transfer_one = mtk_spi_transfer_one; master->can_dma = mtk_spi_can_dma;master->setup = mtk_spi_setup; // 设置了设备时钟速率、SPI模式和字大小of_id = of_match_node(mtk_spi_of_match, pdev->dev.of_node);mdata = spi_master_get_devdata(master);mdata->dev_comp = of_id->data;if (mdata->dev_comp->must_tx)master->flags = SPI_MASTER_MUST_TX;platform_set_drvdata(pdev, master);// 資源解析res = platform_get_resource(pdev, IORESOURCE_MEM, 0);mdata->base = devm_ioremap_resource(&pdev->dev, res);//中断相关配置irq = platform_get_irq(pdev, 0);ret = devm_request_irq(&pdev->dev, irq, mtk_spi_interrupt,IRQF_TRIGGER_NONE, dev_name(&pdev->dev), master);// clk 相关配置mdata->sel_clk = devm_clk_get(&pdev->dev, "sel-clk");mdata->spi_clk = devm_clk_get(&pdev->dev, "spi-clk");ret = clk_prepare_enable(mdata->spi_clk);ret = clk_set_parent(mdata->sel_clk, mdata->parent_clk);clk_disable_unprepare(mdata->spi_clk);pm_runtime_enable(&pdev->dev);// spi master 注册ret = devm_spi_register_master(&pdev->dev, master);

在 probe 中调用 spi_alloc_master 分配 spi_master 设备,对 spi_master 进行赋值等,最后进行通过devm_spi_master 进行注册。

devm_spi_register_master 实现:

int devm_spi_register_controller(struct device *dev, struct spi_controller *ctlr)
{...ret = spi_register_controller(ctlr);  //主要关注...
}
EXPORT_SYMBOL_GPL(devm_spi_register_controller);

1.5.2 spi_register_controller

int spi_register_controller(struct spi_controller *ctlr)
{.../** 检查传入的 spi_controller 结构中,挂接的 ops 是不是全的,也就是说 core 层需要的一些必* 要的钩子函数是不是已经指定好了,这里需要确定 SPI 控制器的那个传输的函数 transfer 、* transfer_one、transfer_one_message 至少挂接一个才行,也就是说,芯片厂家必须实现* 至少一种控制实际 SoC 的传输 SPI 的方式,Core 层才能够正常运行* /status = spi_controller_check_ops(ctlr); ...//一些链表,spin_lock 的初始化INIT_LIST_HEAD(&ctlr->queue);...//底层设备模型添加dev_set_name(&ctlr->dev, "spi%u", ctlr->bus_num);.../** 如果芯片厂家没有实现 transfer 钩子函数的话,那么至少使用了挂接了 transfer_one * 或者 transfer_one_message,这意味着将会使用 spi core 层的新的传输机制,* 这里初始化 queue 机制,后面马上进行详细描述;* /if (ctlr->transfer) {dev_info(dev, "controller is unqueued, this is deprecated\n");} else if (ctlr->transfer_one || ctlr->transfer_one_message) {status = spi_controller_initialize_queue(ctlr);    ...// 将spi_controller 挂接到全局的 spi_controller_list 链表ist_add_tail(&ctlr->list, &spi_controller_list);...// 匹配 borad_info,也就是 spi slave 的 结构 spi_deviceist_add_tail(&ctlr->list, &spi_controller_list);spi_match_controller_to_boardinfo(ctlr, &bi->board_info);...
}

整个过程中,需要着重理解的是 transfer 的部分,在很久以前的结构中,spi_controller 叫做 spi_master 的时代,其实是只有 transfer 对接函数的,也就是芯片厂家底层实现 transfer(获取数据,指挥实际 SoC 的硬件进行数据传输),然后 SPI Core 软件层来用这个。现在提供了 transfer 、transfer_one、transfer_one_message 三种:

  • transfer 是添加一个 message 到传输 queue;
  • transfer_one 是最底层指挥硬件去传输一个单一的 spi_transfer 内容
  • transfer_one_message 是最底层指挥硬件去传输一个单一的 spi_message 内容
    note: transfer_one 和 transfer_one_message 两个接口互斥, 当两者都设置了时,通用子系统不会调用 transfer_one回调
    此外,master->transfer 不能睡眠。它的责任是安排传输的发生和它的complete()回调的发出。这两种情况通常会在其他传输完成后发生,如果控制器是空闲的,则需要启动它。该方法不用于队列控制器,如果实现了transfer_one_message()(un)prepare_transfer_hardware()则必须为NULL。目前已经弃用。

既然现在都是用 transfer_one(或者transfer_one_message)那着重看看这个初始化 spi_controller_initialize_queue

1.5.3 spi_controller_initialize_queue

static int spi_controller_initialize_queue(struct spi_controller *ctlr)
{.../** 先将 transfer 赋值成为 spi_queued_transfer 调用(因为只有当transfer 没有挂* 指针的时候,才会走进这个函数* /ctlr->transfer = spi_queued_transfer;   /** 如果没有指定 transfer_one_message,那么为它挂上core 层的 * spi_transfer_one_message,需要记住:*  spi_controller->transfer 和 spi_controller->spi_transfer_one_message * 已经赋了 core 层的函数钩子;* /if (!ctlr->transfer_one_message)         ctlr->transfer_one_message = spi_transfer_one_message;.../** 初始化了 spi_controller 结构的 kthread_worker 和 kthread_work * 结构(内核 kthread_worker 和 kthread_work 机制)这个机制相当于是* 内核线程,并指定了 work 的执行函数为 spi_pump_messages ,最后* 如果对接底层芯片的 spi_controller 指定了 spi_controller->rt 的话,意* 思是开启 realtime 发送,那么将执行线程变为 SCHED_FIFO 的实时调* 度类型,也就是更高的优先级(实时优先级)* /ret = spi_init_queue(ctlr);...ret = spi_start_queue(ctlr);...//注冊 spi slave devicelist_for_each_entry(bi, &board_list, list)spi_match_controller_to_boardinfo(ctlr, &bi->board_info);...
}

1.6 LINUX SPI 外设驱动

不同的 spi device 在 SPI 总线通信上的需求不尽相同,比如模式、时钟速率、片选脚等等。这些跟具体硬件相关的信息通常用 dts 来描述:

&spi1 {status = "okay";#address-cells = <1>;#size-cells = <0>;spi_test@0 {compatible = "demo,spi_test";reg = <0>;spi-max-frequency = <5200000>;status = "okay";spi2axi@0 {

驱动中对应的配置:

static const struct of_device_id spitest_dt_ids[] = {{ .compatible = "demo,spi_test", },{ }
};static struct spi_driver spi_test_driver = {.driver = {.name = "spitest",.of_match_table = of_match_ptr(spiteste_dt_ids),},.probe  = spi_test_probe,.remove = spi_test_remove,
};static int __init spi_test_init(void)
{int ret = 0;ret = spi_register_driver(&spi_test_driver);if (ret) {pr_err("spi driver register failed!\n");return ret;}/* 注册设备文件,留给上层使用 */ret = misc_register(&spi_test_misc);return ret;
}
module_init(spi_test_init);

1.6.1 SPI Driver Register

不管是设备还是驱动,都是挂接在某条总线上的,也就是说我们根据总线类型的不同来区分各种设备和驱动。spi device 的注册会调用到 core layer 的 spi 注册函数 spi_register_driver来将spi device注册到 spi bus总线上,所以我们先看下 core layer 的初始化,同时包含了spi bus的注册。spi core layer 的初始化函数是 spi_init(void),该函数的主要实现如下:

kernel-4.14/drivers/spi/spi.c
struct bus_type spi_bus_type = {      //内核通过struct bus_type结构,抽象Bus.name           = "spi",                 //总线名称.dev_groups     = spi_dev_groups,/** 由具体的bus driver实现的回调函数。当任何属于该Bus的device或者* device_driver添加到内核时,内核都会调用该接口,如果新加的* device或device_driver匹配上了自己的另一半的话,* 该接口要返回非零值,此时Bus模块的核心逻辑就会执行后续的处理。*/.match          = spi_match_device,/* * spi_uevent主要是在spi设备与spi驱动完成绑定时,发送给应用层的uevent条目,* 主要是增加 “MODALIAS=spi:spi->modalias”的uevent条目。*/.uevent         = spi_uevent,
};
EXPORT_SYMBOL_GPL(spi_bus_type);static int __init spi_init(void)
{int     status;/* 申请的buf空间用于在spi数据传输中 */buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL); /* 总线注册和类注册, platform_bus_init同样也调用这个函数 */status = bus_register(&spi_bus_type)     /* 注册主设备的class */status = class_register(&spi_master_class);...
}
postcore_initcall(spi_init);

可以看到 spi core layer 的初始化 使用的宏 postcore_initcall(spi_init); 此宏在系统初始化时调用,早于于module_init()执行的,
也即 spi_driver_register所在的 moule_init 函数的执行。spi_init 函数主要完成了spi总线的注册和和申请由于spi 数据传输的buf, 下面看下SPI 总线的注册。

1.6.1.1 SPI 总线注册

kernel-4.14/drivers/base/bus.c

int bus_register(struct bus_type *bus)
{int retval;struct subsys_private *priv;   //申请一个subsys_private结构体内存struct lock_class_key *key = &bus->lock_key;priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);if (!priv)return -ENOMEM;priv->bus = bus;bus->p = priv;BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);/* 总线的名字”spi”, 一个kobject对应一个目录,这里为这个目录赋值名字 */retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);if (retval)goto out;priv->subsys.kobj.kset = bus_kset;priv->subsys.kobj.ktype = &bus_ktype;priv->drivers_autoprobe = 1;/* 创建devices命名的目录 */retval = kset_register(&priv->subsys);if (retval)goto out;/* 创建属性文件 */retval = bus_create_file(bus, &bus_attr_uevent);if (retval)goto bus_uevent_fail;priv->devices_kset = kset_create_and_add("devices", NULL, &priv->subsys.kobj);if (!priv->devices_kset) {retval = -ENOMEM;goto bus_devices_fail;}priv->drivers_kset = kset_create_and_add("drivers", NULL, &priv->subsys.kobj);if (!priv->drivers_kset) {retval = -ENOMEM;goto bus_drivers_fail;}

从总线注册函数bus_register(struct bus_type *bus)中可以发现,首先申请了一个subsys_private 结构体内存。该结构体中包含了三个kset 结构,分别是:

  • struct kset subsys、
  • struct kset *devices_kset
  • struct kset *drivers_kset
struct subsys_private {struct kset subsys;  //代表bus在sysfs中的类型struct kset *devices_kset;   //代表bus目录下地devices子目录struct list_head interfaces;struct mutex mutex;struct kset *drivers_kset; //代表bus目录下的drivers子目录struct klist klist_devices;  //存了本bus下所有的devicestruct klist klist_drivers;   //存了本bus下所有的driver//用于在总线上内容发送变化时调用特定的函数struct blocking_notifier_head bus_notifier; //用于控制该bus下的drivers或者device是否自动probeunsigned int drivers_autoprobe:1; struct bus_type *bus;  //保存上层的bus指针。struct kset glue_dirs;struct class *class; //保存上层的class
};

当发现一个设备或者驱动的时候,对于每一次设备或者驱动注册(设备是被插入了,驱动就是.ko模块被加载),都得分配一个device或者device_drive结构,每一次都需要将device结构挂入drivers或devices(kset结构)链表中,这样才能通过总线找到挂接在这个总线上的所有设备和驱动。

这里仅仅将设备和驱动挂接在总线上,并不能表明设备和驱动之间的关系,这样的处理仅仅表明了驱动、设备与总线的关系,它们申明了我现在挂接在这条总线上,以后操作我就通过这条总线。

总线注册完成后,在后续的spi device 注册的时候就会先调用 spi bus总线上的 spi_match_device函数,下面先介绍下该
函数。

1.6.1.2 spi_match_device 介绍

kernel-4.14/drivers/spi/spi.c
static int spi_match_device(struct device *dev, struct device_driver *drv)
{const struct spi_device *spi = to_spi_device(dev);const struct spi_driver *sdrv = to_spi_driver(drv);/* Attempt an OF style match */if (of_driver_match_device(dev, drv))return 1;/* Then try ACPI */if (acpi_driver_match_device(dev, drv))return 1;if (sdrv->id_table)return !!spi_match_id(sdrv->id_table, spi);return strcmp(spi->modalias, drv->name) == 0;
}
  1. 首先查看驱动和设备是否匹配,如果不匹配,退出;
  2. 判断驱动中是否支持id数组,如果支持,查找匹配此idspi_device;
  3. 比较设备的名字的和驱动的名字是否相同;
    在上面的匹配成功后就会调用到驱动中的 xxx_probe函数(如上面的spi_test_probe)。接下来就可以利用spi子系统为我们完成数据交互了。

这里还有一个疑问?系统是如何及何时 触发 spi_match_device调用的呢?

1.6.1.3 spi driver match

驱动是如何插入到/sys/bus/drivers/spi目录下的?先看spi_register_driver的实现:

int __spi_register_driver(struct module *owner, struct spi_driver *sdrv)
{sdrv->driver.owner = owner;sdrv->driver.bus = &spi_bus_type;if (sdrv->probe)sdrv->driver.probe = spi_drv_probe;if (sdrv->remove)sdrv->driver.remove = spi_drv_remove;if (sdrv->shutdown)sdrv->driver.shutdown = spi_drv_shutdown;// 完成挂接驱动至总线及生成设备树的过程return driver_register(&sdrv->driver);
}
EXPORT_SYMBOL_GPL(__spi_register_driver);

在 driver_register->driver_register->bus_add_driver 函数中有个重要的语句 drv->kobj.kset = &bus->drivers,
这里就是将 driver 的 kobj 所属的 kset 挂接上总线的 kset,接下一层一层来看:

driver_register 层

int driver_register(struct device_driver *drv)
{...other = driver_find(drv->name, drv->bus); //在spi总线上查找该驱动是否已经存在,如果存在,忙退出if (other) {printk(KERN_ERR "Error: Driver '%s' is already registered, "aborting...\n", drv->name);return -EBUSY;}ret = bus_add_driver(drv);  //如果该驱动在SPI总线上不存在,调用bus_add_driver(drv)增加该驱动if (ret)return ret;ret = driver_add_groups(drv, drv->groups);   //增加驱动组...
}
EXPORT_SYMBOL_GPL(driver_register);

bus_add_driver 层

int bus_add_driver(struct device_driver *drv)
{struct bus_type *bus;struct driver_private *priv;int error = 0;bus = bus_get(drv->bus);if (!bus)return -EINVAL;pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);priv = kzalloc(sizeof(*priv), GFP_KERNEL);...klist_init(&priv->klist_devices, NULL, NULL);priv->driver = drv;drv->p = priv;priv->kobj.kset = bus->p->drivers_kset;  error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,"%s", drv->name);...klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);...error = driver_attach(drv);  //如果驱动总线支持自动探测, 走到這裏...

driver_attach layer

int driver_attach(struct device_driver *drv)
{return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);  //调用__driver_attach函数
}
EXPORT_SYMBOL_GPL(driver_attach);static int __driver_attach(struct device *dev, void *data)
{...if (!dev->driver)driver_probe_device(drv, dev);  //此函数就是我们要找的函数...
}int driver_probe_device(struct device_driver *drv, struct device *dev)
{...ret = really_probe(dev, drv);...
}static int really_probe(struct device *dev, struct device_driver *drv)
{...if (dev->bus->probe) {        //因为在 BUS 中并没有给 probe 赋值,所以会直接调用驱动中的的 probeTIME_LOG_START();ret = dev->bus->probe(dev);TIME_LOG_END();bootprof_probe(ts, dev, drv, (unsigned long)dev->bus->probe);if (ret)goto probe_failed;} else if (drv->probe) {TIME_LOG_START();ret = drv->probe(dev);  //调用驱动中对应的xxx_probe函数TIME_LOG_END();bootprof_probe(ts, dev, drv, (unsigned long)drv->probe);if (ret)goto probe_failed;}

SPI是专门独立出来了一个子系统,它的平台初台化是专门在spi.c中用 __init spi_init(void) 函数,和在一般的 platform驱动还是稍微有一些区别的。而平台总线因为是通用的,所以在platform.c 中调用 int __init platform_bus_init(void) 函数来实现

1.7 SPI 数据传输

1.7.1 spi 数据传输相关概念

SPI数据传输可以有两种方式:同步方式和异步方式。
同步方式:是指数据传输的发起者必须等待本次传输的结束,期间不能做其它事情,用代码来解释就是,调用传输的函数后,直到数据传输完成,函数才会返回。
异步方式:数据传输的发起者无需等待传输的结束,数据传输期间还可以做其它事情,用代码来解释就是,调用传输的函数后,函数会立刻返回而不用等待数据传输完成,我们只需设置一个回调函数,传输完成后,该回调函数会被调用以通知发起者数据传送已经完成。

同步方式简单易用,很适合处理那些少量数据的单次传输。但是对于数据量大、次数多的传输来说,异步方式就显得更加合适。

1.7.1.1 SPI 异步方式难点

对于SPI控制器来说,要支持异步方式必须要考虑以下两种状况:
1)对于同一个数据传输的发起者,既然异步方式无需等待数据传输完成即可返回,返回后,该发起者可以立刻又发起一个message,而这时上一个message还没有处理完。
2)对于另外一个不同的发起者来说,也有可能同时发起一次message传输请求。

1.7.1.2 SPI 队列化传输方式

队列化正是为了为了解决SPI异步传输的问题。
所谓队列化,是指把等待传输的 message 放入一个等待队列中,发起一个传输操作,其实就是把对应的 message按先后顺序放入一个等待队列中,系统会在不断检测队列中是否有等待传输的 message,如果有就不停地调度数据传输内核线程,逐个取出队列中的message进行处理,直到队列变空为止。SPI通用接口层为我们实现了队列化的基本框架。

1.7.2 spi_transfer 的队列化

先理解下面两个结构的含义:( 这两个结构的定义及详细注释参见 include/linux/spi/spi.h)
spi_message:描述一次完整的传输,即 CS 信号从高->底->高的传输
spi_transfer: 多个 spi_transfer 够成一个spi_message

对协议驱动来说,一个spi_message是一次数据交换的原子请求,而spi_message由多个spi_transfer结构组成,这些spi_transfer通过一个链表组织在一起,看这两个数据结构关于spi_transfer链表的相关字段:

struct spi_transfer {  ......  const void      *tx_buf;  void            *rx_buf;  ......  struct list_head transfer_list;
};  struct spi_message {  struct list_head        transfers;  struct spi_device       *spi;  ......          struct list_head        queue;  ......
};

可见,一个spi_message结构有一个链表头字段:transfers,而每个spi_transfer结构都包含一个链表头字段:transfer_list,通过这两个链表头字段,所有属于这次message传输的transfer都会挂在spi_message.transfers字段下面。可以通过以下API向spi_message结构中添加一个spi_transfer结构:

static inline void
spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
{  list_add_tail(&t->transfer_list, &m->transfers);
}

core layer 会以一个message为单位,在工作线程中调用控制器驱动的 transfer_one_message回调函数来完成spi_transfer链表的处理和传输工作。

1.7.3 spi_message 发送请求

以下的API可以被协议驱动程序用于发起一个message传输操作:

extern int spi_async(struct spi_device *spi, struct spi_message *message);

spi_async函数是发起一个异步传输的API,它会把spi_message结构挂在spi_master的queue字段下,然后启动专门为spi传输准备的内核工作线程,由该工作线程来实际处理message的传输工作,因为是异步操作,所以该函数会立刻返回,不会等待传输的完成,这时,协议驱动程序(可能是另一个协议驱动程序)可以再次调用该API,发起另一个message传输请求,结果就是,当工作线程被唤醒时,spi_master下面可能已经挂了多个待处理的spi_message结构,工作线程会按先进先出的原则来逐个处理这些message请求,每个message传送完成后,对应spi_message结构的complete回调函数就会被调用,以通知协议驱动程序准备下一帧数据。这就是spi_message的队列化。

1.7.4 spi_transfer 数据发送

以 kernel-4.14/drivers/gpu/drm/panel/panel-sitronix-st7789v.c 函数发送一个 cmd(8bit) 为例。

static int st7789v_spi_write(struct st7789v *ctx, enum st7789v_prefix prefix, u8 data)
{struct spi_transfer xfer = { };  // 定义了一个spi_transferstruct spi_message msg; // 定义了一个spi_messageu16 txbuf = ((prefix & 1) << 8) | data;spi_message_init(&msg);// 初始化其 transfers 链表xfer.tx_buf = &txbuf;   // 赋值transfer的写指针xfer.bits_per_word = 9;xfer.len = sizeof(txbuf); // 赋值transfer的写长度spi_message_add_tail(&xfer, &msg);   //添加到 spi_messagereturn spi_sync(ctx->spi, &msg);     //进行同步方式发送,// spi_sync为同步方式发送,
}

上面 ctx->spi 为 struct spi_device 结构体指针, struct spi_device 结构体包含了所依赖的spi_controller,在调用spi_sync的时候会通过spi_device找到对应的spi_controller。

用户也可以选择一些封装好的函数,见 include/linux/spi/spi.h,如:

int spi_write_then_read(struct spi_device *spi, const u8 *txbuf, unsigned n_tx, u8 *rxbuf, unsigned n_rx);
int spi_write_then_read(struct spi_device *spi, const void *txbuf, unsigned n_tx, void *rxbuf, unsigned n_rx)

1.7.5 spi 信息统计

由于spi controller 是通过平台设备注册的额,所以可以在devices/platform看到其相关信息,比如查看数据发送/数据接收时,可以直接cat bytes_tx/bytes_rx/transfer

demo:/sys/devices/platform/xxx.spi2/spi_master/spi2/statistics # ls
bytes      spi_sync                        transfer_bytes_histo_16-31        transfer_bytes_histo_32768-65535  transfer_bytes_histo_8-15
bytes_rx   spi_sync_immediate              transfer_bytes_histo_16384-32767  transfer_bytes_histo_4-7          transfer_bytes_histo_8192-16383
bytes_tx   timedout                        transfer_bytes_histo_2-3          transfer_bytes_histo_4096-8191    transfers
errors     transfer_bytes_histo_0-1        transfer_bytes_histo_2048-4095    transfer_bytes_histo_512-1023     transfers_split_maxsize
messages   transfer_bytes_histo_1024-2047  transfer_bytes_histo_256-511      transfer_bytes_histo_64-127
spi_async  transfer_bytes_histo_128-255    transfer_bytes_histo_32-63        transfer_bytes_histo_65536+

1.8 linux spi 通用驱动spidev.c

/driver/spi/spidev.c是linux内核提供的一个spi通用驱动。如果不想写具体的spi芯片驱动,使用这个驱动能给我们带来很多便利,非常方便。在内核代码下的Documentation/spi/spidev是对spidev的描述。其中还提供一个spidev_test.c的测试程序。
使用很简单,只要在你的BSP代码中将spi_board_info的modalias定义为.modalias = "spidev",这样设备启动后就可以在/dev看到spidev0.0字样. 0.0 就是bus_num.chip_select(对应BSP中的设置)

spidev注册成字符设备,可以方便的使用其提供的标准read/write/ioctl功能函数对设备进行读写设置操作。设备的全双工半双工都得以实现。具体使用可以细看spidev.c或借鉴spidev_test.c

本文引用:
[https://blog.csdn.net/xiaochongtou123/article/details/7751280]
https://blog.csdn.net/weixin_42118770/article/details/116838615
https://blog.csdn.net/kunkliu/article/details/77989964https://blog.csdn.net/zhoutaopower/article/details/100040318

【Linux SPI Framework】相关推荐

  1. 【翻译】【linux设备驱动】linux地址类型

    [翻译][linux设备驱动]linux地址类型 Linux中使用的地址类型列表: 用户虚拟地址(User virtual addresses) 用户空间程序可见的普通地址.用户虚拟地址的长度为32位 ...

  2. 【CentOS Linux 7】实验2【Linux多用户管理】

    [CentOS Linux 7]实验1[Linux文件目录管理] [CentOS Linux 7]实验2[Linux多用户管理] 目   录 一.实验目的 二.实验内容 三.实验步骤和结果

  3. 【Linux系统编程】进程间通信之无名管道

    00. 目录 文章目录 00. 目录 01. 管道概述 02. 管道创建函数 03. 管道的特性 04. 管道设置非阻塞 05. 附录 01. 管道概述 管道也叫无名管道,它是是 UNIX 系统 IP ...

  4. 【Linux系统编程】信号 (下)

    00. 目录 文章目录 00. 目录 01. 信号集 02. 信号阻塞集 03. sigaction函数 04. 附录 01. 信号集 为了方便对多个信号进行处理,一个用户进程常常需要对多个信号做出处 ...

  5. 【Linux系统编程】信号 (上)

    00. 目录 文章目录 00. 目录 01. 信号概述 02. 信号编号 03. 信号产生方式 04. kill发送信号 05. pause等待信号 06. 信号处理方式 07. 信号处理函数 08. ...

  6. 【Linux系统编程】进程间通信概述

    00. 目录 文章目录 00. 目录 01. 进程间通信概述 02. 进程间通信目的 03. 进程间通信机制 04. 附录 01. 进程间通信概述 进程是一个独立的资源分配单元,不同进程(这里所说的进 ...

  7. 【Linux系统编程】进程替换:exec 函数族

    00. 目录 文章目录 00. 目录 01. exec函数族 02. 参考示例 2.1 execl函数示例 2.2 execv函数示例 2.3 execlp() 或 execvp()函数示例 2.4 ...

  8. 【Linux系统编程】特殊进程之守护进程

    00. 目录 文章目录 00. 目录 01. 守护进程概述 02. 守护进程查看方法 03. 编写守护进程的步骤 04. 守护进程代码 05. 附录 01. 守护进程概述 守护进程(Daemon Pr ...

  9. 【Linux系统编程】特殊进程之孤儿进程

    00. 目录 文章目录 00. 目录 01. 孤儿进程概述 02. 孤儿进程代码 03. 附录 01. 孤儿进程概述 父进程运行结束,但子进程还在运行的子进程就称为孤儿进程(Orphan Proces ...

最新文章

  1. LeetCode简单题之Fizz Buzz
  2. java时间转换为字符串格式错误_字符串转换为日期时间格式及其错误处理(转)
  3. RocketMQ:消息存储机制详解与源码解析
  4. 二倍图(精灵图的用法)
  5. 利用Pytorch的C++前端(libtorch)读取预训练权重并进行预测
  6. C语言九十五之实现经典的反转数组(通过指针或数组下标操作)
  7. php mysql 平均分_平均评级计算mysql php
  8. ajax 2分钟超时_ajax和axios、fetch的区别
  9. 机器学习交易——如何使用回归预测股票价格?【翻译】
  10. Jmeter系列之Jmeter+Grafana+InfluxDB实时监控
  11. SpringMVC-@RequestMapping的参数和用法
  12. node.js读取JSON文件
  13. SAP - 采购价格确定 ①
  14. 部署scrapy爬虫到AWS Ubuntu 18.04,用crontab定时执行
  15. 虚拟机屏幕自适应问题
  16. 【程序设计训练】3-15 公交系统
  17. TransReID学习记录
  18. 如何得到1-100中的质数
  19. 网络骗子的特征。大家一定要转载。
  20. QT实现简易的计算器

热门文章

  1. RedisTemplate添加List类型数据
  2. OPENGL报错NVD3DREL: GR-805 : DX9 Overlay is DISABLED
  3. win11蓝牙耳机电脑无法识别为耳机的可能解决办法
  4. js中会改变原数组的方法及不改变原数组的方法整理
  5. Centos7 Openssl安装/升级流程
  6. 格林威尔8000 OLT的使用
  7. matlab实现srt,SRT计划项目申请书提交版.doc
  8. java map遍历删除_Java Map在遍历过程中删除元素
  9. 华为mate40和苹果12pro参数对比 哪个好?看了这篇再决定
  10. vsto 启用 禁用加载项_新的Outlook VSTO加载项:如何禁用Outlook 2007中的全部答复和转发...