1. SPI總線結構
SPI串行外設接口,是一種高速的,全雙工,同步的通信總線。采用主從模式架構,支持多個slave,一般僅支持單Master
SPI接口共有4根信號線,分別是:
設備選擇線(SS)、時鐘線(SCK)、串行輸出數據線(MOSI)、串行輸入數據線(MISO).
2. 數據傳輸過程
主節點通過MOSI線輸出數據,從節點在SIMO處從主節點讀取數據。同時,也通過SMOI輸出MSB(最高位),
主節點會在MISO處讀取從節點的數據,整個過程將一直持續,直至交換完所有的數據。
3. 總線時序
SPI裸機驅動程序設計:
1. SPI控制器工作流程
開發板上沒有SPI外設,這裡貼上別人整過SPI裸機驅動測試的鏈接:
http://blog.chinaunix.net/uid-24219701-id-3748675.html
http://blog.csdn.net/cp1300/article/details/8041760
http://blog.csdn.net/wanyeye/article/details/42494559
SPI子系統架構:
1. SPI核心
SPI控制器驅動和設備驅動之間的紐帶,它提供了SPI控制器驅動和設備驅動的注冊、注銷方法等。
2. SPI控制器驅動
對SPI控制器驅動的實現
3. SPI設備驅動
對SPI從設備的驅動實現,如 spi flash
首先看看SPI核心驅動中的源碼:
還是先上初始化模塊部分:可以看到也是平台總線驅動模型!直接跳到probe函數中(本文件中的probe函數)
static int __init s3c24xx_spi_probe(struct platform_device *pdev)這兒函數內容比較多
static int __init s3c24xx_spi_probe(struct platform_device *pdev) { struct s3c2410_spi_info *pdata; struct s3c24xx_spi *hw; struct spi_master *master; struct resource *res; int err = 0; master = spi_alloc_master(&pdev->dev, sizeof(struct s3c24xx_spi)); if (master == NULL) { dev_err(&pdev->dev, "No memory for spi_master\n"); err = -ENOMEM; goto err_nomem; } hw = spi_master_get_devdata(master); memset(hw, 0, sizeof(struct s3c24xx_spi)); hw->master = spi_master_get(master); hw->pdata = pdata = pdev->dev.platform_data; hw->dev = &pdev->dev; if (pdata == NULL) { dev_err(&pdev->dev, "No platform data supplied\n"); err = -ENOENT; goto err_no_pdata; } platform_set_drvdata(pdev, hw); init_completion(&hw->done); /* initialise fiq handler */ s3c24xx_spi_initfiq(hw); /* setup the master state. */ /* the spi->mode bits understood by this driver: */ master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; master->num_chipselect = hw->pdata->num_cs; master->bus_num = pdata->bus_num; /* setup the state for the bitbang driver */ hw->bitbang.master = hw->master; hw->bitbang.setup_transfer = s3c24xx_spi_setupxfer; hw->bitbang.chipselect = s3c24xx_spi_chipsel; hw->bitbang.txrx_bufs = s3c24xx_spi_txrx; hw->master->setup = s3c24xx_spi_setup; hw->master->cleanup = s3c24xx_spi_cleanup; dev_dbg(hw->dev, "bitbang at %p\n", &hw->bitbang); /* find and map our resources */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res == NULL) { dev_err(&pdev->dev, "Cannot get IORESOURCE_MEM\n"); err = -ENOENT; goto err_no_iores; } hw->ioarea = request_mem_region(res->start, resource_size(res), pdev->name); if (hw->ioarea == NULL) { dev_err(&pdev->dev, "Cannot reserve region\n"); err = -ENXIO; goto err_no_iores; } hw->regs = ioremap(res->start, resource_size(res)); if (hw->regs == NULL) { dev_err(&pdev->dev, "Cannot map IO\n"); err = -ENXIO; goto err_no_iomap; } hw->irq = platform_get_irq(pdev, 0); if (hw->irq < 0) { dev_err(&pdev->dev, "No IRQ specified\n"); err = -ENOENT; goto err_no_irq; } err = request_irq(hw->irq, s3c24xx_spi_irq, 0, pdev->name, hw);//中斷相關部分 if (err) { dev_err(&pdev->dev, "Cannot claim IRQ\n"); goto err_no_irq; } hw->clk = clk_get(&pdev->dev, "spi"); if (IS_ERR(hw->clk)) { dev_err(&pdev->dev, "No clock for device\n"); err = PTR_ERR(hw->clk); goto err_no_clk; } /* setup any gpio we can */ if (!pdata->set_cs) { if (pdata->pin_cs < 0) { dev_err(&pdev->dev, "No chipselect pin\n"); goto err_register; } err = gpio_request(pdata->pin_cs, dev_name(&pdev->dev)); if (err) { dev_err(&pdev->dev, "Failed to get gpio for cs\n"); goto err_register; } hw->set_cs = s3c24xx_spi_gpiocs; gpio_direction_output(pdata->pin_cs, 1); } else hw->set_cs = pdata->set_cs; s3c24xx_spi_initialsetup(hw);//硬件相關部分的初始化 /* register our spi controller */ //向SPI核心注冊驅動 err = spi_bitbang_start(&hw->bitbang); if (err) { dev_err(&pdev->dev, "Failed to register SPI master\n"); goto err_register; } return 0; err_register: if (hw->set_cs == s3c24xx_spi_gpiocs) gpio_free(pdata->pin_cs); clk_disable(hw->clk); clk_put(hw->clk); err_no_clk: free_irq(hw->irq, hw); err_no_irq: iounmap(hw->regs); err_no_iomap: release_resource(hw->ioarea); kfree(hw->ioarea); err_no_iores: err_no_pdata: spi_master_put(hw->master); err_nomem: return err; }
當然讀寫還有中斷部分也是SPI的核心部分,看源碼喽!
下面來簡要介紹SPI從設備驅動程序設計:
內核源碼文件m25p80.c 一種SPI接口的FLASH驅動!(SPI外設,這裡先簡單領略一下SPI外設驅動)
首先還是先看上面的模塊初始化部分!這裡先看看m25p80參數類型:
當驅動遇到了相應的設備的時候就會調用上面的m25p_probe函數
/* * board specific setup should have ensured the SPI clock used here * matches what the READ command supports, at least until this driver * understands FAST_READ (for clocks over 25 MHz). */ static int __devinit m25p_probe(struct spi_device *spi) { const struct spi_device_id *id = spi_get_device_id(spi); struct flash_platform_data *data; struct m25p *flash; struct flash_info *info; unsigned i; struct mtd_partition *parts = NULL; int nr_parts = 0; /* Platform data helps sort out which chip type we have, as * well as how this board partitions it. If we don't have * a chip ID, try the JEDEC id commands; they'll work for most * newer chips, even if we don't recognize the particular chip. */ data = spi->dev.platform_data; if (data && data->type) { const struct spi_device_id *plat_id; for (i = 0; i < ARRAY_SIZE(m25p_ids) - 1; i++) { plat_id = &m25p_ids[i]; if (strcmp(data->type, plat_id->name)) continue; break; } if (i < ARRAY_SIZE(m25p_ids) - 1) id = plat_id; else dev_warn(&spi->dev, "unrecognized id %s\n", data->type); } info = (void *)id->driver_data; if (info->jedec_id) { const struct spi_device_id *jid; jid = jedec_probe(spi); if (IS_ERR(jid)) { return PTR_ERR(jid); } else if (jid != id) { /* * JEDEC knows better, so overwrite platform ID. We * can't trust partitions any longer, but we'll let * mtd apply them anyway, since some partitions may be * marked read-only, and we don't want to lose that * information, even if it's not 100% accurate. */ dev_warn(&spi->dev, "found %s, expected %s\n", jid->name, id->name); id = jid; info = (void *)jid->driver_data; } } flash = kzalloc(sizeof *flash, GFP_KERNEL); if (!flash) return -ENOMEM; flash->command = kmalloc(MAX_CMD_SIZE + FAST_READ_DUMMY_BYTE, GFP_KERNEL); if (!flash->command) { kfree(flash); return -ENOMEM; } flash->spi = spi; mutex_init(&flash->lock); dev_set_drvdata(&spi->dev, flash); /* * Atmel, SST and Intel/Numonyx serial flash tend to power * up with the software protection bits set */ if (JEDEC_MFR(info->jedec_id) == CFI_MFR_ATMEL || JEDEC_MFR(info->jedec_id) == CFI_MFR_INTEL || JEDEC_MFR(info->jedec_id) == CFI_MFR_SST) { write_enable(flash); write_sr(flash, 0); } if (data && data->name) flash->mtd.name = data->name; else flash->mtd.name = dev_name(&spi->dev); flash->mtd.type = MTD_NORFLASH; flash->mtd.writesize = 1; flash->mtd.flags = MTD_CAP_NORFLASH; flash->mtd.size = info->sector_size * info->n_sectors; flash->mtd.erase = m25p80_erase; flash->mtd.read = m25p80_read; /* sst flash chips use AAI word program */ if (JEDEC_MFR(info->jedec_id) == CFI_MFR_SST) flash->mtd.write = sst_write; else flash->mtd.write = m25p80_write; /* prefer "small sector" erase if possible */ if (info->flags & SECT_4K) { flash->erase_opcode = OPCODE_BE_4K; flash->mtd.erasesize = 4096; } else { flash->erase_opcode = OPCODE_SE; flash->mtd.erasesize = info->sector_size; } if (info->flags & M25P_NO_ERASE) flash->mtd.flags |= MTD_NO_ERASE; flash->mtd.dev.parent = &spi->dev; flash->page_size = info->page_size; if (info->addr_width) flash->addr_width = info->addr_width; else { /* enable 4-byte addressing if the device exceeds 16MiB */ if (flash->mtd.size > 0x1000000) { flash->addr_width = 4; set_4byte(flash, info->jedec_id, 1); } else flash->addr_width = 3; } dev_info(&spi->dev, "%s (%lld Kbytes)\n", id->name, (long long)flash->mtd.size >> 10); DEBUG(MTD_DEBUG_LEVEL2, "mtd .name = %s, .size = 0x%llx (%lldMiB) " ".erasesize = 0x%.8x (%uKiB) .numeraseregions = %d\n", flash->mtd.name, (long long)flash->mtd.size, (long long)(flash->mtd.size >> 20), flash->mtd.erasesize, flash->mtd.erasesize / 1024, flash->mtd.numeraseregions); if (flash->mtd.numeraseregions) for (i = 0; i < flash->mtd.numeraseregions; i++) DEBUG(MTD_DEBUG_LEVEL2, "mtd.eraseregions[%d] = { .offset = 0x%llx, " ".erasesize = 0x%.8x (%uKiB), " ".numblocks = %d }\n", i, (long long)flash->mtd.eraseregions[i].offset, flash->mtd.eraseregions[i].erasesize, flash->mtd.eraseregions[i].erasesize / 1024, flash->mtd.eraseregions[i].numblocks); /* partitions should match sector boundaries; and it may be good to * use readonly partitions for writeprotected sectors (BP2..BP0). */ if (mtd_has_cmdlinepart()) { static const char *part_probes[] = { "cmdlinepart", NULL, }; nr_parts = parse_mtd_partitions(&flash->mtd, part_probes, &parts, 0); } if (nr_parts <= 0 && data && data->parts) { parts = data->parts; nr_parts = data->nr_parts; } #ifdef CONFIG_MTD_OF_PARTS if (nr_parts <= 0 && spi->dev.of_node) { nr_parts = of_mtd_parse_partitions(&spi->dev, spi->dev.of_node, &parts); } #endif if (nr_parts > 0) { for (i = 0; i < nr_parts; i++) { DEBUG(MTD_DEBUG_LEVEL2, "partitions[%d] = " "{.name = %s, .offset = 0x%llx, " ".size = 0x%llx (%lldKiB) }\n", i, parts[i].name, (long long)parts[i].offset, (long long)parts[i].size, (long long)(parts[i].size >> 10)); } flash->partitioned = 1; } return mtd_device_register(&flash->mtd, parts, nr_parts) == 1 ? -ENODEV : 0; //注冊一個mtd設備 硬盤分區 初始化部分一個很重要的操作 }
/* * Write an address range to the flash chip. Data must be written in * FLASH_PAGESIZE chunks. The address range may be any size provided * it is within the physical boundaries. */ static int m25p80_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { struct m25p *flash = mtd_to_m25p(mtd); u32 page_offset, page_size; struct spi_transfer t[2];//這個結構和下面一行的結構非常重要 可以看看結合上面的圖來看 struct spi_message m; DEBUG(MTD_DEBUG_LEVEL2, "%s: %s %s 0x%08x, len %zd\n", dev_name(&flash->spi->dev), __func__, "to", (u32)to, len); *retlen = 0; /* sanity checks */ if (!len) return(0); if (to + len > flash->mtd.size) return -EINVAL; spi_message_init(&m);//初始化 memset(t, 0, (sizeof t));//數組清零 t[0].tx_buf = flash->command;//命令和數據分開 其實都是數據 t[0].len = m25p_cmdsz(flash); spi_message_add_tail(&t[0], &m);//掛到message中 鏈表 准確的說是隊列 t[1].tx_buf = buf; spi_message_add_tail(&t[1], &m);//掛到message中 mutex_lock(&flash->lock); /* Wait until finished previous write command. */ if (wait_till_ready(flash)) { mutex_unlock(&flash->lock); return 1; } write_enable(flash); /* Set up the opcode in the write buffer. */ flash->command[0] = OPCODE_PP; m25p_addr2cmd(flash, to, flash->command); page_offset = to & (flash->page_size - 1); /* do all the bytes fit onto one page? */ if (page_offset + len <= flash->page_size) { t[1].len = len; spi_sync(flash->spi, &m);//把message提交給控制器處理 控制器在合適的時候發送到SPI總線上去 *retlen = m.actual_length - m25p_cmdsz(flash); } else { u32 i; /* the size of data remaining on the first page */ page_size = flash->page_size - page_offset; t[1].len = page_size; spi_sync(flash->spi, &m); *retlen = m.actual_length - m25p_cmdsz(flash); /* write everything in flash->page_size chunks */ for (i = page_size; i < len; i += page_size) { page_size = len - i; if (page_size > flash->page_size) page_size = flash->page_size; /* write the next page to flash */ m25p_addr2cmd(flash, to + i, flash->command); t[1].tx_buf = buf + i; t[1].len = page_size; wait_till_ready(flash); write_enable(flash); spi_sync(flash->spi, &m); *retlen += m.actual_length - m25p_cmdsz(flash); } } mutex_unlock(&flash->lock); return 0; }讀數據和上面的大概流程差不多!具體SPI從設備驅動和SPI控制器驅動又是通過什麼聯系起來的呢?
上面的的其中一張函數調用關系圖分析的比較詳細!