嵌入式系统中flash的作用相当于PC上的硬盘来存放永久的数据,不像内存那样掉电数据就会消失。而我们使用的是nor flash,操作也比较简单,直接读取flash的地址就可以将数据通过memcpy到内存(当然是需要给相关寄存器配一些值),然后传给上层使用。
专用SPI控制器主要有CTRL,CMD,DATA,ADDR,STATUS寄存器。
相关命令
/* Command Value N Description Addr Dummy Data */
#define MX25L_WREN 0x06 /* 1 Write Enable 0 0 0 */
#define MX25L_WRDI 0x04 /* 1 Write Disable 0 0 0 */
#define MX25L_RDID 0x9f /* 1 Read Identification 0 0 1-3 */
#define MX25L_RDSR 0x05 /* 1 Read Status Register 0 0 >=1 */
#define MX25L_WRSR 0x01 /* 1 Write Status Register 0 0 1 */
#define MX25L_READ 0x03 /* 1 Read Data Bytes 3 0 >=1 */
#define MX25L_FAST_READ 0x0b /* 1 Higher speed read 3 1 >=1 */
#define MX25L_PP 0x02 /* 1 Page Program 3 0 1-256 */
#define MX25L_SE 0x20 /* 1 Sector Erase 3 0 0 */
#define MX25L_BE 0xd8 /* 1 Block Erase 3 0 0 */
#define MX25L_CE 0xc7 /* 1 chip Erase 0 0 0 */
#define MX25L_DP 0xb9 /* 2 Deep power down 0 0 0 */
#define MX25L_RES 0xab /* 2 Read Electronic Signature 0 3 >=1 */
#define MX25L_NOP 0x00 /* 2 No operation */
首先是对专门的SPI控制器进行初始化,也就是控制寄存器中设置分频系数和片选端,地址和命令寄存器中全部写0.并且提前写好扇区大小,页数等相关值,这些值放在一个全局结构体变量中,会被后面所用到。
#define MX25L_MX25L6433f_SECTOR_SHIFT 12 /* block size 1 << 12 = 4k */
#define MX25L_MX25L6433f_NSECTORS 2048 // 8M
#define MX25L_MX25L6433f_PAGE_SHIFT 9 /* Page size 1 << 9 = */
#define MX25L_MX25L6433f_NPAGES 8192*2
然后注册一些回调函数,主要是读写相关,上层最终调用的还是这些函数。
priv->mtd.erase = drv_porting_flash_erase;
priv->mtd.bread = drv_porting_flash_bread;
priv->mtd.bwrite = drv_porting_flash_bwrite;
priv->mtd.read = drv_porting_flash_read;
priv->mtd.ioctl = drv_porting_flash_ioctl;
flash属于一个块设备,所以也就会有分区的概念,这些分区信息(分几个区,每个区的起始地址和大小),是在config文件里面就事先写好的。
创建分区的函数
FAR struct mtd_dev_s *mtd_partition(FAR struct mtd_dev_s *mtd, off_t firstblock,off_t nblocks)
该函数,主要是创建相关结构体, 并且把相关回调函数注册好
part->child.erase = part_erase;
part->child.bread = part_bread;
part->child.bwrite = part_bwrite;
part->child.read = mtd->read ? part_read : NULL;
part->child.ioctl = part_ioctl;
#ifdef CONFIG_MTD_BYTE_WRITE
part->child.write = mtd->write ? part_write : NULL;
#endif
part->parent = mtd;
part->firstblock = erasestart * blkpererase;
part->neraseblocks = eraseend - erasestart;
part->blocksize = geo.blocksize;
part->blkpererase = blkpererase;
这些函数最终还是会调用到刚刚之前注册号的最底层的SPI控制器相关的回调函数中去。
Note:Nor flash介质和内核之间还有一个FTL(flash translation layer)中间层,这么做的主要原因还是因为SPI每次写之前是需要擦除的,所以也就是flash的读写次数是有寿命的,中间的ftl转换层,作为一个缓冲可以增加flash的使用寿命。
所以需要调用ftl相关函数
int ftl_initialize(int minor, FAR struct mtd_dev_s *mtd)
该函数最主要的还是创建ftl相关结构体,并且注册块设备函数。
register_blockdriver(devname, &g_bops, 0, dev);
相关注册函数
static const struct block_operations g_bops =
{
ftl_open, /* open */
ftl_close, /* close */
ftl_read, /* read */
#ifdef CONFIG_FS_WRITABLE
ftl_write, /* write */
#else
NULL, /* write */
#endif
ftl_geometry, /* geometry */
ftl_ioctl /* ioctl */
};
当然,flash也可以做一个字符设备,也就可以调用字符设备注册的相关函数。
int bchdev_register(FAR const char *blkdev, FAR const char *chardev,bool readonly)
该函数主要完成两步:
/* Setup the BCH lib functions */
ret = bchlib_setup(blkdev, readonly, &handle);
/* Then setup the character device */
ret = register_driver(chardev, &bch_fops, 0666, handle);
相关回调函数和注册块设备的是相同的。
总的来说,也就是相当于注册了三次回调函数,然后最上层开始一层一层调用,一般都是类似****_i_node->read()的方式来调用的。