FreeScale mpc8xxx + vxWorks平台下spi flash驱动开发三步走

  最近在弄PowerPC平台上的spi flash的驱动程序,总体比较简单,在借鉴了U-Boot中的相关源码后,花了两周左右的时间搞定了,对于spi总线之前一直都有了解,但未能实际接触,这次在vxWorks上尝试了一下,确实有不小的收获。由于网上关于vxWorks平台的资料稀少,就写下此文,以备查阅。

     这次驱动的对象是Spansion的S25FLXX系列的,扇区64K,相对比较低端,但原理都是相通的。核心板的SPI总线提供时钟和MOSI、MISO,用GPIO实现片选。要实现对flash的驱动无非“三步走”:初始化SPI总线、完成spi读写驱动、在spi总线基础上完成flash读写驱动

     先看第一步,这个比较简单,对于mpc8xxx系列,主要需要配置的就是模式寄存器SPMODE了(当然,像Fsl的另一款处理器P2020ds采用的eSPI,就不止这一个寄存器,还有Command要复杂配置,有兴趣的可以看下)如下图:

    首位LOOP指是否开启LoopBack模式,用于测试SPI传输的,一般不建议打开;CI、CP一起用于设置SPI时钟,这个要根据对应flash的数据手册来,像我的这款说了支持00和11两种模式,这里设置为00;DIV16用于为SPI BRG设置时钟源,这个不是很懂,就设置为0了;M/S设置SPI工作模式,CPU要控制flash,这里当然是master模式了;还有个PM,用于设置时钟分频的,以u-boot为准,设置为1,即SYSCLK/8。详细的配置代码如下:

[cpp]   view plain copy
  1. /*SPI模式寄存器配置位*/  
  2. #define SPI_LOOP        (0x01<<30)/*开启Loopback模式,此处不开启*/  
  3. #define CI_CP           (0x00<<28)/*时钟模式为00(还是11),与SPI Flash时序相对应*/  
  4. #define SPI_CLK         (0x0<<27)   /*此位设置为0,即原始时钟频率*/   
  5. #define REV_DATA        (0x1<<26)   /*设置数据模式为MSB先收发*/  
  6. #define MS              (0x1<<25)   /*设置为master模式*/  
  7. #define PM              (0x0001<<16)/*设置SYSCLK/8为时钟*/  
  8. #define SPI_ENA         (0x1<<24)   /*打开SPI*/  
  9. #define CH_LEN          (0x0000<<20) /*设置数据长为32位,即一次可传输4个字节*/  
  10. #define SPIMODE_INIT      (CI_CP | SPI_CLK | REV_DATA | MS | PM | CH_LEN)  
  11. /*SPI初始化*/  
  12. STATUS init_spi()  
  13. {  
  14.      /*一开始要禁止片选(此处为拉高GPIO)*/  
  15.     spi_cs_assert();  
  16.     /*设置SPI Mode并打开*/  
  17.     WRITE_ADDR_INT32(SPMODE,SPIMODE_INIT);  
  18.   
  19.     /*先清空事件*/  
  20.     WRITE_ADDR_INT32(SPIE,SPI_EV_CLEAR);      
  21.     /*再使能*/  
  22.     WRITE_ADDR_INT32(SPMODE,(READ_ADDR_INT32(SPMODE)) | SPI_ENA);  
  23.     /*屏蔽所有中断*/  
  24.     WRITE_ADDR_INT32(SPIM,0x0);  
  25.     return OK;  
  26. }  

 

好了,到这里第一步的工作就做完了,比较简单,只要弄清楚flash的时序配置起来就会方便不少了。

    然后就开始第二步,这一步是最关键的,但其中在这一步中搞清楚两点也不会很困难,1、注意片选信号和flash读写时序的关系;2、SPI总线的全双工特性。这里特别声明一下,由于SPI总线是全双工的,所以在编写驱动时最好把读写放在一起实现,写完即读缓冲区,方便有效。在U-boot中,实现这个功能的函数是spi_xfer函数,这里以FreeScale的Mpc8xxx系列为例简单的解释下:

