原创 阅读理解:关于SPI驱动

2007-11-27 17:34 7098 5 5 分类: MCU/ 嵌入式
关于IIC驱动,本论坛已经有深入的讨论研究了,例如:
http://www.hhcn.com/cgi-bin/topic.cgi?forum=1&topic=148&show=0
因为IIC在嵌入式系统开发中实在是太重要了,在我们的教材第三、四章也有介绍。
另外,这里也有讨论:
http://www.hhcn.com/cgi-bin/topic.cgi?forum=5&topic=498&show=0
SPI的驱动一般要比IIC的简单很多,因为没有复杂的时序。
S3C2410的SPI总线支持主从模式,支持轮询、中断(
#define IRQ_SPI0        22  /* SPI interrupt */)和DMA三种方式(SMOD):
Polling, Interrupt, and DMA transfer mode

其中采用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,&regdata);
   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,&regdata);
   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,&regdata);
   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,数据就自动填进来,收发完了产生一个中断,这样就可以作为结束条件,把收到的数据返回应用层了。就这么简单啦。

PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
EE直播间
更多
我要评论
0
5
关闭 站长推荐上一条 /3 下一条