百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

【驱动】SPI驱动分析(四)-关键API解析

cac55 2025-03-20 12:39 14 浏览 0 评论

## 关键API

### 设备树

#### 设备树解析

我们以Firefly 的SPI demo 分析下dts中对spi的描述:

```c

/* Firefly SPI demo */

&spi1 {

spi_demo: spi-demo@00{

status = "okay";

compatible = "firefly,rk3399-spi";

reg = <0x00>;

spi-max-frequency = <48000000>;

/* rk3399 driver support SPI_CPOL | SPI_CPHA | SPI_CS_HIGH */

//spi-cpha; /* SPI mode: CPHA=1 */

//spi-cpol; /* SPI mode: CPOL=1 */

//spi-cs-high;

};

};

&spidev0 {

status = "disabled";

};

```

- `status`:如果要启用 SPI,则设为 `okay`,如不启用,设为 `disable`。

- `spi-demo@00`:由于本例子使用 CS0,故此处设为 `00`,如果使用 CS1,则设为 `01`。

- `compatible`:这里的属性必须与驱动中的结构体:`of_device_id` 中的成员 `compatible` 保持一致。

- `reg`:此处与 `spi-demo@00` 保持一致,本例设为:0x00。

- `spi-max-frequency`:此处设置 spi 使用的最高频率。Firefly-RK3399 最高支持 48000000。

- `spi-cpha,spi-cpol`:SPI 的工作模式在此设置,本例所用的模块 SPI 工作模式为 `SPI_MODE_0` 或者 `SPI_MODE_3`,这里我们选用 `SPI_MODE_0`,如果使用 `SPI_MODE_3`,spi_demo 中打开 `spi-cpha` 和 `spi-cpol` 即可。

- `spidev0`: 由于 `spi_demo` 与 `spidev0` 使用一样的硬件资源,需要把 `spidev0` 关掉才能打开 `spi_demo`

#### 设备树API解析

##### of_spi_register_master

`of_spi_register_master`主要目的是从设备树中获取SPI主控制器的GPIO引脚编号信息,并将其存储在主控制器的`cs_gpios`数组中。通过读取设备树中的属性,该函数确定了主控制器具有多少个芯片选择线,并为其分配了足够的内存来存储GPIO引脚编号。源码位于`kernel\drivers\spi\spi.c`

```c

static int of_spi_register_master(struct spi_master *master)

{

int nb, i, *cs;

struct device_node *np = master->dev.of_node;

if (!np)

return 0;

// 获取设备节点中名为"cs-gpios"的GPIO数量

nb = of_gpio_named_count(np, "cs-gpios");

// 将获取到的GPIO数量与主控制器的num_chipselect取较大值,并赋值给主控制器的num_chipselect

master->num_chipselect = max_t(int, nb, master->num_chipselect);

/* Return error only for an incorrectly formed cs-gpios property */

// 如果"cs-gpios"属性未定义或定义错误,则返回0

if (nb == 0 || nb == -ENOENT)

return 0;

// 如果获取GPIO数量出错,则返回错误代码

else if (nb < 0)

return nb;

// 分配内存以保存GPIO的引脚编号

cs = devm_kzalloc(&master->dev,

sizeof(int) * master->num_chipselect,

GFP_KERNEL);

master->cs_gpios = cs;

if (!master->cs_gpios)

return -ENOMEM;

// 初始化所有引脚编号为-ENOENT

for (i = 0; i < master->num_chipselect; i++)

cs[i] = -ENOENT;

// 从设备节点中获取"cs-gpios"属性对应的每个GPIO引脚编号,并存储到cs数组中

for (i = 0; i < nb; i++)

cs[i] = of_get_named_gpio(np, "cs-gpios", i);

// 返回成功状态码

return 0;

}

```

1. 函数首先获取设备节点中名为"cs-gpios"的GPIO数量,并将其与主控制器的`num_chipselect`(芯片选择线数量)进行比较,取较大值。

2. 然后,函数会为主控制器分配足够的内存来存储GPIO的引脚编号,并将分配的内存地址赋值给`cs_gpios`指针。

3. 接下来,函数会将分配的内存初始化为`-ENOENT`,然后从设备节点中获取`cs-gpios`属性对应的每个GPIO引脚编号,并将其存储到分配的内存中。

4. 最后,函数返回状态码,表示注册过程的成功与否。

##### of_register_spi_devices

`of_register_spi_devices`作用是将子节点设备注册到SPI总线上。源码位于`kernel\drivers\spi\spi.c`

```c

/**

* of_register_spi_devices() - Register child devices onto the SPI bus

* @master: Pointer to spi_master device

*

* Registers an spi_device for each child node of master node which has a 'reg'

* property.

*/

static void of_register_spi_devices(struct spi_master *master)

{

struct spi_device *spi;

struct device_node *nc;

if (!master->dev.of_node)

return;

for_each_available_child_of_node(master->dev.of_node, nc) {

spi = of_register_spi_device(master, nc);

if (IS_ERR(spi))

dev_warn(&master->dev, "Failed to create SPI device for %s\n",

nc->full_name);

}

}

```

1. 首先检查主控制器的设备节点是否存在,如果不存在(为空),则直接返回,不执行任何操作。

2. 通过`
for_each_available_child_of_node`宏遍历主控制器设备节点的每个可用子节点,其中`nc`是当前子节点的指针。

3. 在每次迭代中,函数调用`of_register_spi_device`来为当前子节点创建一个`spi_device`对象,并将其赋值给`spi`。

4. 如果`of_register_spi_device`函数返回的`spi`是一个错误指针(通过`IS_ERR`宏检查),则打印警告信息,表示未能为该子节点创建SPI设备。

##### of_find_spi_master_by_node

`
of_find_spi_master_by_node`作用是根据设备节点查找对应的SPI主控制器。源码位于`drivers\spi\spi.c`

```c

/* the spi masters are not using spi_bus, so we find it with another way */

static struct spi_master *of_find_spi_master_by_node(struct device_node *node)

{

struct device *dev;

dev = class_find_device(&spi_master_class, NULL, node,

__spi_of_master_match);

if (!dev)

return NULL;

/* reference got in class_find_device */

return container_of(dev, struct spi_master, dev);

}

```

1. 首先声明一个`struct device`类型的指针`dev`。

2. 通过调用`class_find_device`函数来从`spi_master_class`类中查找与给定设备节点`node`匹配的设备。其中,`&spi_master_class`是指向`spi_master_class`的指针,`NULL`表示在整个类中查找,`node`表示要匹配的设备节点,`__spi_of_master_match`是一个匹配函数。

3. 如果未找到匹配的设备,即`dev`为空,则返回`NULL`表示未找到对应的SPI主控制器。

4. 如果找到匹配的设备,说明找到了对应的SPI主控制器,此时通过`container_of`宏计算出`spi_master`结构体的指针。它利用了设备结构体中的成员偏移来计算包含该成员的结构体的指针。

5. 最后,返回计算得到的`spi_master`指针。

#### of_spi_notify

`of_spi_notify`作为SPI设备树变更的通知回调函数。源码位于`drivers\spi\spi.c`

```c

static int of_spi_notify(struct notifier_block *nb, unsigned long action,

void *arg)

{

struct of_reconfig_data *rd = arg;

struct spi_master *master;

struct spi_device *spi;

// 通过调用
of_reconfig_get_state_change函数获取设备树变更的状态

switch (of_reconfig_get_state_change(action, arg)) {

case OF_RECONFIG_CHANGE_ADD:

// 根据父节点查找对应的SPI主控制器

master = of_find_spi_master_by_node(rd->dn->parent);

if (master == NULL)

return NOTIFY_OK; /* not for us */

// 为设备注册SPI设备

spi = of_register_spi_device(master, rd->dn);

put_device(&master->dev);

if (IS_ERR(spi)) {

pr_err("%s: failed to create for '%s'\n",

__func__, rd->dn->full_name);

return notifier_from_errno(PTR_ERR(spi));

}

break;

case OF_RECONFIG_CHANGE_REMOVE:

/* find our device by node */

// 根据节点查找对应的SPI设备

spi = of_find_spi_device_by_node(rd->dn);

if (spi == NULL)

return NOTIFY_OK; /* no? not meant for us */

/* unregister takes one ref away */

// 注销SPI设备

spi_unregister_device(spi);

/* and put the reference of the find */

// 释放对设备的引用

put_device(&spi->dev);

break;

}

return NOTIFY_OK;

}

```

1. 该函数接受一个通知器块(`notifier_block`)指针`nb`,一个表示动作的无符号长整型`action`,以及一个指向设备树重配置数据的指针`arg`。

2. 将`arg`转换为`struct of_reconfig_data`类型的指针`rd`,用于访问设备树重配置数据。

3. 通过调用`
of_reconfig_get_state_change`函数,根据传入的`action`和`arg`参数获取设备树变更的状态。

4. 根据状态进行不同的操作:

5. 如果状态为`OF_RECONFIG_CHANGE_ADD`,表示设备被添加:

- 通过设备节点的父节点找到对应的SPI主控制器,将其赋值给`master`。

- 如果未找到对应的主控制器,返回`NOTIFY_OK`表示该设备不属于我们处理的范围。

- 调用`of_register_spi_device`函数为设备注册SPI设备,并将返回的`spi_device`指针赋值给`spi`。

- 释放对主控制器设备的引用计数,即调用`put_device`函数。

- 如果注册失败,打印错误信息并返回相应的错误码。

6. 如果状态为`OF_RECONFIG_CHANGE_REMOVE`,表示设备被移除:

- 根据设备节点查找对应的SPI设备,将其赋值给`spi`。

- 如果未找到对应的SPI设备,返回`NOTIFY_OK`表示该设备不属于我们处理的范围。

- 调用`spi_unregister_device`函数注销SPI设备。

- 释放对设备的引用计数,即调用`put_device`函数。

7. 最后,函数返回`NOTIFY_OK`表示通知处理成功。

### SPI子系统初始化

#### spi_init

`spi_init`函数的作用是初始化SPI子系统。它分配一个内核内存缓冲区,注册SPI总线类型和SPI主控制器类,并在需要时注册设备树重配置的通知回调。函数定义在`drivers/spi/spi.c`文件中:

```c

static int __init spi_init(void)

{

int status;

buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);

if (!buf) {

status = -ENOMEM;

goto err0;

}

status = bus_register(&spi_bus_type);

if (status < 0)

goto err1;

status = class_register(&spi_master_class);

if (status < 0)

goto err2;

if (IS_ENABLED(CONFIG_OF_DYNAMIC))

WARN_ON(of_reconfig_notifier_register(&spi_of_notifier));

return 0;

err2:

bus_unregister(&spi_bus_type);

err1:

kfree(buf);

buf = NULL;

err0:

return status;

}

```