[cpp]   view plain copy
  1. /* spi_xfer为spi总线读写驱动函数 
  2.  * 参数1:spi_salve在只有一个设备时可以无视,用于表示spi从设备,这里为spi flash 
  3.  * 参数2、3、4为输入和输出的数据及长度,若不想要数据则设为NULL 
  4.  * 参数5为传输开始或结束的标示,以此控制片选信号*/  
  5. int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout,  
  6.         void *din, unsigned long flags)  
  7. {  
  8.     volatile spi8xxx_t *spi = &((immap_t *) (CONFIG_SYS_IMMR))->spi;  
  9.     unsigned int tmpdout, tmpdin, event;  
  10.     int numBlks = bitlen / 32 + (bitlen % 32 ? 1 : 0);/*此处这样设置是因为模式寄存器中将传输长度设为32位*/  
  11.     int tm, isRead = 0;  
  12.     unsigned char charSize = 32;  
  13.   
  14.     debug("spi_xfer: slave %u:%u dout %08X din %08X bitlen %u\n",  
  15.           slave->bus, slave->cs, *(uint *) dout, *(uint *) din, bitlen);  
  16.   
  17.       /*判断若为开始则激活片选,在mpc8xxx系列中为拉低对应的GPIO信号*/  
  18.     if (flags & SPI_XFER_BEGIN)  
  19.         spi_cs_activate(slave);  
  20.   
  21.     spi->event = 0xffffffff; /* 清空SPI 事件 */  
  22.   
  23.     /* handle data in 32-bit chunks */  
  24.     while (numBlks--) {  
  25.         tmpdout = 0;  
  26.         charSize = (bitlen >= 32 ? 32 : bitlen);  
  27.   
  28.         /* 调整数据传送模式为MSB在前*/  
  29.         tmpdout = *(u32 *) dout >> (32 - charSize);  
  30.   
  31.         /* 这里做的很精妙,根据剩余数据位数调整模式 
  32.          * 寄存器中的数据位 
  33.          */  
  34.         if (bitlen <= 16) {  
  35.             if (bitlen <= 4)  
  36.                 spi->mode = (spi->mode & 0xff0fffff) |  
  37.                             (3 << 20);  
  38.             else  
  39.                 spi->mode = (spi->mode & 0xff0fffff) |  
  40.                             ((bitlen - 1) << 20);  
  41.         } else {  
  42.             spi->mode = (spi->mode & 0xff0fffff);  
  43.             /* Set up the next iteration if sending > 32 bits */  
  44.             bitlen -= 32;  
  45.             dout += 4;  
  46.         }  
  47.         spi->tx = tmpdout;   /* Write the data out */  
  48.         debug("*** spi_xfer: ... %08x written\n", tmpdout);  
  49.   
  50.         /* 等待SPI传输超时,之后清空事件寄存器*/  
  51.         for (tm = 0, isRead = 0; tm < SPI_TIMEOUT; ++tm) {  
  52.             event = spi->event;  
  53.             if (event & SPI_EV_NE) {  
  54.                 tmpdin = spi->rx;  
  55.                 spi->event |= SPI_EV_NE;  
  56.                 isRead = 1;  
  57.                 *(u32 *) din = (tmpdin << (32 - charSize));  
  58.                 if (charSize == 32) {  
  59.                     /* Advance output buffer by 32 bits */  
  60.                     din += 4;  
  61.                 }  
  62.             }  
  63.             /* 
  64.              * Only bail when we've had both NE and NF events. 
  65.              * This will cause timeouts on RO devices, so maybe 
  66.              * in the future put an arbitrary delay after writing 
  67.              * the device.  Arbitrary delays suck, though... 
  68.              */  
  69.             if (isRead && (event & SPI_EV_NF))  
  70.                 break;  
  71.         }  
  72.         if (tm >= SPI_TIMEOUT)  
  73.             puts("*** spi_xfer: Time out during SPI transfer");  
  74.   
  75.         debug("*** spi_xfer: transfer ended. Value=%08x\n", tmpdin);  
  76.     }  
  77.   
  78.     /*传输完成后关闭片选,对应的为拉高GPIO信号*/  
  79.     if (flags & SPI_XFER_END)  
  80.         spi_cs_deactivate(slave);  
  81.     return 0;  
  82. }  

    我的代码就是从上面的程序演变过来的,改动很小就可以使用在vxWorks平台了,感谢u-boot,感谢W.Denk大师啊!!

     做完第二步之后,第三步就简单多了,只是在读写操作的时候要加上一个操作码和操作地址(组成4个字节的帧发送),写操作之前还要有写使能等操作。对于Spansion的S25FLXX系列Spi Flash,操作码都是一样的,下图为各操作码定义:

发送完了flash操作码后,再下读写指令就可以了,下面是一个页编程的函数代码,有误请指正:

/*整页写*/

STATUS flash_PagePro

(

       UINT32 destAddr,  //目的地址

       UINT8  *data,    //传输的数据

       UINT32 dataLen   //数据长度(字节数)

)

{

       UINT32 cmd32;

       flash_WrEnable();  //写使能

       if((destAddr > 0xffffff) || ((destAddr + dataLen) > 0xffffff))

              return ERROR;

       cmd32 = (FLASH_PP <<24) | (destAddr & 0xffffff);

       trans_data((UINT8 *)&cmd32,CMD_LEN + ADDR_LEN);

       trans_data(data,dataLen);

       /*检查flash扇区擦除进度*/

       UINT32 i;

       i = 0;

       while((flash_ReadStat() & WIP) && (i < FLASH_ERASE_TIMEOUT))

       {

              taskDelay (1);

              i++;

       }

       if (i >= FLASH_ERASE_TIMEOUT)

              return ERROR;

       return OK;

}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值