设备树学习之(九)SPI设备注册过程

本文详细解析了SPI设备在Linux系统中的注册过程,包括设备树配置的重要性、控制器节点及子节点的处理方式,以及如何通过设备树属性正确配置SPI设备参数。

 

转载地址:https://blog.youkuaiyun.com/lizuobin2/article/details/54565166

开发板:tiny4412SDK + S702 + 4GB Flash 

要移植的内核版本:Linux-4.4.0 (支持device tree) 
u-boot版本:友善之臂自带的 U-Boot 2010.12 
busybox版本:busybox 1.25

目标: 
同 i2c 一样,分析 spi 设备的注册过程,其实是一模一样的。

int spi_register_master(struct spi_master *master)  //注册控制器驱动
    of_register_spi_devices(master);
        for_each_available_child_of_node(master->dev.of_node, nc) //控制器节点的子节点
            of_register_spi_device(master, nc);
static struct spi_device * of_register_spi_device(struct spi_master *master, struct device_node *nc)
{
    struct spi_device *spi;
    int rc;
    u32 value;

    /* 分配一个 spi device ,从属于 master*/
    spi = spi_alloc_device(master);

    /* 获取 compatibel 属性,具体看后边 */
    rc = of_modalias_node(nc, spi->modalias, sizeof(spi->modalias));

    /* 获取 reg 属性作为片选编号 */
    rc = of_property_read_u32(nc, "reg", &value);
    spi->chip_select = value;

    /* mode 设置 */
    if (of_find_property(nc, "spi-cpha", NULL))
        spi->mode |= SPI_CPHA;
    if (of_find_property(nc, "spi-cpol", NULL))
        spi->mode |= SPI_CPOL;
    if (of_find_property(nc, "spi-cs-high", NULL))
        spi->mode |= SPI_CS_HIGH;
    if (of_find_property(nc, "spi-3wire", NULL))
        spi->mode |= SPI_3WIRE;
    if (of_find_property(nc, "spi-lsb-first", NULL))
        spi->mode |= SPI_LSB_FIRST;

    /* Device DUAL/QUAD mode */
    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(&master->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(&master->dev,
                "spi-rx-bus-width %d not supported\n",
                value);
            break;
        }
    }

    /* 获取最大速度 */
    rc = of_property_read_u32(nc, "spi-max-frequency", &value);
    spi->max_speed_hz = value;

    /* Store a pointer to the node in the device structure */
    of_node_get(nc);
    spi->dev.of_node = nc;

    /* 注册 spi device */
    rc = spi_add_device(spi);
    return spi;
}
int of_modalias_node(struct device_node *node, char *modalias, int len)
{
    const char *compatible, *p;
    int cplen;

    compatible = of_get_property(node, "compatible", &cplen);
    if (!compatible || strlen(compatible) > cplen)
        return -ENODEV;
    p = strchr(compatible, ',');
    strlcpy(modalias, p ? p + 1 : compatible, len);//如果compatibel属性中有“,”则取“,”之后的内容,否则取全部,它作为匹配依据
    return 0;
}
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);  

    if (sdrv->id_table)  
        return !!spi_match_id(sdrv->id_table, spi);  

    return strcmp(spi->modalias, drv->name) == 0;  
}  
static const struct spi_device_id *spi_match_id(const struct spi_device_id *id, const struct spi_device *sdev)
{
    while (id->name[0]) {
        if (!strcmp(sdev->modalias, id->name))
            return id;
        id++;
    }
    return NULL;
}

启动报错:

[    0.962673] spi spi0.0: child node 'controller-data' not found               
[    0.968455] spi spi0.0: No CS for SPI(0)                                     
[    0.972360] s3c64xx-spi 13920000.spi: can't setup spi0.0, status -19         
[    0.978693] spi_device register error /spi@13920000/spidev@0                 
[    0.984339] spi_master spi0: Failed to create SPI device for /spi@13920000/s0
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) 

    /* Set the bus ID string */
    spi_dev_set_name(spi);

    mutex_lock(&spi_add_lock);

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

    if (master->cs_gpios)
        spi->cs_gpio = master->cs_gpios[spi->chip_select];

    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;
}
EXPORT_SYMBOL_GPL(spi_add_device);

 