1. 该函数首先声明一个整数类型的变量`status`,用于存储函数执行过程中的状态。

2. 接下来,通过调用`kmalloc`函数为SPI子系统分配一个大小为`SPI_BUFSIZ`的内核内存缓冲区,并将返回的指针赋值给`buf`。

3. 如果缓冲区分配失败(`buf`为空指针),则将`status`设置为`-ENOMEM`表示内存分配失败,并跳转到`err0`标签处进行错误处理。

4. 接下来,通过调用`bus_register`函数注册SPI总线类型,将其添加到系统总线列表中。如果注册失败,将`status`设置为返回的错误码,并跳转到`err1`标签处进行错误处理。

5. 然后,通过调用`class_register`函数注册SPI主控制器类,将其添加到系统设备类列表中。如果注册失败,将`status`设置为返回的错误码,并跳转到`err2`标签处进行错误处理。

6. 如果系统启用了动态设备树功能(`CONFIG_OF_DYNAMIC`宏定义存在),则调用`
of_reconfig_notifier_register`函数注册设备树重配置的通知回调函数`spi_of_notifier`。

7. 最后,如果所有的初始化操作都成功执行,函数返回0表示初始化成功。

8. 如果在初始化过程中发生错误,将依次进行错误处理:注销SPI总线类型、释放缓冲区内存,然后返回存储的错误码`status`。

### SPI控制器注册

#### spi_register_master

`spi_register_master`函数用于将SPI主控制器注册到SPI总线上,以便与SPI设备进行通信。函数定义在`drivers/spi/spi.c`文件中:

SPI主控制器通过某种非SPI总线(如平台总线)连接到其驱动程序。在驱动程序的`probe()`函数的最后阶段,会调用`spi_register_master`函数来完成与SPI总线的连接。

SPI控制器使用特定于板级(通常是特定于SoC)的总线编号,并将这些编号与芯片选择编号相结合进行板级特定的设备寻址。由于SPI不直接支持动态设备识别,因此需要使用配置表来告知哪个芯片位于哪个地址上。

```c

/**

* spi_register_master - register SPI master controller

* @master: initialized master, originally from spi_alloc_master()

* Context: can sleep

*

* SPI master controllers connect to their drivers using some non-SPI bus,

* such as the platform bus. The final stage of probe() in that code

* includes calling spi_register_master() to hook up to this SPI bus glue.

*

* SPI controllers use board specific (often SOC specific) bus numbers,

* and board-specific addressing for SPI devices combines those numbers

* with chip select numbers. Since SPI does not directly support dynamic

* device identification, boards need configuration tables telling which

* chip is at which address.

*

* This must be called from context that can sleep. It returns zero on

* success, else a negative error code (dropping the master's refcount).

* After a successful return, the caller is responsible for calling

* spi_unregister_master().

*

* Return: zero on success, else a negative error code.

*/

int spi_register_master(struct spi_master *master)

{

static atomic_t dyn_bus_id = ATOMIC_INIT((1<<15) - 1);

struct device *dev = master->dev.parent;

struct boardinfo *bi;

int status = -ENODEV;

int dynamic = 0;

if (!dev)

return -ENODEV;

status = of_spi_register_master(master);

if (status)

return status;

/* even if it's just one always-selected device, there must

* be at least one chipselect

*/

if (master->num_chipselect == 0)

return -EINVAL;

if ((master->bus_num < 0 master->dev.of_node)

master->bus_num = of_alias_get_id(master->dev.of_node, "spi");

/* convention: dynamically assigned bus IDs count down from the max */

if (master->bus_num < 0) {

/* FIXME switch to an IDR based scheme, something like

* I2C now uses, so we can't run out of "dynamic" IDs

*/

master->bus_num = atomic_dec_return(&dyn_bus_id);

dynamic = 1;

}

INIT_LIST_HEAD(&master->queue);

spin_lock_init(&master->queue_lock);

spin_lock_init(&master->bus_lock_spinlock);

mutex_init(&master->bus_lock_mutex);

master->bus_lock_flag = 0;

init_completion(&master->xfer_completion);

if (!master->max_dma_len)

master->max_dma_len = INT_MAX;

/* register the device, then userspace will see it.

* registration fails if the bus ID is in use.

*/

dev_set_name(&master->dev, "spi%u", master->bus_num);

status = device_add(&master->dev);

if (status < 0)

goto done;

dev_dbg(dev, "registered master %s%s\n", dev_name(&master->dev),

dynamic ? " (dynamic)" : "");

/* If we're using a queued driver, start the queue */

if (master->transfer)

dev_info(dev, "master is unqueued, this is deprecated\n");

else {

status = spi_master_initialize_queue(master);

if (status) {

device_del(&master->dev);

goto done;

}

}

/* add statistics */

spin_lock_init(&master->statistics.lock);

mutex_lock(&board_lock);

list_add_tail(&master->list, &spi_master_list);

list_for_each_entry(bi, &board_list, list)

spi_match_master_to_boardinfo(master, &bi->board_info);

mutex_unlock(&board_lock);

/* Register devices from the device tree and ACPI */

of_register_spi_devices(master);

acpi_register_spi_devices(master);

done:

return status;

}

```

1. 首先,通过调用`of_spi_register_master`函数,尝试根据设备树信息注册SPI主控制器。

2. 检查主控制器的`num_chipselect`属性,确保至少有一个芯片选择(chip select)。

3. 如果主控制器的`bus_num`属性小于0且具有有效的设备树节点,则尝试从设备树中获取总线编号。

4. 如果总线编号仍然小于0,则使用动态分配的总线编号。这里使用了一个静态的原子变量`dyn_bus_id`作为计数器,从最大值开始递减分配动态总线编号。

5. 初始化主控制器的一些字段和锁。使用`dev_set_name`设置主控制器设备的名称。

6. 通过调用`device_add`注册主控制器设备,并将其添加到总线上,以便用户空间可以看到它。

7. 如果主控制器使用队列驱动程序,则启动队列。如果使用的是非队列驱动程序,则显示警告信息。

8. 添加统计信息并将主控制器添加到全局主控制器列表中。

9. 从设备树中注册SPI设备。

10. 返回操作的结果代码。

#### spi_alloc_master

`spi_alloc_master` 用于分配SPI主控制器(SPI master controller)的内存空间。函数定义在`drivers/spi/spi.c`文件中:

```c

/**

* spi_alloc_master - allocate SPI master controller

* @dev: the controller, possibly using the platform_bus

* @size: how much zeroed driver-private data to allocate; the pointer to this

* memory is in the driver_data field of the returned device,

* accessible with spi_master_get_devdata().

* Context: can sleep

*

* This call is used only by SPI master controller drivers, which are the

* only ones directly touching chip registers. It's how they allocate

* an spi_master structure, prior to calling spi_register_master().

*

* This must be called from context that can sleep.

*

* The caller is responsible for assigning the bus number and initializing

* the master's methods before calling spi_register_master(); and (after errors

* adding the device) calling spi_master_put() to prevent a memory leak.

*

* Return: the SPI master structure on success, else NULL.

*/

struct spi_master *spi_alloc_master(struct device *dev, unsigned size)

{

struct spi_master *master;

if (!dev)

return NULL;

master = kzalloc(size + sizeof(*master), GFP_KERNEL);

if (!master)

return NULL;

device_initialize(&master->dev);

master->bus_num = -1;

master->num_chipselect = 1;

master->dev.class = &spi_master_class;

master->dev.parent = dev;

spi_master_set_devdata(master, &master[1]);

return master;

}

```

1. 首先,检查传入的设备指针`dev`是否为空,如果为空则返回NULL,表示分配失败。

2. 接下来,通过调用`kzalloc`函数为SPI主控制器分配内存空间,大小为`size + sizeof(*master)`。其中`size`是驱动程序私有数据的大小,`sizeof(*master)`是`spi_master`结构体本身的大小。这样做的原因是为了确保分配的内存空间足够容纳驱动程序私有数据以及`spi_master`结构体本身。通过将两者的大小相加,可以得到分配所需的总内存大小。

3. 如果内存分配失败,即`master`为空指针,表示分配失败,返回NULL。

4. 然后,通过调用`device_initialize`函数初始化主控制器的设备结构体`dev`,确保主控制器的设备结构体被正确初始化,设置默认值和状态,并分配必要的资源。

5. 接着,设置主控制器的属性:`bus_num`设置为-1表示未指定总线编号,`num_chipselect`设置为1表示主控制器支持的片选信号数量为1,`dev.class`设置为指向`spi_master_class`的指针表示主控制器的设备类。

6. 最后,设置主控制器的私有数据指针,即通过调用`spi_master_set_devdata`函数将指针设置为分配的内存空间的结尾处,即`&master[1]`。

由于`master`是指向`spi_master`结构体的指针,因此`&master[1]`表示`master`指针所指向的内存之后的一个位置。这种设置的目的是将私有数据指针与分配的内存空间相关联。通过将私有数据指针设置为`&master[1]`,可以确保私有数据位于分配的内存空间中,以便在需要时可以通过访问该指针来访问和操作驱动程序特定的数据。

7. 函数返回分配的SPI主控制器结构体的指针`master`。

### SPI设备和驱动匹配

#### spi_match_device

`spi_match_device` 函数是用于在SPI总线上匹配设备和驱动程序的。函数定义在`drivers/spi/spi.c`文件中:

```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. 尝试使用设备树风格的匹配。调用`of_driver_match_device`函数,检查设备与驱动程序是否匹配。如果匹配成功,则返回1。

2. 如果设备与驱动程序未通过设备树匹配,尝试使用ACPI匹配。调用`acpi_driver_match_device`函数,检查设备与驱动程序是否匹配。如果匹配成功,则返回1。

3. 如果SPI驱动程序具有`id_table`字段,则使用`spi_match_id`函数尝试通过ID表进行匹配。如果匹配成功,则返回1。

4. 如果以上匹配方法都失败,则通过比较设备的`modalias`和驱动程序的`name`字段来进行匹配。如果两者相等,则返回1;否则返回0。

#### 驱动的四种匹配方式

##### 设备树匹配

通过在设备树中为SPI设备和驱动程序分配节点,并在节点中指定属性和配置信息,可以实现设备与驱动程序的匹配。在设备树匹配过程中,驱动程序可以根据设备节点的属性信息来判断与其匹配的设备。

举例`spi-imx.c`

```c

static const struct of_device_id spi_imx_dt_ids[] = {

{ .compatible = "fsl,imx6q-spi", .data = (void *)&imx6q_spi_pdata },

{ },

};

MODULE_DEVICE_TABLE(of, spi_imx_dt_ids);

