Linux设备树中的SPI驱动使用详解 (linux下设备树spi使用)

Linux设备树是一种描述硬件设备拓扑结构、资源分配等信息的机制,它的出现为移植嵌入式系统带来了很大的便利性,但也带来了新的学习和应用难度。SPI(Serial Peripheral Interface)总线是一种高速串行总线协议,常用于连接嵌入式系统中的外设。本文将从设备树的角度出发,详细介绍如何在Linux内核中配置和使用SPI总线设备驱动。

一、设备树中SPI总线设备的描述

设备树是一棵树形结构,每个节点对应一个硬件设备或资源。下图是一个简单的设备树结构示例:

“`

/ {

spi@a0001000 {

compatible = “atmel,at91sam9263-spi”;

reg = ;

interrupt-parent = &pioA;

interrupts = ;

pinctrl-names = “default”;

pinctrl-0 = ;

};

};

“`

我们来逐个研究每个节点的含义和属性:

1. 根节点“/”

在设备树中,根节点表示整个硬件平台。它常常是唯一的一个节点,也可以有多个,比如多核处理器系统可以为每个核心生成一个根节点。根节点的属性通常是平台相关的,可以包括CPU类型、时钟频率、内存布局等等。

2. SPI总线设备节点“spi@a0001000”

这是SPI设备的节点名,它由两部分组成:“spi”表示节点类型,”a0001000”是设备的物理地址。SPI节点的属性可以包括以下内容:

* compatible:表示设备的兼容性信息,通常是一个字符串数组,字符串的格式由具体的硬件厂商或芯片开发者定义。

* reg:表示设备在总线上的地址范围。

* interrupt-parent:表示IRQ控制器的父节点,在AT91SAM9263芯片中使用的是PIOA。

* interrupts:表示设备的中断号。

* pinctrl-names和pinctrl:表示设备的引脚控制信息,用于配置设备的引脚(IO口)。

3. 设备树编译

设备树用源码形式编写,通常保存在.dts或.dtb文件中。为了将设备树文件编译成.dtb文件,要使用dtc工具。我们可以在Linux内核源码中找到dtc,并使用以下命令编译设备树:

“`

$ dtc -I dts -O dtb -o spi.dtb spi.dts

“`

其中-I参数指定输入格式为dts,-O参数指定输出格式为dtb。编译成功后,将得到一个名为spi.dtb的设备树二进制文件。

二、设备驱动模型

Linux内核的设备驱动模型通过一套抽象的结构体和接口,将设备和驱动程序之间进行解耦,以便更好地支持平台移植和驱动模块化。我们来看一下涉及到SPI总线设备驱动的关键结构体和接口。

1. struct spi_master

该结构体表示SPI总线控制器的主设备,它具有如下字段:

* bus_num:SPI总线控制器的编号,一般从0开始分配。

* max_speed_hz:该总线的更大总线速度,单位为Hz。

* mode_bits:该总线支持的SPI总线模式。

* chip_select:该总线所连接的芯片。

* num_chipselect:该总线支持的CS信号线数量。

* mode_bits:支持的SPI总线模式。

2. struct spi_device

该结构体表示一个SPI设备,它具有如下字段:

* max_speed_hz:该设备的更大总线速度,单位为Hz。

* chip_select:设备所使用的芯片选择线。

* mode:设备的SPI总线模式。

* bits_per_word:设备使用的每个字的位数。

* controller_data:和控制器相关的设备数据。

3. struct spi_board_info

该结构体描述了一个SPI设备的常规属性,用于创建一个spi_device结构体。

* modalias:设备驱动名称。

* max_speed_hz:更大总线速度。

* bus_num:控制器编号。

* chip_select:芯片选择线编号。

* mode:SPI总线模式。

* platform_data:设备的平台数据。

4. SPI传输函数

主要使用以下函数与SPI设备进行数据传输:

* spi_setup:对SPI传输进行一些基础的设置,如极性、相位、总线速率等。

* spi_setup_transfer:配置传输参数(字长、模式、速度等)。

* spi_message_init:初始化SPI消息,用于将多个传输组织在一起。

* spi_sync:发送数据到SPI设备并等待回应。

* spi_write_then_read:从设备中读取和写入数据。

* spi_write:写入数据到设备。

以上函数的具体定义和使用方法详见Linux内核源码和相关文档。

三、SPI设备驱动的实现步骤

SPI设备驱动的实现步骤如下:

1. 注册SPI主设备

SPI主设备的注册可以在平台设备的probe函数中执行,可以使用spi_register_master函数进行注册。在注册SPI主设备时,需要先通过platform_get_resource函数获取到一个指向设备树节点的指针,再从该指针中读取设备节点的属性。

“`

static int spi_probe(struct platform_device *pdev)

{

struct resource *res;

struct spi_master *master;

struct device *dev = &pdev->dev;

res = platform_get_resource_byname(pdev, IORESOURCE_MEM, “spi”);

if (!res) {

dev_err(dev, “no memory resource specified\n”);

return -ENXIO;

}

master = spi_alloc_master(&pdev->dev, sizeof(struct sam_spi));

if (!master)

return -ENOMEM;

master->bus_num = pdev->id;

master->num_chipselect = SAM_CS_NUM;

master->mode_bits = SPI_CPOL | SPI_CPHA;

master->max_speed_hz = 24000000;

master->dev.of_node = dev->of_node;

platform_set_drvdata(pdev, master);

return spi_register_master(master);

}

“`

其中,spi_alloc_master函数用于创建一个新的SPI主设备,sizeof(struct sam_spi)表示设备私有数据结构的大小。

2. 注册SPI子设备

SPI子设备的注册可以在平台设备的probe函数中执行。需要使用spi_new_device函数创建一个spi_device结构体,并传入spi_master指针、spi_board_info结构体等参数。在注册SPI子设备时,需要根据设备树节点的属性设置相应的参数。

“`

static int spi_probe(struct platform_device *pdev)

{

……

for (i = 0; i

if (!board_info[i].modalias)

continue;

if (board_info[i].bus_num != pdev->id)

continue;

/* create a new slave device */

slave = spi_new_device(master, &board_info[i]);

if (!slave) {

dev_err(&master->dev, “can’t create SPI slave %d\n”, i);

continue;

}

priv = spi_master_get_devdata(master);

priv->devices[i] = slave;

}

……

return 0;

}

“`

spi_new_device函数将会自动设置spi_device的max_speed_hz、chip_select、mode、bits_per_word四个参数。其中max_speed_hz的值依据设备树节点的某个属性值自动设置的。

3. SPI传输

在驱动程序中使用SPI传输数据的例子:

“`

static int spi_xfer(struct spi_device *spi, struct spi_transfer *tfr)

{

int ret;

ret = spi_setup_transfer(spi, tfr);

if (ret)

return ret;

ret = spi_sync(spi, tfr);

if (ret)

return ret;

return 0;

}

“`

该函数将使用spi_setup_transfer函数以及spi_sync函数来实现SPI传输。spi_setup_transfer函数用于设置SPI传输的模式和时钟速度等参数;spi_sync函数用于发送SPI信号并等待响应。在spi_transfer结构体中,填充好了tx_buf和rx_buf两个缓冲区,它们分别存放待发送和接收的数据。spi_transfer结构体中的属性例如bit_per_word、speed_hz等,表示该传输的位数和时钟速度,之前已经通过spi_new_device函数自动设置。

四、设备树使用示例

设备树的配置有很多种方式,下面给出一种情况下SPI设备树的相关配置方式。

在设备树文件中添加SPI节点:

“`

spi@11007000 {

compatible = “atmel,sam9x60-spi”;

reg = ;

interrupts = ;

clocks = , ;

clock-names = “spi”, “peripheral_clk”;

pinctrl-names = “default”;

pinctrl-0 = ;

};

pinctrl_spi0_default: spi0-default {

atmel,pins = ;

atmel,groups = “spi0_grp”;

};

“`

其中spi节点描述如前面二节所述,pinctrl_spi0_default节点描述了该SPI总线的引脚控制信息。

用户驱动程序代码:

“`

static struct spi_board_info spidev_board_info[] = {

{

.modalias = “spidev”,

.max_speed_hz = 1000000,

.bus_num = 0,

.chip_select = 0,

.mode = SPI_MODE_0,

},

};

static int __init spidev_init(void)

{

struct spi_master *master;

struct spi_device *spi;

master = spi_busnum_to_master(0);

if (!master) {

pr_err(“spi_busnum_to_master(%d) returned NULL\n”, 0);

return -ENODEV;

}

spi = spi_new_device(master, &spidev_board_info[0]);

if (!spi) {

pr_err(“spi_new_device fled\n”);

return -EBUSY;

}

spi_setup(spi);

return 0;

}

module_init(spidev_init);

“`

以上代码使用spi_busnum_to_master函数获取到SPI主设备,然后使用spi_new_device函数创建一个SPI子设备,并进行spi_setup。由于在设备树文件中设置了唯一的一个SPI总线节点,因此我们可以在代码中写死SPI总线的bus_num为0,而不必再通过设备树来查找SPI总线的节点。


数据运维技术 » Linux设备树中的SPI驱动使用详解 (linux下设备树spi使用)