1、背景介绍

板子硬件设计如下:

ZYNQ通过PS内的SPI0控制器读写BCM5396寄存器,配置port状态并进行网络破环操作。由于BCM5396在上电配置一次后就不用管了,原先的做法是将配置代码全部放在UBOOT中实现,参考https://blog.csdn.net/zhaoxinfan/article/details/69662074。现在有需求要在Linux系统下读写BCM5396寄存器。

 

2、内核配置

由于Xilinx提供的linux源码中并不包含BCM5396交换芯片的驱动,所以只能将BCM5396当成通用的SPI从设备进行访问,并且将BCM5396的操作过程放在用户应用程序中实现。内核中需要启用SPIDEV:

编译内核时能看到spidev.c被编入内核

为了匹配设备数中的bcm5396,这里在spidev.c中增加了一个匹配项

 

3、devicetree配置

在devicetree中,参考spiflash,在spi控制器下创建一个子节点,如下:

由于ZYNQ SPI控制器只接了一个BCM5396,reg为1,spi-max-frequency这里配置了2MHZ,原因是BCM5396 datasheet中有下面一句话:

 

4、应用开发

编译完内核后和devicetree,启动加载linux,能在dev下找到spidev设备

接下来只需要按照BCM5396的操作序列对寄存器进行读写即可。

如果不愿意看datasheet,可以直接参考uboot中对BCM5396配置的代码。

unsigned int readBCM5396Reg(unsigned char page,unsigned char offset,unsigned char regType)
{
	unsigned char wr_buf[32],rd_buf[32],*bp;
	int len, status;
	struct spi_ioc_transfer xfer[2];
	int file;
	unsigned int result;

	file = open("/dev/spidev1.1", O_RDWR);
	if (file<0) {
		printf("open 5396 error\n");
		return 1;
	}
	memset(wr_buf, 0, sizeof(wr_buf));
	memset(rd_buf, 0, sizeof(rd_buf));
	memset(xfer,0,sizeof(xfer));

	wr_buf[0] = NREAD;
	wr_buf[1] = STS;
	xfer[0].tx_buf = (unsigned long) wr_buf;
	xfer[0].len = 2;

	xfer[1].rx_buf = (unsigned long) rd_buf;
	xfer[1].len = 1;
	status = ioctl(file, SPI_IOC_MESSAGE(2), xfer);

	wr_buf[0] = NWRITE;
	wr_buf[1] = SPG;
	wr_buf[2] = page;
	xfer[0].tx_buf = (unsigned long) wr_buf;
	xfer[0].len = 3;

	xfer[1].rx_buf = (unsigned long) rd_buf;
	xfer[1].len = 0;
	status = ioctl(file, SPI_IOC_MESSAGE(1), xfer);

	wr_buf[0] = NREAD;
	wr_buf[1] = offset;
	xfer[0].tx_buf = (unsigned long) wr_buf;
	xfer[0].len = 2;

	xfer[1].rx_buf = (unsigned long) rd_buf;
	xfer[1].len = 1;
	status = ioctl(file, SPI_IOC_MESSAGE(2), xfer);

	wr_buf[0] = NREAD;
	wr_buf[1] = STS;
	xfer[0].tx_buf = (unsigned long) wr_buf;
	xfer[0].len = 2;

	xfer[1].rx_buf = (unsigned long) rd_buf;
	xfer[1].len = 1;
	status = ioctl(file, SPI_IOC_MESSAGE(2), xfer);

	wr_buf[0] = NREAD;
	wr_buf[1] = SIO;
	xfer[0].tx_buf = (unsigned long) wr_buf;
	xfer[0].len = 2;

	xfer[1].rx_buf = (unsigned long) rd_buf;
	xfer[1].len = regType/8;
	status = ioctl(file, SPI_IOC_MESSAGE(2), xfer);

	len=status;
	if (status < 0) {
		perror("SPI_IOC_MESSAGE");
		return -1;
	}

//	printf("NREAD response(%d): ", status);
//	for (bp = rd_buf; len; len--)
//		printf("%02x ", *bp++);
//	printf("\n");

	bp = rd_buf;
	memcpy(&result,bp,regType/8);
	printf("read result is 0x%x\n",result);
	close(file);
	return result;
}

 

