1. 程式人生 > >S3C6410 SPI全雙工讀寫流程分析(原創)

S3C6410 SPI全雙工讀寫流程分析(原創)

S3C6410 SPI全雙工讀寫流程分析

一、SPI控制器datasheet

2 SPI的所有暫存器都是對映到核心空間的,採用基地址+偏移地址的方式訪問

static volatile void  __iomem *spiregs;                            //global variable for mapping spiregister

spiregs = (volatile)ioremap(0x7F00B000,0x30);  //just request for the spi0

3 下文可能用到的偏移地址

#define S3C_CH_CFG                (0x00)     //SPI configuration
#define S3C_CLK_CFG               (0x04)      //Clock configuration
#define S3C_MODE_CFG                   (0x08)     //SPI FIFO control
#define S3C_SLAVE_SEL            (0x0C)      //Slave selection
#define S3C_SPI_INT_EN                   (0x10)      //SPI interrupt enable
#define S3C_SPI_STATUS          (0x14)      //SPI status
#define S3C_SPI_TX_DATA                (0x18)      //SPI TX data
#define S3C_SPI_RX_DATA                (0x1C)      //SPI RX data
#define S3C_PACKET_CNT                (0x20)      //count how many data master gets
#define S3C_PENDING_CLR             (0x24)      //Pending clear
#define S3C_SWAP_CFG           (0x28)      //SWAPconfig register
#define S3C_FB_CLK                  (0x28)     //SWAP FB config register
 
 
#define SPI_CH_SW_RST                   (1<<5)
#define SPI_CH_MASTER                  (0<<4)
#define SPI_CH_SLAVE              (1<<4)
#define SPI_CH_RISING            (0<<3)
#define SPI_CH_FALLING                   (1<<3)
#define SPI_CH_FORMAT_A             (0<<2)
#define SPI_CH_FORMAT_B             (1<<2)
#define SPI_CH_RXCH_OFF              (0<<1)
#define SPI_CH_RXCH_ON               (1<<1)
#define SPI_CH_TXCH_OFF               (0<<0)
#define SPI_CH_TXCH_ON                (1<<0)
 
#define SPI_CLKSEL_PCLK                 (0<<9)
#define SPI_CLKSEL_USBCLK   (1<<9)
#define SPI_CLKSEL_ECLK                 (2<<9)
#define SPI_ENCLK_DISABLE  (0<<8)
#define SPI_ENCLK_ENABLE   (1<<8)
 
#define SPI_MODE_CH_TSZ_BYTE (0<<29)
#define SPI_MODE_CH_TSZ_HALFWORD       (1<<29)
#define SPI_MODE_CH_TSZ_WORD       (2<<29)
#define SPI_MODE_BUS_TSZ_BYTE        (0<<17)
#define SPI_MODE_BUS_TSZ_HALFWORD     (1<<17)
#define SPI_MODE_BUS_TSZ_WORD     (2<<17)
#define SPI_MODE_RXDMA_OFF    (0<<2)
#define SPI_MODE_RXDMA_ON     (1<<2)
#define SPI_MODE_TXDMA_OFF    (0<<1)
#define SPI_MODE_TXDMA_ON     (1<<1)
#define SPI_MODE_SINGLE              (0<<0)
#define SPI_MODE_4BURST             (1<<0)
 
#define SPI_SLAVE_MAN                   (0<<1)
#define SPI_SLAVE_AUTO                  (1<<1)
#define SPI_SLAVE_SIG_ACT   (0<<0)
#define SPI_SLAVE_SIG_INACT        (1<<0)
 
#define SPI_INT_TRAILING_DIS      (0<<6)
#define SPI_INT_TRAILING_EN       (1<<6)
#define SPI_INT_RX_OVERRUN_DIS       (0<<5)
#define SPI_INT_RX_OVERRUN_EN        (1<<5)
#define SPI_INT_RX_UNDERRUN_DIS    (0<<4)
#define SPI_INT_RX_UNDERRUN_EN     (1<<4)
#define SPI_INT_TX_OVERRUN_DIS        (0<<3)
#define SPI_INT_TX_OVERRUN_EN         (1<<3)
#define SPI_INT_TX_UNDERRUN_DIS    (0<<2)
#define SPI_INT_TX_UNDERRUN_EN     (1<<2)
#define SPI_INT_RX_FIFORDY_DIS (0<<1)
#define SPI_INT_RX_FIFORDY_EN  (1<<1)
#define SPI_INT_TX_FIFORDY_DIS (0<<0)
#define SPI_INT_TX_FIFORDY_EN  (1<<0)
 
#define SPI_STUS_TX_DONE   (1<<21)
#define SPI_STUS_TRAILCNT_ZERO        (1<<20)
#define SPI_STUS_RX_OVERRUN_ERR   (1<<5)
#define SPI_STUS_RX_UNDERRUN_ERR         (1<<4)
#define SPI_STUS_TX_OVERRUN_ERR    (1<<3)
#define SPI_STUS_TX_UNDERRUN_ERR          (1<<2)
#define SPI_STUS_RX_FIFORDY       (1<<1)
#define SPI_STUS_TX_FIFORDY       (1<<0)
 