static struct spi_driver spi_imx_driver = {

.driver = {

.name = "spi-imx",

.of_match_table = spi_imx_dt_ids,

},

.probe = spi_imx_probe,

.remove = spi_imx_remove,

};

```

##### ACPI匹配

ACPI(Advanced Configuration and Power Interface)是一种用于配置和管理电源和设备的标准。对于使用ACPI进行配置的SPI设备和驱动程序,可以通过ACPI匹配来确定它们之间的关联关系。ACPI匹配通过比较设备的ACPI设备句柄与驱动程序的ACPI设备句柄来判断它们是否匹配。

举例`spi-bcm2835.c`

```c

static const struct acpi_device_id spi_bcm2835_acpi_match[] = {

{ "BCM2835SPI", 0 },

{ },

};

MODULE_DEVICE_TABLE(acpi, spi_bcm2835_acpi_match);

static struct spi_driver spi_bcm2835_driver = {

.driver = {

.name = "spi-bcm2835",

.acpi_match_table = ACPI_PTR(spi_bcm2835_acpi_match),

},

.probe = spi_bcm2835_probe,

.remove = spi_bcm2835_remove,

};

```

##### ID表匹配

驱动程序可以定义一个ID表(`struct spi_device_id`数组),其中包含了一组用于识别SPI设备的ID信息,如厂商ID、设备ID等。当驱动程序加载时,会尝试将SPI设备的ID与ID表中的项进行匹配。如果SPI设备的ID与ID表中的某一项匹配成功,则可以确定设备与驱动程序之间的关联关系。

举例`spi-s3c64xx.c`

```c

static const 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,

}, {

.name = "s5pv210-spi",

.driver_data = (kernel_ulong_t)&s5pv210_spi_port_config,

}, {

.name = "exynos4210-spi",

.driver_data = (kernel_ulong_t)&exynos4_spi_port_config,

},

{ },

};

MODULE_DEVICE_TABLE(spi, s3c64xx_spi_driver_ids);

static struct platform_driver s3c64xx_spi_driver = {

.driver = {

.name = "s3c64xx-spi",

.pm = &s3c64xx_spi_pm,

.of_match_table = of_match_ptr(s3c64xx_spi_dt_match),

},

.probe = s3c64xx_spi_probe,

.remove = s3c64xx_spi_remove,

.id_table = s3c64xx_spi_driver_ids,

};

```

##### 名称匹配

SPI设备和驱动程序都有一个名称字段,分别是设备的`modalias`和驱动程序的`name`。通过比较设备的`modalias`和驱动程序的`name`,可以判断它们是否匹配。如果两者相等,则可以确定设备与驱动程序之间的关联关系。

举例`spi-omap2-mcspi.c`

```c

static const struct spi_device_id spi_imx_id[] = {

{ "spi_imx", 0 },

{ },

};

MODULE_DEVICE_TABLE(spi, spi_imx_id);

static struct spi_driver spi_imx_driver = {

.driver = {

.name = "spi_imx",

.owner = THIS_MODULE,

},

.id_table = spi_imx_id,

};

```

### SPI从设备注册

#### spi_register_board_info

`spi_register_board_info`用于注册给定板级信息中的SPI设备。这些设备节点在相关的SPI控制器(bus_num)定义之后才创建。这样,即使重新加载控制器驱动程序,Linux也不会忘记这些硬件设备。函数定义在`drivers/spi/spi.c`文件中:

```c

/**

* spi_register_board_info - register SPI devices for a given board

* @info: array of chip descriptors

* @n: how many descriptors are provided

* Context: can sleep

*

* Board-specific early init code calls this (probably during arch_initcall)

* with segments of the SPI device table. Any device nodes are created later,

* after the relevant parent SPI controller (bus_num) is defined. We keep

* this table of devices forever, so that reloading a controller driver will

* not make Linux forget about these hard-wired devices.

*

* Other code can also call this, e.g. a particular add-on board might provide

* SPI devices through its expansion connector, so code initializing that board

* would naturally declare its SPI devices.

*

* The board info passed can safely be __initdata ... but be careful of

* any embedded pointers (platform_data, etc), they're copied as-is.

*

* Return: zero on success, else a negative error code.

*/

int spi_register_board_info(struct spi_board_info const *info, unsigned n)

{

struct boardinfo *bi;

int i;

if (!n)

return -EINVAL;

bi = kzalloc(n * sizeof(*bi), GFP_KERNEL);

if (!bi)

return -ENOMEM;

for (i = 0; i < n; i++, bi++, info++) {

struct spi_master *master;

memcpy(&bi->board_info, info, sizeof(*info));

mutex_lock(&board_lock);

list_add_tail(&bi->list, &board_list);

list_for_each_entry(master, &spi_master_list, list)

spi_match_master_to_boardinfo(master, &bi->board_info);

mutex_unlock(&board_lock);

}

return 0;

}

```

1. `spi_register_board_info`接受一个指向SPI设备描述符数组的指针`info`,以及描述符的数量`n`。这些描述符包含了每个SPI设备的配置信息,如设备节点、SPI控制器的总线编号、最大速度、模块别名等。

2. 函数内部会为每个设备描述符分配一个`boardinfo`结构体,并将其添加到全局的`board_list`链表中。

3. 然后,遍历已注册的SPI控制器(通过`spi_master_list`链表),并将匹配到的控制器与设备描述符关联起来,以便后续的设备创建与配置。

4. 最后,函数返回0表示成功,否则返回负数错误代码表示失败。

#### spi_alloc_device

`spi_alloc_device`分配一个新的SPI设备。函数定义在`drivers/spi/spi.c`文件中:

```c

/**

* spi_alloc_device - Allocate a new SPI device

* @master: Controller to which device is connected

* Context: can sleep

*

* Allows a driver to allocate and initialize a spi_device without

* registering it immediately. This allows a driver to directly

* fill the spi_device with device parameters before calling

* spi_add_device() on it.

*

* Caller is responsible to call spi_add_device() on the returned

* spi_device structure to add it to the SPI master. If the caller

* needs to discard the spi_device without adding it, then it should

* call spi_dev_put() on it.

*

* Return: a pointer to the new device, or NULL.

*/

struct spi_device *spi_alloc_device(struct spi_master *master)

{

struct spi_device *spi;

if (!spi_master_get(master))

return NULL;

spi = kzalloc(sizeof(*spi), GFP_KERNEL);

if (!spi) {

spi_master_put(master);

return NULL;

}

spi->master = master;

spi->dev.parent = &master->dev;

spi->dev.bus = &spi_bus_type;

spi->dev.release = spidev_release;

spi->cs_gpio = -ENOENT;

spin_lock_init(&spi->statistics.lock);

device_initialize(&spi->dev);

return spi;

}

```

1. 首先,函数接收一个`spi_master`指针作为参数,表示要连接的SPI控制器。

2. 函数通过调用`spi_master_get`增加SPI控制器的引用计数,确保控制器的有效性。如果引用计数增加失败(返回0),则返回NULL。

3. 函数通过调用`kzalloc`为新的SPI设备分配内存空间,并返回一个指向该设备的指针。如果内存分配失败,则释放之前增加的SPI控制器的引用计数,并返回NULL。

4. 函数对新分配的`spi_device`结构体进行初始化。它设置了设备的主控制器(`master`)、父设备(`parent`)为SPI控制器的设备结构体,总线(`bus`)为SPI总线类型,释放函数(`release`)为`spidev_release`,表示在设备释放时调用该函数进行清理,片选GPIO(`cs_gpio`)初始化为`-ENOENT`表示未设置。

5. 函数使用`spin_lock_init`初始化统计信息(`statistics`)的自旋锁。

6. 最后,函数调用`device_initialize`对设备进行初始化,并返回指向新设备的指针。

#### spi_add_device

`spi_add_device` 将使用`spi_alloc_device`函数分配的`spi_device`结构体添加到SPI总线上。 函数定义在`drivers/spi/spi.c`文件中:

```c

/**

* spi_add_device - Add spi_device allocated with spi_alloc_device

* @spi: spi_device to register

*

* Companion function to spi_alloc_device. Devices allocated with

* spi_alloc_device can be added onto the spi bus with this function.

*

* Return: 0 on success; negative errno on failure

*/

int spi_add_device(struct spi_device *spi)

{

static DEFINE_MUTEX(spi_add_lock);

struct spi_master *master = spi->master;

struct device *dev = master->dev.parent;

int status;

/* Chipselects are numbered 0..max; validate. */

if (spi->chip_select >= master->num_chipselect) {

dev_err(dev, "cs%d >= max %d\n",

spi->chip_select,

master->num_chipselect);

return -EINVAL;

}

/* Set the bus ID string */

spi_dev_set_name(spi);

/* We need to make sure there's no other device with this

* chipselect **BEFORE** we call setup(), else we'll trash

* its configuration. Lock against concurrent add() calls.

*/

mutex_lock(&spi_add_lock);

status = bus_for_each_dev(&spi_bus_type, NULL, spi, spi_dev_check);

if (status) {

dev_err(dev, "chipselect %d already in use\n",

spi->chip_select);

goto done;

}

if (master->cs_gpios)

spi->cs_gpio = master->cs_gpios[spi->chip_select];

/* Drivers may modify this initial i/o setup, but will

* normally rely on the device being setup. Devices

* using SPI_CS_HIGH can't coexist well otherwise...

*/

status = spi_setup(spi);

if (status < 0) {

dev_err(dev, "can't setup %s, status %d\n",

dev_name(&spi->dev), status);

goto done;

}

/* Device may be bound to an active driver when this returns */

status = device_add(&spi->dev);

if (status < 0)

dev_err(dev, "can't add %s, status %d\n",

dev_name(&spi->dev), status);

else

dev_dbg(dev, "registered child %s\n", dev_name(&spi->dev));

done:

mutex_unlock(&spi_add_lock);

return status;

}

```

1. 首先,函数获取到要添加的`spi_device`结构体中的`spi_master`指针以及其父设备的指针。

2. 然后,函数对`spi_device`的`chip_select`进行验证,确保其值在有效范围内。

3. 接下来,函数设置`spi_device`的总线ID字符串,用于标识该设备在SPI总线上的位置。

4. 为了防止与已存在的设备冲突,函数使用互斥锁对添加操作进行了保护。

5. 函数通过调用`bus_for_each_dev`遍历SPI总线上的所有设备,检查是否有其他设备使用了相同的`chip_select`。如果存在冲突,函数会返回错误。

6. 如果SPI主控制器设置了`cs_gpios`(片选GPIO引脚),函数会将相应的GPIO引脚分配给`spi_device`。

7. 接下来,函数调用`spi_setup`对`spi_device`进行初始化设置,以确保其正常工作。

8. 最后,函数使用`device_add`将`spi_device`添加到设备层次结构中,使其在系统中可见。

#### spi_new_device

`spi_new_device`作用是在特定的SPI控制器上实例化一个新的SPI设备。函数定义在`drivers/spi/spi.c`文件中:

```c

/**

* spi_new_device - instantiate one new SPI device

* @master: Controller to which device is connected

* @chip: Describes the SPI device

* Context: can sleep

*

* On typical mainboards, this is purely internal; and it's not needed

* after board init creates the hard-wired devices. Some development

* platforms may not be able to use spi_register_board_info though, and

* this is exported so that for example a USB or parport based adapter

* driver could add devices (which it would learn about out-of-band).

*

* Return: the new device, or NULL.

*/

struct spi_device *spi_new_device(struct spi_master *master,

struct spi_board_info *chip)

{

struct spi_device *proxy;

int status;

/* NOTE: caller did any chip->bus_num checks necessary.

*

* Also, unless we change the return value convention to use

* error-or-pointer (not NULL-or-pointer), troubleshootability

* suggests syslogged diagnostics are best here (ugh).

*/

proxy = spi_alloc_device(master);

if (!proxy)

return NULL;

WARN_ON(strlen(chip->modalias) >= sizeof(proxy->modalias));

proxy->chip_select = chip->chip_select;

proxy->max_speed_hz = chip->max_speed_hz;

proxy->mode = chip->mode;

proxy->irq = chip->irq;

strlcpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias));

proxy->dev.platform_data = (void *) chip->platform_data;

proxy->controller_data = chip->controller_data;

proxy->controller_state = NULL;

status = spi_add_device(proxy);

if (status < 0) {

spi_dev_put(proxy);

return NULL;

}

return proxy;

}

```

1. 首先,函数接收一个`spi_master`指针和一个`spi_board_info`结构体指针作为参数,分别表示要连接的SPI控制器和描述SPI设备的信息。

2. 函数通过调用`spi_alloc_device`为新的SPI设备分配内存空间,并返回一个指向该设备的指针。如果内存分配失败,则返回NULL。

3. 函数将SPI设备的属性从`spi_board_info`结构体中复制到新分配的`spi_device`结构体中。这些属性包括设备的片选引脚号(`chip_select`)、最大传输速率(`max_speed_hz`)、工作模式(`mode`)、中断号(`irq`)、模块别名(`modalias`)等。

4. 如果`spi_add_device`函数成功将SPI设备添加到SPI总线上,则函数返回指向新设备的指针。否则,函数会释放先前分配的内存,并返回NULL。

#### spi_setup

`spi_setup`实现了SPI设备的设置和配置。函数定义在`drivers/spi/spi.c`文件中:

```c

/**

* spi_setup - setup SPI mode and clock rate

* @spi: the device whose settings are being modified

* Context: can sleep, and no requests are queued to the device

*

* SPI protocol drivers may need to update the transfer mode if the

* device doesn't work with its default. They may likewise need

* to update clock rates or word sizes from initial values. This function

* changes those settings, and must be called from a context that can sleep.

* Except for SPI_CS_HIGH, which takes effect immediately, the changes take

* effect the next time the device is selected and data is transferred to

* or from it. When this function returns, the spi device is deselected.

*

* Note that this call will fail if the protocol driver specifies an option

* that the underlying controller or its driver does not support. For

* example, not all hardware supports wire transfers using nine bit words,

* LSB-first wire encoding, or active-high chipselects.

*

* Return: zero on success, else a negative error code.

*/

int spi_setup(struct spi_device *spi)

{

unsigned bad_bits, ugly_bits;

int status;

/* check mode to prevent that DUAL and QUAD set at the same time

*/

if (((spi->mode & SPI_TX_DUAL) && (spi->mode & SPI_TX_QUAD)) ||

((spi->mode & SPI_RX_DUAL) && (spi->mode & SPI_RX_QUAD))) {

dev_err(&spi->dev,

"setup: can not select dual and quad at the same time\n");

return -EINVAL;

}

/* if it is SPI_3WIRE mode, DUAL and QUAD should be forbidden

*/

if ((spi->mode & SPI_3WIRE) && (spi->mode &

(SPI_TX_DUAL | SPI_TX_QUAD | SPI_RX_DUAL | SPI_RX_QUAD)))

return -EINVAL;

/* help drivers fail *cleanly* when they need options

* that aren't supported with their current master

*/

bad_bits = spi->mode & ~spi->master->mode_bits;

ugly_bits = bad_bits &

(SPI_TX_DUAL | SPI_TX_QUAD | SPI_RX_DUAL | SPI_RX_QUAD);

if (ugly_bits) {

dev_warn(&spi->dev,

"setup: ignoring unsupported mode bits %x\n",

ugly_bits);

spi->mode &= ~ugly_bits;

bad_bits &= ~ugly_bits;

}

if (bad_bits) {

dev_err(&spi->dev, "setup: unsupported mode bits %x\n",

bad_bits);

return -EINVAL;

}

if (!spi->bits_per_word)

spi->bits_per_word = 8;

status = __spi_validate_bits_per_word(spi->master, spi->bits_per_word);

if (status)

return status;

if (!spi->max_speed_hz)

spi->max_speed_hz = spi->master->max_speed_hz;

if (spi->master->setup)

status = spi->master->setup(spi);

spi_set_cs(spi, false);

dev_dbg(&spi->dev, "setup mode %d, %s%s%s%s%u bits/w, %u Hz max --> %d\n",

(int) (spi->mode & (SPI_CPOL | SPI_CPHA)),

(spi->mode & SPI_CS_HIGH) ? "cs_high, " : "",

(spi->mode & SPI_LSB_FIRST) ? "lsb, " : "",

(spi->mode & SPI_3WIRE) ? "3wire, " : "",

(spi->mode & SPI_LOOP) ? "loopback, " : "",

spi->bits_per_word, spi->max_speed_hz,

status);

return status;

}

```

1. 函数首先检查传输模式(mode)的合法性。如果同时设置了`SPI_TX_DUAL`和`SPI_TX_QUAD`,或者同时设置了`SPI_RX_DUAL`和`SPI_RX_QUAD`,会返回错误。如果设置了`SPI_3WIRE`模式,并且同时设置了`SPI_TX_DUAL`、`SPI_TX_QUAD`、`SPI_RX_DUAL`或`SPI_RX_QUAD`,也会返回错误。

2. 函数检查传输模式是否被支持。如果设备的模式中包含控制器不支持的模式位,会给出警告并将不支持的模式位清除。如果存在不支持的模式位,会返回错误。

3. 如果设备的每字位数(`bits_per_word`)为0,则设置默认值为8位。

4. 函数调用`
__spi_validate_bits_per_word`验证每字位数是否被支持。如果不被支持,会返回错误。

5. 如果设备的最大时钟速度(max_speed_hz)为0,则设置默认值为控制器的最大时钟速度。

6. 如果控制器定义了`setup`函数,会调用该函数进行设备的设置。

7. 设置片选(chip select)为非激活状态。

8. 最后,函数打印设备的设置信息,并返回设置的结果。

### SPI驱动注册

#### spi_register_driver

`spi_register_driver` 完成SPI驱动的注册。函数定义在`drivers/spi/spi.c`文件中:

```c

/**

* __spi_register_driver - register a SPI driver

* @owner: owner module of the driver to register

* @sdrv: the driver to register

* Context: can sleep

*

* Return: zero on success, else a negative error code.

*/

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);

}

```

1. 函数接收两个参数,`owner`表示驱动的所有者模块,`sdrv`表示要注册的SPI驱动。

2. 函数设置驱动的相关属性。首先将驱动的所有者模块设置为`owner`,将驱动的总线类型设置为`spi_bus_type`,表示该驱动属于SPI总线类型。

3. 如果驱动定义了`probe`函数,将该函数指定为驱动的`probe`函数。`probe`函数在设备与驱动匹配成功后调用,用于初始化和注册设备。

4. 如果驱动定义了`remove`函数,将该函数指定为驱动的`remove`函数。`remove`函数在设备与驱动解除匹配时调用,用于清理和注销设备。

5. 如果驱动定义了`shutdown`函数,将该函数指定为驱动的`shutdown`函数。`shutdown`函数在系统关机时调用,用于执行驱动的关机处理。

6. 最后,调用`driver_register`函数注册驱动,并返回注册的结果。

### 消息队列初始化

#### spi_master_initialize_queue

`
spi_master_initialize_queue`初始化一个SPI主设备的队列,并启动队列的操作。源码位于`drivers\spi\spi.c`。

```c

static int spi_master_initialize_queue(struct spi_master *master)

{

int ret;

master->transfer = spi_queued_transfer;

if (!master->transfer_one_message)

master->transfer_one_message = spi_transfer_one_message;

/* Initialize and start queue */

ret = spi_init_queue(master);

if (ret) {

dev_err(&master->dev, "problem initializing queue\n");

goto err_init_queue;

}

master->queued = true;

ret = spi_start_queue(master);

if (ret) {

dev_err(&master->dev, "problem starting queue\n");

goto err_start_queue;

}

return 0;

err_start_queue:

spi_destroy_queue(master);

err_init_queue:

return ret;

}

```

1. 设置SPI主设备的传输函数为`spi_queued_transfer`,该函数用于执行队列中的数据传输操作。

2. 检查是否定义了`transfer_one_message`函数,如果没有定义,则将其设置为默认的`spi_transfer_one_message`函数。

3. 调用`spi_init_queue`函数初始化队列。这个函数可能会设置队列的相关参数,分配内存等。

4. 如果`spi_init_queue`返回非零值,表示初始化队列出现问题,会打印错误信息,并跳转到错误处理标签`err_init_queue`,然后返回错误码。

5. 将`master->queued`标志设置为true,表示队列已经初始化。

6. 调用`spi_start_queue`函数启动队列。这个函数可能会启动硬件传输和处理队列中的数据。

7. 如果`spi_start_queue`返回非零值,表示启动队列出现问题,会打印错误信息,并跳转到错误处理标签err_start_queue。

8. 最后,如果所有步骤都成功执行,函数返回0表示初始化和启动队列成功。

#### spi_init_queue

`spi_init_queue` 函数初始化了`spi_master`结构的 `kthread_worker` 和 `kthread_work` 结构(内核 `kthread_worker`和`kthread_work`机制)这个机制相当于是内核线程,并指定了work的执行函数为 `spi_pump_messages` ,最后如果`spi_master` 定了`spi_controller->rt`的话,意思是开启 realtime 发送,那么将执行线程变为`SCHED_FIFO`的实时调度类型,也就是更高的优先级(实时优先级)。源码位于`drivers\spi\spi.c`。

```c

static int spi_init_queue(struct spi_master *master)

{

struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 };

master->running = false;

master->busy = false;

init_kthread_worker(&master->kworker);

