关于xilinx zynq petalinux 使用32MB(W25Q256)的SPI FLSH无法正常读写16M以上内存的BUG的调试过程

        最近在玩zynq petalinux时发现了一个蛋疼的BUG,我的板子使用的是W25Q256芯片,32MB容量,被我分成了u-boot,env,kernel,rootfs几个mtd块,其中的rootfs是放在flash末尾,也就是超过了spiflash一半(16MB),当系统正常启动后,挂载rootfs时始终不成功,并且使用flash_erase   /dev/mtd3  这种命令对rootfs区域进行擦除时竟然把flash前面部分的u-boot和kernel给擦掉了,心里一万个MLGB,又是xilinx官方驱动有坑了,没办法,只能去啃官方代码了

        首先,分析现象,启动kernel正常,而且kernel有一部分也超过了16MB地址,因此u-boot应该没问题,问题出在kernel mtd或者qspi flash驱动内。w25q芯片默认使用的是3个字节地址寻址,3个字节最大只能16MB,因此要寻址超过16MB时需要增长寻址位数,在w25q256中增加寻址位数有两种方式,一种是直接切换为4直接寻址,有专门的命令切换,切换后读写命令都使用4直接地址。第二种是使用EAR寄存器,读写数据时还是使用3字节地址,但是通过EAR寄存器可以切换读写的是低16M还是高16M

        经过一番艰苦的debug分析,最终定位到了内核mtd驱动层的xxxxx/linux-xlnx/drivers/mtd/spi-nor/spi-nor.c文件内,在int spi_nor_scan函数中,xilinx官方竟然强制把qspi驱动限制死了3字节地址模式,看不懂他这种骚操作是什么意思,怪不得我即使给W25q256 的属性里面添加了4字节地址支持的属性也无效

......
	if (nor->addr_width) {
		/* already configured from SFDP */
	} else if (info->addr_width) {
		nor->addr_width = info->addr_width;
	} else if (mtd->size > 0x1000000) {
#ifdef CONFIG_OF                                //关键在这里,xilinx判断设备树种的qspi 描述
		np_spi = of_get_next_parent(np);
		if (of_property_match_string(np_spi, "compatible",
					     "xlnx,zynq-qspi-1.0") >= 0) {
			int status;

			nor->addr_width = 3;                //强制使用了3字节地址模式
			set_4byte(nor, info, 0);
			status = read_ear(nor, info);
			if (status < 0)
				dev_warn(dev, "failed to read ear reg\n");
			else
				nor->curbank = status & EAR_SEGMENT_MASK;
		} else {
#endif
			/*
			 * enable 4-byte addressing
			 * if the device exceeds 16MiB
			 */
			nor->addr_width = 4;
			if (JEDEC_MFR(info) == SNOR_MFR_SPANSION ||
			    info->flags & SPI_NOR_4B_OPCODES)
				spi_nor_set_4byte_opcodes(nor, info)
......

后来想想,xilinx这样做应该是想通过ear寄存器来操作读写,不想直接切换成4字节地址,后面继续分析也验证了这个想法,看下面的代码就清楚了,只不过不知道xilinx是忘了改还是瞧不起WINBON,在判断是否操作EAR寄存器时漏掉了一个WINBON判断,导致我使用W25q256时这个判断就失效了,从而导致高16MB的flash读写出错,具体修改看下面的代码注释就知道了

static int spi_nor_write_ear(struct spi_nor *nor, u32 ear)
{
	u8 code = SPINOR_OP_WREAR;
	u8 addr;
	int ret;
	struct mtd_info *mtd = &nor->mtd;


	if (mtd->size <= (0x1000000) << nor->shift)
		return 0;

	ear = ear % (u32)mtd->size;
	addr = ear >> 24;

	if (!nor->isstacked && addr == nor->curbank)
		return 0;

	if (nor->isstacked && mtd->size <= 0x2000000)
		return 0;

	if (nor->jedec_id == CFI_MFR_AMD)
		code = SPINOR_OP_BRWR;
	if (nor->jedec_id == CFI_MFR_ST ||
	    nor->jedec_id == CFI_MFR_MACRONIX ||
	    nor->jedec_id == SNOR_MFR_ISSI ||
	    nor->jedec_id == SNOR_MFR_WINBOND) {     //WINBON是我加的,原本是没有的
		write_enable(nor);
		code = SPINOR_OP_WREAR;
	}
	nor->bouncebuf[0] = addr;

	if (nor->spimem) {
		struct spi_mem_op op =
			SPI_MEM_OP(SPI_MEM_OP_CMD(code, 1),
				   SPI_MEM_OP_NO_ADDR,
				   SPI_MEM_OP_NO_DUMMY,
				   SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 1));

		ret = spi_mem_exec_op(nor->spimem, &op);
	} else {
		ret =  nor->write_reg(nor, code, nor->bouncebuf, 1);
	}
	nor->curbank = addr;

	return ret;
}


static int read_ear(struct spi_nor *nor, struct flash_info *info)
{
	int ret;
	u8 code;

	/* This is actually Spansion */
	if (JEDEC_MFR(info) == CFI_MFR_AMD)
		code = SPINOR_OP_BRRD;
	/* This is actually Micron */
	else if (JEDEC_MFR(info) == CFI_MFR_ST ||
		 JEDEC_MFR(info) == CFI_MFR_MACRONIX ||
		 JEDEC_MFR(info) == SNOR_MFR_ISSI ||
		 JEDEC_MFR(info) == SNOR_MFR_WINBOND)        //WINBON是我加的,原本是没有的
		code = SPINOR_OP_RDEAR;
	else
		return -EINVAL;
	if (nor->spimem) {
		struct spi_mem_op op =
			SPI_MEM_OP(SPI_MEM_OP_CMD(code, 1),
				   SPI_MEM_OP_NO_ADDR,
				   SPI_MEM_OP_NO_DUMMY,
				   SPI_MEM_OP_DATA_IN(1, nor->bouncebuf, 1));

		ret = spi_mem_exec_op(nor->spimem, &op);
	} else {
		ret = nor->read_reg(nor, code, nor->bouncebuf, 1);
	}
	if (ret < 0) {
		pr_err("error %d reading EAR\n", ret);
		return ret;
	}

	return nor->bouncebuf[0];
}

按照上面的代码注释添加WINBON的判断后重新编译下载启动都正常了,后面查看了petalinux2020.2的代码,发现这个bug还是存在,难道就没人在zynq上用超过16M的spiflah?

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值