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总线的节点。