Nor Flash工作原理

时间:2023-01-03 10:41:32

http://blog.chinaunix.net/uid-26876150-id-3723678.html

Nor Flash 具有像内存一样的接口,它可以像内存一样读,却不可以像内存一样写,Nor Flash 的写、擦除都需要发出特定的命令。谈到 Nor Flash 通常就会涉及到 CFI ([Common Flash Interface) 接口,一般 Nor Flash 都支持发命令来读取厂家 ID 和 设备 ID 等基本信息,但并不是所有的 Nor Flash 都支持发命令来获取和芯片本身容量大小、扇区数、擦除块大小等信息。为了让将来的 Nor Flash 兼容性更好,引进了 CFI 接口,将芯片有关的信息都写入芯片内部,通过 CFI 命令就可以获取这些信息。
    Linux 内核中对各种型号的 Nor Flash 都有很好的支持 ,但是其组织复杂,不利于分析。这里选用 u-boot 里面的 Nor Flash 代码来分析。代码位于:u-boot-2010.06/board/samsung/smdk2410/flash.c 。
    通常内核里面要识别一个 Nor Flash 有两种方法:一种是 jedec 探测,就是在内核里面事先定义一个数组,该数组里面放有不同厂家各个芯片的一些参数,探测的时候将 flash 的 ID 和数组里面的 ID 一一比较,如果发现相同的,就使用该数组的参数。另一种是 cfi 探测,就是直接发各种命令来读取芯片的信息,比如 ID、容量等。jedec 探测的优点就是简单,缺点是如果内核要支持的 flash 种类很多,这个数组就会很庞大。../samsung/smdk2410/flash.c 文件采用的是第一种方法,但是还是有些区别的,内核里面用 jedec 探测一个芯片时,是先通过发命令来获取 flash 的 ID,然后和数组比较,但是 flash.c 中连 ID 都是自己通过宏配置的。
unsigned long flash_init (void)
{
    for (i = 0; i < CONFIG_SYS_MAX_FLASH_BANKS; i++) 
    {
        ulong flashbase = 0;
        //设置 flash_id ,这个标志保存厂家 ID 和 设备 ID
        flash_info[i].flash_id =
#if defined(CONFIG_AMD_LV400)
            (AMD_MANUFACT & FLASH_VENDMASK) | (AMD_ID_LV400B & FLASH_TYPEMASK);
#elif defined(CONFIG_AMD_LV800)
            (AMD_MANUFACT & FLASH_VENDMASK) | (AMD_ID_LV800B & FLASH_TYPEMASK);
#else
            #error "Unknown flash configured"
#endif
        //设置 flash 大小和扇区数
        flash_info[i].size = FLASH_BANK_SIZE;
        flash_info[i].sector_count = CONFIG_SYS_MAX_FLASH_SECT;
        //对于 flash 的每个扇区,都需要保存扇区的首地址
        for (j = 0; j < flash_info[i].sector_count; j++)     
        {
            ......      
            flash_info[i].start[j] = flashbase + (j - 3) * MAIN_SECT_SIZE;
        }
        size += flash_info[i].size;    //片外所有flash 的总大小
    }
    //对代码区的扇区设置写保护,这里只是软件的一种设定
    flash_protect (FLAG_PROTECT_SET, CONFIG_SYS_FLASH_BASE,
               CONFIG_SYS_FLASH_BASE + monitor_flash_len - 1,
               &flash_info[0]);  
    //如果环境变量保存在 nor 里面,还需对这些扇区设置写保护
    flash_protect (FLAG_PROTECT_SET, CONFIG_ENV_ADDR,
               CONFIG_ENV_ADDR + CONFIG_ENV_SIZE - 1, &flash_info[0]);
                            
    return size;    //返回 flash 大小
}
    flash_init() 函数主要是做一些 flash 的初始化,比如设置 flash 的 ID、大小、扇区数等来构造 flash_info_t 结构体,但是从上面的代码可以看出,在该初始化函数中并没有做任何与硬件有关的初始化,所有的值都是通过外部赋值,也就是说我们可以给这些成员变量赋任何我们想要的值,哪怕这些值并不是 flash 真正的参数,虽然这些值并不影响本函数的调用,但是和下面这些函数就有密切关系。 
int flash_erase (flash_info_t * info, int s_first, int s_last)
{
    //参看是否有写保护扇区,有直接返回错误
    prot = 0;
    for (sect = s_first; sect <= s_last; ++sect)
    {
        if (info->protect[sect]) 
            prot++;
    }
    if (prot)
        return ERR_PROTECTED;
    //关闭中断等,防止擦除过程被中断
    cflag = icache_status ();
    icache_disable ();
    iflag = disable_interrupts ();

/* Start erase on unprotected sectors */
    for (sect = s_first; sect <= s_last && !ctrlc (); sect++) 
    {
        printf ("Erasing sector %2d ... ", sect);

/* arm simple, non interrupt dependent timer */
        reset_timer_masked ();

if (info->protect[sect] == 0)    //此处的判断有点多余 
        {   /* not protected */
            //取扇区的首地址
            vu_short *addr = (vu_short *) (info->start[sect]);    
            //发解锁和擦除扇区命令
            MEM_FLASH_ADDR1 = CMD_UNLOCK1;    //往地址 0x555<<1 写入 0xAA
            MEM_FLASH_ADDR2 = CMD_UNLOCK2;    //往地址 0x2AA<<1 写入 0x55
            MEM_FLASH_ADDR1 = CMD_ERASE_SETUP;//往地址 0x555<<1 写入 0x80

MEM_FLASH_ADDR1 = CMD_UNLOCK1;    
            MEM_FLASH_ADDR2 = CMD_UNLOCK2;
            *addr = CMD_ERASE_CONFIRM;    //往地址 0x555<<1 写入 0x30

/* wait until flash is ready */
            chip = 0;
            do 
            {
                result = *addr;    //读取该扇区首地址里面的值
                /* check timeout */
                if (get_timer_masked () > CONFIG_SYS_FLASH_ERASE_TOUT)
                {
                    MEM_FLASH_ADDR1 = CMD_READ_ARRAY;
                    chip = TMO;
                    break;
                }
                //BIT_ERASE_DONE = 0x80,即判断 DQ7 是否为 1
                if (!chip && (result & 0xFFFF) & BIT_ERASE_DONE)  
                    chip = READY;
                //BIT_PROGRAM_ERROR = 0x20,即判断 DQ5 是否为 1
                if (!chip && (result & 0xFFFF) & BIT_PROGRAM_ERROR)
                    chip = ERR;
            } while (!chip);

MEM_FLASH_ADDR1 = CMD_READ_ARRAY; //往地址 0x555<<1 写入 0xF0
            ......
            printf ("ok.\n");
        } 
        else 
        {    /* it was protected */
            printf ("protected!\n");
        }
    }
    ......
    /* allow flash to settle - wait 10 ms */
    udelay_masked (10000);

return rc;
}
    对于擦除工作,不可能瞬间完成,如何检测芯片是否已经完成擦除工作是我们所关心的,当然你可以用一个很长的延时来确保芯片肯定已经完成擦除,但是一款芯片一定会提供相应的状态供我们检测,利用这些状态位可以减少程序中不必要的等待。下面这些描述是摘自芯片手册。  
    Nor Flash工作原理  
Sector Erase Command Sequence
When the Embedded Erase algorithm is complete, the device returns to reading array data and addresses are no longer latched. The system can determine the status of the erase operation by using DQ7, DQ6, DQ2, or RY/BY#. (Refer to “Write Operation Status” for information on these status bits.)
                                Nor Flash工作原理
WRITE OPERATION STATUS
The device provides several bits to determine the status of a write operation: DQ2, DQ3, DQ5, DQ6,DQ7, and RY/BY#. Table 10 and the following subsections describe the functions of these bits. DQ7, RY/BY#, and DQ6 each offer a method for determining whether a program or erase operation is complete or in progress. These three bits are discussed first.
对于擦除过程:
During the Embedded Erase algorithm, Data# Polling produces a “0” on DQ7. When the Embedded Erase algorithm is complete, or if the device enters the Erase Suspend mode, Data# Polling produces a “1” on DQ7. This is analogous to the complement/true datum output described for the Embedded Program algorithm: the erase function changes all the bits in a sector to “1”; prior to this, the device outputs the “complement,” or“0.” The system must provide an address within any of the sectors selected for erasure to read valid status information on DQ7.
   Embedded Erase algorithm 是指”嵌入的擦除算法程序“,当我们发出擦除命令的时候 Nor Flash 内部就会执行一系列指令来进行擦除工作,在这过程它通过检测 Data = FF?(如上图所示)来判断擦除状态,但是这是 Nor Flash 内部的判断方法,与之对应,外部的内存控制器可以通过Data# Polling 来检测 。
When the system detects DQ7 has changed from the complement to true data, it can read valid data at DQ7–DQ0 on the following read cycles. This is because DQ7 may change asynchronously with DQ0–DQ6 while Output Enable (OE#) is asserted low. Figure 19, Data#Polling Timings (During Embedded Algorithms), in the“AC Characteristics” section illustrates this.
     Nor Flash工作原理

如果你实在不想花太多时间看手册,或对英文文档头疼,可以看看下面的总结。
   Nor Flash 提供几个数据位来确定一个写操作的状态,它们分别是: DQ2, DQ3, DQ5, DQ6,DQ7, and RY/BY# 。其中DQ7, RY/BY#引脚, 和 DQ6 中的每一个都提供了一种方法来判断一个编程或者擦除操作是否已经完成或正在进行中。实际编程中只需要使用其中的一种。

DQ7:Data# Polling bit,在编程过程从正在编程的地址中读出的数据的DQ7为要写入数据位的补码。比如写入的数据为0x0000,即输入的DQ7为 0 ,则在编程中读出的数据为 1 ;当编程完成时读出的数据又变回输入的数据 0 。在擦除过程中DQ7输出为  0 ;擦除完成后输出为 1 ;注意读取的地址必须是擦除范围内的地址。RY/BY#高电平表示‘就绪’,低电平表示‘忙’。

DQ6轮转位1(Toggle Bit 1),在编程和擦除期间,读任意地址都会导致DQ6的轮转(0,1间相互变换)。当操作完成后,DQ6停止转换。

DQ2:轮转位2(Toggle Bit 2),当某个扇区被选中擦除时,读有效地址(地址都在擦除的扇区范围内)会导致DQ2的轮转。

注意:DQ2只能判断一个特定的扇区是否被选中擦除,但不能区分这个扇区是否正在擦除中或者正处于擦除暂停状态。相比之下,DQ6可以区分Nor Flash是否处于擦除中或者擦除暂停状态,但不能区分哪个扇区被选中擦除,因此需要这2个位来确定扇区和模式状态信息。

DQ5: 超时位(Exceeded Timing Limits) ,当编程或擦除操作超过了一个特定内部脉冲计数时 DQ5=1,表明操作失败。当编程时把 0 改为 1 就会导致 DQ5=1,因为只有擦除擦做才能把 0 改为 1。当错误发生后需要执行复位命令才能返回到读数据状态。

DQ3: (扇区擦除计时位)Sector Erase Timer ,只在扇区擦除指令时起作用。当擦除指令真正开始工作时 DQ3=1 ,此时输入的命令(除擦除暂停命令外)都被忽略,DQ3=0 时,可以添加附加的扇区用于多扇区擦除。

Nor Flash工作原理    
    看了上面的分析就知道为什么在 flash_erase() 函数中当发出擦除命令后要检测 DQ7 是否为 1,if (!chip && (result & 0xFFFF) & BIT_ERASE_DONE) 。因为擦除过程用到了扇区的首地址,所以在 flash_init() 函数中就不能随便设置这些值,要保持和实际的 flash 一致。

/* Copy memory to flash. */
int write_buff (flash_info_t * info, uchar * src, ulong addr, ulong cnt)
{
    ulong cp, wp;
    int l;
    int i, rc;
    ushort data;
    //保持16位地址对齐
    wp = (addr & ~1);    /* get lower word aligned address */

/* handle unaligned start bytes */
    if ((l = addr - wp) != 0) 
    {
        data = 0;
        for (i = 0, cp = wp; i < l; ++i, ++cp) 
            data = (data >> 8) | (*(uchar *) cp << 8);
      
        for (; i < 2 && cnt > 0; ++i) 
        {
            data = (data >> 8) | (*src++ << 8);
            --cnt;
            ++cp;
        }
        //条件不成立,此语句不起作用
        for (; cnt == 0 && i < 2; ++i, ++cp) 
            data = (data >> 8) | (*(uchar *) cp << 8);
      
        if ((rc = write_hword (info, wp, data)) != 0) 
            return (rc);
  
        wp += 2;
    }

/* handle word aligned part */
    while (cnt >= 2)
    {
        data = *((vu_short *) src);
        if ((rc = write_hword (info, wp, data)) != 0) 
            return (rc);
        
        src += 2;
        wp += 2;
        cnt -= 2;
    }

if (cnt == 0) 
        return ERR_OK;
    
    /* handle unaligned tail bytes */
    data = 0;
    for (i = 0, cp = wp; i < 2 && cnt > 0; ++i, ++cp) 
    {
        data = (data >> 8) | (*src++ << 8);
        --cnt;
    }
    //条件不成立,此语句不起作用
    for (; i < 2; ++i, ++cp) 
        data = (data >> 8) | (*(uchar *) cp << 8);

return write_hword (info, wp, data);
}
    write_buff() 函数拷贝内存里面的数据到 Nor Flash ,这个函数首先要保证16位地址对齐,由 /* handle unaligned start bytes */ 包含的语句处理,虽然写得比较复杂,但是做得事情很简单。假如要往 flash 地址 3 处(这里的地址 3 是相对于 ARM 而言)开始写一段数据,先把地址 2 处(这里的地址 2 是相对于 ARM 而言)的数据读出,和要写入地址 3 的数据组成一个16为的数据,写入地址 2 处,这样地址 2 里面的数据不变,地址 3 处就是写入的数据。
注意:这里的地址都是站在 ARM 角度说的,对于 flash 地址 2 对于它的地址 1,这里面涉及到地址线的对齐关系。
    write_buff() 函数里面是调用 write_hword() 来实现底层的写操作。
static int write_hword (flash_info_t * info, ulong dest, ushort data)
{
    vu_short *addr = (vu_short *) dest;
    ushort result;
    int rc = ERR_OK;
    int cflag, iflag;
    int chip;
    /* Check if Flash is (sufficiently) erased */
    result = *addr;
    if ((result & data) != data)
        return ERR_NOT_ERASED;

/*
     * Disable interrupts which might cause a timeout
     * here. Remember that our exception vectors are
     * at address 0 in the flash, and we don't want a
     * (ticker) exception to happen while the flash
     * chip is in programming mode.
     */
    cflag = icache_status ();
    icache_disable ();
    iflag = disable_interrupts ();

MEM_FLASH_ADDR1 = CMD_UNLOCK1;    //往地址 0x555<<1 写入 0xAA
    MEM_FLASH_ADDR2 = CMD_UNLOCK2;    //往地址 0x2AA<<1 写入 0x55
    MEM_FLASH_ADDR1 = CMD_UNLOCK_BYPASS;    //往地址 0x555<<1 写入 0x20
    *addr = CMD_PROGRAM;    //往地址 0x555<<1 写入 0xA0  编程
    *addr = data;    //往地址 addr 写入数据

/* arm simple, non interrupt dependent timer */
    reset_timer_masked ();

/* wait until flash is ready */
    chip = 0;
    do 
    {
        result = *addr;
        /* check timeout */
        if (get_timer_masked () > CONFIG_SYS_FLASH_ERASE_TOUT) 
        {
            chip = ERR | TMO;
            break;
        }
        if (!chip && ((result & 0x80) == (data & 0x80)))
            chip = READY;
        if (!chip && ((result & 0xFFFF) & BIT_PROGRAM_ERROR)) 
        {
            result = *addr;

if ((result & 0x80) == (data & 0x80))
                chip = READY;
            else
                chip = ERR;
        }

} while (!chip);
    *addr = CMD_READ_ARRAY;    //往地址 0x555<<1 写入 0xF0  复位
    ......
    return rc;
}
    写入数据后通过判断 DQ7 是否和写入的一致来确认编程操作结束,对应的代码是 if (!chip && ((result & 0x80) == (data & 0x80))) 。        
During the Embedded Program algorithm, the device outputs on DQ7 the complement of the datum programmed to DQ7. This DQ7 status also applies to programming during Erase Suspend. When the Embedded Program algorithm is complete, the device outputs the datum programmed to DQ7. The system
must provide the program address to read valid status information on DQ7. If a program address falls within a protected sector, Data# Polling on DQ7 is active for
approximately 1 μs, then the device returns to reading array data.
   Nor Flash工作原理

Nor Flash工作原理
    关于Unlock Bypass的相关概念和操作可以参考手册里面的介绍,简单来讲,Unlock Bypass 就是忽略解锁的意思,因为每次编程 flash 都要发出解锁命令,再发出编程命令 0xA0,最后才写入16位数据,设想如果写入的数据量很大,每次写入一个 word(16位) 都要重新解锁一下,效率很低。Unlock Bypass 允许我们写入数据的时候忽略解锁命令,直接发出 0xA0 命令后再往相应地址写入数据。
Unlock Bypass Command Sequence
The unlock bypass feature allows the system to program bytes or words to the device faster than using the standard program command sequence. The unlock bypass
command sequence is initiated by first writing two unlock cycles. This is followed by a third write cycle containing the unlock bypass command, 20h. The device then enters the unlock bypass mode. A two-cycle unlock bypass program command sequence is all that is required to program in this mode. The first cycle in this sequence contains the unlock bypass program command, A0h; the second cycle contains the program address and data. Additional data is programmed in the same manner. This mode dispenses with the initial two unlock cycles required in the standard program command sequence, resulting in faster total programming time. Table 9 shows the requirements for the command sequence.