spi-mem


前言

  spi-mem: 為SPI存儲器生態帶來一些一致性,該框架實現了 在spi nor設備和常規spi設備以及spi nand 設備上復用spi 控制器驅動程序。

Linux spi 存儲控制器

  Linux支持雙線SPI和四線SPI模式已經有一段時間了(v3.12), SPI設備驅動程序可以為每個SPI傳輸指定I/O通道的數量。使用這種方式,對SPI存儲的操作可以被分為多次SPI傳輸,每次SPI傳輸使用預定義數量的I/O通道進行傳輸。

  這種方式可以正常工作,直到一些IP供應商決定讓它們的SPI控制器更加智能,嵌入某種高級接口,可以在單個的步驟中執行SPI存儲器的操作,而不是使用分開的多次傳輸操作。(事實上,大多數SPI控制器甚至比這更加智能,可以允許你直接將SPI存儲映射到CPU的地址空間)。在這種情況下,我們需要賦予SPI控制器更多的控制權,這樣它就可以決定具體該做什么,而不必從一組分散的SPI傳輸命令中,重建SPI存儲器操作。

  當時的決定是,將這些控制器專門用於一個任務,控制SPI NORs(當時這是唯一會用到雙線和四線模式的情況),SPI NOR框架就是為此而創建的。SPI NOR框架用於連接SPI NOR控制器驅動和SPI NOR的邏輯代碼(spi-nor 子系統),同時我們有常規的SPI控制器驅動,可以進行基礎的SPI傳輸(spi 子系統)。然而,從硬件的角度看,能為SPI NOR提供特殊特性的SPI控制器,一般也擁有進行基本傳輸的能力,即可用於控制常規的SPI設備。不幸的是,基於當前的spi-nor 子系統和spi 子系統是分裂開來的情況,如果一個SPI控制器被spi-nor子系統的驅動支持了,它將無法被用於與spi子系統中的常規設備進行通信。

  作為一個針對這個問題的部分的解決方案,在struct spi_nor結構體中嵌入一個const struct spi_nor_controller_ops *controller_ops,該結構體里定義了一些操作spi nor的函數指針,這允許spi nor控制器填充這些回調函數,以支持各種專用的spi nor控制器。同時這允許spi子系統中的常規spi控制器驅動提供一個較優的方式,來從SPI NOR存儲中讀取數據,這種方式被通用SPI NOR驅動m25p80所使用。然而,這個解決方案是部分的,因為它只優化了讀取,並且僅限於SPI NORs。

  

是什么促使我們提出SPI存儲器接口?

   我們之前已經看到,基於SPI NOR框架,SPI NOR存儲器已經得到了適當的支持。但NORs 並非SPI總線上唯一的存儲設備,SPI NANDs 正在變得越來越流行。Peter Pan提出了一個遵循SPI NOR模型的,用於支持SPI NAND設備的框架: SPI控制器必須實現SPI NAND控制器接口才能控制SPI NAND。但是當我們更深入地參與到這個開發中時,我們很快意識到沿着這條路走會有多么麻煩,因為這意味着,如果SPI控制器想要同時控制兩種設備,就必須同時實現SPI NOR和SPI NAND接口。當SPI NVRAM或任何其他類型的存儲制造商決定采用SPI總線時,將會發生什么?再添加一個SPI控制器必須實現的接口?這聽起來不是個好主意。

因此我們決定用另外的方式解決這個問題,嘗試找出SPI NANDs和SPI NORs的共同點。SPI NORs和SPI NANDs 指令集不同,行為和約束也不同(主要是由於NOR和NAND本身的不同),但當與設備交互時,都遵循同樣的SPI存儲器操作語義,這也是高級控制器都在嘗試優化的部分。

SPI 存儲器層只是提供一種方式給SPI控制器驅動,用於傳遞高級SPI存儲器操作,而不是讓它們處理SPI傳輸細節並自行嘗試優化它們。這同樣簡化了SPI存儲器驅動,因為它們只需要按照SPI存儲器規范發送SPI存儲器操作指令,不需要關心復雜的、不斷發展的、依賴具體存儲器的接口。

  

   有了這個新的架構,SPI NOR和SPI NAND都可以基於相同的SPI控制器驅動進行支持了。m25p80驅動將被修改成,使用spi-mem接口,取代具有局限性的->spi_flash_read()接口。目前,我們仍然有專用的SPI NOR控制器驅動,但最終目標是移除它們,並將它們移植為 drivers/spi 下的普通SPI控制器驅動。