master->kworker_task = kthread_run(kthread_worker_fn,

&master->kworker, "%s",

dev_name(&master->dev));

if (IS_ERR(master->kworker_task)) {

dev_err(&master->dev, "failed to create message pump task\n");

return PTR_ERR(master->kworker_task);

}

init_kthread_work(&master->pump_messages, spi_pump_messages);

/*

* Master config will indicate if this controller should run the

* message pump with high (realtime) priority to reduce the transfer

* latency on the bus by minimising the delay between a transfer

* request and the scheduling of the message pump thread. Without this

* setting the message pump thread will remain at default priority.

*/

if (master->rt) {

dev_info(&master->dev,

"will run message pump with realtime priority\n");

sched_setscheduler(master->kworker_task, SCHED_FIFO, Pm);

}

return 0;

}

```

1. 创建一个`sched_param`结构体,并将其初始化为`MAX_RT_PRIO-1`。这个结构体用于设置线程的调度参数,包括实时优先级。

2. 将`master->running`和`master->busy`标志设置为false,表示队列当前处于未运行和未忙碌状态。

3. 使用`init_kthread_worker`函数初始化`master->kworker`结构体,这个结构体用于管理队列中的工作项。

4. 调用`kthread_run`函数创建一个内核线程,并将其绑定到`kthread_worker_fn`函数上。该线程用于执行队列中的工作项。

5. 如果`kthread_run`返回一个错误指针(`PTR_ERR`),表示创建线程失败,会打印错误信息并返回错误码。

6. 使用`init_kthread_work`函数初始化`master->pump_messages`结构体,并将其与`spi_pump_messages`函数关联起来。这个函数表示在消息泵线程中要执行的工作。

7. 如果`master->rt`为真,则将`kworker_task`线程设置为实时优先级(`SCHED_FIFO`),以减小传输延迟。函数使用`sched_setscheduler`函数设置线程的调度器和参数。

8. 如果设置了实时优先级,会打印一条信息说明消息泵将以实时优先级运行。

9. 最后,函数返回0表示初始化队列成功。

#### spi_start_queue

```c

static int spi_start_queue(struct spi_master *master)

{

unsigned long flags;

spin_lock_irqsave(&master->queue_lock, flags);

if (master->running || master->busy) {

spin_unlock_irqrestore(&master->queue_lock, flags);

return -EBUSY;

}

master->running = true;

master->cur_msg = NULL;

spin_unlock_irqrestore(&master->queue_lock, flags);

queue_kthread_work(&master->kworker, &master->pump_messages);

return 0;

}

```

1. 使用`spin_lock_irqsave`函数获取队列自旋锁,以确保在操作期间的原子性。自旋锁用于保护多线程访问的临界区域,以防止竞态条件。

2. 检查队列是否已经在运行或忙碌。如果是,则表示队列已经在操作中,无法启动新的操作。函数会释放自旋锁并返回-EBUSY错误码。

3. 将running标志设置为true,表示队列正在运行。同时,将cur_msg设置为NULL,表示当前没有正在处理的消息。

4. 使用`spin_unlock_irqrestore`函数释放自旋锁,允许其他线程继续访问队列。

5. 使用`queue_kthread_work`函数将`pump_messages`工作项加入`kworker`队列。

6. 最后,函数返回0表示启动队列操作成功。

### 数据准备

#### spi_message_init

`spi_message_init`用于初始化一个SPI消息结构体"spi_message"。源码位于 `include\linux\spi\spi.h`。

```c

static inline void spi_message_init(struct spi_message *m)

{

memset(m, 0, sizeof *m);

INIT_LIST_HEAD(&m->transfers);

}

```

1. 这段代码中使用了memset函数和`INIT_LIST_HEAD`宏。memset函数用于将指定内存区域的内容设置为特定的值,这里将`spi_message`结构体的内存设置为零。`INIT_LIST_HEAD`宏用于初始化一个链表头,将其`next`和`prev`指针都指向自身,表示链表为空。

2. `spi_message_init`通常用于在使用`spi_message`结构体之前对其进行初始化,以确保结构体的所有字段都具有正确的初始值。这样可以避免在使用`spi_message`结构体时出现未初始化的字段导致的错误。

#### spi_message_add_tail

`spi_message_add_tail`通常用于构建SPI消息,在执行SPI传输时将多个`spi_transfer`结构体添加到`spi_message`的传输列表中。这样可以按照特定的顺序执行一系列SPI传输操作。源码位于 `include\linux\spi\spi.h`。

```c

static inline void

spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)

{

list_add_tail(&t->transfer_list, &m->transfers);

}

```

1. `spi_message_add_tail`使用了`list_add_tail`函数,它是Linux内核中的一个双向链表操作函数。它用于将一个节点插入到链表的尾部,确保节点的顺序与添加的顺序相符。

#### spi_message_init_with_transfers

`
spi_message_init_with_transfers` 可以一次性初始化一个`spi_message`对象并将一组`spi_transfer`结构体添加到其中。这样可以更方便地构建包含多个传输操作的SPI消息。源码位于 `include\linux\spi\spi.h`。

```c

/**

* spi_message_init_with_transfers - Initialize spi_message and append transfers

* @m: spi_message to be initialized

* @xfers: An array of spi transfers

* @num_xfers: Number of items in the xfer array

*

* This function initializes the given spi_message and adds each spi_transfer in

* the given array to the message.

*/

static inline void

spi_message_init_with_transfers(struct spi_message *m,

struct spi_transfer *xfers, unsigned int num_xfers)

{

unsigned int i;

spi_message_init(m);

for (i = 0; i < num_xfers; ++i)

spi_message_add_tail(&xfers[i], m);

}

```

1. 调用`spi_message_init`函数对给定的`spi_message`进行初始化,确保它是一个空的消息对象。

2. 使用循环遍历传入的`spi_transfer`数组,逐个将`spi_transfer`结构体添加到`spi_message`的传输列表中。

3. 在每次迭代中,使用`spi_message_add_tail`函数将当前的`spi_transfer`结构体添加到`spi_message`的传输列表的尾部。


### 数据传输

SPI数据传输的整体流程大致如下:

1. 主设备初始化`spi_master_setup()`:主设备被配置为适当的工作模式,包括时钟速率、数据位顺序和传输模式等。主设备也会初始化相关的硬件资源和寄存器。

2. 主设备选中从设备`spi_setup()`:通过将从设备的片选线(Slave Select)拉低,主设备选择要与之通信的从设备。可以有多个从设备连接到同一个主设备,并通过不同的片选线进行选择。

3. 传输开始`spi_transfer_one_message()`:主设备产生时钟信号(SCLK)并将其发送到从设备。时钟信号定义了数据传输的时序和速率。

4. 数据传输`spi_sync_transfer()`: 用于同步进行SPI数据传输,等待传输完成。

`spi_async_transfer()`: 用于异步进行SPI数据传输,可以在传输过程中执行其他任务。

在每个时钟周期中,主设备通过主设备输出线(MOSI)将一个数据位发送给从设备,同时从设备通过主设备输入线(MISO)返回一个数据位。数据的传输顺序可以是最高有效位(MSB)先传输或最低有效位(LSB)先传输,取决于设备的配置。

5. 时钟信号控制`spi_transfer_one()`、`spi_sync_transfer()`、`spi_async_transfer()`:时钟信号的边沿(上升沿或下降沿)用于控制数据的采样和传输。具体的时钟边沿使用方式取决于主设备和从设备之间的协议和时序要求。

6. 数据传输完成`spi_chipselect()`:完成所需的数据传输后,主设备可以通过将从设备的片选线拉高来结束传输。这会释放从设备,并允许它响应其他主设备的请求。

7. 传输结束`
spi_finalize_current_message()`:在所有的数据传输完成后,可以根据需要执行清理操作,关闭相关的硬件资源,或者进行进一步的处理和分析。

同步传输(Synchronous Transfer):

- 同步传输是指在进行SPI数据传输时,主设备会等待传输完成后再继续执行后续的代码。

- 主设备在发起数据传输后会一直等待传输完成,然后再返回结果给调用者。

- 在同步传输期间,主设备会阻塞(即挂起)其它任务的执行,直到数据传输完成。

- 同步传输适用于需要等待传输结果并依赖传输结果的场景。

同步传输通常用于对传输结果实时性要求较高的情况,或者需要确保传输结果可靠性的场景。由于同步传输会阻塞主设备的执行,因此在某些实时性要求高的应用中可能更合适。

异步传输(Asynchronous Transfer):

- 异步传输是指在进行SPI数据传输时,主设备可以继续执行后续的代码,而不会等待传输完成。

- 主设备发起数据传输后会立即返回给调用者,传输过程在后台执行。

- 主设备可以在数据传输进行的同时执行其他任务,不会阻塞其它操作。

- 异步传输适用于不需要立即获取传输结果,或者需要同时进行多个传输操作的场景。

异步传输通常用于对传输实时性要求相对较低的情况,或者需要同时进行多个传输操作的场景。由于异步传输不会阻塞主设备的执行,因此可以更好地提高系统的响应性和并发性。

#### 异步方式

##### spi_async

`spi_async`函数用于实现异步传输,异步传输允许在传输过程中执行其他任务,而不需要等待传输完成。传输完成后,将调用回调函数来处理传输结果。源码位于`drivers\spi\spi.c`。

```c

/**

* spi_async - asynchronous SPI transfer

* @spi: device with which data will be exchanged

* @message: describes the data transfers, including completion callback

* Context: any (irqs may be blocked, etc)

*

* This call may be used in_irq and other contexts which can't sleep,

* as well as from task contexts which can sleep.

*

* The completion callback is invoked in a context which can't sleep.

* Before that invocation, the value of message->status is undefined.

* When the callback is issued, message->status holds either zero (to

* indicate complete success) or a negative error code. After that

* callback returns, the driver which issued the transfer request may

* deallocate the associated memory; it's no longer in use by any SPI

* core or controller driver code.

*

* Note that although all messages to a spi_device are handled in

* FIFO order, messages may go to different devices in other orders.

* Some device might be higher priority, or have various "hard" access

* time requirements, for example.

*

* On detection of any fault during the transfer, processing of

* the entire message is aborted, and the device is deselected.

* Until returning from the associated message completion callback,

* no other spi_message queued to that device will be processed.

* (This rule applies equally to all the synchronous transfer calls,

* which are wrappers around this core asynchronous primitive.)

*

* Return: zero on success, else a negative error code.

*/

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

{

struct spi_master *master = spi->master;

int ret;

unsigned long flags;

ret = __spi_validate(spi, message);

if (ret != 0)

return ret;

spin_lock_irqsave(&master->bus_lock_spinlock, flags);

if (master->bus_lock_flag)

ret = -EBUSY;

else

ret = __spi_async(spi, message);

spin_unlock_irqrestore(&master->bus_lock_spinlock, flags);

return ret;

}