static int s3c64xx_spi_setup(struct spi_device *spi)
{
    struct s3c64xx_spi_csinfo *cs = spi->controller_data;
    struct s3c64xx_spi_driver_data *sdd;
    struct s3c64xx_spi_info *sci;
    int err;

    sdd = spi_master_get_devdata(spi->master);
    if (spi->dev.of_node) {
        cs = s3c64xx_get_slave_ctrldata(spi);
        spi->controller_data = cs;
    } else if (cs) {
        /* On non-DT platforms the SPI core will set spi->cs_gpio
         * to -ENOENT. The GPIO pin used to drive the chip select
         * is defined by using platform data so spi->cs_gpio value
         * has to be override to have the proper GPIO pin number.
         */
        spi->cs_gpio = cs->line;
    }

    if (IS_ERR_OR_NULL(cs)) {
        dev_err(&spi->dev, "No CS for SPI(%d)\n", spi->chip_select);
        return -ENODEV;
    }

    if (!spi_get_ctldata(spi)) {
        if (gpio_is_valid(spi->cs_gpio)) {
            err = gpio_request_one(spi->cs_gpio, GPIOF_OUT_INIT_HIGH,
                           dev_name(&spi->dev));
            if (err) {
                dev_err(&spi->dev,
                    "Failed to get /CS gpio [%d]: %d\n",
                    spi->cs_gpio, err);
                goto err_gpio_req;
            }
        }

        spi_set_ctldata(spi, cs);
    }

    sci = sdd->cntrlr_info;

    pm_runtime_get_sync(&sdd->pdev->dev);

    /* Check if we can provide the requested rate */
    if (!sdd->port_conf->clk_from_cmu) {
        u32 psr, speed;

        /* Max possible */
        speed = clk_get_rate(sdd->src_clk) / 2 / (0 + 1);

        if (spi->max_speed_hz > speed)
            spi->max_speed_hz = speed;

        psr = clk_get_rate(sdd->src_clk) / 2 / spi->max_speed_hz - 1;
        psr &= S3C64XX_SPI_PSR_MASK;
        if (psr == S3C64XX_SPI_PSR_MASK)
            psr--;

        speed = clk_get_rate(sdd->src_clk) / 2 / (psr + 1);
        if (spi->max_speed_hz < speed) {
            if (psr+1 < S3C64XX_SPI_PSR_MASK) {
                psr++;
            } else {
                err = -EINVAL;
                goto setup_exit;
            }
        }

        speed = clk_get_rate(sdd->src_clk) / 2 / (psr + 1);
        if (spi->max_speed_hz >= speed) {
            spi->max_speed_hz = speed;
        } else {
            dev_err(&spi->dev, "Can't set %dHz transfer speed\n",
                spi->max_speed_hz);
            err = -EINVAL;
            goto setup_exit;
        }
    }

    pm_runtime_mark_last_busy(&sdd->pdev->dev);
    pm_runtime_put_autosuspend(&sdd->pdev->dev);
    if (!(sdd->port_conf->quirks & S3C64XX_SPI_QUIRK_CS_AUTO))
        writel(S3C64XX_SPI_SLAVE_SIG_INACT, sdd->regs + S3C64XX_SPI_SLAVE_SEL);
    return 0;

setup_exit:
    pm_runtime_mark_last_busy(&sdd->pdev->dev);
    pm_runtime_put_autosuspend(&sdd->pdev->dev);
    /* setup() returns with device de-selected */
    if (!(sdd->port_conf->quirks & S3C64XX_SPI_QUIRK_CS_AUTO))
        writel(S3C64XX_SPI_SLAVE_SIG_INACT, sdd->regs + S3C64XX_SPI_SLAVE_SEL);

    if (gpio_is_valid(spi->cs_gpio))
        gpio_free(spi->cs_gpio);
    spi_set_ctldata(spi, NULL);

err_gpio_req:
    if (spi->dev.of_node)
        kfree(cs);

    return err;
}
static struct s3c64xx_spi_csinfo *s3c64xx_get_slave_ctrldata( struct spi_device *spi)
{
    struct s3c64xx_spi_csinfo *cs;
    struct device_node *slave_np, *data_np = NULL;
    u32 fb_delay = 0;

