欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

linux挂载QSPI FLASH

程序员文章站 2022-06-27 20:14:40
常用的flash设备有:nor-flash、nand-flash、qspi-flash。对于qspi-flash设备,linux要想正常挂载的话,必须要注册qspi控制器驱动、qspi设备驱动两种驱动文件。qspi控制器驱动主要是初始化好控制器的寄存器,而qspi设备驱动则是对应的flash操作接口以及调用到块设备驱动,进行块设备解析。对于flash设备驱动的架构,可以参阅博主的上篇文章。这里我们从字符设备的角度分析下qspi-flash的驱动架构。以zynq为例来分析:一、qspi控制器驱动q...

常用的flash设备有:nor-flash、nand-flash、qspi-flash。对于qspi-flash设备,linux要想正常挂载的话,必须要注册qspi控制器驱动、qspi设备驱动两种驱动文件。qspi控制器驱动主要是初始化好控制器的寄存器,而qspi设备驱动则是对应的flash操作接口以及调用到块设备驱动,进行块设备解析。

对于flash设备驱动的架构,可以参阅博主的上篇文章。这里我们从字符设备的角度分析下qspi-flash的驱动架构。以zynq为例来分析:

一、qspi控制器驱动

qspi控制器驱动代码位于drivers\spi\spi-zynq-qspi.c中。在drivers\spi\目录下有很多spi驱动的代码,如果你并不知道qspi驱动应该看哪个文件,可以通过设备树中qspi节点中的compatiabe属性来反推,例如设备树中的qspi属性为xlnx,zynq-qspi-1.0,在内核源码里搜索xlnx,zynq-qspi-1.0,就可以找到对应的驱动文件。

static int zynq_qspi_probe(struct platform_device *pdev)
{
	int ret = 0;
	struct spi_master *master;
	struct zynq_qspi *xqspi;
	struct resource *res;
	u32 num_cs;

	master = spi_alloc_master(&pdev->dev, sizeof(*xqspi));
	if (!master)
		return -ENOMEM;

	xqspi = spi_master_get_devdata(master);
	master->dev.of_node = pdev->dev.of_node;
	platform_set_drvdata(pdev, master);

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	xqspi->regs = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(xqspi->regs)) {
		ret = PTR_ERR(xqspi->regs);
		goto remove_master;
	}

	if (of_property_read_u32(pdev->dev.of_node, "is-dual",
				 &xqspi->is_dual)) {
		dev_warn(&pdev->dev, "couldn't determine configuration info");
		dev_warn(&pdev->dev, "about dual memories. defaulting to single memory\n");
	}

	xqspi->pclk = devm_clk_get(&pdev->dev, "pclk");
	if (IS_ERR(xqspi->pclk)) {
		dev_err(&pdev->dev, "pclk clock not found.\n");
		ret = PTR_ERR(xqspi->pclk);
		goto remove_master;
	}

	xqspi->refclk = devm_clk_get(&pdev->dev, "ref_clk");
	if (IS_ERR(xqspi->refclk)) {
		dev_err(&pdev->dev, "ref_clk clock not found.\n");
		ret = PTR_ERR(xqspi->refclk);
		goto remove_master;
	}

	ret = clk_prepare_enable(xqspi->pclk);
	if (ret) {
		dev_err(&pdev->dev, "Unable to enable APB clock.\n");
		goto remove_master;
	}

	ret = clk_prepare_enable(xqspi->refclk);
	if (ret) {
		dev_err(&pdev->dev, "Unable to enable device clock.\n");
		goto clk_dis_pclk;
	}

	/* QSPI controller initializations */
	zynq_qspi_init_hw(xqspi);

	xqspi->irq = platform_get_irq(pdev, 0);
	if (xqspi->irq <= 0) {
		ret = -ENXIO;
		dev_err(&pdev->dev, "irq resource not found\n");
		goto remove_master;
	}
	ret = devm_request_irq(&pdev->dev, xqspi->irq, zynq_qspi_irq,
			       0, pdev->name, master);
	if (ret != 0) {
		ret = -ENXIO;
		dev_err(&pdev->dev, "request_irq failed\n");
		goto remove_master;
	}

	ret = of_property_read_u32(pdev->dev.of_node, "num-cs",
				   &num_cs);
	if (ret < 0)
		master->num_chipselect = ZYNQ_QSPI_DEFAULT_NUM_CS;
	else
		master->num_chipselect = num_cs;

	master->setup = zynq_qspi_setup;
	master->set_cs = zynq_qspi_chipselect;
	master->transfer_one = zynq_qspi_start_transfer;
	master->prepare_transfer_hardware = zynq_prepare_transfer_hardware;
	master->unprepare_transfer_hardware = zynq_unprepare_transfer_hardware;
	master->flags = SPI_MASTER_QUAD_MODE;

	master->max_speed_hz = clk_get_rate(xqspi->refclk) / 2;
	master->bits_per_word_mask = SPI_BPW_MASK(8);
	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_RX_DUAL | SPI_RX_QUAD |
			    SPI_TX_DUAL | SPI_TX_QUAD;

	ret = spi_register_master(master);
	if (ret) {
		dev_err(&pdev->dev, "spi_register_master failed\n");
		goto clk_dis_all;
	}

	return ret;