#define SPI_PACKET_CNT_DIS         (0<<16)
#define SPI_PACKET_CNT_EN (1<<16)


二、重點引數及初始化步驟


1 雙通道SPI管腳配置



2 傳輸模型配置

/*     Set transfer type (CPOL & CPHA set)    */

         spi_chcfg= SPI_CH_RISING | SPI_CH_FORMAT_A;

         spi_chcfg|= SPI_CH_MASTER;

         writel(spi_chcfg , spiregs + S3C_CH_CFG);

3 時鐘配置


使用PCLK,外部時鐘66M,100分屏:

/*     Set clock configuration register    

        *       SPIclockout = clock source / (2 * (prescaler +1))    

         *       PCLK=66Mhz, SPI clockout = clock source /(2 * (prescaler +1))      */

         spi_clkcfg= SPI_ENCLK_ENABLE;

         spi_clkcfg|= SPI_CLKSEL_PCLK;

         writel(spi_clkcfg , spiregs + S3C_CLK_CFG);

         spi_clkcfg= readl( spiregs + S3C_CLK_CFG);

         spi_clkcfg|= 49;       // the least spi speed =660Khz

         writel(spi_clkcfg , spiregs + S3C_CLK_CFG);

4 SPI 模組設定


/*     Set SPI MODE configuration register    */

         spi_modecfg= SPI_MODE_CH_TSZ_BYTE| SPI_MODE_BUS_TSZ_BYTE;

         spi_modecfg|= SPI_MODE_TXDMA_OFF| SPI_MODE_SINGLE| SPI_MODE_RXDMA_OFF;

         spi_modecfg&= ~( 0x3f << 5);

         spi_modecfg|= ( 0x1 << 5);    // Tx FIFOtrigger level in INT mode

         spi_modecfg&= ~( 0x3f << 11);

         spi_modecfg|= ( 0x1 << 11);           // Rx FIFOtrigger level in INT mode

         spi_modecfg&= ~( 0x3ff << 19);     

         spi_modecfg|= ( 0x1 << 19);   // Counting ofTailing Bytes

         writel(spi_modecfg,spiregs + S3C_MODE_CFG);

設定在fifo和bus中的資料寬度為byte,關閉DMA訪問fifo,並設定fifo中儲存最大位元組數為1,設定接收和傳送fifo的觸發值為1byte。

5 中斷配置


/*     SetSPI INT_EN register   */

         writel(spi_inten,spiregs + S3C_SPI_INT_EN); //u32 spi_inten =0x00

         writel(0x1f,spiregs + S3C_PENDING_CLR);

6 設定最大接收資料包數量

SPI can control the number of packets to bereceived in master mode. If there is any number of packets to bereceived, justset the SFR (Packet_Count_reg). SPI stops generating SPICLK when the number ofpackets is thesame as what you set. It is mandatory to follow software orhardware reset before this function is reloaded.(Software reset can clear allregisters except special function registers, but hardware reset clears allregisters.)

這裡使能並設定為最大值。

/*     Set Packet Count configuration register        */

         spi_packet= SPI_PACKET_CNT_EN;

         spi_packet|= 0xffff;

writel(spi_packet,spiregs + S3C_PACKET_CNT);


7 開啟spi接收和傳送通道

/*     SetTx or Rx Channel on   */

         spi_chcfg= readl(spiregs + S3C_CH_CFG);

         spi_chcfg|= SPI_CH_TXCH_OFF | SPI_CH_RXCH_OFF;

         spi_chcfg|= SPI_CH_TXCH_ON;

         spi_chcfg|= SPI_CH_RXCH_ON;

         writel(spi_chcfg,spiregs + S3C_CH_CFG);

8 啟動傳送/接收和關閉送法/接收


通過置0和置1NSSOUT位來開啟和關閉。

三 SPI全雙工收發協議分析

1 全雙工,就是TX和RX同時進行。

2 往哪裡收?往哪裡發?


datasheet上:CPU (or DMA) mustwrite data on the register SPI_TX_DATA, to write data in FIFO. Data on theregister areautomatically moved to Tx FIFOs. To read data from Rx FIFOs, CPU (orDMA) must access the registerSPI_RX_DATA and then data are automatically sentto the register SPI_RX_DATA.

所以,對於處在核心空間的驅動來說,傳送資料是往SPI_TX_DATA暫存器裡寫資料,接收是往SPI_RX_DATA暫存器裡讀資料。當然沒這麼簡單!!

3 SPI發資料流程分析

提醒一點:SPI支援全雙工,注意外設是半雙工還是全雙工?

按照上文設定,我們每次傳送和接收一個byte。

(1)置0 NSSOUT