    slave_np = spi->dev.of_node;
    if (!slave_np) {
        dev_err(&spi->dev, "device node not found\n");
        return ERR_PTR(-EINVAL);
    }

    data_np = of_get_child_by_name(slave_np, "controller-data");
    if (!data_np) {
        dev_err(&spi->dev, "child node 'controller-data' not found\n");
        return ERR_PTR(-EINVAL);
    }

    cs = kzalloc(sizeof(*cs), GFP_KERNEL);
    if (!cs) {
        of_node_put(data_np);
        return ERR_PTR(-ENOMEM);
    }

    of_property_read_u32(data_np, "samsung,spi-feedback-delay", &fb_delay);
    cs->fb_delay = fb_delay;
    of_node_put(data_np);
    return cs;
}

​​​​​​​

最开始,就是因为没有指定 controller-data 导致设备无法匹配到驱动程序,查了半天。下面给出后面要写的 spi flash 的设备树:

&spi_0 {
        status = "okay";

        cs-gpios = <&gpb 1 GPIO_ACTIVE_HIGH>;
        spi_flash@0 {
                compatible = "tiny4412,spi_flash";
                spi-max-frequency = <10000000>;
                reg = <0>;
                controller-data {
                        samsung,spi-feedback-delay = <0>;
                };
        };
};
<think>好的,我现在需要帮助用户解答关于LinuxSPI设备树驱动开发的问题。首先,用户明确提到了设备树配置,所以我要确保回答中包含设备树的相关内容。接下来,我需要回忆一下Linux驱动开发的基本流程,尤其是SPI驱动的部分。用户可能已经了解一些驱动开发的基础,但可能需要具体的步骤和示例。我应该分步骤说明,从设备树配置开始,然后到驱动模块的编写,最后是加载和测试。这样结构清晰,用户容易跟随。 设备树部分,需要解释如何添加SPI节点,包括兼容性字符串、寄存器地址、时钟频率等。同时,要提到具体的示例代码,比如spi_icm20608这样的设备节点,这样用户有参考的实例。记得使用正确的LaTeX格式,行内公式用$...$,独立公式用$$...$$,不过这个问题可能涉及代码较多,数学公式较少,但还是要检查是否有需要的地方。接下来是驱动模块的开发,这里需要提到probe函数、初始化流程,比如分配内存、注册字符设备、创建设备节点等。引用中的probe函数示例可能有用,可以引用[^3]来说明。同时,要提到SPI核心API,如spi_setup、spi_sync等,这些都是关键点。然后,用户可能需要知道如何编译和加载驱动模块,这部分要给出具体的命令,比如insmod或者modprobe,以及如何将模块复制到开发板的系统目录,引用[^1]中的路径示例可能会有帮助。此外,测试部分需要说明如何通过应用程序与驱动交互,或者使用sysfs进行简单测试。最后,相关问题的生成需要考虑用户可能的后续疑问,比如设备树语法、调试方法、用户空间交互以及实际项目案例。这些问题能够帮助用户深入学习和解决问题。 需要确保回答中的引用正确,比如设备树配置引用[^2],驱动代码结构引用[^3],路径操作引用[^1]。同时,语言要简洁明了,避免过于技术化的术语,但又要准确无误。检查是否有遗漏的步骤,比如设备树的编译和加载,这在开发过程中很重要,必须提及。 总结一下,回答的结构应该是:设备树配置 ->驱动开发步骤 ->编译加载->测试验证->参考资料和相关问题。确保每个部分都有示例代码或命令,这样用户可以直接应用。同时,注意使用中文,符合用户要求,并且LaTeX格式正确,虽然这个问题可能涉及代码较多,但数学公式可能较少,但如果有的话要正确应用。</think>### Linux SPI设备树驱动开发教程 #### 一、设备树配置 在Linux SPI驱动开发中,**设备树(Device Tree)** 是硬件描述的核心。以下是典型SPI设备节点的配置示例: ```dts &spi1 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&spi1_pins>; cs-gpios = <&gpio 20 GPIO_ACTIVE_LOW>; icm20608: icm20608@0 { compatible = "invensense,icm20608"; // 驱动匹配标识[^3] reg = <0>; // 片选信号编号 spi-max-frequency = <10000000>; // SPI时钟频率 interrupt-parent = <&gpio>; interrupts = <21 IRQ_TYPE_EDGE_RISING>; }; }; ``` - **关键参数**: - `compatible`:与驱动代码中的`of_device_id`匹配 - `spi-max-frequency`:定义通信速率(单位:Hz) - `reg`:指定片选信号编号(对应`cs-gpios`数组索引) 设备树编译后需通过`bootloader`加载到内核[^2]。 --- #### 二、驱动开发步骤 1. **驱动框架定义** ```c static const struct of_device_id spi_ids[] = { { .compatible = "invensense,icm20608" }, // 匹配设备树节点[^3] {} }; static struct spi_driver my_spi_driver = { .probe = spi_probe, .remove = spi_remove, .driver = { .name = "icm20608", .of_match_table = spi_ids, } }; ``` 2. **Probe函数实现** ```c static int spi_probe(struct spi_device *spi) { struct icm20608_dev *dev; // 1. 分配设备内存 dev = devm_kzalloc(&spi->dev, sizeof(*dev), GFP_KERNEL); // 2. 初始化SPI通信参数 spi->mode = SPI_MODE_0; // 时钟相位/极性配置 spi_setup(spi); // 应用参数设置[^3] // 3. 注册字符设备 alloc_chrdev_region(&dev->devno, 0, 1, "icm20608"); cdev_init(&dev->cdev, &fops); cdev_add(&dev->cdev, dev->devno, 1); // 4. 创建设备节点 dev->class = class_create(THIS_MODULE, "icm20608"); device_create(dev->class, NULL, dev->devno, NULL, "icm20608"); // 5. 数据收发示例 u8 tx_buf[2] = {0x75, 0x00}; // 读取WHO_AM_I寄存器 u8 rx_buf[2]; spi_write_then_read(spi, tx_buf, 2, rx_buf, 2); } ``` --- #### 三、驱动编译与加载 1. **Makefile配置** ```makefile obj-m += spi_icm20608.o KDIR := /path/to/kernel/source all: make -C $(KDIR) M=$(PWD) modules ``` 2. **部署到开发板** ```bash # 将驱动模块拷贝到目标系统 sudo cp spi_icm20608.ko /lib/modules/$(uname -r)/kernel/drivers/spi/[^1] # 加载驱动模块 depmod -a modprobe spi_icm20608 ``` --- #### 四、测试与调试 1. **sysfs接口验证** ```bash # 查看SPI设备列表 ls /sys/bus/spi/devices/ # 读取寄存器值(需驱动实现sysfs接口) cat /sys/class/icm20608/icm20608/whoami ``` 2. **逻辑分析仪调试** 建议使用PulseView等工具抓取SCLK/MOSI/MISO波形,验证时序是否符合设备规格。 --- #### 五、参考资料 1. [Linux内核SPI子系统文档](https://www.kernel.org/doc/html/latest/driver-api/spi.html) [^2] 2. `spi.h`头文件中的API定义(包含spi_sync、spi_message等函数) ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值