为了验证正确性,这里选择BCM5396 modeID寄存器

如果读出来是0x96,那么就大功告成了。

 

5、调试

可惜事与愿违,读出来的值完全不对。

再看zynq standalone下读取的值,完全正确。

接下来就只好对比linux下与standalone下的spi读写这部分的区别了。

在linux中,SPI发送时调用spi-candence.c这个驱动,发送函数如下,为了便于调试,这里增加了对SPI控制器寄存器的输出:

在standalone中发送调用的是bsp中的XSpiPs_PolledTransfer

值得一提的是,在该函数上面发现了XSpiPs_Transfer(很显然就是中断方式),这两个BSP函数实现贴在下面:

s32 XSpiPs_PolledTransfer(XSpiPs *InstancePtr, u8 *SendBufPtr,
				u8 *RecvBufPtr, u32 ByteCount)
{
	u32 StatusReg;
	u32 ConfigReg;
	u32 TransCount;
	u32 CheckTransfer;
	s32 Status_Polled;
	u8 TempData;


	u32 tempdata;

	/*
	 * The RecvBufPtr argument can be NULL.
	 */
	Xil_AssertNonvoid(InstancePtr != NULL);
	Xil_AssertNonvoid(SendBufPtr != NULL);
	Xil_AssertNonvoid(ByteCount > 0U);
	Xil_AssertNonvoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);

	/*
	 * Check whether there is another transfer in progress. Not thread-safe.
	 */
	if (InstancePtr->IsBusy == TRUE) {
		Status_Polled = (s32)XST_DEVICE_BUSY;
	} else {

		/*
		 * Set the busy flag, which will be cleared when the transfer is
		 * entirely done.
		 */
		InstancePtr->IsBusy = TRUE;

		/*
		 * Set up buffer pointers.
		 */
		InstancePtr->SendBufferPtr = SendBufPtr;
		InstancePtr->RecvBufferPtr = RecvBufPtr;

		InstancePtr->RequestedBytes = ByteCount;
		InstancePtr->RemainingBytes = ByteCount;

		/*
		 * If manual chip select mode, initialize the slave select value.
		 */
	     if (XSpiPs_IsManualChipSelect(InstancePtr) == TRUE) {
			ConfigReg = XSpiPs_ReadReg(InstancePtr->Config.BaseAddress,
						 XSPIPS_CR_OFFSET);
			/*
			 * Set the slave select value.
			 */
			ConfigReg &= (u32)(~XSPIPS_CR_SSCTRL_MASK);
			ConfigReg |= InstancePtr->SlaveSelect;
			XSpiPs_WriteReg(InstancePtr->Config.BaseAddress,
					 XSPIPS_CR_OFFSET, ConfigReg);
		}

		/*
		 * Enable the device.
		 */
		XSpiPs_Enable(InstancePtr);

		tempdata=XSpiPs_ReadReg(InstancePtr->Config.BaseAddress, XSPIPS_CR_OFFSET);
		xil_printf("CR REG 0x%x\r\n",tempdata);

		tempdata=XSpiPs_ReadReg(InstancePtr->Config.BaseAddress, XSPIPS_SR_OFFSET);
		xil_printf("SR REG 0x%x\r\n",tempdata);

		tempdata=XSpiPs_ReadReg(InstancePtr->Config.BaseAddress, XSPIPS_IER_OFFSET);
		xil_printf("IER REG 0x%x\r\n",tempdata);

		tempdata=XSpiPs_ReadReg(InstancePtr->Config.BaseAddress, XSPIPS_IDR_OFFSET);
		xil_printf("IDR REG 0x%x\r\n",tempdata);

		tempdata=XSpiPs_ReadReg(InstancePtr->Config.BaseAddress, XSPIPS_IMR_OFFSET);
		xil_printf("IMR REG 0x%x\r\n",tempdata);

		tempdata=XSpiPs_ReadReg(InstancePtr->Config.BaseAddress, XSPIPS_ER_OFFSET);
		xil_printf("ER REG 0x%x\r\n",tempdata);

		tempdata=XSpiPs_ReadReg(InstancePtr->Config.BaseAddress, XSPIPS_DR_OFFSET);
		xil_printf("DR REG 0x%x\r\n",tempdata);

		tempdata=XSpiPs_ReadReg(InstancePtr->Config.BaseAddress, XSPIPS_RXD_OFFSET);
		xil_printf("RXD REG 0x%x\r\n",tempdata);

		tempdata=XSpiPs_ReadReg(InstancePtr->Config.BaseAddress, XSPIPS_SICR_OFFSET);
		xil_printf("SICR REG 0x%x\r\n",tempdata);

		tempdata=XSpiPs_ReadReg(InstancePtr->Config.BaseAddress, XSPIPS_TXWR_OFFSET);
		xil_printf("TXWR REG 0x%x\r\n",tempdata);

		tempdata=XSpiPs_ReadReg(InstancePtr->Config.BaseAddress, XSPIPS_RXWR_OFFSET);
		xil_printf("RXWR REG 0x%x\r\n",tempdata);

		while((InstancePtr->RemainingBytes > (u32)0U) ||
			(InstancePtr->RequestedBytes > (u32)0U)) {
			TransCount = 0U;
			/*
			 * Fill the TXFIFO with as many bytes as it will take (or as
			 * many as we have to send).
			 */
			while ((InstancePtr->RemainingBytes > (u32)0U) &&
				((u32)TransCount < (u32)XSPIPS_FIFO_DEPTH)) {
				XSpiPs_SendByte(InstancePtr->Config.BaseAddress,
						*InstancePtr->SendBufferPtr);
				InstancePtr->SendBufferPtr += 1;
				InstancePtr->RemainingBytes--;
				++TransCount;
			}

			/*
			 * If master mode and manual start mode, issue manual start
			 * command to start the transfer.
			 */
			if ((XSpiPs_IsManualStart(InstancePtr) == TRUE)
				&& (XSpiPs_IsMaster(InstancePtr) == TRUE)) {
				ConfigReg = XSpiPs_ReadReg(
						InstancePtr->Config.BaseAddress,
						 XSPIPS_CR_OFFSET);
				ConfigReg |= XSPIPS_CR_MANSTRT_MASK;
				XSpiPs_WriteReg(InstancePtr->Config.BaseAddress,
						 XSPIPS_CR_OFFSET, ConfigReg);
			}

			/*
			 * Wait for the transfer to finish by polling Tx fifo status.
			 */
	        CheckTransfer = (u32)0U;
	        while (CheckTransfer == 0U)
	        {
	        	StatusReg = XSpiPs_ReadReg(InstancePtr->Config.BaseAddress,XSPIPS_SR_OFFSET);
				if ( (StatusReg & XSPIPS_IXR_MODF_MASK) != 0U)
				{
					xil_printf("clear mode fail bit\r\n");
					/*
					 * Clear the mode fail bit
					 */
					XSpiPs_WriteReg(InstancePtr->Config.BaseAddress,XSPIPS_SR_OFFSET,XSPIPS_IXR_MODF_MASK);
					return (s32)XST_SEND_ERROR;
				}

		        CheckTransfer = (StatusReg &XSPIPS_IXR_TXOW_MASK);
		    }

			/*
			 * A transmit has just completed. Process received data and
			 * check for more data to transmit.
			 * First get the data received as a result of the transmit
			 * that just completed. Receive data based on the
			 * count obtained while filling tx fifo. Always get the
			 * received data, but only fill the receive buffer if it
			 * points to something (the upper layer software may not
			 * care to receive data).
			 */
			while (TransCount != (u32)0U)
			{
				TempData = (u8)XSpiPs_RecvByte(InstancePtr->Config.BaseAddress);
				if (InstancePtr->RecvBufferPtr != NULL)
				{
					*(InstancePtr->RecvBufferPtr) = TempData;
					InstancePtr->RecvBufferPtr += 1;
				}
				InstancePtr->RequestedBytes--;
				--TransCount;
			}
		}

		/*
		 * Clear the slave selects now, before terminating the transfer.
		 */
		if (XSpiPs_IsManualChipSelect(InstancePtr) == TRUE) {
			ConfigReg = XSpiPs_ReadReg(InstancePtr->Config.BaseAddress,
						XSPIPS_CR_OFFSET);
			ConfigReg |= XSPIPS_CR_SSCTRL_MASK;
			XSpiPs_WriteReg(InstancePtr->Config.BaseAddress,
					 XSPIPS_CR_OFFSET, ConfigReg);
		}

		/*
		 * Clear the busy flag.
		 */
		InstancePtr->IsBusy = FALSE;

		/*
		 * Disable the device.
		 */
		XSpiPs_Disable(InstancePtr);
		Status_Polled = (s32)XST_SUCCESS;
	}
	return Status_Polled;
}