spi-mem framework

  linux 中 spi-mem的核心代碼在drivers/spi/spi-mem.c   該框架提供給spi 存儲控制器驅動的api由 include/linux/spi/spi-mem.h定義。注意,可能linux內核版本不一樣,以下說明可能有所出入,但大同小異,原理相同。

struct spi_mem

  spi-mem框架用該結構體描述一個spi存儲設備:

struct spi_mem {
	struct spi_device *spi;
	void *drvpriv;
	const char *name;
};
  •   spi:底層的spi device,可以看出spi_mem是對spi_device的簡單封裝;
  •   drvpriv:spi_mem_driver 的私有數據
  •   name:該spi-mem的name

struct spi_mem_op

  該結構體表示一次對spi存儲器的操作:

struct spi_mem_op {
	struct {
		u8 nbytes;
		u8 buswidth;
		u16 opcode;
	} cmd;

	struct {
		u8 nbytes;
		u8 buswidth;
		u64 val;
	} addr;

	struct {
		u8 nbytes;
		u8 buswidth;
	} dummy;

	struct {
		u8 buswidth;
		enum spi_mem_data_dir dir;
		unsigned int nbytes;
		union {
			void *in;
			const void *out;
		} buf;
	} data;
};

  通常對spi存儲器的操作包括cmd、addr、dummy、data。

spi mem 控制器端

  一個希望優化SPI存儲器操作的SPI控制器,可以實現spi_controller_mem_ops接口:

struct spi_controller_mem_ops {
	int (*adjust_op_size)(struct spi_mem *mem, struct spi_mem_op *op);
	bool (*supports_op)(struct spi_mem *mem,
			    const struct spi_mem_op *op);
	int (*exec_op)(struct spi_mem *mem,
		       const struct spi_mem_op *op);
	const char *(*get_name)(struct spi_mem *mem);
	int (*dirmap_create)(struct spi_mem_dirmap_desc *desc);
	void (*dirmap_destroy)(struct spi_mem_dirmap_desc *desc);
	ssize_t (*dirmap_read)(struct spi_mem_dirmap_desc *desc,
			       u64 offs, size_t len, void *buf);
	ssize_t (*dirmap_write)(struct spi_mem_dirmap_desc *desc,
				u64 offs, size_t len, const void *buf);
};
  •   adjust_op_size:調整存儲器操作的數據傳輸大小,以符合對齊要求和最大FIFO大小的約束
  •   supports_op:檢查這個存儲器操作是否支持
  •   exec_op:執行存儲器操作,如果不支持則返回-ENOTSUPP
  •   get_name:自定義 struct spi_mem->name,這個name通常會傳遞給mtd->name,可以通過這個來兼容不同spi存儲器的mtdparts,不過必須要注意的是如果這name是動態分配的內存,則應該調用devm_xxx()相關的接口,因為沒有提供free_name的接口
  •   dirmap_create:創建一個直接映射的描述符,用來通過訪問memory來訪問存儲器,當控制器能做到將spi存儲器映射到cpu的地址空間時,可以實現這個。此接口由spi_mem_dirmap_create函數調用。
  •   dirmap_destroy:銷毀dirmap_create所創建的描述符,此接口會由spi_mem_dirmap_destroy調用。
  •   dirmap_read:直接從memory讀取spi存儲器的數據,由spi_mem_dirmap_read調用。
  •   dirmap_write:直接往memory寫數據來寫spi存儲器的內容,由spi_mem_dirmap_write調用。

注意,當spi_controller_mem_ops沒有實現時,core層將通過創建由多個SPI傳輸組成的SPI消息,來添加對該特性的通用支持,就像以前通用SPI NOR控制器驅動程序(名為m25p80)所做的那樣。

  對於支持直接讀寫內存來讀寫flash的控制器來說,需要對struct spi_mem_dirmap_desc這樣的結構體進行操作:

