Linux设备驱动模型SPI之二
来源:互联网 发布:js 按钮显示隐藏 编辑:程序博客网 时间:2024/05/19 21:00
本文详细分析TI Davinci DM368 SPI接口的平台设备注册、外部设备注册、平台驱动注册、外部驱动程序注册。应该注意以下四点:
- 平台设备描述DM368芯片集成的SPI接口硬件描述
- 平台设备驱动描述如何控制DM368芯片的SPI接口,从而能够收发数据
- 外部设备描述的是挂在SPI接口上的设备(以下称为spi设备)
- 外部驱动程序描述的是如何控制SPI接口上的外部设备(以下称为spi驱动)
如下图所示平台设备与平台驱动通过平台总线连接在一起,类似的spi设备与spi驱动通过spi总线连接在一起
描述spi有三个数据结构:
- spi_master 描述SPI主机控制器,其实就是platform driver
- spi_driver 描述spi驱动
- 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这款软件进行代码的追踪实在是太方便了,我们可以查看不懂数据结构的定义、函数的定义等等,本文就是在这种工具的帮助上进行代码追踪的。
最后,敬请各位网友批评指正,也欢迎各位和我交流共同提高!
- Linux设备驱动模型SPI之二
- Linux设备驱动剖析之SPI(二)
- Linux设备驱动剖析之SPI(二)
- Linux设备驱动剖析之SPI(二)
- Linux设备驱动模型 SPI之一
- Linux SPI总线设备驱动模型详解
- Linux SPI总线设备驱动模型详解
- Linux SPI总线设备驱动模型详解
- spi设备驱动模型
- [Linux] SPI 设备驱动模型(SPI 协议基础)
- LINUX设备驱动之设备模型二--kset
- LINUX设备驱动之设备模型二--kset
- LINUX设备驱动之设备模型二--kset
- LINUX设备驱动之设备模型二--kset
- Linux SPI总线和设备驱动架构之二:SPI通用接口层
- Linux SPI总线和设备驱动架构之二:SPI通用接口层
- Linux SPI总线和设备驱动架构之二:SPI通用接口层
- Linux SPI总线和设备驱动架构之二:SPI通用接口层
- leetcode 19. Remove Nth Node From End of List
- 求含有n个元素的集合的幂集
- 详细解读Python中的__init__()方法
- hive入门学习:hive的调优(二)
- java.io.NotSerializableException错误解决方法
- Linux设备驱动模型SPI之二
- 使用StringBuffer来判断一个数字是否为回文数
- 优雅的linq润滑在复杂的业务环境中(上)
- java面试题1
- HDU 2069 Coin Change (暴力)
- Java基础知识 -- 多线程
- PHP文件上传实例
- QML 开发神秘加成之为网络资源设置本地缓存
- 理工 动物统计