s32 XSpiPs_Transfer(XSpiPs *InstancePtr, u8 *SendBufPtr,
			u8 *RecvBufPtr, u32 ByteCount)
{
	u32 ConfigReg;
	u8 TransCount = 0U;
	s32 StatusTransfer;

	/*
	 * The RecvBufPtr argument can be null
	 */
	Xil_AssertNonvoid(InstancePtr != NULL);
	Xil_AssertNonvoid(SendBufPtr != NULL);
	Xil_AssertNonvoid(ByteCount > 0U);
	Xil_AssertNonvoid(InstancePtr->IsReady == (u32)XIL_COMPONENT_IS_READY);

	/*
	 * Check whether there is another transfer in progress. Not thread-safe.
	 */
	if (InstancePtr->IsBusy == TRUE) {
		StatusTransfer = (s32)XST_DEVICE_BUSY;
	} else {

		/*
		 * Set the busy flag, which will be cleared in the ISR when the
		 * transfer is entirely done.
		 */
		InstancePtr->IsBusy = TRUE;

		/*
		 * Set up buffer pointers.
		 */
		InstancePtr->SendBufferPtr = SendBufPtr;
		InstancePtr->RecvBufferPtr = RecvBufPtr;

		InstancePtr->RequestedBytes = ByteCount;
		InstancePtr->RemainingBytes = ByteCount;

	/*
	 * If manual chip select mode, initialize the slave select value.
	 */
	if (XSpiPs_IsManualChipSelect(InstancePtr) != FALSE) {
		ConfigReg = XSpiPs_ReadReg(InstancePtr->Config.BaseAddress,
					 XSPIPS_CR_OFFSET);
		/*
		 * Set the slave select value.
		 */
		ConfigReg &= (u32)(~XSPIPS_CR_SSCTRL_MASK);
		ConfigReg |= InstancePtr->SlaveSelect;
		XSpiPs_WriteReg(InstancePtr->Config.BaseAddress,
				 XSPIPS_CR_OFFSET, ConfigReg);
	}

		/*
		 * Enable the device.
		 */
		XSpiPs_Enable(InstancePtr);

		/*
		 * Clear all the interrrupts.
		 */
		XSpiPs_WriteReg(InstancePtr->Config.BaseAddress, XSPIPS_SR_OFFSET,
				XSPIPS_IXR_WR_TO_CLR_MASK);

		/*
		 * Fill the TXFIFO with as many bytes as it will take (or as many as
		 * we have to send).
		 */
		while ((InstancePtr->RemainingBytes > 0U) &&
			(TransCount < XSPIPS_FIFO_DEPTH)) {
			XSpiPs_SendByte(InstancePtr->Config.BaseAddress,
				  *InstancePtr->SendBufferPtr);
                  InstancePtr->SendBufferPtr += 1;
			InstancePtr->RemainingBytes--;
			TransCount++;
		}

		/*
		 * Enable interrupts (connecting to the interrupt controller and
		 * enabling interrupts should have been done by the caller).
		 */
		XSpiPs_WriteReg(InstancePtr->Config.BaseAddress,
				XSPIPS_IER_OFFSET, XSPIPS_IXR_DFLT_MASK);

		/*
		 * If master mode and manual start mode, issue manual start command
		 * to start the transfer.
		 */
	     if ((XSpiPs_IsManualStart(InstancePtr) == TRUE)
		&& (XSpiPs_IsMaster(InstancePtr) == TRUE)) {
			ConfigReg = XSpiPs_ReadReg(InstancePtr->Config.BaseAddress,
						   XSPIPS_CR_OFFSET);
				ConfigReg |= XSPIPS_CR_MANSTRT_MASK;
			XSpiPs_WriteReg(InstancePtr->Config.BaseAddress,
					 XSPIPS_CR_OFFSET, ConfigReg);
		 }
		StatusTransfer = (s32)XST_SUCCESS;
	}
	return StatusTransfer;
}

 

