其中采用polling模式的SPI传输的代码尤其简单。例如HHARM9-EDU通过SPI接MCP2510跟ADS7846都是polling模式,下面就是一个典型的通过SPI从AD芯片读取数据的例子:
(其实其中的时序是AD芯片的要求,SPI收、发数据就是一句话(SPRDAT0/SPTDAT0寄存器的读写)而已,非常的简单,要比IIC简单得多)。
spi_tx_data(0xD0);
x_upper = rSPRDAT0;//dummy data
spi_tx_data(0x00);
x_upper = rSPRDAT0;
spi_tx_data(0x90);
x_lower = rSPRDAT0;
spi_tx_data(0x00);
y_upper = rSPRDAT0;
spi_tx_data(0x00);
y_lower = rSPRDAT0;
如果是使用DMA方式的SPI传输,代码也很简单,以接收数据read为例:
spi_rd
/*set SPCON0 to configure properly the SPI module,
DMA mode ,SCK enable, master mode , active high , format A,
Tx auto garbage data mode
*/
rSPCON0 = 0x5a;
/*DMA is configured properly*/
/*1.Base address of source data to transfer*/
rDISRC1 = 0x59000014;
/*2.LOC=1:the source is in the peripheral bus*/
/*INC=0:the address is increased by its data size*/
rDISRCC1 = 0x00000002;
/*3.Base address of destination for the transfer, to SDRAM*/
rDIDST1 = dbuf;
/*4.LOC=0:the source is in the system bus*/
/*INC=0:the address is increased by its data size*/
rDISTC1 = 0x00000000;
/*5.DMD_HS=1:Handshake mode is selected;*/
/*SYNC=1:DREQ and DACK are synchronized to HCLK*/
/*INT=1:interrupt request is generated when all the transfer is done*/
/*TSZ=0:a unit transfer is performed*/
/*SERVMODE=0:Single service mode*/
/*HWSRCSEL=011:SPI request DMA;SWHW_SEL=1:select the DMA source from hardware*/
/*RELOAD=1;DSZ=00:Byte to be transferred;TC=length */
rDCON1 = DCON1_DMD_HS|DCON1_SYNC|DCON1_INT|DCON1_TSZ|
DCON1_SERVMODE|DCON1_HWSRCSEL|DCON1_SWHW_SEL|RELOAD|
DCON1_DSZ|length;
/*6.STOP=1;ON_OFF=1;SW_TRIG=0 start/enable DMA*/
rDMASKTRIG1 = 0x006;
若采用SPI的中断时,这你的驱动代码就要稍微复杂一些了,因为引入中断方式就意味着优化驱动的通信性能的可能性,例如用tasklet方式在中断处理函数中释放CPU,或者用interruptible_sleep_on/wake_up_interruptible来实现阻塞方式的接收。
做IIC/SPI这些总线的驱动时,你必须清楚
1、这些总线的驱动其实就只要提供两个函数即可,即收(read)和发(write)
2、要分清哪些是IIC/SPI通信的时序要求,哪些是所接的器件的时序要求,例如IIC接的X1227/24LC04/UDA1380/IP175等。SPI其实没有任何时序,非常简单,如果用POLLING方式,驱动代码不超过100行。例如接CAN总线控制器MCP2510,参见参见教材《ARM9嵌入式Linux系统构建与应用》的P137页,收发就是对SPRDAT0/SPTDAT0的简单读取和赋值而已;而我们看SPI接ADS7846这个AD接四线触摸屏的时候,它的驱动就写得比较复杂,但它的复杂性在于结合了触摸屏的实际应用特性在里面,例如它的read读取坐标就不能像CAN那样死等,而必须是如果没有按下等操作就必须返回的noblock方式,这样才不会造成上层GUI+触摸屏应用程序的反应迟钝和死锁,而来了数据也不能在中断ISR里面完成,而要用tasklet并配合一个timer――digi_sam_callback来尽量让CPU不会停留在内核里面的数据读取上,而实际真正的SPI读取数据还是那个touch_pan_read_dev里面的那几句AD芯片的时序,详细分析参见上面教材的P232~P238.
/HHARM9-EDU/experiments/EXP19/driver/2410spi.c
这个驱动里面就是当年尝试用SPI的DMA+中断方式写的SPI驱动,但最后没有仔细调试,他们偷懒就放弃还是用POLLING模式了。
这个代码里面:
request_irq(INT_DMA1(/*#define IRQ_DMA1 18 /* DMA channel 1 interrupt */*/,...),
有数据来了产生的中断,因为中断肯定是用来通知接收数据的。
关于SPI与DMA
S3C2410支持4路DMA,但不能支持外部总线的DMA,只能是内部集成的peripherals跟local bus(SDRAM)之间的DMA,BF则支持EBIU的DMA。
The S3C2410X supports four-channel DMA controller that is located between the system bus and the peripheral bus.
S3C2410的内部peripherals的DMA是用户通过软件设置从这4路DMA中选择的(当然不是完全任意选择的,是有限范围的选择,看下面图表Table 8-1),而BlackFin则是每个peripheral自己绑定专用的,例如SPI拥有自己绑定的DMA,例如设置DMA时都不需要设置发送时的SRC地址和接收时的目标地址DES,而只要设置Start Address Register
(DMAx_START_ADDR/MDMA_yy_START_ADDR)
即可。
core bus/peripheral bus(CPU内部集成的接口模块)/EBIU
DMA需要了解BUS
Ø16bit的MMR Port --- PAB
Ø64bit的P Port --- EBIU
Ø32bit的D Port --- DMA
BF的SPI拥有自己的DMA通道,并且有16 bit 4-word的FIFO(到底多少个字节啊?),DMA传送就是从SPISPI_RDBR寄存器到FIFO再到SDRAM。 SPI使用DMA传送的说明参见BF53X manual P462页:Master Mode DMA Operation
带DMA的SPI传送驱动代码是(据说新升级的uClinux for blackfin已经彻底升级了驱动的框架,但我们只要理解SPI的收发操作即可,与驱动的大框架无关):
uClinux-dist/linux-2.6.x/drivers/char/adsp-spidma.c
这个驱动的框架清晰,代码量不大(1000行代码,一多半是注释和说明)
主体代码就是下面的操作函数
static struct file_operations spi_fops = {
owner: THIS_MODULE,
read: spi_read,
write: spi_write,
ioctl: spi_ioctl,
open: spi_open,
release: spi_release,
fasync: spi_fasync,
};
另外就是DMA传送完毕后的中断处理函数。
下面逐一看来:
入口点的初始化函数:spidma_init里面没什么,就是注册:
register_chrdev(SPI_MAJOR, SPI_DEVNAME, &spi_fops);
但下面两句看不懂???
*pPORT_MUX |= PFS4E; //???
*pPORTF_FER |= 0x7c40;
__builtin_bfin_ssync(); //????这个专用函数做什么的?
spi_open函数里面:
先是disable SPI:
void spidma_reg_reset(spi_device_t *pdev)
{
unsigned short sdata = 0;
/* Ctrl register */
sdata = BIT_CTL_OPENDRAIN | BIT_CTL_PHASE | BIT_CTL_TIMOD_DMA_RX;
set_spi_reg(SPI_CTL, sdata); /* Disable SPI, open drain */
set_spi_reg(SPI_FLG, 0xff00); /* Disable pin, out 3 state*/
set_spi_reg(SPI_BAUD, SPI_DEFAULT_BARD); /* 设置默认的波特率Default clock. */
set_spi_reg(SPI_STAT, 0xffff); /* Clear all status bits.*/
}
然后注册中断,这里有个封装了request_irq的函数set_dma_callback。
request_dma(CH_SPI, "BF533_SPI_DMA");
set_dma_callback(CH_SPI, (void*) spidma_irq,filp->private_data); //表示DMA接收数据完毕DMA_DONE
BF SPI在使用DMA进行收发数据的时候,DMA的设置都是在收、发函数里面设置的,因为方向要变化。
static ssize_t spi_read (struct file *filp, char *buf, size_t count, loff_t *pos)
{
unsigned short regdata;
int ierr;
spi_device_t *pdev = filp->private_data;
pdev->done=0;
pdev->tmode=RECEIVE;
/* Invalidate allocated memory in Data Cache */
// TODO: remove this line as soon GFP_DMA memory allocation is in place
blackfin_dcache_invalidate_range(buf, buf+(count)*2);
// configure spi port for DMA TIMOD RX
get_spi_reg(SPI_CTL,®data);
set_spi_reg(SPI_CTL, regdata | BIT_CTL_TIMOD_DMA_RX); //P440 10-10
pdev->dma_config |= ( WNR | RESTART | DI_EN );//P375 9-13
set_dma_config(CH_SPI, pdev->dma_config);
set_dma_start_addr(CH_SPI, buf);//P374 9-12 直接传到用户态传递来的buf里去
set_dma_x_count(CH_SPI, (count));//P379 9-17
if(pdev->length == CFG_SPI_WORDSIZE16)
set_dma_x_modify(CH_SPI, 2); //P379 9-17
else
set_dma_x_modify(CH_SPI, 1);
__builtin_bfin_ssync();
enable_dma(CH_SPI); //开始DMA传送接收数据。
// enable spi
get_spi_reg(SPI_CTL,®data);
set_spi_reg(SPI_CTL,regdata | BIT_CTL_ENABLE); //P440 10-10
//下面就等待DMA传送完毕,这里看起来是死循环,但其中通过wait_event_interruptible释放CPU
/* Wait for data available */
if(1)
{
if(pdev->nonblock) //如果是非阻塞方式
return -EAGAIN;
else //阻塞方式的处理
{
DPRINTK("SPI wait_event_interruptible\n");
ierr = wait_event_interruptible(*(pdev->rx_avail),pdev->done);//在2.4上面是用down或者interruptible_sleep_on的
if(ierr)
{
/* waiting is broken by a signal */
printk("SPI wait_event_interruptible ierr\n");
return ierr;
}
}
}
DPRINTK("SPI wait_event_interruptible done\n");
//等到中断了,下面就可以返回收到的数据了。
return count;
}
//下面是DMA中断的处理函数,是通过set_dma_callback设置的,收发都要用到这个中断
static irqreturn_t spidma_irq(int irq, void *dev_id, struct pt_regs *regs)
{
unsigned short regdata;
spi_device_t *pdev = (spi_device_t*)dev_id;
DPRINTK("spidma_irq: \n");
/* Acknowledge DMA Interrupt*/
clear_dma_irqstat(CH_SPI);//进入中断首先要清中断pending bit P391 9-29
pdev->done = 1; // Found
/* Give a signal to user program. */
if(pdev->fasyc)
kill_fasync(&(pdev->fasyc), SIGIO, POLLIN);
DPRINTK("spidma_irq: wake_up_interruptible pdev->done=%d\n",pdev->done);
/* wake up read/write block. */
wake_up_interruptible(pdev->rx_avail);
if( pdev->tmode==TRANSMIT )
{ /* Make sure TX buffer is empty before disabling SPI */
while(*pSPI_STAT & TXS);
while(*pSPI_STAT & TXS);
} else
{
while(*pSPI_STAT & RXS);
while(*pSPI_STAT & RXS);
};
/* 收发数据完毕后要disable spi */
get_spi_reg(SPI_CTL,®data);
set_spi_reg(SPI_CTL, regdata & ~BIT_CTL_ENABLE);
DPRINTK("spidma_irq: return \n");
return IRQ_HANDLED;
注:
关于DMA的函数,封装在
uClinux-dist/linux-2.6.x/arch/blackfin/kernel/simple_dma.c
文件里面,例如set_dma_start_addr/set_dma_x_count等,就类似mizi的armlinux for s3c2410一样,封装了GPIO的操作函数如set_gpio_bit等,封装这种函数的形式方便于阅读和维护,而如果裸写这些寄存器,例如
*(volatile unsigned long *)rDmaIrqState|=0xfffffffe;
这样的代码就不易维护和阅读。
另外一个代码比较多的就是spi_ioctl,主要完成例如调整波特率等杂项,都是细节了,与我们收发无关了。
从上可见,SPI驱动本身收发数据是非常简单的,只要打开DMA,就开始哗哗的传送了,设置好要接收的count和buffer,数据就自动填进来,收发完了产生一个中断,这样就可以作为结束条件,把收到的数据返回应用层了。就这么简单啦。
文章评论(0条评论)
登录后参与讨论