clk_dis_all:
	clk_disable_unprepare(xqspi->refclk);
clk_dis_pclk:
	clk_disable_unprepare(xqspi->pclk);
remove_master:
	spi_master_put(master);
	return ret;
}

从上述代码中可以看到,控制器驱动代码做的工作就是获取设备树中的硬件资源,初始化硬件,以及实现spi-master中的方法。如果编译内核时将spi编译进去,在生成的操作系统中,我们可以通过

ls /sys/bus/

可以看到sys/bus目录下有spi总线。

linux挂载QSPI FLASH

但是这时候你在sys/bus/spi/driver目录下看不到任何驱动,因为你注册的zynq-qspi驱动是平台总线下的,我么可以利用  ls sys/bus/platform/drivers

linux挂载QSPI FLASH

现在控制器驱动已经加载上去了,spi总线也注册了,但是spi的设备驱动还没有加载,因此还需要将spi设备驱动编译进内核。spi设备常用的就是mtd设备,因此需要去看看块设备驱动中包含哪些东西?

这时候就需要看硬件上采用的flash型号了,市面上flash型号太多了,linux不可能把每个型号都列出来,只能列出一部分的型号,但好在flash的接口是标准的,要么是JEDEC标准,要么是CFI标准,因此,内核驱动源码中只要实现这两种接口的驱动就行。

我们以m25p80.c为例,来分析spi设备驱动的代码。

static int m25p_probe(struct spi_device *spi)
{
	struct flash_platform_data	*data;
	struct m25p *flash;
	struct spi_nor *nor;
	enum read_mode mode = SPI_NOR_NORMAL;
	char *flash_name;
	int ret;

	data = dev_get_platdata(&spi->dev);

	flash = devm_kzalloc(&spi->dev, sizeof(*flash), GFP_KERNEL);
	if (!flash)
		return -ENOMEM;

	nor = &flash->spi_nor;

	/* install the hooks */
	nor->read = m25p80_read;
	nor->write = m25p80_write;
	nor->write_reg = m25p80_write_reg;
	nor->read_reg = m25p80_read_reg;

	nor->dev = &spi->dev;
	spi_nor_set_flash_node(nor, spi->dev.of_node);
	nor->priv = flash;

	spi_set_drvdata(spi, flash);
	flash->spi = spi;
	nor->spi = spi;

	if (spi->mode & SPI_RX_QUAD)
		mode = SPI_NOR_QUAD;
	else if (spi->mode & SPI_RX_DUAL)
		mode = SPI_NOR_DUAL;

	if (data && data->name)
		nor->mtd.name = data->name;

	/* For some (historical?) reason many platforms provide two different
	 * names in flash_platform_data: "name" and "type". Quite often name is
	 * set to "m25p80" and then "type" provides a real chip name.
	 * If that's the case, respect "type" and ignore a "name".
	 */
	if (data && data->type)
		flash_name = data->type;
	else if (!strcmp(spi->modalias, "spi-nor"))
		flash_name = NULL; /* auto-detect */
	else
		flash_name = spi->modalias;

	ret = spi_nor_scan(nor, flash_name, mode);
	if (ret)
		return ret;

	return mtd_device_register(&nor->mtd, data ? data->parts : NULL,
				   data ? data->nr_parts : 0);
}

显然,probe函数中主要实现了flash设备的读写操作,以及注册块设备。在probe函数中有个很重要的函数spi_nor_scan,追踪下这个函数:

spi_nor_scan->spi_nor_read_id->spi_nor_ids

在spi_nor_ids中基本列举了所有的flash型号。因此,就可以理解,为什么设备树中描述的实际的flash型号,但驱动却无法与设备匹配上的问题。

这是因为spi设备驱动程序中支持的id_table中只列举了少许flash设备,而驱动和设备匹配的规则是先比较of_match_table,如果of_match_table没有匹配的,则比较id_table中的设备,因此,在写flash设备树树时必须使用id_table中列举的设备名称,这样设备和驱动才能match上,驱动程序的probe函数才能被执行。

spi_nor_scan函数会去读取flash设备的ID,而并不是直接用设备树中的compatible属性作为硬件信息,如果实际的flash  ID与设备树中定义的不同时,则会打印

linux挂载QSPI FLASH

因此,在写设备树文件时,只需要写一个id_table中列举的与实际flash设备相近的设备就可以了,spi设备驱动会自动读取flash的ID来获取硬件信息。

查看设备与驱动有没有匹配上可以通过  ls sys/bus/spi/drivers/m25p80/来查看

linux挂载QSPI FLASH

之前将设备树中的compatible属性写为s25fl128s,驱动就一直无法执行probe函数,而将compatible属性改为s25fl256s1,就可以看到驱动和设备match上了,probe函数正常执行。

linux挂载QSPI FLASH

SPI通信是通过主机发起的,因此CPU侧外挂的控制器要作为主机控制器,来发起对SPI设备的访问。

本文地址:https://blog.csdn.net/m0_37765662/article/details/110541016