美文网首页
Linux SPI通过设备树文件添加设备

Linux SPI通过设备树文件添加设备

作者: xujiawei | 来源:发表于2020-01-08 10:01 被阅读0次

一、设备树文件编写

spi_master: spi_master
{
    #address-cells = <1>;
    #size-cells = <0>;
    compatible = "spi_master";
    io_phy_addr = <0x1f000000>;
    banks = <0x1110>,<0x1111>,<0x1038>,<0x101E>;
    interrupts = <GIC_SPI INT_IRQ_MSPI_0 IRQ_TYPE_LEVEL_HIGH>,<GIC_SPI INT_IRQ_MSPI_1 IRQ_TYPE_LEVEL_HIGH>;
    spi0_mode = <1>;
    spi1_mode = <3>;
    status = "okay";

    spi_device: spi_device
    {
         compatible = "spi_device";
         reg = <0x0>;
         spi-max-frequency = <10000000>;
         status = "okay";
    };
};

如上DTS文件片段,SPI Device 节点必须定义在 SPI Master 节点下,其中 compatible 属性和 reg 属性,以上 compatible 属性用于匹配对应的 Driver 程序,reg 属性用于指定使用的 SPI Master 的编号,SPI 相关设备树文件识别见下文讲解。


二、代码流程

匹配设备树文件在SPI子系统中有两个地方:在 spi_register_master() 中匹配和在 device register 时通过内核的通知链(notifier_block)来调用设备树匹配相关程序。

  • 在 spi_register_master() 中匹配:
//driver/spi/spi.c
int spi_register_master(struct spi_master *master)
{
    static atomic_t      dyn_bus_id = ATOMIC_INIT((1<<15) - 1);
    struct device        *dev = master->dev.parent;
    struct boardinfo     *bi;
    int            status = -ENODEV;
    int            dynamic = 0;

    if (!dev)
        return -ENODEV;

    status = of_spi_register_master(master);
    if (status)
        return status;

    /* even if it's just one always-selected device, there must
     * be at least one chipselect
     */
    if (master->num_chipselect == 0)
        return -EINVAL;

    if ((master->bus_num < 0) && master->dev.of_node)
        master->bus_num = of_alias_get_id(master->dev.of_node, "spi");

    /* convention:  dynamically assigned bus IDs count down from the max */
    if (master->bus_num < 0) {
        /* FIXME switch to an IDR based scheme, something like
         * I2C now uses, so we can't run out of "dynamic" IDs
         */
        master->bus_num = atomic_dec_return(&dyn_bus_id);
        dynamic = 1;
    }

    INIT_LIST_HEAD(&master->queue);
    spin_lock_init(&master->queue_lock);
    spin_lock_init(&master->bus_lock_spinlock);
    mutex_init(&master->bus_lock_mutex);
    mutex_init(&master->io_mutex);
    master->bus_lock_flag = 0;
    init_completion(&master->xfer_completion);
    if (!master->max_dma_len)
        master->max_dma_len = INT_MAX;

    /* register the device, then userspace will see it.
     * registration fails if the bus ID is in use.
     */
    dev_set_name(&master->dev, "spi%u", master->bus_num);
    status = device_add(&master->dev);
    if (status < 0)
        goto done;
    dev_dbg(dev, "registered master %s%s\n", dev_name(&master->dev),
            dynamic ? " (dynamic)" : "");

    /* If we're using a queued driver, start the queue */
    if (master->transfer)
        dev_info(dev, "master is unqueued, this is deprecated\n");
    else {
        status = spi_master_initialize_queue(master);
        if (status) {
            device_del(&master->dev);
            goto done;
        }
    }
    /* add statistics */
    spin_lock_init(&master->statistics.lock);

    mutex_lock(&board_lock);
    list_add_tail(&master->list, &spi_master_list);
    list_for_each_entry(bi, &board_list, list)
        spi_match_master_to_boardinfo(master, &bi->board_info);
    mutex_unlock(&board_lock);

    /* Register devices from the device tree and ACPI */
    of_register_spi_devices(master);  // 设备树匹配操作
    acpi_register_spi_devices(master);
done:
    return status;
}
//driver/spi/spi.c
static void of_register_spi_devices(struct spi_master *master)
{
    struct spi_device *spi;
    struct device_node *nc;

    if (!master->dev.of_node)
        return;

    for_each_available_child_of_node(master->dev.of_node, nc) {
        if (of_node_test_and_set_flag(nc, OF_POPULATED))
            continue;
        spi = of_register_spi_device(master, nc);  // 设备树匹配操作
        if (IS_ERR(spi)) {
            dev_warn(&master->dev, "Failed to create SPI device for %s\n",
                nc->full_name);
            of_node_clear_flag(nc, OF_POPULATED);
        }
    }
}
  • 在 device register 时匹配:
//driver/spi/spi.c
static int __init spi_init(void)
{
    int    status;

    buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
    if (!buf) {
        status = -ENOMEM;
        goto err0;
    }

    status = bus_register(&spi_bus_type);
    if (status < 0)
        goto err1;

    status = class_register(&spi_master_class);
    if (status < 0)
        goto err2;

    if (IS_ENABLED(CONFIG_OF_DYNAMIC))
        WARN_ON(of_reconfig_notifier_register(&spi_of_notifier));
    if (IS_ENABLED(CONFIG_ACPI))
        WARN_ON(acpi_reconfig_notifier_register(&spi_acpi_notifier));

    return 0;

err2:
    bus_unregister(&spi_bus_type);
err1:
    kfree(buf);
    buf = NULL;
err0:
    return status;
}

postcore_initcall(spi_init);

在 device register 时,需配置 CONFIG_OF_DYNAMIC 宏以开启动态匹配才能够使用设备树添加设备,该宏在 menuconfig/Device Drivers/Device Tree and Open Firmware support 中开启,如下图:


//driver/spi/spi.c
static struct notifier_block spi_of_notifier = {
    .notifier_call = of_spi_notify,
};

static int of_spi_notify(struct notifier_block *nb, unsigned long action,
             void *arg)
{
    struct of_reconfig_data *rd = arg;
    struct spi_master *master;
    struct spi_device *spi;

    switch (of_reconfig_get_state_change(action, arg)) {
    case OF_RECONFIG_CHANGE_ADD:
        master = of_find_spi_master_by_node(rd->dn->parent);
        if (master == NULL)
            return NOTIFY_OK;    /* not for us */

        if (of_node_test_and_set_flag(rd->dn, OF_POPULATED)) {
            put_device(&master->dev);
            return NOTIFY_OK;
        }

        spi = of_register_spi_device(master, rd->dn);  // 设备树匹配操作
        put_device(&master->dev);

        if (IS_ERR(spi)) {
            pr_err("%s: failed to create for '%s'\n",
                    __func__, rd->dn->full_name);
            of_node_clear_flag(rd->dn, OF_POPULATED);
            return notifier_from_errno(PTR_ERR(spi));
        }
        break;

    case OF_RECONFIG_CHANGE_REMOVE:
        /* already depopulated? */
        if (!of_node_check_flag(rd->dn, OF_POPULATED))
            return NOTIFY_OK;

        /* find our device by node */
        spi = of_find_spi_device_by_node(rd->dn);
        if (spi == NULL)
            return NOTIFY_OK;    /* no? not meant for us */

        /* unregister takes one ref away */
        spi_unregister_device(spi);

        /* and put the reference of the find */
        put_device(&spi->dev);
        break;
    }

    return NOTIFY_OK;
}
//driver/spi/spi.c
static struct spi_device *
of_register_spi_device(struct spi_master *master, struct device_node *nc)
{
    struct spi_device *spi;
    int rc;
    u32 value;

    /* Alloc an spi_device */
    spi = spi_alloc_device(master);
    if (!spi) {
        dev_err(&master->dev, "spi_device alloc error for %s\n",
            nc->full_name);
        rc = -ENOMEM;
        goto err_out;
    }

    /* Select device driver */
    rc = of_modalias_node(nc, spi->modalias,
                sizeof(spi->modalias));
    if (rc < 0) {
        dev_err(&master->dev, "cannot find modalias for %s\n",
            nc->full_name);
        goto err_out;
    }

    /* Device address */
    rc = of_property_read_u32(nc, "reg", &value);
    if (rc) {
        dev_err(&master->dev, "%s has no valid 'reg' property (%d)\n",
            nc->full_name, rc);
        goto err_out;
    }
    spi->chip_select = value;

    /* Mode (clock phase/polarity/etc.) */
    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;
        }
    }

    /* Device speed */
    rc = of_property_read_u32(nc, "spi-max-frequency", &value);
    if (rc) {
        dev_err(&master->dev, "%s has no valid 'spi-max-frequency' property (%d)\n",
            nc->full_name, rc);
        goto err_out;
    }
    spi->max_speed_hz = value;

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

    /* Register the new device */
    rc = spi_add_device(spi);
    if (rc) {
        dev_err(&master->dev, "spi_device register error %s\n",
            nc->full_name);
        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);
}

相关文章

网友评论

      本文标题:Linux SPI通过设备树文件添加设备

      本文链接:https://www.haomeiwen.com/subject/ysvjactx.html