struct spi_mem_dirmap_desc {
	struct spi_mem *mem;
	struct spi_mem_dirmap_info info;
	unsigned int nodirmap;
	void *priv;
};
  •   mem:該描述符所屬的spi_mem設備
  •   info:在創建描述符時所需要的信息,下面會說明;
  •   nodirmap:如果spi controller沒有實現mem_ops->dirmap_create回調函數,則設置為1;或者在調用mem_ops->dirmap_create時出錯(超出映射的內存區域)時設置為1;當此值為1時,所有跟spi_mem_dirmap_{read,write}()相關的函數就會使用spi_mem_exec_op函數來操作flash;
  •   priv:指向controller的私有數據結構;

spi mem 設備端

  在spi存儲器的設備驅動中,應該將自己聲明為struct spi_mem_driver:

struct spi_mem_driver {
	struct spi_driver spidrv;
	int (*probe)(struct spi_mem *mem);
	int (*remove)(struct spi_mem *mem);
	void (*shutdown)(struct spi_mem *mem);
};

  該結構體集成自struct spi_driver ,spi存儲器的設備驅動需要實現probe、remove函數,他們傳入的參數是一個spi_mem對象。

  我們以通用spi nor設備端驅動程序為例,在drivers/mtd/spi-nor/core.c中:

static const struct of_device_id spi_nor_of_table[] = {
	/*
	 * Generic compatibility for SPI NOR that can be identified by the
	 * JEDEC READ ID opcode (0x9F). Use this, if possible.
	 */
	{ .compatible = "jedec,spi-nor" },
	{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, spi_nor_of_table);

/*
 * REVISIT: many of these chips have deep power-down modes, which
 * should clearly be entered on suspend() to minimize power use.
 * And also when they're otherwise idle...
 */
static struct spi_mem_driver spi_nor_driver = {
	.spidrv = {
		.driver = {
			.name = "spi-nor",
			.of_match_table = spi_nor_of_table,
		},
		.id_table = spi_nor_dev_ids,
	},
	.probe = spi_nor_probe,
	.remove = spi_nor_remove,
	.shutdown = spi_nor_shutdown,
};
module_spi_mem_driver(spi_nor_driver);

  這是linux中通用的spi-nor的驅動程序,我們簡單看一下它是如何使用spi-mem框架對spi存儲器進行操作的。以spi nor write為例

  spi_nor_write

    >>spi_nor_write_data  //此函數返回實際寫入的字節數,如果少於上一步請求寫入的字節數,就會循壞執行這步,直到所有請求的字節數寫完

      >>spi_nor_spimem_write_data

        >>if (nor->dirmap.wdesc) 執行spi_mem_dirmap_write函數  //說明一下,在spi_nor_driver驅動中的spi_nor_probe函數中,是有創建wdesc和rdesc的,不過在創建過程中會根據實際情況給desc->nodirmap賦值

          >>如果desc->nodirmap為真,則執行spi_mem_no_dirmap_write函數

          >>否則就調用spi_controller->mem_ops->dirmap_write回調函數來寫flash

        >>else 執行spi_nor_spimem_exec_op函數 //如果其他的spi mem設備端驅動里沒有創建描述符,則直接執行spi_nor_spimem_exec_op

static ssize_t spi_mem_no_dirmap_write(struct spi_mem_dirmap_desc *desc,
				       u64 offs, size_t len, const void *buf)
{
	struct spi_mem_op op = desc->info.op_tmpl;
	int ret;

	op.addr.val = desc->info.offset + offs;
	op.data.buf.out = buf;
	op.data.nbytes = len;
	ret = spi_mem_adjust_op_size(desc->mem, &op);
	if (ret)
		return ret;

	ret = spi_mem_exec_op(desc->mem, &op);
	if (ret)
		return ret;

	return op.data.nbytes;
}

  從上面的函數調用來看,spi nor write最后會調用spi_mem_exec_op函數來執行對spi nor的寫操作。此函數實現如下:

/**
 * spi_mem_exec_op() - Execute a memory operation
 * @mem: the SPI memory
 * @op: the memory operation to execute
 *
 * Executes a memory operation.
 *
 * This function first checks that @op is supported and then tries to execute
 * it.
 *
 * Return: 0 in case of success, a negative error code otherwise.
 */
int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
{
	unsigned int tmpbufsize, xferpos = 0, totalxferlen = 0;
	struct spi_controller *ctlr = mem->spi->controller;
	struct spi_transfer xfers[4] = { };
	struct spi_message msg;
	u8 *tmpbuf;
	int ret;

	ret = spi_mem_check_op(op);
	if (ret)
		return ret;

	if (!spi_mem_internal_supports_op(mem, op))
		return -ENOTSUPP;

	if (ctlr->mem_ops && !mem->spi->cs_gpiod) {
		ret = spi_mem_access_start(mem);
		if (ret)
			return ret;

		ret = ctlr->mem_ops->exec_op(mem, op);

		spi_mem_access_end(mem);

		/*
		 * Some controllers only optimize specific paths (typically the
		 * read path) and expect the core to use the regular SPI
		 * interface in other cases.
		 */
		if (!ret || ret != -ENOTSUPP)
			return ret;
	}

	tmpbufsize = op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes;

	/*
	 * Allocate a buffer to transmit the CMD, ADDR cycles with kmalloc() so
	 * we're guaranteed that this buffer is DMA-able, as required by the
	 * SPI layer.
	 */
	tmpbuf = kzalloc(tmpbufsize, GFP_KERNEL | GFP_DMA);
	if (!tmpbuf)
		return -ENOMEM;

	spi_message_init(&msg);

	tmpbuf[0] = op->cmd.opcode;
	xfers[xferpos].tx_buf = tmpbuf;
	xfers[xferpos].len = op->cmd.nbytes;
	xfers[xferpos].tx_nbits = op->cmd.buswidth;
	spi_message_add_tail(&xfers[xferpos], &msg);
	xferpos++;
	totalxferlen++;

	if (op->addr.nbytes) {
		int i;

		for (i = 0; i < op->addr.nbytes; i++)
			tmpbuf[i + 1] = op->addr.val >>
					(8 * (op->addr.nbytes - i - 1));

		xfers[xferpos].tx_buf = tmpbuf + 1;
		xfers[xferpos].len = op->addr.nbytes;
		xfers[xferpos].tx_nbits = op->addr.buswidth;
		spi_message_add_tail(&xfers[xferpos], &msg);
		xferpos++;
		totalxferlen += op->addr.nbytes;
	}

	if (op->dummy.nbytes) {
		memset(tmpbuf + op->addr.nbytes + 1, 0xff, op->dummy.nbytes);
		xfers[xferpos].tx_buf = tmpbuf + op->addr.nbytes + 1;
		xfers[xferpos].len = op->dummy.nbytes;
		xfers[xferpos].tx_nbits = op->dummy.buswidth;
		spi_message_add_tail(&xfers[xferpos], &msg);
		xferpos++;
		totalxferlen += op->dummy.nbytes;
	}

	if (op->data.nbytes) {
		if (op->data.dir == SPI_MEM_DATA_IN) {
			xfers[xferpos].rx_buf = op->data.buf.in;
			xfers[xferpos].rx_nbits = op->data.buswidth;
		} else {
			xfers[xferpos].tx_buf = op->data.buf.out;
			xfers[xferpos].tx_nbits = op->data.buswidth;
		}

		xfers[xferpos].len = op->data.nbytes;
		spi_message_add_tail(&xfers[xferpos], &msg);
		xferpos++;
		totalxferlen += op->data.nbytes;
	}

	ret = spi_sync(mem->spi, &msg);

	kfree(tmpbuf);

	if (ret)
		return ret;

	if (msg.actual_length != totalxferlen)
		return -EIO;

	return 0;
}

  可以看到當 spi_controller中的mem_ops有實現時,就會調用mem_ops->exec_op回調;否則就會將通過創建由多個spi_transfer 組成的spi_message ,調用spi_sync函數來支持通用spi的傳輸。

spi mem 控制器驅動編寫步驟

  1.   申請一個struct spi_controller的對象,初始化其必要的成員變量
  2.   根據控制器實現struct spi_controller_mem_ops操作集,並賦值給struct spi_controller的mem_ops成員。
  3.   最后調用devm_spi_register_controller函數把這個spi controller注冊進內核

以下有個demo代碼可供參考:

#include <linux/spi/spi.h>
#include <linux/spi/spi-mem.h>

struct demo_data_struct {
	struct device *dev;
    //添加私有屬性,比如控制器的寄存器地址,時鍾等
};

static int demo_setup(struct spi_device *spi)
{
    struct demo_data_struct	*f = spi_master_get_devdata(spi->master);
    //添加代碼設置spi片選,速度
}

static int demo_exec_mem_op(struct spi_mem *mem, struct spi_mem_op *op)
{
    struct demo_data_struct *f = spi_controller_get_devdata(mem->spi->master);
    //添加代碼執行一個spi_mem_op
}

static int demo_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
{
    //添加代碼調整存儲器操作的數據傳輸大小,以符合對齊要求和最大FIFO大小的約束。
}

static const char *demo_get_name(struct spi_mem *mem)
{
    struct demo_data_struct *f = spi_controller_get_devdata(mem->spi->master);
	struct device *dev = &mem->spi->dev;
	const char *name;

    /*動態分配name的內存空間,此name會寫入到mem->name,並最終可能傳入到mtd->name*/
	name = devm_kasprintf(dev, GFP_KERNEL,
			      "%s-%d", dev_name(f->dev),
			      mem->spi->chip_select);

	if (!name) {
		dev_err(dev, "failed to get memory for custom flash name\n");
		return ERR_PTR(-ENOMEM);
	}

	return name;
}

static int demo_dirmap_create(struct spi_mem_dirmap_desc *desc)
{
    //創建spi存儲器到內存的映射,如果控制器支持將存儲器的內容映射到cpu地址空間的話
}

static void demo_dirmap_destroy(struct spi_mem_dirmap_desc *desc)
{
    //dirmap_create的逆操作
}

static ssize_t demo_dirmap_read(struct spi_mem_dirmap_desc *desc, u64 offs, size_t len, void *buf)
{
    //直接從映射的內存中讀取spi存儲器的數據,返回實際讀取的字節數,注意不要超過映射的區域
}

static ssize_t demo_dirmap_write(struct spi_mem_dirmap_desc *desc,u64 offs, size_t len, const void *buf)
{
    //和dirmap_read類似
}

static const struct spi_controller_mem_ops demo_mem_ops = {
	.exec_op = demo_exec_mem_op,
	.adjust_op_size = demo_adjust_op_size,
    .supports_op = demo_supports_op,
    .get_name = demo_get_name,
    .dirmap_create = demo_dirmap_create,
    .dirmap_destroy = demo_dirmap_destroy,
    .dirmap_read = demo_dirmap_read,
    .dirmap_write = demo_dirmap_write,
};

static int demo_probe(struct platform_device *pdev)
{
    struct spi_controller *ctlr;
    struct demo_data_struct *f;
    struct device *dev = &pdev->dev;
    struct device_node *np = dev->of_node;

    ctlr = spi_alloc_master(&pdev->dev, sizeof(*f));
	if (!master)
		return -ENOMEM;
    
    ctlr->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD;//根據控制器自行修改
    ctlr->setup = demo_setup;

    f = spi_controller_get_devdata(ctlr);
	f->dev = dev;
    platform_set_drvdata(pdev, f);

    ctlr->bus_num = -1;
	ctlr->num_chipselect = 1;//片選數
	ctlr->mem_ops = &nxp_fspi_mem_ops;

    ctlr->dev.of_node = np;

    return devm_spi_register_controller(&pdev->dev, ctlr);//注冊spi控制器
}

static int demo_remove(struct platform_device *pdev)
{

}

static const struct of_device_id demo_match[] = {
	{.compatible = "xxxx" },
	{},
};

static struct platform_driver demo_driver = {
	.probe	= demo_probe,
	.remove = demo_remove,
	.driver = {
		.name	= "ti-qspi",
		.of_match_table = demo_match,
	}
};

module_platform_driver(demo_driver);

 

參考鏈接

  spi-mem發展史

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM