android SPI接口的读写时间问题

最近遇到某客户需要调试一款SPI接口的NFC模块fm17550,它采用SPI接口,规格书上说明最高支持10Mbps。但是客户对寄存器的读写时间有要求:每个寄存器(8位)的读取时间不能超过60us。
采用Android 驱动原生的代码,增加打印:
printk(KERN_ERR “start read reg \n”);
tmp = Read_Reg((u8)tmp);
printk(KERN_ERR “end cmd=%d %02X\n”, cmd, (u8)tmp);

[ 900.232668] start read reg
[ 900.235575] cmd=8 00
[ 900.237496] start read reg
[ 900.240704] cmd=8 03
读寄存器的时间大约4ms,(当然增加printk打印会多耗费时间的)这个时间的差距和客户的要求不在一个数量级的。

linux驱动中spi的读写时间耗在哪儿了呢?继续增加打印:
Read_Reg–>
spi_write_then_read (kernel\drivers\spi\spi.c)–>
spi_sync -->
__spi_sync -->
wait_for_completion(&done);
这里wait_for_completion是需要等待进程完成的。
高通的SPI驱动涉及到管道pipe的设置、中断的设置,写寄存器之前要检查spi的状态、读寄存器要等待中断
writel_relaxed(word, dd->base + SPI_OUTPUT_FIFO);
data_in = readl_relaxed(dd->base + SPI_INPUT_FIFO);
如果用IOctrl的方式,从用户层调用到驱动,时间上也是不能满足要求的。
究其原因,linux并不是一个实时系统,spi驱动一般都不考虑实时性,都是将message挂到spi控制器的queue上,然后调用queue_work或者采用类似方法。

在驱动层修改的路走不通,参考网上的提示,在用户空间把GPIO的地址映射过来,直接操作寄存器试试。
参考代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <unistd.h> 
#include <sys/mman.h>

#define Anticollision 0x02
#define TIMEOUT_Err 0x20

#define CommandReg 0x01
#define ComIEnReg 0x02
#define DivIEnReg 0x03
#define ComIrqReg 0x04
#define DivIrqReg 0x05
#define ErrorReg 0x06
#define Status1Reg 0x07
#define Status2Reg 0x08
#define FIFODataReg 0x09
#define FIFOLevelReg 0x0A
#define WaterLevelReg 0x0B
#define ControlReg 0x0C
#define BitFramingReg 0x0D
#define CollReg 0x0E
#define ModeReg 0x11
#define TxModeReg 0x12
#define RxModeReg 0x13
#define TxControlReg 0x14
#define TxAutoReg 0x15
#define TxSelReg 0x16
#define RxSelReg 0x17
#define RxThresholdReg 0x18
#define DemodReg 0x19
#define MfTxReg 0x1C
#define MfRxReg 0x1D
#define SerialSpeedReg 0x1F
#define CRCMSBReg 0x21
#define CRCLSBReg 0x22
#define ModWidthReg 0x24
#define GsNOffReg 0x23
#define TxBitPhaseReg 0x25
#define RFCfgReg 0x26
#define GsNReg 0x27
#define CWGsPReg 0x28
#define ModGsPReg 0x29
#define TModeReg 0x2A
#define TPrescalerReg 0x2B
#define TReloadMSBReg 0x2C
#define TReloadLSBReg 0x2D
#define TCounterValMSBReg 0x2E
#define TCounterValLSBReg 0x2F
#define TestSel1Reg 0x31
#define TestSel2Reg 0x32
#define TestPinEnReg 0x33
#define TestPinValueReg 0x34
#define TestBusReg 0x35
#define AutoTestReg 0x36
#define VersionReg 0x37
#define AnalogTestReg 0x38
#define TestDAC1Reg 0x39
#define TestDAC2Reg 0x3A
#define TestADCReg 0x3B

#define TLMM_BASE_ADDR              0x1000000
#define GPIO_CONFIG_ADDR(x)         (TLMM_BASE_ADDR + (x)*0x1000)
#define GPIO_IN_OUT_ADDR(x)         (TLMM_BASE_ADDR + 0x00000004 + (x)*0x1000)

//#define MAP_SIZE        0x100
#define MAP_SIZE        0x4

/* GPIO TLMM: Direction */
#define GPIO_INPUT      2
#define GPIO_OUTPUT     0

/* GPIO TLMM: Pullup/Pulldown */
#define GPIO_NO_PULL    0
#define GPIO_PULL_DOWN  1
#define GPIO_KEEPER     2
#define GPIO_PULL_UP    3

/* GPIO TLMM: Drive Strength */
#define GPIO_2MA        0
#define GPIO_4MA        1
#define GPIO_6MA        2
#define GPIO_8MA        3
#define GPIO_10MA       4
#define GPIO_12MA       5
#define GPIO_14MA       6
#define GPIO_16MA       7

/* GPIO TLMM: Status */
#define GPIO_ENABLE     0
#define GPIO_DISABLE    1

/* GPIO_IN_OUT register shifts. */
#define GPIO_IN         1
#define GPIO_OUT        2

static int dev_fd;

//stone added for nfc-test
unsigned char *g_CsnMapBase;
unsigned char *g_ClkMapBase;
unsigned char *g_MosiMapBase;
unsigned char *g_MisoMapBase;

#define NFC_CSN         2 
#define NFC_CLK         3
#define NFC_MOSI        0
#define NFC_MISO        98//1 

#define NFC_CSN_LOW     *(volatile unsigned int *)(g_CsnMapBase + 4) = 0;
#define NFC_CSN_HIGH    *(volatile unsigned int *)(g_CsnMapBase + 4) = 02; 
#define NFC_CLK_LOW     *(volatile unsigned int *)(g_ClkMapBase + 4) = 0;
#define NFC_CLK_HIGH    *(volatile unsigned int *)(g_ClkMapBase + 4) = 02; 
#define NFC_MOSI_LOW    *(volatile unsigned int *)(g_MosiMapBase + 4) = 0;
#define NFC_MOSI_HIGH   *(volatile unsigned int *)(g_MosiMapBase + 4) = 02; 
#define NFC_GET_MISO    (*(volatile unsigned int *)(g_MisoMapBase + 4))& GPIO_IN;
/***********************************************/
void SpiSendByte( unsigned char ucByte )
{
    unsigned int i;
    for (i=0; i<8; i++)
    {
        NFC_CLK_LOW;
        //usleep(1);
        if (ucByte & 0x80)
        {
            NFC_MOSI_HIGH;
        }
        else
        {
            NFC_MOSI_LOW;
        }

        ucByte=ucByte<<1;
        //TODO:usleep may need
        //usleep(1);
        NFC_CLK_HIGH;
        //TODO:usleep may need
        //usleep(1);
    }
}


unsigned char SpiSendByteThenRecv( unsigned char ucByte )
{
    unsigned int i;
    unsigned char ucData=0;
    unsigned char ucMISO=0;

    for (i=0; i<8; i++)
    {
        NFC_CLK_LOW;
        //usleep(1);
        if (ucByte & 0x80)
        {
            NFC_MOSI_HIGH;
        }
        else
        {
            NFC_MOSI_LOW;
        }

        ucByte=ucByte<<1;
        //TODO:usleep may need
        usleep(1);
		//上升沿发送数据
        NFC_CLK_HIGH;
        //usleep(1);
        //TODO:usleep may need
    }
	
	usleep(2);
	
    for (i=0; i<8; i++)
    {
        ucData = ucData<<1;
        NFC_CLK_LOW;
        usleep(1);

        NFC_CLK_HIGH;
		
		//g_MisoMapBase=(unsigned char * )mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED  , dev_fd, GPIO_CONFIG_ADDR(NFC_MISO) );
		//*(volatile unsigned int *)(g_MisoMapBase) = GPIO_NO_PULL|(GPIO_INPUT<<2)|(GPIO_6MA<<6)|(GPIO_DISABLE<<9);
        //usleep(1);
		//上升沿读取数据
        ucMISO = NFC_GET_MISO;

        if (ucMISO == 1)
            ucData = ucData | 1;
        //TODO:usleep may need
        //usleep(1);
    }

    return ucData;

}

unsigned char Read_Reg(unsigned char address)
{
    unsigned char buf, reg_data;

    NFC_CSN_LOW;
    buf = 0x80|(address<<1);
    reg_data = SpiSendByteThenRecv(buf);
    NFC_CSN_HIGH;

    return reg_data;
}

unsigned char Write_Reg(unsigned char address, unsigned char reg_data)
{
    NFC_CSN_LOW;
    SpiSendByte( address<<1 );
    SpiSendByte( reg_data );
    NFC_CSN_HIGH;

    return 0;
}
/***********************************************/

int main(int argc, char **argv)
{ 
	dev_fd = open("/dev/mem", O_RDWR | O_NDELAY);      

	if (dev_fd < 0)  
	{
		printf("open(/dev/mem) failed.");    
		return 0;
	}  
	
	//配置GPIO2为输出功能 CS
	g_CsnMapBase=(unsigned char * )mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED  , dev_fd, GPIO_CONFIG_ADDR(NFC_CSN) );
	*(volatile unsigned int *)(g_CsnMapBase) = GPIO_PULL_DOWN|(GPIO_OUTPUT<<2)|(GPIO_16MA<<6)|(GPIO_DISABLE<<9);
	
	//配置GPIO1为输入功能 MISO
	g_MisoMapBase=(unsigned char * )mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED  , dev_fd, GPIO_CONFIG_ADDR(NFC_MISO) );
	*(volatile unsigned int *)(g_MisoMapBase) = GPIO_NO_PULL|(GPIO_INPUT<<2)|(GPIO_6MA<<6)|(GPIO_DISABLE<<9);

	//配置GPIO0为输出功能 MOSI
	g_MosiMapBase=(unsigned char * )mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED  , dev_fd, GPIO_CONFIG_ADDR(NFC_MOSI) );
	*(volatile unsigned int *)(g_MosiMapBase) = GPIO_PULL_DOWN|(GPIO_OUTPUT<<2)|(GPIO_16MA<<6)|(GPIO_DISABLE<<9);
	
	//配置GPIO3为输出功能 CLK
	g_ClkMapBase=(unsigned char * )mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED  , dev_fd, GPIO_CONFIG_ADDR(NFC_CLK) );
	*(volatile unsigned int *)(g_ClkMapBase) = GPIO_PULL_DOWN|(GPIO_OUTPUT<<2)|(GPIO_16MA<<6)|(GPIO_DISABLE<<9);
	
	//printf("set GPIO_0,2,3 func \n"); 
	
	//int i;
	//for(i=0; i<10; i++)
	Write_Reg(ControlReg, 0x10);		// Start Reader mode  [4]:1,As an NFC communication initiator 
	Write_Reg(GsNReg, 0xF0|0x04);	// CWGsN = 0xF; ModGsN = 0x4 
	char ucRegVal = Read_Reg(GsNReg);	// Determine if the SPI interface communicates properly
	if(ucRegVal!=0xF4)	//Verify that the interface is correct
		printf("GsNReg set error! \n"); 	
	else
		printf("GsNReg set ok! \n"); 	

	while(1)
	{
	#if 1
		char tmp = Read_Reg(7);
        Write_Reg(0x16,0x5a);
		printf("get Reg_7=%d \n", tmp); 
		usleep(2);
	#endif
	
	#if 0
		NFC_CSN_LOW;
		printf("set cs=0 \n"); 
		usleep(1000000);
		NFC_CSN_HIGH;
		printf("set cs=1 \n"); 
		usleep(1000000);
	#endif
	
	#if 0
		unsigned char ucMISO = NFC_GET_MISO;
		printf("MISO  =%d\n", ucMISO); 
		usleep(200000);
	#endif
	}
	
	if(dev_fd)
		close(dev_fd);

	munmap(g_CsnMapBase,MAP_SIZE);
	munmap(g_MisoMapBase,MAP_SIZE);
	munmap(g_MosiMapBase,MAP_SIZE);
	munmap(g_ClkMapBase,MAP_SIZE);

	return 0;
}

这样修改后,测试读取一个寄存器的时间在60us左右,满足客户要求。
当然,这么修改还是有局限性的,用GPIO模拟的方式在容错、批量传输上都是不能保证的,只是在寄存器比较少的情况下对时效要求比较高时可以采用。

/dev/mem:物理内存的全镜像。可以用来访问物理内存。由于应用运行都在用户空间,使用的是虚拟内存,不能直接访问物理地址空间,通过/dev/mem文件可以用来访问系统的全部寻址空间。

使用mmap函数时,物理地址的起始地址有一些讲究,不是随便给个地址就直接映射了,而是给的地址需要是页对齐的,即4K对齐,例如下面的例子中,某个gpio的地址是0x11000000 + 0x100,基地址是0x11000000,偏移是0x100,但是当我直接映射0x11000100地址到用户空间,然后对返回的虚拟地址进行读写操作,这是不对的,因为在/dev/mem的驱动中,会把0x11000100地址进行4K对齐,然后给返回用户空间,此时放回的地址不是我们想要的。正确的做法是应该映射0x11000000地址到用户空间,然后在返回的虚拟机地址vadd加上0x100即可。

另外要注意:使用/dev/mem时需要在config中打开CONFIG_DEVMEM。

猜你喜欢

转载自blog.csdn.net/cornerstone1/article/details/112557139