在XSpiPs_Transfer中找到下面一条语句:

这和linux下cdns_transfer_one函数中的最后一句完全一样。

由此可以得出结论,standalone和linux通过spi访问BCM5396的方式不一样,正确的方式是采用轮询方式,而非中断方式。

为了验证这个判断,将standalone下的XSpiPs_PolledTransfer全部改成XSpiPs_Transfer,这时发现读写就失败了。

 

虽然找到了linux下读写BCM5396失败的原因,但由于spi-candence.c驱动中并没有轮询模式,所以只能另想办法。

通过对比linux下和standalone下SPI发送数据时寄存器的值能发现主要的区别在XSPIPS_CR_OFFSET

主要区别在后面几位

很可能就是CPHA,CPOL这几位导致了Linux下无法正确进行数据传输。

 

最容易想到的办法就是在linux下SPI发送数据前将CR寄存器值设置为和standalone一样。

于是在spi-cadence.c中增加了如下一句话,得以保证在每次传输前将CR寄存器配置一遍。

重新编译内核,加载运行,居然成功了。

应用程序完整代码如下:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#define NREAD	(0x60)
#define NWRITE	(0x61)

#define SIO (0xF0)
#define STS (0xFE)
#define SPG (0xFF)

#define SPIF	(0x80)
#define RACK	(0x20)
#define RXRDY	(0x02)
#define TXRDY	(0x01)

unsigned int readBCM5396Reg(unsigned char page,unsigned char offset,unsigned char regType)
{
	unsigned char wr_buf[32],rd_buf[32],*bp;
	int len, status;
	struct spi_ioc_transfer xfer[2];
	int file;
	unsigned int result;

	file = open("/dev/spidev1.1", O_RDWR);
	if (file<0) {
		printf("open 5396 error\n");
		return 1;
	}
	memset(wr_buf, 0, sizeof(wr_buf));
	memset(rd_buf, 0, sizeof(rd_buf));
	memset(xfer,0,sizeof(xfer));

	wr_buf[0] = NREAD;
	wr_buf[1] = STS;
	xfer[0].tx_buf = (unsigned long) wr_buf;
	xfer[0].len = 2;

	xfer[1].rx_buf = (unsigned long) rd_buf;
	xfer[1].len = 1;
	status = ioctl(file, SPI_IOC_MESSAGE(2), xfer);

	wr_buf[0] = NWRITE;
	wr_buf[1] = SPG;
	wr_buf[2] = page;
	xfer[0].tx_buf = (unsigned long) wr_buf;
	xfer[0].len = 3;

	xfer[1].rx_buf = (unsigned long) rd_buf;
	xfer[1].len = 0;
	status = ioctl(file, SPI_IOC_MESSAGE(1), xfer);

	wr_buf[0] = NREAD;
	wr_buf[1] = offset;
	xfer[0].tx_buf = (unsigned long) wr_buf;
	xfer[0].len = 2;

	xfer[1].rx_buf = (unsigned long) rd_buf;
	xfer[1].len = 1;
	status = ioctl(file, SPI_IOC_MESSAGE(2), xfer);

	wr_buf[0] = NREAD;
	wr_buf[1] = STS;
	xfer[0].tx_buf = (unsigned long) wr_buf;
	xfer[0].len = 2;

	xfer[1].rx_buf = (unsigned long) rd_buf;
	xfer[1].len = 1;
	status = ioctl(file, SPI_IOC_MESSAGE(2), xfer);

	wr_buf[0] = NREAD;
	wr_buf[1] = SIO;
	xfer[0].tx_buf = (unsigned long) wr_buf;
	xfer[0].len = 2;

	xfer[1].rx_buf = (unsigned long) rd_buf;
	xfer[1].len = regType/8;
	status = ioctl(file, SPI_IOC_MESSAGE(2), xfer);

	len=status;
	if (status < 0) {
		perror("SPI_IOC_MESSAGE");
		return -1;
	}

//	printf("NREAD response(%d): ", status);
//	for (bp = rd_buf; len; len--)
//		printf("%02x ", *bp++);
//	printf("\n");

	bp = rd_buf;
	memcpy(&result,bp,regType/8);
	printf("read result is 0x%x\n",result);
	close(file);
	return result;
}


unsigned int writeBCM5396Reg(unsigned char page,unsigned char offset,unsigned char *pBuffer,unsigned char regType)
{
	unsigned char wr_buf[32],rd_buf[32],*bp;
	int len, status;
	struct spi_ioc_transfer xfer[2];
	int file,i;
	unsigned int result;

	file = open("/dev/spidev1.1", O_RDWR);
	if (file<0) {
		printf("open 5396 error\n");
		return 1;
	}
	memset(wr_buf, 0, sizeof(wr_buf));
	memset(rd_buf, 0, sizeof(rd_buf));
	memset(xfer,0,sizeof(xfer));

	wr_buf[0] = NREAD;
	wr_buf[1] = STS;
	xfer[0].tx_buf = (unsigned long) wr_buf;
	xfer[0].len = 2;

	xfer[1].rx_buf = (unsigned long) rd_buf;
	xfer[1].len = 1;
	status = ioctl(file, SPI_IOC_MESSAGE(2), xfer);

	wr_buf[0] = NWRITE;
	wr_buf[1] = SPG;
	wr_buf[2] = page;
	xfer[0].tx_buf = (unsigned long) wr_buf;
	xfer[0].len = 3;

	xfer[1].rx_buf = (unsigned long) rd_buf;
	xfer[1].len = 0;
	status = ioctl(file, SPI_IOC_MESSAGE(1), xfer);

	wr_buf[0] = NWRITE;
	wr_buf[1] = offset;
	for(i=0;i<regType/8;i++)
	{
		wr_buf[2+i] = pBuffer[i];
	}
	xfer[0].tx_buf = (unsigned long) wr_buf;
	xfer[0].len = 2+(regType/8);;

	xfer[1].rx_buf = (unsigned long) rd_buf;
	xfer[1].len = 0;
	status = ioctl(file, SPI_IOC_MESSAGE(1), xfer);
	close(file);
	return status;
}

int main()
{
	unsigned int temp;
	unsigned char buf[10];
	int res=0;

	buf[0]=0x00;
	buf[1]=0x0f;
	buf[2]=0x00;

	temp=readBCM5396Reg(2,0x30,8);
	printf("page 0x2 offset 0x30 is 0x%x\n",temp);

	temp=readBCM5396Reg(0x31,0x00,32);
	printf("before write page 0x31 offset 0x0 is 0x%x\n",temp);

	res=writeBCM5396Reg(0x31, 0x00, buf,32);//Port 0 - Slot 10
	if(res<0)
	{
		printf("write bcm5396 error\n");
	}

	temp=readBCM5396Reg(0x31,0x00,32);
	printf("after write page 0x31 offset 0x0 is 0x%x\n",temp);
	return 0;
}

 

输出如下:

 

6、一点补充

在调试过程中还想过在vivado中抓一下信号,standalone信号如下图:

这是正确的情况,而linux下抓的信号如下:

对比发现,linux下面和standalone信号前半部分几乎一样,可是后面突然没有了clk信号,很诡异,怀疑linux下数据根本没有发给BCM5396,因为这个现象查了好久。不过现在来看,这个排查方向是错误的,因为读数正确情况下Linux信号也和这完全一样。虽然发数时Linux下clk和standalone下的clk一样,但在中断方式下linux下的等待时间可能更长,此时没有clk也正常。

所以以后还是老老实实看代码,只有从代码中才能找到问题根源,抓到的信号只是表象罢了。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