(2)往SPI_TX_DATA暫存器寫一個byte,byte資料會自動移入TX FIFO,因為(1)中已經選中了外設,所以SPI master會自動讀取TX FIFO中的byte資料移入TX移位暫存器,並開始發往bus。

(3)也就是說,驅動只需要從核心空間往SPI_TX_DATA暫存器上寫資料,就完成了SPI發資料的操作了。

(4)全雙工,意味著,發資料的同時也要從SPI_RX_DATA暫存器中讀一個byte資料。但是,有可能第一次讀並不會真正讀到資料,因為沒有資料被接收到RX移位暫存器。所以忽略這次的讀取資料。

(5)置1 NSSOUT

4 SPI接收資料流程分析

如果外設是單雙工,考慮在讀資料的時候對spi復位!因為如果讀資料發生在寫資料後面,資料已經在發資料時讀取到RX FIFO中了,只需從SPI_RX_DATA中取出資料就行了。

(1)置0 NSSOUT

(2)因為全雙工,在傳送資料的同時,spi master會讀取byte位元組並移入RX FIFO。所以從SPI_RX_DATA暫存器讀取一個byte,byte資料會自動R從X FIFO移入到SPI_RX_DATA暫存器。(3)也就是說,驅動只需要從核心空間往SPI_RX_DATA暫存器讀取資料,就完成了SPI接收資料的操作了。

(4)全雙工,意味著,接收資料的同時也要從SPI_TX_DATA暫存器中傳送一個byte資料。但是,讀資料的時候傳送的資料可能不是真正想要傳送的資料,因為有些外設不是全雙工工作。

(5)置1 NSSOUT

5 SPI收發條件判斷

這是SPI收發協議裡最難的部分了。

SPI狀態暫存器:


1 是否準備好發資料?

/*     spi_wait_TX_ready()- wait for TX_READY and TX_DONE       */
static BOOL spi_wait_TX_ready( void)
{
         unsignedlong loops = msecs_to_loops(10);
         u32val = 0;
         do{
                   val= readl(spiregs + S3C_SPI_STATUS);
         }while(!((val & SPI_STUS_TX_DONE) && (val & SPI_STUS_TX_FIFORDY))&& loops--);
        
         if(loops == 0)
                   returnFALSE;
         else
                   returnTRUE;
}


2 發資料是否完成?

/*     spi_wait_TX_done()- wait for TX_DONE         */
static BOOL spi_wait_TX_done( void)
{
         unsignedlong loops = msecs_to_loops(10);
         u32val = 0;
         do{
                   val= readl(spiregs + S3C_SPI_STATUS);
         }while(!(val & SPI_STUS_TX_DONE)  &&loops--);
        
         if(loops == 0)
                   returnFALSE;
         else
                   returnTRUE;
}


3 是否準備好接收資料?

/*     spi_wait_RX_ready()- wait for RX_READY      */
static BOOL spi_wait_RX_ready( void)
{
         unsignedlong loops = msecs_to_loops(10);
         u32val = 0;
         do{
                   val= readl(spiregs + S3C_SPI_STATUS);
         }while(!(val & SPI_STUS_TRAILCNT_ZERO) && loops--);
        
         if(loops == 0)
                   returnFALSE;
         else
                   returnTRUE;
}

6 最終全雙工SPI從半雙工外設傳送和接收資料函式
BOOL spi_sendbyte( BYTE data)
{
         BYTEchr;
         u32spi_chcfg = spiregs + S3C_CH_CFG;
 
         if(!spi_wait_TX_ready())
         {
                   printk("%s:failed to get tx channel.\n");
                   returnFALSE;
         }
         writel(data, spiregs + S3C_SPI_TX_DATA);
         while(!spi_wait_RX_ready());
         readl(spiregs + S3C_SPI_RX_DATA);
         returnTRUE;
}
 
/*     spi_flush_fifo()- Clear the TxFIFO , RxFIFO and TX/RX shift register
 *     @spiregs: the SPI register address*/
VOID spi_flush_fifo(void *spiregs)
{
         /*     soft rest the spi controller, flush theFIFO       */
         if(spi_wait_TX_done())
         {
                   writel(readl(spiregs+ S3C_CH_CFG) | SPI_CH_SW_RST, spiregs + S3C_CH_CFG);
                   writel(readl(spiregs+ S3C_CH_CFG) & ~SPI_CH_SW_RST, spiregs + S3C_CH_CFG);
         }
}
 
/*     spi_readbyte()- Read a byte received on SPI0         */
BYTE spi_readbyte( void)
{
         u32tmp;
         u32spi_chcfg = spiregs + S3C_CH_CFG;
         BYTEret;
 
         if(!spi_wait_TX_ready())
                   returnFALSE;
         spi_flush_fifo(spiregs);
         writel(0xFF, spiregs + S3C_SPI_TX_DATA);
        
         if(spi_wait_RX_ready())
         {
                   tmp= readl(spiregs + S3C_SPI_RX_DATA);
                   ret= tmp & 0xff;
         }
         returnret;
}