Zynq-Linux移植学习笔记之41-linux下通过SPI访问broadcom 5396交换芯片
1、背景介绍板子硬件设计如下:ZYNQ通过PS内的SPI0控制器读写BCM5396寄存器,配置port状态并进行网络破环操作。由于BCM5396在上电配置一次后就不用管了,原先的做法是将配置代码全部放在UBOOT中实现,参考https://blog.csdn.net/zhaoxinfan/article/details/69662074。现在有需求要在Linux系统下读写BCM5396...
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也正常。
所以以后还是老老实实看代码,只有从代码中才能找到问题根源,抓到的信号只是表象罢了。

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