spi驱动分析
?
? 转载的地址:http://www.diybl.com/course/6_system/linux/Linuxjs/200868/123621.html 今天折腾了一天的SPI设备的驱动加载,甚至动用了逻辑分析仪来查看spi总线的波形,主要包括两个SPI设备,at45db321d和mcp2515,一个是串行的dataflash,一个是can总线设备芯片。前者对于我们来说非常重要,我们可以借助该设备对uboot和kernel以及根文件系统进行更新。 ?
其实还有一个问题可能大家没有注意到,没有解释清楚,其实是有问题的,我们的at91_add_device_spi函数如下: static struct spi_board_info ek_spi_devices[] = { #if !defined(CONFIG_MMC_AT91) ??? {??? /* DataFlash chip */ ??? ??? .modalias??? = "mtd_dataflash",??? ??? .chip_select??? = 1,??? ??? .max_speed_hz??? = 15 * 1000 * 1000,??? ??? .bus_num??? = 0,??? },#if defined(CONFIG_MTD_AT91_DATAFLASH_CARD) ??? {??? /* DataFlash card */ ??? ??? .modalias??? = "mtd_dataflash",??? ??? .chip_select??? = 0,#endif #endif #if defined(CONFIG_SND_AT73C213) || defined(CONFIG_SND_AT73C213_MODULE) ??? {??? /* AT73C213 DAC */ ??? ??? .modalias??? = "at73c213",??? ??? .max_speed_hz??? = 10 * 1000 * 1000,??? ??? .bus_num??? = 1,#endif /* spi can,add by mrz */ #if defined(CONFIG_CAN_MCP2515_MODULE) ||defined(CONFIG_CAN_MCP2515) //defined(CONFIG_CAN_MCP2515)? ??? { ??? ??? .modalias = "mcp2515",??? ??? .chip_select = 0,//??? ??? .controller_data = AT91_PIN_PB3,??? ??? .irq = AT91_PIN_PC6,//AT91SAM9260_ID_IRQ0,??? ??? .platform_data = &mcp251x_data,??? ??? .max_speed_hz = 10 * 1000 * 1000,??? ??? .bus_num = 1,??? ??? .mode = 0,??? /* ??? { ??? ??? .modalias = "mcp2515",??? ??? .chip_select = 1,//??? ??? .controller_data = AT91_PIN_PC5,??? ??? .irq = AT91_PIN_PC7,//AT91SAM9260_ID_IRQ1,*/ #elif defined(CONFIG_CAN_MCP251X) ??? { ??? ??? .modalias = "mcp251x",??? { ??? ??? .modalias = "mcp251x",#endif } void __init at91_add_device_spi(struct spi_board_info *devices,int nr_devices) { ??? int i; ??? unsigned long cs_pin; ??? short enable_spi0 = 0; ??? short enable_spi1 = 0; ??? /* Choose SPI chip-selects */ ??? /*这里加载我们定义的spi_board_info结构体,也就是两个spi设备的信息,注意,他们这里没有使用spi_device结构体来做,而是使用一个板级信息体来完成。*/ ??? for (i = 0; i < nr_devices; i++) { ??? /*该成员定义的就是cs引脚*/ ??? ??? if (devices[i].controller_data) ??? ??? ??? cs_pin = (unsigned long) devices[i].controller_data; ??? ??? else if (devices[i].bus_num == 0) ??? ??? ??? cs_pin = spi0_standard_cs[devices[i].chip_select]; ??? ??? else ??? ??? ??? cs_pin = spi1_standard_cs[devices[i].chip_select]; ??? /*根据需要加载的设备,确定需要打开哪几个SPI控制器,我们系统中有两个控制器,所以我们在以模块的方式加载驱动的时候,我们的设备必须在刚开始就被初始化!*/ ??? ??? if (devices[i].bus_num == 0) ??? ??? ??? enable_spi0 = 1; ??? ??? else ??? ??? ??? enable_spi1 = 1; ??? ??? /* enable chip-select pin */ ?? ?? /*将片选引脚设置为输出*/ ??? ??? at91_set_gpio_output(cs_pin,1); ??? ??? /* pass chip-select pin to driver */ ??? ??? devices[i].controller_data = (void *) cs_pin; ??? } ??? /*到此,循环执行完毕,向内核注册这些板级信息体*/ ??? spi_register_board_info(devices,nr_devices); ??? /* Configure SPI bus(es) */ ??? /*如果发现spi0上有设备注册,则打开spi0*/ ??? if (enable_spi0) { ??? ??? at91_set_A_periph(AT91_PIN_PA0,0);??? /* SPI0_MISO */ ??? ??? at91_set_A_periph(AT91_PIN_PA1,0);??? /* SPI0_MOSI */ ??? ??? at91_set_A_periph(AT91_PIN_PA2,0);??? /* SPI1_SPCK */ ??? ??? at91_clock_associate("spi0_clk",&at91sam9260_spi0_device.dev,"spi_clk"); ??? ??? platform_device_register(&at91sam9260_spi0_device); ??? } ??? /*spi0设备也是如此*/ ??? if (enable_spi1) { ??? ??? at91_set_A_periph(AT91_PIN_PB0,0);??? /* SPI1_MISO */ ??? ??? at91_set_A_periph(AT91_PIN_PB1,0);??? /* SPI1_MOSI */ ??? ??? at91_set_A_periph(AT91_PIN_PB2,0);??? /* SPI1_SPCK */ ??? ??? at91_clock_associate("spi1_clk",&at91sam9260_spi1_device.dev,"spi_clk"); ??? ??? platform_device_register(&at91sam9260_spi1_device); ??? } } 从上面这个函数我们可以看出,这个函数就完成了两个功能: 1、向内核完成spi板级信息结构体的注册 2、注册了两个platform_device:spi0与spi1,这两个设备是spi总线控制器! 那么我们客户端spi_device设备的注册是如何完成的?不知道,呵呵 我今天仔细的看代码才发现玄机所在。 内核的注释很清晰的告诉我们,我们的spi设备是不允许热插拔!!这是由于spi设备驱动的框架不允许,我们的spi_device设备注册不是在板级初始化的时候完成的。 在spi控制器的驱动加载的时候,也就是platform_driver:atmel_spi驱动加载的时候, driver/spi/atmel_spi.c文件中: static int __init atmel_spi_probe(struct platform_device *pdev) { ??? struct resource??? ??? *regs; ??? int??? ??? ??? irq; ??? struct clk??? ??? *clk; ??? int??? ??? ??? ret; ??? struct spi_master??? *master; ??? struct atmel_spi??? *as; ??? regs = platform_get_resource(pdev,IORESOURCE_MEM,0); ??? if (!regs) ??? ??? return -ENXIO; ??? irq = platform_get_irq(pdev,0); ??? if (irq < 0) ??? ??? return irq; ??? clk = clk_get(&pdev->dev,"spi_clk"); ??? if (IS_ERR(clk)) ??? ??? return PTR_ERR(clk);
?/* setup spi core then atmel-specific driver state */ ??? ret = -ENOMEM; ??? master = spi_alloc_master(&pdev->dev,sizeof *as); ??? if (!master) ??? ??? goto out_free; ??? master->bus_num = pdev->id; ??? master->num_chipselect = 4; ??? master->setup = atmel_spi_setup; ??? master->transfer = atmel_spi_transfer; ??? master->cleanup = atmel_spi_cleanup; ??? platform_set_drvdata(pdev,master); ??? as = spi_master_get_devdata(master); ??? as->buffer = dma_alloc_coherent(&pdev->dev,BUFFER_SIZE,??? ??? ??? ??? ??? &as->buffer_dma,GFP_KERNEL); ??? if (!as->buffer) ??? ??? goto out_free; ??? spin_lock_init(&as->lock); ??? INIT_LIST_HEAD(&as->queue); ??? as->pdev = pdev; ??? as->regs = ioremap(regs->start,(regs->end - regs->start) + 1); ??? if (!as->regs) ??? ??? goto out_free_buffer; ??? as->irq = irq; ??? as->clk = clk; #ifdef CONFIG_ARCH_AT91 ??? if (!cpu_is_at91rm9200()) ??? ??? as->new_1 = 1; #endif ??? ret = request_irq(irq,atmel_spi_interrupt,??? ??? ??? pdev->dev.bus_id,master); ??? if (ret) ??? ??? goto out_unmap_regs; ??? /* Initialize the hardware */ ??? clk_enable(clk); ??? spi_writel(as,CR,SPI_BIT(SWRST)); ??? spi_writel(as,MR,SPI_BIT(MSTR) | SPI_BIT(MODFDIS)); ??? spi_writel(as,PTCR,SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS)); ??? spi_writel(as,SPI_BIT(SPIEN)); ??? /* go! */ ??? dev_info(&pdev->dev,"Atmel SPI Controller at 0x%08lx (irq %d)n",??? ??? ??? (unsigned long)regs->start,irq); ??? /*spi注册这个主控制器*/ ??? ret = spi_register_master(master); ??? if (ret) ??? ??? goto out_reset_hw; ??? return 0; out_reset_hw: ??? spi_writel(as,SPI_BIT(SWRST)); ??? clk_disable(clk); ??? free_irq(irq,master); out_unmap_regs: ??? iounmap(as->regs); out_free_buffer: ??? dma_free_coherent(&pdev->dev,as->buffer,??? ??? ??? as->buffer_dma); out_free: ??? clk_put(clk); ??? spi_master_put(master); ??? return ret; } 而这个spi_register_master位于driver/spi/spi.c文件中,该函数调用了scan_boardinfo(master),扫描该spi master下面设备。该函数就存在于该文件下:该函数调用了spi_new_device(master,chip),这个chip就是一个spi_board_info结构体,这就是at91_add_device_spi第一个作用的用处:向内核的链表注册spi_board_info结构体的用处所在。我们来看函数的调用过程: atmel_spi_probe----->spi_register_master----->scan_boardinfo ---->spi_new_device 我们来看这个spi_new_device函数: struct spi_device *spi_new_device(struct spi_master *master,??? ??? ??? ??? ? struct spi_board_info *chip) { ??? struct spi_device??? *proxy; ??? struct device??? ??? *dev = master->cdev.dev; ??? int??? ??? ??? status; ??? /* NOTE:? caller did any chip->bus_num checks necessary */ ??? if (!spi_master_get(master)) ??? ??? return NULL; ??? /*靠,终于找到你了,先暴打一顿,舒服了。。这里就分配了我们重要的spi_device结构体*/ ??? proxy = kzalloc(sizeof *proxy,GFP_KERNEL); ??? if (!proxy) { ??? ??? dev_err(dev,"can't alloc dev for cs%dn",??? ??? ??? chip->chip_select); ??? ??? goto fail; ??? } /*这就是将我们的信息体中的数据转化为spi_device识别的数据*/ ??? proxy->master = master; ??? proxy->chip_select = chip->chip_select; ??? proxy->max_speed_hz = chip->max_speed_hz; ??? proxy->mode = chip->mode; ??? proxy->irq = chip->irq; ??? proxy->modalias = chip->modalias; ??? snprintf(proxy->dev.bus_id,sizeof proxy->dev.bus_id,??? ??? ??? "%s.%u",master->cdev.class_id,??? ??? ??? chip->chip_select); ??? proxy->dev.parent = dev; ??? proxy->dev.bus = &spi_bus_type; ??? /*这里很重要,如果你的spi设备是dataflash的话,保存的就是你的分区表!!!所以我们要返回去修改我们的spi_boardinfo结构体*/ ??? proxy->dev.platform_data = (void *) chip->platform_data; ??? /*片选信号*/ ??? proxy->controller_data = chip->controller_data; ??? proxy->controller_state = NULL; ??? proxy->dev.release = spidev_release; ??? /* drivers may modify this default i/o setup */ ??? status = master->setup(proxy); ??? if (status < 0) { ??? ??? dev_dbg(dev,"can't %s %s,status %dn",??? ??? ??? ??? "setup",proxy->dev.bus_id,status); ??? ??? goto fail; ??? } ??? /* driver core catches callers that misbehave by defining ??? ?* devices that already exist. ??? ?*/ ??? /*看到这句话,大家放心了吧,大家也就知道怎么找到spi_driver驱动的。。。*/ ??? status = device_register(&proxy->dev); ??? if (status < 0) { ??? ??? dev_dbg(dev,??? ??? ??? ??? "add",status); ??? ??? goto fail; ??? } ??? dev_dbg(dev,"registered child %sn",proxy->dev.bus_id); ??? return proxy; fail: ??? spi_master_put(master); ??? kfree(proxy); ??? return NULL; } 下面我们要解决最后的一个问题,dataflash的分区的问题,看了这么多,大家应该知道怎么解决了吧! 我们看mtd_dataflash.c文件中驱动加载函数调用了下面这个函数来添加flash设备。。 static int __devinit add_dataflash(struct spi_device *spi,char *name,??? ??? int nr_pages,int pagesize,int pageoffset) { ??? struct dataflash??? ??? *priv; ??? struct mtd_info??? ??? ??? *device; ??? /*这里就告诉我们要在spi_boardinfo结构体的platform_data成员指向一个我们需要的flash_platform_data数据!*/ ??? struct flash_platform_data??? *pdata = spi->dev.platform_data; ??? priv = kzalloc(sizeof *priv,GFP_KERNEL); ??? if (!priv) ??? ??? return -ENOMEM; ??? init_MUTEX(&priv->lock); ??? priv->spi = spi; ??? priv->page_size = pagesize; ??? priv->page_offset = pageoffset; ??? /* name must be usable with cmdlinepart */ ??? sprintf(priv->name,"spi%d.%d-%s",??? ??? ??? spi->master->bus_num,spi->chip_select,??? ??? ??? name); ??? device = &priv->mtd; ??? device->name = (pdata && pdata->name) ? pdata->name : priv->name; ??? device->size = nr_pages * pagesize; ??? device->erasesize = pagesize; ??? device->writesize = pagesize; ??? device->owner = THIS_MODULE; ??? device->type = MTD_DATAFLASH; ??? device->flags = MTD_WRITEABLE; ??? device->erase = dataflash_erase; ??? device->read = dataflash_read; ??? device->write = dataflash_write; ??? device->priv = priv; ??? dev_info(&spi->dev,"%s (%d KBytes)n",name,device->size/1024); ??? dev_set_drvdata(&spi->dev,priv); ??? if (mtd_has_partitions()) { ??? ??? struct mtd_partition??? *parts; ??? ??? int??? ??? ??? nr_parts = 0; ??? /*我们这里没有定义该宏,所以不会在命令行传递分区表*/ #ifdef CONFIG_MTD_CMDLINE_PARTS ??? ??? static const char *part_probes[] = { "cmdlinepart",NULL,}; ??? ??? nr_parts = parse_mtd_partitions(device,part_probes,&parts,0); #endif ??? ??? if (nr_parts <= 0 && pdata && pdata->parts) { ??? ??? ??? parts = pdata->parts; ??? ??? ??? nr_parts = pdata->nr_parts; ??? ??? } ??? ??? if (nr_parts > 0) { ??? ??? ??? priv->partitioned = 1; ??? ??? ??? return add_mtd_partitions(device,parts,nr_parts); ??? ??? } ??? } else if (pdata && pdata->nr_parts) ??? ??? dev_warn(&spi->dev,"ignoring %d default partitions on %sn",??? ??? ??? ??? pdata->nr_parts,device->name); ??? return add_mtd_device(device) == 1 ? -ENODEV : 0; } 所以我们需要修改这个文件: arch/arm/mach-at91/board-sam9260ek.c文件: 添加如下: #if? !defined(CONFIG_MMC_AT91) #define??? SIZE_1PAGE??? 528 #define??? SIZE_1M (unsigned long)(1024*1024) static struct mtd_partition ek_dataflash_partition[] = { ??? { ??? ??? .name??? = "U-boot ENV",??? ??? .offset??? = 0,??? ??? .size??? = 64*SIZE_1PAGE,??? { ??? ??? .name??? = "U-BOOT",??? ??? .offset??? = 64*SIZE_1PAGE,??? ??? .size??? = 400*SIZE_1PAGE,??? { ??? ??? .name ="Kernel",??? ??? .offset=464*SIZE_1PAGE,??? ??? .size??? = 4000*SIZE_1PAGE,??? { ??? ??? .name ="Root fs",??? ??? .offset=4464*SIZE_1PAGE,??? ??? .size??? = (8192-4464)*SIZE_1PAGE,}; struct flash_platform_data dataflash_atmel={ ??? .name="AT45DB321",??? .parts=ek_dataflash_partition,??? .nr_parts=ARRAY_SIZE(ek_dataflash_partition),}; #endif 修改spi_boardinfo结构体: static struct spi_board_info ek_spi_devices[] = { #if !defined(CONFIG_MMC_AT91) ??? {??? /* DataFlash chip */ ??? ??? .modalias??? = "mtd_dataflash",??? ??? .platform_data=&dataflash_atmel,添加platform_data结构成员。 这里我们建立mtd_partition结构体要注意,由于dataflash是以528字节每页的,其实,at45db321x芯片可以设置为512字节每页,这个操作是不可以逆转的,那个位是一个otp位,用过的人就应该知道,但是出厂的时候默认的528字节每页。 如果我们不是以528个字节为单位的话,内核将出警告,强制将分区加载为readonly格式。 到此,分区加载成功,dmesg输出如下信息: <6>mtd_dataflash spi0.1: AT45DB321x (4224 KBytes) <5>Creating 4 MTD partitions on "AT45DB321": <5>0x00000000-0x00008400 : "U-boot ENV" <5>0x00008400-0x0003bd00 : "U-BOOT" <5>0x0003bd00-0x0023f700 : "Kernel" <5>0x0023f700-0x00420000 : "Root fs" linux简直太伟大了,使用得越多,就越能体会到其思想的伟大!灵活!?
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |