FreeScale mpc8xxx + vxWorks平台下spi flash驱动开发三步走
2012-04-12 20:53
351 查看
最近在弄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
plaincopy
/*SPI模式寄存器配置位*/
#define SPI_LOOP (0x01<<30)/*开启Loopback模式,此处不开启*/
#define CI_CP (0x00<<28)/*时钟模式为00(还是11),与SPI Flash时序相对应*/
#define SPI_CLK (0x0<<27) /*此位设置为0,即原始时钟频率*/
#define REV_DATA (0x1<<26) /*设置数据模式为MSB先收发*/
#define MS (0x1<<25) /*设置为master模式*/
#define PM (0x0001<<16)/*设置SYSCLK/8为时钟*/
#define SPI_ENA (0x1<<24) /*打开SPI*/
#define CH_LEN (0x0000<<20) /*设置数据长为32位,即一次可传输4个字节*/
#define SPIMODE_INIT (CI_CP | SPI_CLK | REV_DATA | MS | PM | CH_LEN)
/*SPI初始化*/
STATUS init_spi()
{
/*一开始要禁止片选(此处为拉高GPIO)*/
spi_cs_assert();
/*设置SPI Mode并打开*/
WRITE_ADDR_INT32(SPMODE,SPIMODE_INIT);
/*先清空事件*/
WRITE_ADDR_INT32(SPIE,SPI_EV_CLEAR);
/*再使能*/
WRITE_ADDR_INT32(SPMODE,(READ_ADDR_INT32(SPMODE)) | SPI_ENA);
/*屏蔽所有中断*/
WRITE_ADDR_INT32(SPIM,0x0);
return OK;
}
好了,到这里第一步的工作就做完了,比较简单,只要弄清楚flash的时序配置起来就会方便不少了。
然后就开始第二步,这一步是最关键的,但其中在这一步中搞清楚两点也不会很困难,1、注意片选信号和flash读写时序的关系;2、SPI总线的全双工特性。这里特别声明一下,由于SPI总线是全双工的,所以在编写驱动时最好把读写放在一起实现,写完即读缓冲区,方便有效。在U-boot中,实现这个功能的函数是spi_xfer函数,这里以FreeScale的Mpc8xxx系列为例简单的解释下:
[cpp] view
plaincopy
/* spi_xfer为spi总线读写驱动函数
* 参数1:spi_salve在只有一个设备时可以无视,用于表示spi从设备,这里为spi flash
* 参数2、3、4为输入和输出的数据及长度,若不想要数据则设为NULL
* 参数5为传输开始或结束的标示,以此控制片选信号*/
int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout,
void *din, unsigned long flags)
{
volatile spi8xxx_t *spi = &((immap_t *) (CONFIG_SYS_IMMR))->spi;
unsigned int tmpdout, tmpdin, event;
int numBlks = bitlen / 32 + (bitlen % 32 ? 1 : 0);/*此处这样设置是因为模式寄存器中将传输长度设为32位*/
int tm, isRead = 0;
unsigned char charSize = 32;
debug("spi_xfer: slave %u:%u dout %08X din %08X bitlen %u\n",
slave->bus, slave->cs, *(uint *) dout, *(uint *) din, bitlen);
/*判断若为开始则激活片选,在mpc8xxx系列中为拉低对应的GPIO信号*/
if (flags & SPI_XFER_BEGIN)
spi_cs_activate(slave);
spi->event = 0xffffffff; /* 清空SPI 事件 */
/* handle data in 32-bit chunks */
while (numBlks--) {
tmpdout = 0;
charSize = (bitlen >= 32 ? 32 : bitlen);
/* 调整数据传送模式为MSB在前*/
tmpdout = *(u32 *) dout >> (32 - charSize);
/* 这里做的很精妙,根据剩余数据位数调整模式
* 寄存器中的数据位
*/
if (bitlen <= 16) {
if (bitlen <= 4)
spi->mode = (spi->mode & 0xff0fffff) |
(3 << 20);
else
spi->mode = (spi->mode & 0xff0fffff) |
((bitlen - 1) << 20);
} else {
spi->mode = (spi->mode & 0xff0fffff);
/* Set up the next iteration if sending > 32 bits */
bitlen -= 32;
dout += 4;
}
spi->tx = tmpdout; /* Write the data out */
debug("*** spi_xfer: ... %08x written\n", tmpdout);
/* 等待SPI传输超时,之后清空事件寄存器*/
for (tm = 0, isRead = 0; tm < SPI_TIMEOUT; ++tm) {
event = spi->event;
if (event & SPI_EV_NE) {
tmpdin = spi->rx;
spi->event |= SPI_EV_NE;
isRead = 1;
*(u32 *) din = (tmpdin << (32 - charSize));
if (charSize == 32) {
/* Advance output buffer by 32 bits */
din += 4;
}
}
/*
* Only bail when we've had both NE and NF events.
* This will cause timeouts on RO devices, so maybe
* in the future put an arbitrary delay after writing
* the device. Arbitrary delays suck, though...
*/
if (isRead && (event & SPI_EV_NF))
break;
}
if (tm >= SPI_TIMEOUT)
puts("*** spi_xfer: Time out during SPI transfer");
debug("*** spi_xfer: transfer ended. Value=%08x\n", tmpdin);
}
/*传输完成后关闭片选,对应的为拉高GPIO信号*/
if (flags & SPI_XFER_END)
spi_cs_deactivate(slave);
return 0;
}
我的代码就是从上面的程序演变过来的,改动很小就可以使用在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;
}
这次驱动的对象是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
plaincopy
/*SPI模式寄存器配置位*/
#define SPI_LOOP (0x01<<30)/*开启Loopback模式,此处不开启*/
#define CI_CP (0x00<<28)/*时钟模式为00(还是11),与SPI Flash时序相对应*/
#define SPI_CLK (0x0<<27) /*此位设置为0,即原始时钟频率*/
#define REV_DATA (0x1<<26) /*设置数据模式为MSB先收发*/
#define MS (0x1<<25) /*设置为master模式*/
#define PM (0x0001<<16)/*设置SYSCLK/8为时钟*/
#define SPI_ENA (0x1<<24) /*打开SPI*/
#define CH_LEN (0x0000<<20) /*设置数据长为32位,即一次可传输4个字节*/
#define SPIMODE_INIT (CI_CP | SPI_CLK | REV_DATA | MS | PM | CH_LEN)
/*SPI初始化*/
STATUS init_spi()
{
/*一开始要禁止片选(此处为拉高GPIO)*/
spi_cs_assert();
/*设置SPI Mode并打开*/
WRITE_ADDR_INT32(SPMODE,SPIMODE_INIT);
/*先清空事件*/
WRITE_ADDR_INT32(SPIE,SPI_EV_CLEAR);
/*再使能*/
WRITE_ADDR_INT32(SPMODE,(READ_ADDR_INT32(SPMODE)) | SPI_ENA);
/*屏蔽所有中断*/
WRITE_ADDR_INT32(SPIM,0x0);
return OK;
}
好了,到这里第一步的工作就做完了,比较简单,只要弄清楚flash的时序配置起来就会方便不少了。
然后就开始第二步,这一步是最关键的,但其中在这一步中搞清楚两点也不会很困难,1、注意片选信号和flash读写时序的关系;2、SPI总线的全双工特性。这里特别声明一下,由于SPI总线是全双工的,所以在编写驱动时最好把读写放在一起实现,写完即读缓冲区,方便有效。在U-boot中,实现这个功能的函数是spi_xfer函数,这里以FreeScale的Mpc8xxx系列为例简单的解释下:
[cpp] view
plaincopy
/* spi_xfer为spi总线读写驱动函数
* 参数1:spi_salve在只有一个设备时可以无视,用于表示spi从设备,这里为spi flash
* 参数2、3、4为输入和输出的数据及长度,若不想要数据则设为NULL
* 参数5为传输开始或结束的标示,以此控制片选信号*/
int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout,
void *din, unsigned long flags)
{
volatile spi8xxx_t *spi = &((immap_t *) (CONFIG_SYS_IMMR))->spi;
unsigned int tmpdout, tmpdin, event;
int numBlks = bitlen / 32 + (bitlen % 32 ? 1 : 0);/*此处这样设置是因为模式寄存器中将传输长度设为32位*/
int tm, isRead = 0;
unsigned char charSize = 32;
debug("spi_xfer: slave %u:%u dout %08X din %08X bitlen %u\n",
slave->bus, slave->cs, *(uint *) dout, *(uint *) din, bitlen);
/*判断若为开始则激活片选,在mpc8xxx系列中为拉低对应的GPIO信号*/
if (flags & SPI_XFER_BEGIN)
spi_cs_activate(slave);
spi->event = 0xffffffff; /* 清空SPI 事件 */
/* handle data in 32-bit chunks */
while (numBlks--) {
tmpdout = 0;
charSize = (bitlen >= 32 ? 32 : bitlen);
/* 调整数据传送模式为MSB在前*/
tmpdout = *(u32 *) dout >> (32 - charSize);
/* 这里做的很精妙,根据剩余数据位数调整模式
* 寄存器中的数据位
*/
if (bitlen <= 16) {
if (bitlen <= 4)
spi->mode = (spi->mode & 0xff0fffff) |
(3 << 20);
else
spi->mode = (spi->mode & 0xff0fffff) |
((bitlen - 1) << 20);
} else {
spi->mode = (spi->mode & 0xff0fffff);
/* Set up the next iteration if sending > 32 bits */
bitlen -= 32;
dout += 4;
}
spi->tx = tmpdout; /* Write the data out */
debug("*** spi_xfer: ... %08x written\n", tmpdout);
/* 等待SPI传输超时,之后清空事件寄存器*/
for (tm = 0, isRead = 0; tm < SPI_TIMEOUT; ++tm) {
event = spi->event;
if (event & SPI_EV_NE) {
tmpdin = spi->rx;
spi->event |= SPI_EV_NE;
isRead = 1;
*(u32 *) din = (tmpdin << (32 - charSize));
if (charSize == 32) {
/* Advance output buffer by 32 bits */
din += 4;
}
}
/*
* Only bail when we've had both NE and NF events.
* This will cause timeouts on RO devices, so maybe
* in the future put an arbitrary delay after writing
* the device. Arbitrary delays suck, though...
*/
if (isRead && (event & SPI_EV_NF))
break;
}
if (tm >= SPI_TIMEOUT)
puts("*** spi_xfer: Time out during SPI transfer");
debug("*** spi_xfer: transfer ended. Value=%08x\n", tmpdin);
}
/*传输完成后关闭片选,对应的为拉高GPIO信号*/
if (flags & SPI_XFER_END)
spi_cs_deactivate(slave);
return 0;
}
我的代码就是从上面的程序演变过来的,改动很小就可以使用在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;
}
相关文章推荐
- FreeScale mpc8xxx + vxWorks平台下spi flash驱动开发三步走
- 嵌入式开发值zynq驱动—— zynq SPI Flash 驱动过程
- spi flash驱动开发注意事项
- 一个x86平台的spi flash驱动移植笔记
- android系统平台显示驱动开发简要:Samsung LCD接口篇『三』
- Android平台开发-WIFI 驱动移植 -- 详细
- 搭建XP驱动开发IDE平台DDK2003 + VS2005 + DriverStudio3.2
- 作为一个Flash平台开发/设计者应该拥有的10本书
- FS_S5PC100平台上Linux Camera驱动开发详解(一)
- Android平台下驱动的开发及测试框架概述(一)
- Linux下spi驱动开发(1)
- Android平台下驱动的开发及测试框架概述(二)
- Android平台开发-WIFI 驱动移植 -- 详细
- Android平台下驱动的开发及测试框架概述(四)
- VxWorks 设备驱动开发指南(一)--Getting Started
- Android平台下驱动的开发及测试框架概述(五)
- DM8168平台中完成gv7601的spi总线驱动笔记
- Linux_Hi3518E开发_SPI Flash程序下载成功后串口打印信息
- linux下的spi驱动及测试程序移植开发
- 【工业串口和网络软件通讯平台(SuperIO)教程】四.开发设备驱动