RK3568驱动指南|第十六篇 SPI-第186章 通用SPI外设代码框架编写

瑞芯微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设备的注册过程就讲解完成了。

  • 14
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值