Linux设备驱动模型SPI之二

来源:互联网 发布:js 按钮显示隐藏 编辑:程序博客网 时间:2024/05/19 21:00

本文详细分析TI Davinci DM368 SPI接口的平台设备注册、外部设备注册、平台驱动注册、外部驱动程序注册。应该注意以下四点:

  1. 平台设备描述DM368芯片集成的SPI接口硬件描述
  2. 平台设备驱动描述如何控制DM368芯片的SPI接口,从而能够收发数据
  3. 外部设备描述的是挂在SPI接口上的设备(以下称为spi设备
  4. 外部驱动程序描述的是如何控制SPI接口上的外部设备(以下称为spi驱动

如下图所示平台设备与平台驱动通过平台总线连接在一起,类似的spi设备与spi驱动通过spi总线连接在一起
这里写图片描述

描述spi有三个数据结构:

  1. spi_master 描述SPI主机控制器,其实就是platform driver
  2. spi_driver 描述spi驱动
  3. spi_device 描述spi设备

spi_master嵌入在spi_device之中,也就是说spi_device中已经包含了spi控制器,更进一步讲,可以认为spi_device就是连接在spi接口上的spi设备,这样就将平台设备和平台设备驱动相关的知识给隐藏了。从而只专注于spi_driver、spi_device,他们两个也通过总线连接在一起。这样就和平台设备与平台设备驱动通过平台总线连接具有相似性,好处就是,可以实现内核代码公用。

这里写图片描述

本文对代码的追踪是按照调用顺序进行的

平台设备注册、平台设备驱动注册与连接

在…\arch\arm\mach-davinci\dm365.c之下

static struct platform_device dm365_spi0_device = {    .name = "spi_davinci",    .id = 0,    .dev = {        .dma_mask = &dm365_spi0_dma_mask,        .coherent_dma_mask = DMA_BIT_MASK(32),        .platform_data = &dm365_spi0_pdata,    },    .num_resources = ARRAY_SIZE(dm365_spi0_resources),    .resource = dm365_spi0_resources,};//定义了spi平台设备,name为“spi_davinci”,因此必然存在一个spi平台驱动,其名字为“spi_davinci”,注册在平台总线的设备通过这个名字进行匹配void __init dm365_init_spi0(unsigned chipselect_mask,        const struct spi_board_info *info, unsigned len){    davinci_cfg_reg(DM365_SPI0_SCLK);    davinci_cfg_reg(DM365_SPI0_SDI);    davinci_cfg_reg(DM365_SPI0_SDO);    /* not all slaves will be wired up */    if (chipselect_mask & BIT(0))        davinci_cfg_reg(DM365_SPI0_SDENA0);    if (chipselect_mask & BIT(1))        davinci_cfg_reg(DM365_SPI0_SDENA1);    spi_register_board_info(info, len);    platform_device_register(&dm365_spi0_device);//注册spi平台设备,因此在平台设备总线上挂着了一个spi平台设备}
//先初始化设备,然后向platform bus上添加设备int platform_device_register(struct platform_device *pdev){    device_initialize(&pdev->dev);//初始化    return platform_device_add(pdev);//添加设备}
int platform_device_add(struct platform_device *pdev){    int i, ret = 0;    if (!pdev)        return -EINVAL;    if (!pdev->dev.parent)        pdev->dev.parent = &platform_bus;//可以看到是在platform_bus上注册了一个设备,此处的platform_bus是一个全局变量    pdev->dev.bus = &platform_bus_type;    if (pdev->id != -1)        dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);    else        dev_set_name(&pdev->dev, "%s", pdev->name);    for (i = 0; i < pdev->num_resources; i++) {        struct resource *p, *r = &pdev->resource[i];        if (r->name == NULL)            r->name = dev_name(&pdev->dev);        p = r->parent;        if (!p) {            if (resource_type(r) == IORESOURCE_MEM)                p = &iomem_resource;            else if (resource_type(r) == IORESOURCE_IO)                p = &ioport_resource;        }        if (p && insert_resource(p, r)) {            printk(KERN_ERR                   "%s: failed to claim resource %d\n",                   dev_name(&pdev->dev), i);            ret = -EBUSY;            goto failed;        }    }    pr_debug("Registering platform device '%s'. Parent at %s\n",         dev_name(&pdev->dev), dev_name(pdev->dev.parent));    ret = device_add(&pdev->dev);//添加设备    if (ret == 0)        return ret; failed:    while (--i >= 0) {        struct resource *r = &pdev->resource[i];        unsigned long type = resource_type(r);        if (type == IORESOURCE_MEM || type == IORESOURCE_IO)            release_resource(r);    }    return ret;}
//只选取了与我们分析最为密切的一些函数int device_add(struct device *dev){    //...    error = bus_add_device(dev);//在总线上添加平台设备    //...    bus_probe_device(dev);//在总线上寻找与该平台设备相关的平台驱动    //...}
void bus_probe_device(struct device *dev){    struct bus_type *bus = dev->bus;    int ret;    if (bus && bus->p->drivers_autoprobe) {        ret = device_attach(dev);        WARN_ON(ret < 0);    }}//如果找到了平台驱动就将两者连接,没有则返回
//假如有,因为后面平台驱动的注册会有类似的匹配过程:平台驱动定义、平台驱动注册到平台总线上、在平台总线上寻找name匹配的平台驱动,下面我们忽略了平台设备驱动的代码分析过程,读者可以自行分析int device_attach(struct device *dev){    int ret = 0;    down(&dev->sem);    if (dev->driver) {        ret = device_bind_driver(dev);        if (ret == 0)            ret = 1;        else {            dev->driver = NULL;            ret = 0;        }    } else {        pm_runtime_get_noresume(dev);        ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);//在总线上寻找匹配的驱动,如果找到执行__device_attach函数        pm_runtime_put_sync(dev);    }    up(&dev->sem);    return ret;}
static int __driver_attach(struct device *dev, void *data){    struct device_driver *drv = data;    /*     * Lock device and try to bind to it. We drop the error     * here and always return 0, because we need to keep trying     * to bind to devices and some drivers will return an error     * simply if it didn't support the device.     *     * driver_probe_device() will spit a warning if there     * is an error.     */    if (!driver_match_device(drv, dev))//判断是否匹配        return 0;    if (dev->parent)    /* Needed for USB */        down(&dev->parent->sem);    down(&dev->sem);    if (!dev->driver)        driver_probe_device(drv, dev);    up(&dev->sem);    if (dev->parent)        up(&dev->parent->sem);    return 0;}

总结:将平台设备注册到平台总线,如果有对应的平台驱动,则将两者连接起来;将平台驱动注册到平台总线,如果有对应的平台设备,则将两者连接。

spi_master、spi_device与spi_driver注册与连接

在…\drivers\spi\davinci_spi.c下

static int __init davinci_spi_init(void){    return platform_driver_probe(&davinci_spi_driver, davinci_spi_probe);//执行驱动探测函数,该函数执行后调用davinci_spi_probe函数}//在驱动注册的时候首先执行davinci_spi_init函数
static int davinci_spi_probe(struct platform_device *pdev){    struct spi_master *master;    struct davinci_spi *dspi;    struct davinci_spi_platform_data *pdata;    struct resource *r, *mem;    resource_size_t dma_rx_chan = SPI_NO_RESOURCE;    resource_size_t dma_tx_chan = SPI_NO_RESOURCE;    resource_size_t dma_eventq = SPI_NO_RESOURCE;    int i = 0, ret = 0;    u32 spipc0;    pdata = pdev->dev.platform_data;    if (pdata == NULL) {        ret = -ENODEV;        goto err;    }    master = spi_alloc_master(&pdev->dev, sizeof(struct davinci_spi));//分配spi_master    if (master == NULL) {        ret = -ENOMEM;        goto err;    }    dev_set_drvdata(&pdev->dev, master);    dspi = spi_master_get_devdata(master);    if (dspi == NULL) {        ret = -ENOENT;        goto free_master;    }    r = platform_get_resource(pdev, IORESOURCE_MEM, 0);    if (r == NULL) {        ret = -ENOENT;        goto free_master;    }    dspi->pbase = r->start;    dspi->pdata = pdata;    mem = request_mem_region(r->start, resource_size(r), pdev->name);    if (mem == NULL) {        ret = -EBUSY;        goto free_master;    }    dspi->base = ioremap(r->start, resource_size(r));    if (dspi->base == NULL) {        ret = -ENOMEM;        goto release_region;    }    dspi->irq = platform_get_irq(pdev, 0);    if (dspi->irq <= 0) {        ret = -EINVAL;        goto unmap_io;    }    ret = request_irq(dspi->irq, davinci_spi_irq, 0, dev_name(&pdev->dev),                                    dspi);    if (ret)        goto unmap_io;    dspi->bitbang.master = spi_master_get(master);    if (dspi->bitbang.master == NULL) {        ret = -ENODEV;        goto irq_free;    }    dspi->clk = clk_get(&pdev->dev, NULL);    if (IS_ERR(dspi->clk)) {        ret = -ENODEV;        goto put_master;    }    clk_enable(dspi->clk);    master->bus_num = pdev->id;    master->num_chipselect = pdata->num_chipselect;    master->setup = davinci_spi_setup;    dspi->bitbang.chipselect = davinci_spi_chipselect;    dspi->bitbang.setup_transfer = davinci_spi_setup_transfer;    dspi->version = pdata->version;    dspi->bitbang.flags = SPI_NO_CS | SPI_LSB_FIRST | SPI_LOOP;    if (dspi->version == SPI_VERSION_2)        dspi->bitbang.flags |= SPI_READY;    r = platform_get_resource(pdev, IORESOURCE_DMA, 0);    if (r)        dma_rx_chan = r->start;    r = platform_get_resource(pdev, IORESOURCE_DMA, 1);    if (r)        dma_tx_chan = r->start;    r = platform_get_resource(pdev, IORESOURCE_DMA, 2);    if (r)        dma_eventq = r->start;    dspi->bitbang.txrx_bufs = davinci_spi_bufs;    if (dma_rx_chan != SPI_NO_RESOURCE &&        dma_tx_chan != SPI_NO_RESOURCE &&        dma_eventq != SPI_NO_RESOURCE) {        dspi->dma.rx_channel = dma_rx_chan;        dspi->dma.tx_channel = dma_tx_chan;        dspi->dma.eventq = dma_eventq;        ret = davinci_spi_request_dma(dspi);        if (ret)            goto free_clk;        dev_info(&pdev->dev, "DMA: supported\n");        dev_info(&pdev->dev, "DMA: RX channel: %d, TX channel: %d, "                "event queue: %d\n", dma_rx_chan, dma_tx_chan,                dma_eventq);    }    dspi->get_rx = davinci_spi_rx_buf_u8;    dspi->get_tx = davinci_spi_tx_buf_u8;    init_completion(&dspi->done);    ret = davinci_spi_cpufreq_register(dspi);    if (ret) {        pr_info("davinci SPI contorller driver failed to register "                            "cpufreq\n");        goto free_dma;    }    /* Reset In/OUT SPI module */    iowrite32(0, dspi->base + SPIGCR0);    udelay(100);    iowrite32(1, dspi->base + SPIGCR0);    /* Set up SPIPC0.  CS and ENA init is done in davinci_spi_setup */    spipc0 = SPIPC0_DIFUN_MASK | SPIPC0_DOFUN_MASK | SPIPC0_CLKFUN_MASK;    iowrite32(spipc0, dspi->base + SPIPC0);    /* initialize chip selects */    if (pdata->chip_sel) {        for (i = 0; i < pdata->num_chipselect; i++) {            if (pdata->chip_sel[i] != SPI_INTERN_CS)                gpio_direction_output(pdata->chip_sel[i], 1);        }    }    if (pdata->intr_line)        iowrite32(SPI_INTLVL_1, dspi->base + SPILVL);    else        iowrite32(SPI_INTLVL_0, dspi->base + SPILVL);    iowrite32(CS_DEFAULT, dspi->base + SPIDEF);    /* master mode default */    set_io_bits(dspi->base + SPIGCR1, SPIGCR1_CLKMOD_MASK);    set_io_bits(dspi->base + SPIGCR1, SPIGCR1_MASTER_MASK);    set_io_bits(dspi->base + SPIGCR1, SPIGCR1_POWERDOWN_MASK);    ret = spi_bitbang_start(&dspi->bitbang);//设置spi master driver    if (ret)        goto free_dma;    dev_info(&pdev->dev, "Controller at 0x%p\n", dspi->base);    return ret;free_dma:    edma_free_channel(dspi->dma.tx_channel);    edma_free_channel(dspi->dma.rx_channel);    edma_free_slot(dspi->dma.dummy_param_slot);free_clk:    clk_disable(dspi->clk);    clk_put(dspi->clk);put_master:    spi_master_put(master);irq_free:    free_irq(dspi->irq, dspi);unmap_io:    iounmap(dspi->base);release_region:    release_mem_region(dspi->pbase, resource_size(r));free_master:    kfree(master);err:    return ret;}
int spi_bitbang_start(struct spi_bitbang *bitbang){    int status;    if (!bitbang->master || !bitbang->chipselect)        return -EINVAL;    INIT_WORK(&bitbang->work, bitbang_work);    spin_lock_init(&bitbang->lock);    INIT_LIST_HEAD(&bitbang->queue);    if (!bitbang->master->mode_bits)        bitbang->master->mode_bits = SPI_CPOL | SPI_CPHA | bitbang->flags;    if (!bitbang->master->transfer)        bitbang->master->transfer = spi_bitbang_transfer;    if (!bitbang->txrx_bufs) {        bitbang->use_dma = 0;        bitbang->txrx_bufs = spi_bitbang_bufs;        if (!bitbang->master->setup) {            if (!bitbang->setup_transfer)                bitbang->setup_transfer =                     spi_bitbang_setup_transfer;            bitbang->master->setup = spi_bitbang_setup;            bitbang->master->cleanup = spi_bitbang_cleanup;        }    } else if (!bitbang->master->setup)        return -EINVAL;    /* this task is the only thing to touch the SPI bits */    bitbang->busy = 0;    bitbang->workqueue = create_singlethread_workqueue(            dev_name(bitbang->master->dev.parent));    if (bitbang->workqueue == NULL) {        status = -EBUSY;        goto err1;    }    /* driver may get busy before register() returns, especially     * if someone registered boardinfo for devices     */    status = spi_register_master(bitbang->master);//注册spi master    if (status < 0)        goto err2;    return status;err2:    destroy_workqueue(bitbang->workqueue);err1:    return status;}
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;    int         status = -ENODEV;    int         dynamic = 0;    if (!dev)        return -ENODEV;    /* even if it's just one always-selected device, there must     * be at least one chipselect     */    if (master->num_chipselect == 0)        return -EINVAL;    /* 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;    }    /* 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)" : "");    /* populate children from any spi device tables */    scan_boardinfo(master);//注册spi device    status = 0;done:    return status;}
static void scan_boardinfo(struct spi_master *master){    struct boardinfo    *bi;    mutex_lock(&board_lock);    list_for_each_entry(bi, &board_list, list) {        struct spi_board_info   *chip = bi->board_info;        unsigned        n;        for (n = bi->n_board_info; n > 0; n--, chip++) {            if (chip->bus_num != master->bus_num)                continue;            /* NOTE: this relies on spi_new_device to             * issue diagnostics when given bogus inputs             */            (void) spi_new_device(master, chip);//添加新的spi_device        }    }    mutex_unlock(&board_lock);}
struct spi_device *spi_new_device(struct spi_master *master,                  struct spi_board_info *chip){    struct spi_device   *proxy;    int         status;    /* NOTE:  caller did any chip->bus_num checks necessary.     *     * Also, unless we change the return value convention to use     * error-or-pointer (not NULL-or-pointer), troubleshootability     * suggests syslogged diagnostics are best here (ugh).     */    proxy = spi_alloc_device(master);//分配spi device    if (!proxy)        return NULL;    WARN_ON(strlen(chip->modalias) >= sizeof(proxy->modalias));    proxy->chip_select = chip->chip_select;    proxy->max_speed_hz = chip->max_speed_hz;    proxy->mode = chip->mode;    proxy->irq = chip->irq;    strlcpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias));    proxy->dev.platform_data = (void *) chip->platform_data;    proxy->controller_data = chip->controller_data;    proxy->controller_state = NULL;    status = spi_add_device(proxy);//添加spi device    if (status < 0) {        spi_dev_put(proxy);        return NULL;    }    return proxy;}
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) {        dev_err(dev, "cs%d >= max %d\n",            spi->chip_select,            master->num_chipselect);        return -EINVAL;    }    /* Set the bus ID string */    spi_dev_set_name(spi);    /* We need to make sure there's no other device with this     * chipselect **BEFORE** we call setup(), else we'll trash     * its configuration.  Lock against concurrent add() calls.     */    mutex_lock(&spi_add_lock);    status = bus_for_each_dev(&spi_bus_type, NULL, spi, spi_dev_check);    if (status) {        dev_err(dev, "chipselect %d already in use\n",                spi->chip_select);        goto done;    }    if (master->cs_gpios)        spi->cs_gpio = master->cs_gpios[spi->chip_select];    /* Drivers may modify this initial i/o setup, but will     * normally rely on the device being setup.  Devices     * using SPI_CS_HIGH can't coexist well otherwise...     */    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);//注册device到内核,然后会寻找匹配的驱动,如果匹配的驱动寻找到,就将他们连接在一起    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;}

总结:spi_driver的注册过程包含了spi_master、spi_device的注册过程。当device与device驱动匹配之后,他们之间的联系也就建立了。

PS:用source insight这款软件进行代码的追踪实在是太方便了,我们可以查看不懂数据结构的定义、函数的定义等等,本文就是在这种工具的帮助上进行代码追踪的。

最后,敬请各位网友批评指正,也欢迎各位和我交流共同提高!

0 0