Linux SPI驅動設計
1. SPI匯流排結構
SPI序列外設介面,是一種高速的,全雙工,同步的通訊匯流排。採用主從模式架構,支援多個slave,一般僅支援單Master
SPI介面共有4根訊號線,分別是:
裝置選擇線(SS)、時鐘線(SCK)、序列輸出資料線(MOSI)、序列輸入資料線(MISO).
2. 資料傳輸過程
主節點通過MOSI線輸出資料,從節點在SIMO處從主節點讀取資料。同時,也通過SMOI輸出MSB(最高位),
主節點會在MISO處讀取從節點的資料,整個過程將一直持續,直至交換完所有的資料。
3. 匯流排時序
SPI裸機驅動程式設計:
1. SPI控制器工作流程
開發板上沒有SPI外設,這裡貼上別人整過SPI裸機驅動測試的連結:
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,就是驅動是如何把資料通過SPI匯流排寫入FLASH中
/*
* 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控制器驅動又是通過什麼聯絡起來的呢?
上面的的其中一張函式呼叫關係圖分析的比較詳細!
相關推薦
Linux SPI驅動設計
1. SPI匯流排結構 SPI序列外設介面,是一種高速的,全雙工,同步的通訊匯流排。採用主從模式架構,支援多個slave,一般僅支援單Master SPI介面共有4根訊號線,分別是: 裝置選擇線(SS)、時鐘線(SCK)、序列輸出資料線(MOSI)、序列輸入資料線(MISO
linux spi驅動開發學習(四)-----spi驅動程式完整流程分析
所有的應用程式使用dev/目錄下建立的裝置,這些字元裝置的操作函式集在檔案spidev.c中實現。 點選(此處)摺疊或開啟 static const struct file_operations spidev_fops = { .owner = THIS
Linux spi驅動分析(一)----匯流排驅動
一、SPI匯流排驅動介紹 SPI匯流排總共需要四根線,包括MOSI、MISO、CLK和CS。本文首先從SPI設備註冊開始來講述SPI匯流排驅動。 二、設備註冊 在系統啟動的時候,會按照順序執行一些初始化程式,比如device_initcall和module_i
Linux spi驅動分析(三)----spiddev分析
static longspidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){ int err = 0; int retval = 0; struct spid
linux spi驅動
最近在學習Linux spi驅動,中途出現了諸多疑問,天蒼蒼野茫茫,堅持總是可以看到牛羊的,本文以新唐NUC972這顆晶片為例進行逐步分析 參考很有價值的兩篇文章: http://www.th7.cn/system/lin/201507/122488.shtml ht
linux spi驅動開發學習(三)-----spi_bitbang.c詳解
SPI介面在模式0下輸出第一位資料的時刻 SPI介面有四種不同的資料傳輸時序,取決於CPOL和CPHL這兩位的組合。圖1中表現了這四種時序, 時序與CPOL、CPHL的關係也可以從圖中看出。 圖1 CPOL是用來決定SCK時鐘訊號空閒時的電平,CPOL=0,空閒電平為低
Linux spi驅動分析(二)----SPI核心(bus、device_driver和device)
struct device { struct device *parent; struct device_private *p; struct kobject kobj; const char *init_name; /* init
LINUX SPI驅動筆記
static struct spi_board_info s3c_spi_devs[] __initdata = { { .modalias = "m25p10", .mode =
linux SPI驅動——spi協議(一)
一:SPI簡介以及應用 SPI, Serial Perripheral Interface, 序列外圍裝置介面, 是 Motorola 公司推出的一種同步序列介面技術. SPI 匯流排在物理上是通過接在外圍裝置微控制器(PICmicro) 上面的微處理控制單元 (
linux SPI驅動框架(二) -- 裝置驅動
裝置驅動關注的結構體主要有兩個,struct spi_device描述spi從裝置,struct spi_driver是從裝置的裝置驅動。 struct spi_device { struct device dev; str
linux SPI驅動——spi core(四)
一:SPI核心,就是指/drivers/spi/目錄下spi.c檔案中提供給其他檔案的函式,首先看下spi核心的初始化函式spi_init(void)。1: static int __init spi_init(void)2: {3: int status;4: 5: bu
聊聊Linux用戶態驅動設計
曲線 和數 抽象 res 依賴關系 epoll 隔離 如果 高性能 序言 設備驅動可以運行在內核態,也可以運行在用戶態,用戶態驅動的利弊網上有很多的討論,而且有些還上升到政治性上,這裏不再多做討論。不管用戶態驅動還是內核態驅動,他們都有各自的缺點。內核態驅動的問題是:系統調
Linux用戶態驅動設計
使用 同步 eve 顯卡 set等 download wikipedia usb 沒有 序言 設備驅動可以運行在內核態,也可以運行在用戶態,用戶態驅動的利弊網上有很多的討論,而且有些還上升到政治性上,這裏不再多做討論。不管用戶態驅動還是內核態驅動,他們都有各自的缺點
Linux USB裝置驅動程式設計 和 USB下載線驅動設計
Linux USB裝置驅動程式設計 和 USB下載線驅動設計 USB裝置驅動模型 USB裝置包括配置(configuration)、介面(interface)和端點(endpoint),一個USB裝置驅動程式對應一個USB介面,而非整個USB裝置。 在Lin
linux spi主機控制器pl022驅動註冊以及匹配裝置過程
最近看海思的spi比較多,海思3519的spi ip使用的時ARM提供的pl022,這裡對pl022驅動註冊和匹配裝置樹中的裝置這個過程捋一下。 pl022是ARM提供的片內外設,很多廠商都用了這個ip,只在一些細小的區別。所以它的驅動也是非常通用的。pl022的手冊可以看這裡點選開啟連結
Linux驅動設計硬體基礎(六)硬體時序分析
2.6 硬體時序分析2.6.1 時序分析的概念 驅動工程師一般不需要分析硬體的時序,但許多企業內驅動工程師還需要承擔電路板除錯的任務,因此,掌握時序分析的方法也就比較必要了。 對驅動工程師或硬體工程師而言,時序分析是讓晶片之間的訪問滿足晶片資料手冊中時序圖訊號有效
65 linux spi裝置驅動之spi LCD屏驅動
SPI的控制器驅動由平臺裝置與平臺驅動來實現. 驅動後用spi_master物件來描述.在裝置驅動中就可以通過函式spi_write, spi_read, spi_w8r16, spi_w8r8等函式來呼叫控制器. "include/linux/spi/s
Linux SPI匯流排和裝置驅動架構之一:系統概述
SPI是"Serial Peripheral Interface" 的縮寫,是一種四線制的同步序列通訊介面,用來連線微控制器、感測器、儲存裝置,SPI裝置分為主裝置和從裝置兩種,用於通訊和控制的四根線分別是: CS 片選訊號SCK 時鐘訊號MISO 主裝置的資料
linux spi裝置驅動中probe函式何時被呼叫
這兩天被裝置檔案快搞瘋了,也怪自己學東西一知半解吧,弄了幾天總算能把設備註冊理清楚一點點了。就以spi子裝置的註冊為例總結一下,免得自己忘記。 首先以註冊一個spidev的裝置為例: static struct spi_board_info imx5_spi_printe
Linux SPI匯流排和裝置驅動架構之二:SPI通用介面層
通過上一篇文章的介紹,我們知道,SPI通用介面層用於把具體SPI裝置的協議驅動和SPI控制器驅動聯接在一起,通用介面層除了為協議驅動和控制器驅動提供一系列的標準介面API,同時還為這些介面API定義了相應的資料結構,這些資料結構一部分是SPI裝置、SPI協議驅動和SPI控制