瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。
【公众号】迅为电子
【粉丝群】258811263(加群获取驱动文档+例程)
【视频观看】嵌入式学习之Linux驱动(第十六篇 SPI_全新升级)_基于RK3568
【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板
第186章 通用SPI外设代码框架编写
经过前面两个章节的学习,相信大家已经对SPI有了基本的认识,从本章节开始将会逐步编写和完善SPI转CAN模块的驱动代码,而在编写驱动之前首先要对设备树进行完善。
186.1 设备树的完善
SPI转CAN模块要接的iTOP-RK3568的引脚如下所示:
可以根据引脚的网络标号得到要使能的SPI控制器为SPI0,然后开始对iTOP-RK3568的设备树进行修改,首先来到Linux SDK目录下,如下图所示:
然后使用“vim kernel/arch/arm64/boot/dts/rockchip/rk3568-evb1-ddr4-v10.dtsi”命令对rk3568-evb1-ddr4-v10.dtsi设备树文件进行修改,要填加的spi节点内容如下所示:
&spi0 {
status = "okay";
pinctrl-0 = <&spi0m1_cs0 &spi0m1_pins>;
pinctrl-1 = <&spi0m1_cs0 &spi0m1_pins_hs>;
mcp2515:mcp2515@0 {
compatible = "my-mcp2515";
reg = <0>;
spi-max-frequency = <10000000>;
status = "okay";
};
};
第3-4行,指定了要使用的spi pinctrl引脚,默认情况下使用的spi0控制器pinctrl引脚为spi0m0_cs0和spi0m0_pins,而实际用的是第二组pinctrl引脚。
第7行,表述指定片选0。
第7行,设置spi clk输出的时钟频率,这里设置的是10M,RK3568最大设置不超过50M。
如果reg属性和spi-max-frequency不设置在驱动加载时将无法进入probe spi初始化函数,在后面会进行详细的讲解。
添加完成之后如下图所示:
然后保存退出,重新编译内核boot.img镜像,编译完成的内核镜像已经放到了“iTOP-3568开发板\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动程序\114_mcp2515_02\02_编译完成的内核镜像”目录下,然后烧写到开发板上,在开发板上使用以下命令查看引脚复用关系,可以看到SPI0对应的四个引脚已经被成功复用。
cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pins
186.2 注册SPI设备
SPI设备的注册使用的函数为spi_register_driver,被定义在内核源码的“include/linux/spi/spi.h”目录下,具体内容如下所示:
#define spi_register_driver(driver) \
__spi_register_driver(THIS_MODULE, driver)
可以看到spi_register_driver是一个宏定义,这个宏定义的作用是为了简化注册SPI设备驱动程序的过程,实际注册SPI设备的函数为__spi_register_driver,该函数定义在“drivers/spi/spi.c”文件中,具体内容如下所示:
int __spi_register_driver(struct module *owner, struct spi_driver *sdrv)
{
sdrv->driver.owner = owner;
sdrv->driver.bus = &spi_bus_type; //设置驱动结构体的 bus 字段为 spi_bus_type
sdrv->driver.probe = spi_drv_probe; //设置驱动的 probe 回调函数为 spi_drv_probe
sdrv->driver.remove = spi_drv_remove; //设置驱动的 remove 回调函数为 spi_drv_remove
if (sdrv->shutdown)
sdrv->driver.shutdown = spi_drv_shutdown; //设置驱动的shutdown回调函数为 spi_drv_shutdown
return driver_register(&sdrv->driver); //调用 driver_register() 函数完成驱动的注册
}
该函数的主要作用是注册一个SPI驱动。它通过设置驱动结构体的各个字段,并在最后调用内核的driver_register函数,完成驱动的注册过程。__spi_register_driver函数需要传入spi_driver类型的结构体,该结构体的具体内容如下所示:
struct spi_driver {
const struct spi_device_id *id_table;
int (*probe)(struct spi_device *spi);
int (*remove)(struct spi_device *spi);
void (*shutdown)(struct spi_device *spi);
struct device_driver driver;
};
内核的设备驱动模型进行集成,从而实现对 SPI 设备的管理和操作。
186.3 编写driver部分
本实验驱动对应的网盘路径为:iTOP-3568开发板\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动程序\114_mcp2515_02\03_module。
本实验旨在搭建最简单的SPI驱动程序框架,申请注册一个SPI设备,所以只填充了最简单的probe初始化函数和remove移除函数,在后面的章节中会对该驱动程序进行填充。
编写完成的mcp2515.c代码如下所示:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
// MCP2515设备初始化函数
static int mcp2515_probe(struct spi_device *spi)
{
printk("This is mcp2515 probe\n");
return 0;
}
// MCP2515设备移除函数
static int mcp2515_remove(struct spi_device *spi)
{
return 0;
}
// MCP2515设备匹配表,用于设备树匹配
static const struct of_device_id mcp2515_of_match_table[] = {
{ .compatible = "my-mcp2515" },
{}
};
// MCP2515设备ID匹配表,用于总线匹配
static const struct spi_device_id mcp2515_id_table[] = {
{ "mcp2515", 0 },
{}
};
// MCP2515 SPI驱动结构体
static struct spi_driver spi_mcp2515 = {
.probe = mcp2515_probe, // 探测函数
.remove = mcp2515_remove, // 移除函数
.driver = {
.name = "mcp2515", // 驱动名称
.owner = THIS_MODULE, // 所属模块
.of_match_table = mcp2515_of_match_table, // 设备树匹配表
},
.id_table = mcp2515_id_table, // 设备ID匹配表
};
// 驱动初始化函数
static int __init mcp2515_init(void)
{
int ret;
// 注册SPI驱动
ret = spi_register_driver(&spi_mcp2515);
if (ret < 0) {
// 注册失败,打印错误信息
printk("spi_register_driver error\n");
return ret;
}
return ret;
}
// 驱动退出函数
static void __exit mcp2515_exit(void)
{
// 注销SPI驱动
spi_unregister_driver(&spi_mcp2515);
}
module_init(mcp2515_init);
module_exit(mcp2515_exit);
MODULE_LICENSE("GPL");
186.4 运行测试
186.4.1 编译驱动程序
在上一小节中的mcp2515.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下所示:
export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
obj-m += mcp2505.o #此处要和你的驱动源文件同名
KDIR :=/home/topeet/Linux/linux_sdk/kernel #这里是你的内核目录
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules #make操作
clean:
make -C $(KDIR) M=$(PWD) clean #make clean操作
对于Makefile的内容注释已在上图添加,保存退出之后,来到存放mcp2515.c和Makefile文件目录下,如下图所示:
对于Makefile的内容注释已在上图添加,保存退出之后,来到存放mcp2515.c和Makefile文件目录下,如下图所示:
编译完生成ft5x06_driver.ko目标文件,如下图所示:
至此驱动模块就编译成功了。
186.4.2 运行测试
在进行实验之前,首先要确保开发板烧写的是我们在186.1小节中编译出来的boot.img。开发板启动之后,然后使用以下命令进行驱动模块的加载,如下图所示:
insmod mcp2515.ko
可以看到成功打印了在probe函数中的打印,证明我们添加的设备树节点和驱动程序匹配成功了。
然后使用以下命令进行驱动模块的卸载,如下图所示:
rmmod mcp2515.ko
由于没有在remove卸载函数中添加打印相关内容,所以使用rmmod命令卸载驱动之后,没有任何打印,至此,最简单的SPI驱动实验就完成了。
186.5 深入分析
在完善设备树的过程中提到reg和spi-max-frequency是设备树必填属性,如果不完善这两个属性将无法进入驱动的probe函数进行初始化,在本小节将深入分析这其中的原理。
mcp2515是SPI0控制器的子节点,先找到SPI0控制器的设备树根节点,SPI0根节点写在设备树rk3568.dtsi中,具体内容如下所示:
spi0: spi@fe610000 {
compatible = "rockchip,rk3066-spi";
reg = <0x0 0xfe610000 0x0 0x1000>;
interrupts = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>;
#address-cells = <1>;
#size-cells = <0>;
clocks = <&cru CLK_SPI0>, <&cru PCLK_SPI0>;
clock-names = "spiclk", "apb_pclk";
dmas = <&dmac0 20>, <&dmac0 21>;
dma-names = "tx", "rx";
pinctrl-names = "default", "high_speed";
pinctrl-0 = <&spi0m0_cs0 &spi0m0_cs1 &spi0m0_pins>;
pinctrl-1 = <&spi0m0_cs0 &spi0m0_cs1 &spi0m0_pins_hs>;
status = "disabled";
};
然后根据设备树的compatible属性来寻找对应的SPI控制器驱动程序,找到的具体驱动文件路径为spi/spi-rockchip.c,该驱动程序的probe函数内容如下所示:
static int rockchip_spi_probe(struct platform_device *pdev)
{
int ret;
struct rockchip_spi *rs;
struct spi_controller *ctlr;
struct resource *mem;
struct device_node *np = pdev->dev.of_node;
u32 rsd_nsecs;
bool slave_mode;
struct pinctrl *pinctrl = NULL;
// 检查设备节点是否配置为 SPI 从模式
slave_mode = of_property_read_bool(np, "spi-slave");
// 根据从模式或主模式分配 SPI 控制器
if (slave_mode)
ctlr = spi_alloc_slave(&pdev->dev, sizeof(struct rockchip_spi));
else
ctlr = spi_alloc_master(&pdev->dev, sizeof(struct rockchip_spi));
if (!ctlr)
return -ENOMEM;
// 设置平台设备的驱动数据
platform_set_drvdata(pdev, ctlr);
rs = spi_controller_get_devdata(ctlr);
ctlr->slave = slave_mode;
// 获取基本的 IO 资源并映射
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
rs->regs = devm_ioremap_resource(&pdev->dev, mem);
if (IS_ERR(rs->regs)) {
ret = PTR_ERR(rs->regs);
goto err_put_ctlr;
}
rs->apb_pclk = devm_clk_get(&pdev->dev, "apb_pclk");
if (IS_ERR(rs->apb_pclk)) {
dev_err(&pdev->dev, "Failed to get apb_pclk\n");
ret = PTR_ERR(rs->apb_pclk);
goto err_put_ctlr;
}
rs->spiclk = devm_clk_get(&pdev->dev, "spiclk");
if (IS_ERR(rs->spiclk)) {
dev_err(&pdev->dev, "Failed to get spi_pclk\n");
ret = PTR_ERR(rs->spiclk);
goto err_put_ctlr;
}
// 启用 APB PCLK
ret = clk_prepare_enable(rs->apb_pclk);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to enable apb_pclk\n");
goto err_put_ctlr;
}
// 启用 SPI CLK
ret = clk_prepare_enable(rs->spiclk);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to enable spi_clk\n");
goto err_disable_apbclk;
}
// 禁用 SPI 芯片
spi_enable_chip(rs, false);
// 获取平台中断资源
ret = platform_get_irq(pdev, 0);
if (ret < 0)
goto err_disable_spiclk;
// 请求中断
ret = devm_request_threaded_irq(&pdev->dev, ret, rockchip_spi_isr, NULL,
IRQF_ONESHOT, dev_name(&pdev->dev), ctlr);
if (ret)
goto err_disable_spiclk;
rs->dev = &pdev->dev;
rs->freq = clk_get_rate(rs->spiclk);
rs->gpio_requested = false;
// 读取接收采样延迟(以纳秒为单位)
if (!of_property_read_u32(pdev->dev.of_node, "rx-sample-delay-ns", &rsd_nsecs)) {
u32 rsd = DIV_ROUND_CLOSEST(rsd_nsecs * (rs->freq >> 8), 1000000000 >> 8);
if (!rsd) {
dev_warn(rs->dev, "%u Hz are too slow to express %u ns delay\n", rs->freq, rsd_nsecs);
} else if (rsd > CR0_RSD_MAX) {
rsd = CR0_RSD_MAX;
dev_warn(rs->dev, "%u Hz are too fast to express %u ns delay, clamping at %u ns\n",
rs->freq, rsd_nsecs, CR0_RSD_MAX * 1000000000U / rs->freq);
}
rs->rsd = rsd;
}
// 获取 FIFO 长度
rs->fifo_len = get_fifo_len(rs);
if (!rs->fifo_len) {
dev_err(&pdev->dev, "Failed to get fifo length\n");
ret = -EINVAL;
goto err_disable_spiclk;
}
// 设置并启用运行时电源管理
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
ctlr->auto_runtime_pm = true;
ctlr->bus_num = pdev->id;
ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP | SPI_LSB_FIRST | SPI_CS_HIGH;
if (slave_mode) {
ctlr->mode_bits |= SPI_NO_CS;
ctlr->slave_abort = rockchip_spi_slave_abort;
} else {
ctlr->flags = SPI_MASTER_GPIO_SS;
}
ctlr->num_chipselect = ROCKCHIP_SPI_MAX_CS_NUM;
ctlr->dev.of_node = pdev->dev.of_node;
ctlr->bits_per_word_mask = SPI_BPW_MASK(16) | SPI_BPW_MASK(8) | SPI_BPW_MASK(4);
ctlr->min_speed_hz = rs->freq / BAUDR_SCKDV_MAX;
ctlr->max_speed_hz = min(rs->freq / BAUDR_SCKDV_MIN, MAX_SCLK_OUT);
ctlr->set_cs = rockchip_spi_set_cs;
ctlr->setup = rockchip_spi_setup;
ctlr->cleanup = rockchip_spi_cleanup;
ctlr->transfer_one = rockchip_spi_transfer_one;
ctlr->max_transfer_size = rockchip_spi_max_transfer_size;
ctlr->handle_err = rockchip_spi_handle_err;
// 请求 TX DMA 通道
ctlr->dma_tx = dma_request_chan(rs->dev, "tx");
if (IS_ERR(ctlr->dma_tx)) {
if (PTR_ERR(ctlr->dma_tx) == -EPROBE_DEFER) {
ret = -EPROBE_DEFER;
goto err_disable_pm_runtime;
}
dev_warn(rs->dev, "Failed to request TX DMA channel\n");
ctlr->dma_tx = NULL;
}
// 请求 RX DMA 通道
ctlr->dma_rx = dma_request_chan(rs->dev, "rx");
if (IS_ERR(ctlr->dma_rx)) {
if (PTR_ERR(ctlr->dma_rx) == -EPROBE_DEFER) {
ret = -EPROBE_DEFER;
goto err_free_dma_tx;
}
dev_warn(rs->dev, "Failed to request RX DMA channel\n");
ctlr->dma_rx = NULL;
}
// 如果 TX 和 RX DMA 通道均成功请求
if (ctlr->dma_tx && ctlr->dma_rx) {
rs->dma_addr_tx = mem->start + ROCKCHIP_SPI_TXDR;
rs->dma_addr_rx = mem->start + ROCKCHIP_SPI_RXDR;
ctlr->can_dma = rockchip_spi_can_dma;
}
// 检查 SPI 版本并设置 cs_inactive
switch (readl_relaxed(rs->regs + ROCKCHIP_SPI_VERSION)) {
case ROCKCHIP_SPI_VER2_TYPE1:
case ROCKCHIP_SPI_VER2_TYPE2:
if (ctlr->can_dma && slave_mode)
rs->cs_inactive = true;
else
rs->cs_inactive = false;
break;
default:
rs->cs_inactive = false;
}
// 获取引脚控制
pinctrl = devm_pinctrl_get(&pdev->dev);
if (!IS_ERR(pinctrl)) {
rs->high_speed_state = pinctrl_lookup_state(pinctrl, "high_speed");
if (IS_ERR_OR_NULL(rs->high_speed_state)) {
dev_warn(&pdev->dev, "no high_speed pinctrl state\n");
rs->high_speed_state = NULL;
}
}
// 注册 SPI 控制器
ret = devm_spi_register_controller(&pdev->dev, ctlr);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register controller\n");
goto err_free_dma_rx;
}
return 0;
err_free_dma_rx:
if (ctlr->dma_rx)
dma_release_channel(ctlr->dma_rx);
err_free_dma_tx:
if (ctlr->dma_tx)
dma_release_channel(ctlr->dma_tx);
err_disable_pm_runtime:
pm_runtime_disable(&pdev->dev);
err_disable_spiclk:
clk_disable_unprepare(rs->spiclk);
err_disable_apbclk:
clk_disable_unprepare(rs->apb_pclk);
err_put_ctlr:
spi_controller_put(ctlr);
return ret;
}
其中第3-182行内容都是对SPI控制器属性的解析、对SPI控制器的一些设置,最终在185行调用了devm_spi_register_controller函数注册了SPI控制器,devm_spi_register_controller函数内容如下所示:
int devm_spi_register_controller(struct device *dev, struct spi_controller *ctlr)
{
struct spi_controller **ptr;
int ret;
// 分配设备资源管理器(devres)内存,用于自动释放控制器资源
ptr = devres_alloc(devm_spi_unregister, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return -ENOMEM; // 内存分配失败
// 注册 SPI 控制器
ret = spi_register_controller(ctlr);
if (!ret) {
// 注册成功,将指针存储在设备资源管理器中
*ptr = ctlr;
devres_add(dev, ptr);
} else {
// 注册失败,释放分配的资源
devres_free(ptr);
}
return ret; // 返回注册结果
}
该函数又会调用spi_register_controller函数来注册SPI控制器,spi_register_controller函数具体内容如下所示:
int spi_register_controller(struct spi_controller *ctlr)
{
struct device *dev = ctlr->dev.parent;
struct boardinfo *bi;
int status = -ENODEV;
int id, first_dynamic;
if (!dev)
return -ENODEV;
/*
* 在注册 SPI 控制器之前,确保所有必要的操作已实现
*/
status = spi_controller_check_ops(ctlr);
if (status)
return status;
// 如果不是 SPI 从控制器,注册主控制器
if (!spi_controller_is_slave(ctlr)) {
status = of_spi_register_master(ctlr);
if (status)
return status;
}
/* 即使只有一个总是被选中的设备,至少也必须有一个片选 */
if (ctlr->num_chipselect == 0)
return -EINVAL;
if (ctlr->bus_num >= 0) {
/* 固定总线编号的设备必须使用该编号进行检查 */
mutex_lock(&board_lock);
id = idr_alloc(&spi_master_idr, ctlr, ctlr->bus_num,
ctlr->bus_num + 1, GFP_KERNEL);
mutex_unlock(&board_lock);
if (WARN(id < 0, "couldn't get idr"))
return id == -ENOSPC ? -EBUSY : id;
ctlr->bus_num = id;
} else if (ctlr->dev.of_node) {
/* 使用 Linux 的 idr 分配动态总线编号 */
id = of_alias_get_id(ctlr->dev.of_node, "spi");
if (id >= 0) {
ctlr->bus_num = id;
mutex_lock(&board_lock);
id = idr_alloc(&spi_master_idr, ctlr, ctlr->bus_num,
ctlr->bus_num + 1, GFP_KERNEL);
mutex_unlock(&board_lock);
if (WARN(id < 0, "couldn't get idr"))
return id == -ENOSPC ? -EBUSY : id;
}
}
if (ctlr->bus_num < 0) {
first_dynamic = of_alias_get_highest_id("spi");
if (first_dynamic < 0)
first_dynamic = 0;
else
first_dynamic++;
mutex_lock(&board_lock);
id = idr_alloc(&spi_master_idr, ctlr, first_dynamic, 0, GFP_KERNEL);
mutex_unlock(&board_lock);
if (WARN(id < 0, "couldn't get idr"))
return id;
ctlr->bus_num = id;
}
INIT_LIST_HEAD(&ctlr->queue);
spin_lock_init(&ctlr->queue_lock);
spin_lock_init(&ctlr->bus_lock_spinlock);
mutex_init(&ctlr->bus_lock_mutex);
mutex_init(&ctlr->io_mutex);
ctlr->bus_lock_flag = 0;
init_completion(&ctlr->xfer_completion);
if (!ctlr->max_dma_len)
ctlr->max_dma_len = INT_MAX;
/* 注册设备,之后用户空间将看到它。
* 如果总线 ID 正在使用,注册将失败。
*/
dev_set_name(&ctlr->dev, "spi%u", ctlr->bus_num);
status = device_add(&ctlr->dev);
if (status < 0) {
/* 释放总线 ID */
mutex_lock(&board_lock);
idr_remove(&spi_master_idr, ctlr->bus_num);
mutex_unlock(&board_lock);
goto done;
}
dev_dbg(dev, "registered %s %s\n",
spi_controller_is_slave(ctlr) ? "slave" : "master",
dev_name(&ctlr->dev));
/*
* 如果我们使用的是队列驱动程序,则启动队列。注意,如果驱动程序仅支持高级内存操作,
* 则不需要队列逻辑。
*/
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);
if (status) {
device_del(&ctlr->dev);
/* 释放总线 ID */
mutex_lock(&board_lock);
idr_remove(&spi_master_idr, ctlr->bus_num);
mutex_unlock(&board_lock);
goto done;
}
}
/* 添加统计信息 */
spin_lock_init(&ctlr->statistics.lock);
mutex_lock(&board_lock);
list_add_tail(&ctlr->list, &spi_controller_list);
list_for_each_entry(bi, &board_list, list)
spi_match_controller_to_boardinfo(ctlr, &bi->board_info);
mutex_unlock(&board_lock);
/* 注册设备树和 ACPI 中的设备 */
of_register_spi_devices(ctlr);
acpi_register_spi_devices(ctlr);
done:
return status;
}
在该函数的第117行会调用设备树资源注册函数 of_register_spi_devices对SPI的子节点设备树进行注册,该函数的具体内容如下所示:
static void of_register_spi_devices(struct spi_controller *ctlr)
{
struct spi_device *spi;
struct device_node *nc;
// 如果控制器没有设备树节点,则直接返回
if (!ctlr->dev.of_node)
return;
// 遍历控制器设备树节点下的每个子节点
for_each_available_child_of_node(ctlr->dev.of_node, nc) {
// 如果该节点已被标记为已填充,则跳过该节点
if (of_node_test_and_set_flag(nc, OF_POPULATED))
continue;
// 为该节点注册一个 SPI 设备
spi = of_register_spi_device(ctlr, nc);
// 如果注册失败,记录警告信息并清除该节点的已填充标记
if (IS_ERR(spi)) {
dev_warn(&ctlr->dev, "Failed to create SPI device for %pOF\n", nc);
of_node_clear_flag(nc, OF_POPULATED);
}
}
}
第10-24行会遍历控制器设备树节点下的每个子节点,通过of_register_spi_device函数注册该SPI子节点,of_register_spi_device函数具体内容如下所示:
static struct spi_device *
of_register_spi_device(struct spi_controller *ctlr, struct device_node *nc)
{
struct spi_device *spi;
int rc;
/* 分配一个 spi_device */
spi = spi_alloc_device(ctlr);
if (!spi) {
dev_err(&ctlr->dev, "spi_device alloc error for %pOF\n", nc);
rc = -ENOMEM;
goto err_out;
}
/* 选择设备驱动 */
rc = of_modalias_node(nc, spi->modalias, sizeof(spi->modalias));
if (rc < 0) {
dev_err(&ctlr->dev, "cannot find modalias for %pOF\n", nc);
goto err_out;
}
/* 解析设备树中的 SPI 信息 */
rc = of_spi_parse_dt(ctlr, spi, nc);
if (rc)
goto err_out;
/* 在设备结构中存储指向节点的指针 */
of_node_get(nc);
spi->dev.of_node = nc;
spi->dev.fwnode = of_fwnode_handle(nc);
/* 注册新设备 */
rc = spi_add_device(spi);
if (rc) {
dev_err(&ctlr->dev, "spi_device register error %pOF\n", nc);
goto err_of_node_put;
}
return spi;
err_of_node_put:
of_node_put(nc);
err_out:
spi_dev_put(spi);
return ERR_PTR(rc);
}
在该函数的第23行调用了of_spi_parse_dt函数来解析设备树子节点中的SPI信息,of_spi_parse_dt函数具体内容如下所示:
static int of_spi_parse_dt(struct spi_controller *ctlr, struct spi_device *spi,
struct device_node *nc)
{
u32 value;
int rc;
/* 设置模式 (时钟相位/极性等) */
if (of_property_read_bool(nc, "spi-cpha"))
spi->mode |= SPI_CPHA;
if (of_property_read_bool(nc, "spi-cpol"))
spi->mode |= SPI_CPOL;
if (of_property_read_bool(nc, "spi-cs-high"))
spi->mode |= SPI_CS_HIGH;
if (of_property_read_bool(nc, "spi-3wire"))
spi->mode |= SPI_3WIRE;
if (of_property_read_bool(nc, "spi-lsb-first"))
spi->mode |= SPI_LSB_FIRST;
/* 设置设备的 DUAL/QUAD 模式 */
if (!of_property_read_u32(nc, "spi-tx-bus-width", &value)) {
switch (value) {
case 1:
break;
case 2:
spi->mode |= SPI_TX_DUAL;
break;
case 4:
spi->mode |= SPI_TX_QUAD;
break;
default:
dev_warn(&ctlr->dev, "spi-tx-bus-width %d not supported\n", value);
break;
}
}
if (!of_property_read_u32(nc, "spi-rx-bus-width", &value)) {
switch (value) {
case 1:
break;
case 2:
spi->mode |= SPI_RX_DUAL;
break;
case 4:
spi->mode |= SPI_RX_QUAD;
break;
default:
dev_warn(&ctlr->dev, "spi-rx-bus-width %d not supported\n", value);
break;
}
}
/* 如果是 SPI 从设备 */
if (spi_controller_is_slave(ctlr)) {
if (strcmp(nc->name, "slave")) {
dev_err(&ctlr->dev, "%pOF is not called 'slave'\n", nc);
return -EINVAL;
}
return 0;
}
/* 获取设备地址 */
rc = of_property_read_u32(nc, "reg", &value);
if (rc) {
dev_err(&ctlr->dev, "%pOF has no valid 'reg' property (%d)\n", nc, rc);
return rc;
}
spi->chip_select = value;
/* 获取设备速度 */
rc = of_property_read_u32(nc, "spi-max-frequency", &value);
if (rc) {
dev_err(&ctlr->dev, "%pOF has no valid 'spi-max-frequency' property (%d)\n", nc, rc);
return rc;
}
spi->max_speed_hz = value;
return 0;
}
第7-17行:通过检查设备树中的相应属性来设置SPI设备的模式(如时钟相位、极性、高电平片选、三线模式、最低有效位优先)。
第19-50行:从设备树属性 spi-tx-bus-width 和 spi-rx-bus-width 中读取发送和接收总线宽度,并相应地设置 SPI 设备的模式。
第52-59行:检查节点名称是否为 "slave",如果不是,则返回错误。
第61-66行:从设备树属性reg中读取设备地址,并将其设置为 SPI 设备的片选号。
第70-74行:从设备树属性 spi-max-frequency 中读取设备的最大频率,并将其设置为 SPI 设备的最大速度。
如果设备树不存在reg和spi-max-frequency 两个属性则会返回rc,这就导致上一级函数of_register_spi_device会返回错误,从而无法成功注册SPI设备、不能成功解析设备树节点,最终导致编写的SPI设备驱动无法正常匹配,进入probe函数。
至此,对于SPI设备的注册过程就讲解完成了。