```

- 函数接受一个 `spi_device` 结构体指针 `spi` 和一个 `spi_message` 结构体指针 `message` 作为参数,表示要进行异步SPI传输的设备和消息。

- 首先,调用 `__spi_validate()` 函数对 `spi` 和 `message` 进行验证,确保它们有效。如果验证失败,将返回错误代码。

- 接下来,通过获取 `spi_master` 结构体指针 `master` 来获取SPI主设备的相关信息。

- 使用自旋锁 `bus_lock_spinlock` 来保护对主设备的并发访问。

- 在获取自旋锁后,检查 `master->bus_lock_flag` 标志位,如果已经被占用,则返回 `EBUSY` 错误代码,表示总线忙。

- 如果总线未被占用,则调用 `__spi_async()` 函数来执行异步的SPI传输操作。

- 在完成传输后,释放自旋锁并返回传输的结果代码。

##### __spi_async

```c

static int __spi_async(struct spi_device *spi, struct spi_message *message)

{

struct spi_master *master = spi->master;

message->spi = spi;

SPI_STATISTICS_INCREMENT_FIELD(&master->statistics, spi_async);

SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_async);

trace_spi_message_submit(message);

return master->transfer(spi, message);

}

```

- 函数接受一个 `spi_device` 结构体指针 `spi` 和一个 `spi_message` 结构体指针 `message` 作为参数,表示要进行异步SPI传输的设备和消息。

- 首先,将 `message->spi` 设置为传入的 `spi` 设备,以便在后续的传输过程中访问设备信息。

- 使用 `
SPI_STATISTICS_INCREMENT_FIELD()` 宏对主设备和从设备的统计数据进行增加,用于记录异步传输的统计信息。

- 调用 `trace_spi_message_submit()` 函数进行追踪记录,用于跟踪和分析SPI消息的提交情况。

- 最后,调用 `master->transfer()` 函数来执行实际的SPI传输操作,将 `spi` 设备和 `message` 消息传递给传输函数。

##### spi_queued_transfer

```c

/**

* spi_queued_transfer - transfer function for queued transfers

* @spi: spi device which is requesting transfer

* @msg: spi message which is to handled is queued to driver queue

*

* Return: zero on success, else a negative error code.

*/

static int spi_queued_transfer(struct spi_device *spi, struct spi_message *msg)

{

return __spi_queued_transfer(spi, msg, true);

}

```

`spi_queued_transfer` 会调用到`__spi_queued_transfer`。

##### __spi_queued_transfer

`__spi_queued_transfer`函数用于将消息添加到SPI主设备的传输队列中,并启动后台的消息处理工作。源码位于`drivers\spi\spi.c`。

```c

static int __spi_queued_transfer(struct spi_device *spi,

struct spi_message *msg,

bool need_pump)

{

struct spi_master *master = spi->master;

unsigned long flags;

spin_lock_irqsave(&master->queue_lock, flags);

if (!master->running) {

spin_unlock_irqrestore(&master->queue_lock, flags);

return -ESHUTDOWN;

}

msg->actual_length = 0;

msg->status = -EINPROGRESS;

list_add_tail(&msg->queue, &master->queue);

if (!master->busy && need_pump)

queue_kthread_work(&master->kworker, &master->pump_messages);

spin_unlock_irqrestore(&master->queue_lock, flags);

return 0;

}

```

- 函数接受一个 `spi_device` 结构体指针 `spi`,一个 `spi_message` 结构体指针 `msg`,和一个布尔值 `need_pump` 作为参数,表示要进行SPI队列传输的设备、消息和是否需要启动消息处理。

- 首先,使用自旋锁 `queue_lock` 保护对主设备队列的并发访问。

- 如果主设备的状态为非运行状态(`!master->running`),则表示设备已经关闭,无法进行传输操作。此时解锁自旋锁并返回 `-ESHUTDOWN` 错误代码。

- 设置消息的 `actual_length` 为0,表示传输的实际长度尚未确定。将消息的 `status` 设置为 `-EINPROGRESS`,表示传输正在进行中。

- 将消息添加到主设备的队列中,使用 `list_add_tail()` 函数将消息的链表节点添加到主设备队列的尾部。

- 如果主设备当前没有忙碌的传输并且需要启动消息处理(`!master->busy && need_pump`),则使用 `queue_kthread_work()` 函数将消息处理工作项添加到内核线程的工作队列中,以便在后台异步处理消息。

- 最后,解锁自旋锁并返回0,表示消息已成功添加到主设备队列中。

#### 同步方式

##### spi_sync

`spi_sync`函数用于实现同步传输,其数据传输也是调用的`__spi_queued_transfer`函数,只是第三个参数为false。源码在`drivers/spi/spi.c`文件:

```c

/**

* spi_sync - blocking/synchronous SPI data transfers

* @spi: device with which data will be exchanged

* @message: describes the data transfers

* Context: can sleep

*

* This call may only be used from a context that may sleep. The sleep

* is non-interruptible, and has no timeout. Low-overhead controller

* drivers may DMA directly into and out of the message buffers.

*

* Note that the SPI device's chip select is active during the message,

* and then is normally disabled between messages. Drivers for some

* frequently-used devices may want to minimize costs of selecting a chip,

* by leaving it selected in anticipation that the next message will go

* to the same chip. (That may increase power usage.)

*

* Also, the caller is guaranteeing that the memory associated with the

* message will not be freed before this call returns.

*

* Return: zero on success, else a negative error code.

*/

int spi_sync(struct spi_device *spi, struct spi_message *message)

{

return __spi_sync(spi, message, 0);

}

```

##### __spi_sync

`__spi_sync`实现了同步的SPI传输。它根据传输方法的不同(队列传输或其他方式),选择合适的传输方式,并在传输完成后等待通知。源码在`drivers/spi/spi.c`文件:

```c

static int __spi_sync(struct spi_device *spi, struct spi_message *message,

int bus_locked)

{

DECLARE_COMPLETION_ONSTACK(done);

int status;

struct spi_master *master = spi->master;

unsigned long flags;

status = __spi_validate(spi, message);

if (status != 0)

return status;

message->complete = spi_complete;

message->context = &done;

message->spi = spi;

SPI_STATISTICS_INCREMENT_FIELD(&master->statistics, spi_sync);

SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_sync);

if (!bus_locked)

mutex_lock(&master->bus_lock_mutex);

/* If we're not using the legacy transfer method then we will

* try to transfer in the calling context so special case.

* This code would be less tricky if we could remove the

* support for driver implemented message queues.

*/

if (master->transfer == spi_queued_transfer) {

spin_lock_irqsave(&master->bus_lock_spinlock, flags);

trace_spi_message_submit(message);

status = __spi_queued_transfer(spi, message, false);

spin_unlock_irqrestore(&master->bus_lock_spinlock, flags);

} else {

status = spi_async_locked(spi, message);

}

if (!bus_locked)

mutex_unlock(&master->bus_lock_mutex);

if (status == 0) {

/* Push out the messages in the calling context if we

* can.

*/

if (master->transfer == spi_queued_transfer) {

SPI_STATISTICS_INCREMENT_FIELD(&master->statistics,

spi_sync_immediate);

SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics,

spi_sync_immediate);

__spi_pump_messages(master, false);

}

wait_for_completion(&done);

status = message->status;

}

message->context = NULL;

return status;

}

```

- 函数接受一个 `spi_device` 结构体指针 `spi`,一个 `spi_message` 结构体指针 `message`,以及一个整数 `bus_locked`,表示要进行同步SPI传输的设备、消息和总线锁定标志。

- 首先,调用 `__spi_validate()` 函数对设备和消息进行验证,以确保它们的有效性。如果验证失败,则返回相应的错误代码。

- 设置消息的 `complete` 字段为 `spi_complete`,表示传输完成后的回调函数。将 `context` 设置为 `&done`,用于在传输完成时等待通知。将消息的 `spi` 字段设置为当前的SPI设备。

- 增加统计数据,记录同步SPI传输的次数。

- 如果总线未锁定(`!bus_locked`),则获取总线锁定互斥锁。

- 根据主设备的传输方法来决定传输的方式。如果使用队列传输方法(`master->transfer == spi_queued_transfer`),则获取总线锁定自旋锁,将消息提交到队列,并调用 `__spi_queued_transfer()` 函数进行队列传输。否则,调用 `spi_async_locked()` 函数进行异步传输。

- 如果总线未锁定,则释放总线锁定互斥锁。

- 如果传输成功(`status == 0`),根据传输方法决定是否立即推出消息。如果使用队列传输方法,增加相应的统计数据,并调用 `__spi_pump_messages()` 函数在当前上下文中处理队列中的消息。

- 等待传输完成的通知,使用 `wait_for_completion()` 函数挂起当前任务,直到完成通知触发。获取传输的最终状态,即 `message->status`。

- 将 `message->context` 设置为 `NULL`,清除传输完成的上下文。

- 返回传输的最终状态。

与异步传输不同的是:

- 异步传输是将`__spi_pump_messages`作为工作挂到内核工作线程,由内核工作线程来完成数据的传输;

- 而同步传输则是在内部调用了`__spi_pump_messages`方法,针对不同的情景情况而不同(只有同步传输、既有同步传输,也有异步传输):

- `__spi_pump_messages`函数内部可能直接调用ctrl->transfer_one_message通过SPI控制器进行数据传输;

- 也有可能将`__spi_pump_messages`作为工作挂到内核工作线程,由内核工作线程完成数据的传输;

- 此外`__spi_pump_messages`函数传入的第二个参数不同,异步方式传入true,同步方式传入false。

##### spi_complete

SPI数据同步传输是通过completion完成的,completion是一种轻量级的机制,它允许一个线程告诉另一个线程工作已经完成。也就是说我们当前线程要等待内核工作线程把数据传输完成。

在`__spi_sync`函数中,首先通过`
DECLARE_COMPLETION_ONSTATCK`声明了一个`struct completion`类型的变量,

```c

/*

* struct completion - structure used to maintain state for a "completion"

*

* This is the opaque structure used to maintain the state for a "completion".

* Completions currently use a FIFO to queue threads that have to wait for

* the "completion" event.

*

* See also: complete(), wait_for_completion() (and friends _timeout,

* _interruptible, _interruptible_timeout, and _killable), init_completion(),

* reinit_completion(), and macros DECLARE_COMPLETION(),

* DECLARE_COMPLETION_ONSTACK().

*/

struct completion {

unsigned int done; // 用于同步的原子量

wait_queue_head_t wait; // 等待队列头

};

