1. 程式人生 > >Linux SPI驅動設計

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控制