```

宏`
DECLARE_COMPLETION_ONSTATCK`会初始化done成员为0,以及等待队列头wait;等待队列wait用于保存处于睡眠状态的线程。

`__spi_sync`函数最后调用了`wait_for_completion`函数,该函数会将done减一,当done为负数将会导致当前线程进入休眠状态。

直至message通过SPI控制器传输完成,从而在回调函数`spi_complete`将当前线程唤醒。

```c

/* Utility methods for SPI protocol drivers, layered on

* top of the core. Some other utility methods are defined as

* inline functions.

*/

static void spi_complete(void *arg)

{

complete(arg);

}

```

complete函数定义在`kernel/sched/completion.c`:

```c

/**

* complete: - signals a single thread waiting on this completion

* @x: holds the state of this particular completion

*

* This will wake up a single thread waiting on this completion. Threads will be

* awakened in the same order in which they were queued.

*

* See also complete_all(), wait_for_completion() and related routines.

*

* If this function wakes up a task, it executes a full memory barrier before

* accessing the task state.

*/

void complete(struct completion *x)

{

unsigned long flags;

spin_lock_irqsave(&x->wait.lock, flags);

if (x->done != UINT_MAX)

x->done++;

__wake_up_locked(&x->wait, TASK_NORMAL, 1);

spin_unlock_irqrestore(&x->wait.lock, flags);

}

```

complete函数为唤醒函数,当然是将done加一,当done大于等于0则唤醒等待队列中的的线程。

##### __spi_pump_messages

`__spi_pump_messages`会处理队列中的下一条消息,如果队列仍然非空,则继续调用 `__spi_pump_messages()` 函数来处理下一条消息。源码在`drivers/spi/spi.c`文件:

```c

/**

* __spi_pump_messages - function which processes spi message queue

* @master: master to process queue for

* @in_kthread: true if we are in the context of the message pump thread

*

* This function checks if there is any spi message in the queue that

* needs processing and if so call out to the driver to initialize hardware

* and transfer each message.

*

* Note that it is called both from the kthread itself and also from

* inside spi_sync(); the queue extraction handling at the top of the

* function should deal with this safely.

*/

static void __spi_pump_messages(struct spi_master *master, bool in_kthread)

{

unsigned long flags;

bool was_busy = false;

int ret;

/* Lock queue */

spin_lock_irqsave(&master->queue_lock, flags);

/* Make sure we are not already running a message */

if (master->cur_msg) {

spin_unlock_irqrestore(&master->queue_lock, flags);

return;

}

/* If another context is idling the device then defer */

if (master->idling) {

queue_kthread_work(&master->kworker, &master->pump_messages);

spin_unlock_irqrestore(&master->queue_lock, flags);

return;

}

/* Check if the queue is idle */

if (list_empty(&master->queue) || !master->running) {

if (!master->busy) {

spin_unlock_irqrestore(&master->queue_lock, flags);

return;

}

/* Only do teardown in the thread */

if (!in_kthread) {

queue_kthread_work(&master->kworker,

&master->pump_messages);

spin_unlock_irqrestore(&master->queue_lock, flags);

return;

}

master->busy = false;

master->idling = true;

spin_unlock_irqrestore(&master->queue_lock, flags);

kfree(master->dummy_rx);

master->dummy_rx = NULL;

kfree(master->dummy_tx);

master->dummy_tx = NULL;

if (master->unprepare_transfer_hardware &&

master->unprepare_transfer_hardware(master))

dev_err(&master->dev,

"failed to unprepare transfer hardware\n");

if (master->auto_runtime_pm) {

pm_runtime_mark_last_busy(master->dev.parent);

pm_runtime_put_autosuspend(master->dev.parent);

}

trace_spi_master_idle(master);

spin_lock_irqsave(&master->queue_lock, flags);

master->idling = false;

spin_unlock_irqrestore(&master->queue_lock, flags);

return;

}

/* Extract head of queue */

master->cur_msg =

list_first_entry(&master->queue, struct spi_message, queue);

list_del_init(&master->cur_msg->queue);

if (master->busy)

was_busy = true;

else

master->busy = true;

spin_unlock_irqrestore(&master->queue_lock, flags);

if (!was_busy && master->auto_runtime_pm) {

ret = pm_runtime_get_sync(master->dev.parent);

if (ret < 0) {

dev_err(&master->dev, "Failed to power device: %d\n",

ret);

return;

}

}

if (!was_busy)

trace_spi_master_busy(master);

if (!was_busy && master->prepare_transfer_hardware) {

ret = master->prepare_transfer_hardware(master);

if (ret) {

dev_err(&master->dev,

"failed to prepare transfer hardware\n");

if (master->auto_runtime_pm)

pm_runtime_put(master->dev.parent);

return;

}

}

trace_spi_message_start(master->cur_msg);

if (master->prepare_message) {

ret = master->prepare_message(master, master->cur_msg);

if (ret) {

dev_err(&master->dev,

"failed to prepare message: %d\n", ret);

master->cur_msg->status = ret;

spi_finalize_current_message(master);

return;

}

master->cur_msg_prepared = true;

}

ret = spi_map_msg(master, master->cur_msg);

if (ret) {

master->cur_msg->status = ret;

spi_finalize_current_message(master);

return;

}

ret = master->transfer_one_message(master, master->cur_msg);

if (ret) {

dev_err(&master->dev,

"failed to transfer one message from queue\n");

return;

}

}

```

- 首先,通过获取 `master->queue_lock` 自旋锁来锁定队列。

- 检查是否已经有正在运行的消息,如果有,则解锁并返回。

- 检查是否有其他上下文正在空闲设备。如果是,则将任务添加到kworker线程中并解锁,然后返回。

- 检查队列是否为空或主设备处于非运行状态。如果队列为空且设备非运行状态,则解锁并返回。

- 如果主设备不忙,但当前上下文不是kworker线程,则将任务添加到kworker线程中并解锁,然后返回。

- 如果主设备不忙,则设置 `master->busy` 为true,表示设备正在处理消息。

- 如果是第一次处理消息且启用了自动运行时电源管理(auto_runtime_pm),则获取运行时电源,保持设备处于活动状态。

- 如果是第一次处理消息,则记录设备处于忙状态的跟踪信息。

- 如果是第一次处理消息且需要准备传输硬件(prepare_transfer_hardware函数不为空),则调用 `master->prepare_transfer_hardware()` 来准备传输硬件。

- 跟踪消息开始的信息。

- 如果定义了 `master->prepare_message` 函数,则调用该函数来准备消息。

- 对消息进行映射,将消息映射到具体的硬件传输。

- 调用 `master->transfer_one_message()` 函数执行消息传输。

这段代码实际上比较饶人,就我个人人为,它其实就是想做这么一件事:

- 从消息队列去获取`spi_message`,然后调用SPI控制器驱动提供的`transfer_one`函数进行消息的传输,传输完成后就回调`
spi_finalize_current_message`,再次将`__spi_pump_messages`放到内核工作线程执行;

- 当然了当消息队列被输出完的话,内核工作线程就没工作可执行了,所以`__spi_pump_messages`函数内部又多了一些状态的判断,判断何时需要将__`spi_pump_messages`放到内核工作线程执行;

##### spi_transfer_one_message

`spi_transfer_one_message`实现了对SPI消息进行传输的默认处理方式。源码在`drivers/spi/spi.c`文件:

```c

/*

* spi_transfer_one_message - Default implementation of transfer_one_message()

*

* This is a standard implementation of transfer_one_message() for

* drivers which impelment a transfer_one() operation. It provides

* standard handling of delays and chip select management.

*/

static int spi_transfer_one_message(struct spi_master *master,

struct spi_message *msg)

{

struct spi_transfer *xfer;

bool keep_cs = false;

int ret = 0;

unsigned long ms = 1;

struct spi_statistics *statm = &master->statistics;

struct spi_statistics *stats = &msg->spi->statistics;

spi_set_cs(msg->spi, true);

SPI_STATISTICS_INCREMENT_FIELD(statm, messages);

SPI_STATISTICS_INCREMENT_FIELD(stats, messages);

list_for_each_entry(xfer, &msg->transfers, transfer_list) {

trace_spi_transfer_start(msg, xfer);

spi_statistics_add_transfer_stats(statm, xfer, master);

spi_statistics_add_transfer_stats(stats, xfer, master);

if (xfer->tx_buf || xfer->rx_buf) {

reinit_completion(&master->xfer_completion);

ret = master->transfer_one(master, msg->spi, xfer);

if (ret < 0) {

SPI_STATISTICS_INCREMENT_FIELD(statm,

errors);

SPI_STATISTICS_INCREMENT_FIELD(stats,

errors);

dev_err(&msg->spi->dev,

"SPI transfer failed: %d\n", ret);

goto out;

}

if (ret > 0) {

ret = 0;

ms = xfer->len * 8 * 1000 / xfer->speed_hz;

ms += ms + 100; /* some tolerance */

ms = wait_for_completion_timeout(&master->xfer_completion,

msecs_to_jiffies(ms));

}

if (ms == 0) {

SPI_STATISTICS_INCREMENT_FIELD(statm,

timedout);

SPI_STATISTICS_INCREMENT_FIELD(stats,

timedout);

dev_err(&msg->spi->dev,

"SPI transfer timed out\n");

msg->status = -ETIMEDOUT;

}

} else {

if (xfer->len)

dev_err(&msg->spi->dev,

"Bufferless transfer has length %u\n",

xfer->len);

}

trace_spi_transfer_stop(msg, xfer);

if (msg->status != -EINPROGRESS)

goto out;

if (xfer->delay_usecs)

udelay(xfer->delay_usecs);

if (xfer->cs_change) {

if (list_is_last(&xfer->transfer_list,

&msg->transfers)) {

keep_cs = true;

} else {

spi_set_cs(msg->spi, false);

udelay(10);

spi_set_cs(msg->spi, true);

}

}

msg->actual_length += xfer->len;

}

out:

if (ret != 0 || !keep_cs)

spi_set_cs(msg->spi, false);

if (msg->status == -EINPROGRESS)

msg->status = ret;

if (msg->status && master->handle_err)

master->handle_err(master, msg);

spi_finalize_current_message(master);

return ret;

}

```

- 首先,通过调用 `spi_set_cs()` 将SPI片选信号置为激活状态。

- 增加主设备和消息的统计信息中的 `messages` 字段。

- 使用 `list_for_each_entry()` 循环遍历消息中的每个传输项。

- 跟踪当前传输的起始位置。

- 在主设备和消息的统计信息中添加传输的统计数据。

- 如果传输项中的 `tx_buf` 或 `rx_buf` 不为空,则初始化传输完成的等待完成量(`master->xfer_completion`),然后调用 `master->transfer_one()` 函数执行实际的传输操作。如果传输失败(返回值小于0),则增加错误计数,输出错误信息,并跳转到 `out` 标签处。

- 如果传输项的返回值大于0,表示传输成功但需要等待传输完成。计算超时等待时间 `ms`,然后使用 `
wait_for_completion_timeout()` 等待传输完成。

- 如果传输超时(`ms` 等于0),增加超时计数,输出错误信息,并将消息的状态设置为 `-ETIMEDOUT`。

- 如果传输项中的 `tx_buf` 和 `rx_buf` 都为空,并且传输长度不为0,则输出错误信息,表示无缓冲区传输长度非零。

- 跟踪当前传输的结束位置。

- 如果消息的状态不是 `-EINPROGRESS`,则跳转到 `out` 标签处。

- 如果传输项中设置了延迟(`delay_usecs`),则使用 `udelay()` 进行延迟。

- 如果传输项的 `cs_change` 标志为真,则根据是否为传输列表的最后一个传输项来决定是否保持片选信号激活状态。如果是最后一个传输项,则保持激活状态,否则,先将片选信号置为非激活状态,延迟10微秒,然后再将片选信号置为激活状态。

- 增加消息的实际长度。

- 在 `out` 标签处,根据传输的返回值和 `keep_cs` 标志来决定是否将片选信号置为非激活状态。

- 如果消息的状态仍为 `-EINPROGRESS`,则将其设置为传输的返回值。

- 如果消息的状态非零且主设备的 `handle_err` 函数不为空,则调用 `master->handle_err()` 函数处理错误。

- 最后,调用 `
spi_finalize_current_message()` 函数来完成当前消息的处理。

- 返回传输的返回值。

##### spi_finalize_current_message

`
spi_finalize_current_message`用于完成当前消息的处理并将其从队列中移除。源码在`drivers/spi/spi.c`文件:

```c

/**

* spi_finalize_current_message() - the current message is complete

* @master: the master to return the message to

*

* Called by the driver to notify the core that the message in the front of the

* queue is complete and can be removed from the queue.

*/

void spi_finalize_current_message(struct spi_master *master)

{

struct spi_message *mesg;

unsigned long flags;

int ret;

spin_lock_irqsave(&master->queue_lock, flags);

mesg = master->cur_msg;

spin_unlock_irqrestore(&master->queue_lock, flags);

spi_unmap_msg(master, mesg);

if (master->cur_msg_prepared && master->unprepare_message) {

ret = master->unprepare_message(master, mesg);

if (ret) {

dev_err(&master->dev,

"failed to unprepare message: %d\n", ret);

}

}

spin_lock_irqsave(&master->queue_lock, flags);

master->cur_msg = NULL;

master->cur_msg_prepared = false;

queue_kthread_work(&master->kworker, &master->pump_messages);

spin_unlock_irqrestore(&master->queue_lock, flags);

trace_spi_message_done(mesg);

mesg->state = NULL;

if (mesg->complete)

mesg->complete(mesg->context);

}

```

1. 获取当前正在处理的消息 `cur_msg` 并保存到本地变量 `mesg` 中,使用自旋锁保护读取操作,确保原子性。

2. 调用 `spi_unmap_msg()` 函数,解除消息中已映射的缓冲区。这个步骤是为了确保在消息处理完成后,相关的资源可以被释放,防止内存泄漏。

3. 检查当前消息是否已经准备好并且主设备实现了 `unprepare_message()` 函数。如果满足条件,则调用 `unprepare_message()` 函数对消息进行反准备操作。这个操作通常用于释放为消息准备的特定资源,如申请的DMA通道或中断处理程序等。如果反准备操作返回非零值,表示出现了错误,将输出错误信息。

4. 使用自旋锁保护对当前消息和相关标志的修改操作。首先,将 `cur_msg` 指针和准备状态标志 `cur_msg_prepared` 设置为NULL和false,表示当前消息已完成处理。然后,通过调用 `queue_kthread_work()` 将工作项 `pump_messages` 排队到内核工作队列中。这个工作项用于在后台线程中继续处理后续的消息。通过将工作项排队,可以确保消息处理的连续性和顺序性。

5. 使用跟踪功能 `trace_spi_message_done()` 记录消息的完成情况。这可以用于调试和性能分析。

6. 将消息的状态指针 `state` 设置为NULL,表示消息已经完成处理。

7. 如果消息的 `complete` 成员不为空,则调用相应的回调函数,通知上层应用程序消息的完成情况。这个回调函数可以是应用程序自定义的函数,用于处理消息完成后的操作。

## 数据结构关系图

下面来自其他博主绘制的一张数据结构之间的关系图:这种图很清晰的介绍了:

- spi_controller、spi_device、spi_driver之间的关系;

- SPI控制器注册时的初始化部分;

- SPI控制器数据部分。

![](http://linuxdriver.top/Blog/2023/202306241617898.png)

## 本文参考

http://jake.dothome.co.kr/spi-1

http://jake.dothome.co.kr/spi-2

https://blog.csdn.net/m0_64560763/article/details/127335361

https://www.cnblogs.com/xinghuo123/p/12991945.html

https://www.cnblogs.com/xinghuo123/p/12992015.html

https://www.cnblogs.com/xinghuo123/p/12994825.html

https://www.cnblogs.com/xinghuo123/p/12994850.html

https://www.cnblogs.com/xinghuo123/p/13046827.html

https://www.cnblogs.com/xinghuo123/p/13047065.html

https://blog.csdn.net/eastcnme/article/details/104447479

https://stephenzhou.blog.csdn.net/article/details/99866773?spm=1001.2101.3001.6650.5&utm_medium=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-5-99866773-blog-104447479.pc_relevant_3mothn_strategy_and_data_recovery&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-5-99866773-blog-104447479.pc_relevant_3mothn_strategy_and_data_recovery&utm_relevant_index=10

https://blog.csdn.net/u012846795/article/details/114529068**

https://stephenzhou.blog.csdn.net/article/details/100040318

https://blog.csdn.net/qq_42017846/article/details/130515879

相关推荐

用闲置电脑当软路由安装OpenWRT(小白教程)

话说软路由系统OpenWRT用起来真是香,里面的好多功能都是普通路由无法实现的,由于众所周知的原因,在这里就不细说,等安装完自己体验吧。今天就介绍用一台闲置的电脑(自带两个网口)充当软路由,安装Ope...

一招把废旧路由器改成交换机(用旧路由器做交换机)

家里面的路由器用个几年,就会WIFI变卡,新路由器买回来,旧路由器就没什么用了?我在这里教大家把老路由器变成交换机。近两年新出的路由器,基本都是2个LAN口,接网络设备还需要买交换机,淘汰下来的路由器...

如何将PC电脑变成web服务器:将内网主机映射到外网实现远程访问

我是艾西,今天跟大家分享内容还是比较多人问的一个问题:如何将PC电脑变成web服务器。内网主机作为web服务器,内容包括本地内网映射、多层内网映射解决方案、绕过电信80端口封锁、DDNS功能的实现(非...

电脑怎么改Wi-Fi密码(电脑怎么改wifi密码视频教程)

一.电脑打开“任意浏览器ie/google浏览器等”——>地址栏里输入管理ip地址然后按“回车键”打开该地址,如下图所示。二.输入正确的管理员密码——>点击“登录”即可(下图是PC版本的路...

旧路由器不要扔,可当电脑无线网卡使用,你还不知道吧!

家里有旧路由器,卖二手又不值钱,扔了又可惜。想不到路由器还有以下这些功能:扩大Wifi覆盖范围;充当电脑无线网卡;把这个技巧学起来,提升网络冲浪的幸福感!导航栏路由器恢复出厂设置(通用教程)有线桥接无...

硬件大师AIDA64 5.60.3716更新下载:“认准”Win10

著名硬件测试工具AIDA64更新至5.60.3716Beta版,本次更新修复了Win10Build版本号检测错误问题,识别更准确。另外还添加了对ITEIT8738F传感器、ASRock主板、NVI...

互联网病毒木马与盗版软件流量产业链(一)

A.相关地下产业链整体深度分析可能很多用户都有这样的经历,就是不管打开什么网站,甚至根本就没有打开浏览器,都会跳出来一堆的弹窗广告。那么,这个用户要么是中的病毒木马,或者是使用了盗版软件。不管是...

穿越火线tenparty.dat文件损坏怎么办?

很多玩家在玩火线的时候经常会因弹出错误代码,而被退出游戏。下面就教大家一些常见错误代码的解决方案。方法/步骤1SX提示码提示说明:您的电脑出现1,xxx,0(xxx代表任意数字)提示码,存在游...

办公小技巧015:如何关闭Windows Defender安全中心

WindowsDefenderWindowsDefender是Widows中自带杀毒软件,可以检测及清除潜藏在操作系统里的间谍软件及广告软件。为电脑提供最高强度的安全防护,也被誉为Windows的...

Win7/8.1/10团灭:微软发现严重漏洞

据外媒报道称,微软已经停止为Windows7发布新的安全更新了,理由是IE存在严重漏洞。存在严重漏洞的IE按照微软的说法,这个远程代码执行漏洞存在于IE浏览器处理脚本引擎对象的内存中。该漏洞可能以一...

WinCC flexible 2008 SP4 的安装步骤及系统要求

1、软件安装过程安装注意事项(必须严格遵守):软件仅支持以下操作系统(必须是微软原版的操作系统,Ghost版系统不支持,如番茄花园、雨林木风、电脑城装机版等):WinCCflexible2008...

Windows三方杀毒防护软件可能问题以及使用建议

在处理ECSWindows相关案例中,我们遇到很多奇怪的操作系统问题,例如软件安装失败,无法激活操作系统,无法访问本地磁盘,网络访问受到影响,系统蓝屏,系统Hang等,排查发现这与客户安装的各类杀...

杀毒软件被指泄露个人隐私(杀毒软件查出来一定是毒吗)

最近的多篇报道显示,你使用的杀毒软件在监视着你,而不仅仅是你计算机上的文件。2014年的一项研究使用虚拟机监视了杀毒软件产品向企业发送了什么信息。他们发现,所有测试的杀毒软件都给电脑分配了一个唯一的识...

开源杀毒软件ClamAV在推出约20年后终于到达1.0版本

ClamAV是一个开源的反病毒引擎,用于检测木马、病毒、恶意软件和其他恶意威胁。与商业Windows反恶意软件程序相比,它的检测水平相当低,但开发工作已经持续了几十年。该工具可用于所有平台,尽管它主要...

【Excel函数使用】时分秒时间怎么转换成秒?(二)

本节主要分享的函数是IFERROR和NUMBERVALUE上回我们用MID和FIND函数已经将数值提取出来,但是一些错误的返回值显示“#VALUE!”,此时我们需要检验错误返回值,并将错误值返回指定值...

取消回复欢迎 发表评论: