Tuesday, February 12, 2019

Working USB Mass Storage Device on SST26

I have a working example of the USB MSD on SST26 SPI Flash.  It is right at 8MB.  I have done read and write testing, while it is a little slow (SPI clock is 200KHz) it is completely functional.  Next step is see if I can raise the SPI clock speed on the PIC32MX470 Curiosity board.  While I do this I am also working on implementing the HID functionality.  Once I have HID functionality, I should be able to implement the current Brick Controller functionality on the Curiosity board.

Here is a more of detailed list of what I changed in the SST25 driver to make it work with SST26 flash.  A zip file with the changes is located here, drv_sst25.zip.  GoDaddy, who is the website host, changes the file name to some random number string to make it unique.  Rename to the link name, will make it easier to track the file.

You should also diff this with the framework one to see if I changed anything else and forgot about it.  More than likely that happened.   I have not included the .h file,  drv_sst25_local.h  that was modified either.  There were additions to the states and commands opcodes.  The big change that caused major havoc for days was cmdParamsLen in the DRV_SST25_OBJ structure.  It was cast as a byte and with 4 byte write commands (command plus 3 address bytes) and 256 byte page writes, it was rolling over to just 4 commands bytes and thus nothing happened.  I have changed this to uint32_t.  Again diff the one in the zip file with the framework one.

1.  I changed this table to include the Flash Type SST25 or SST26, as determined from the JEDEC ID read.

/***********************************************************************************************************/
/* Table mapping the supported Flash ID's to their sizes. All parts must support the JEDEC-ID Read command
   The SST25VF010A is included for backwards compatibility reasons, but is not supported since
   the 25VF010A flash part does not support the JEDEC-ID Read command.  Thus there is no way to read
   ID the part. */
/***********************************************************************************************************/
const uint32_t gSstFlashIdSizeTable [DRV_SST25_NUM_DEVICE_SUPPORTED][3] = {
    {0x25, 0x49, 0x020000}, /* SST25VF010A - 1 MBit */
    {0x25, 0x8C, 0x040000}, /* SST25VF020B - 2 MBit */
    {0x25, 0x8D, 0x080000}, /* SST25VF040B - 4 MBit */
    {0x25, 0x8E, 0x100000}, /* SST25VF080B - 8 MBit */
    {0x25, 0x41, 0x200000}, /* SST25VF016B - 16 MBit */
    {0x25, 0x4B, 0x800000}, /* SST25VF064C - 64 MBit */
   
    {0x26, 0x41, 0x200000}, /* SST26VF016B    - 16 MBit */
    {0x26, 0x42, 0x400000}, /* SST26VF032B/BA - 32 MBit */
    {0x26, 0x43, 0x800000}  /* SST26VF064b/BA - 64 MBit */
};

2.  I changed the GetFlashSize function to look at the flash type first. The JEDEC ID read function loads all three bytes of the JEDEC ID into the FlashID variable of the DRV_SST25_OBJ.  The GetGeometry function then sets a Boolean in this object to indicate SST26 or SST25.  That variable is passed to this function which reads the table above to get the size of the flash. 

/* This function returns the flash size in bytes for the specified deviceId. A
 * zero is returned if the device id is not supported. */
static uint32_t DRV_SST25_GetFlashSize
(
    uint8_t deviceId,
    bool isSST26Device
)
{
    uint8_t i, beginIndex,endIndex;

    if (isSST26Device == true)
    {
        beginIndex = DRV_SST25_NUM_DEVICE_SUPPORTED - SST26_DEVICE_COUNT;
        endIndex = DRV_SST25_NUM_DEVICE_SUPPORTED;
    }
    else
    {
        beginIndex = 0;
        endIndex = SST25_DEVICE_COUNT;
    }
    for (i = beginIndex; i < endIndex; i++)
    {
        if (deviceId == gSstFlashIdSizeTable[i][1])
        {
            return gSstFlashIdSizeTable[i][2];
        }
    }

    return 0;
}




3.  This change is one of the bigger ones.  Right now it rejects anything that is not a MicroChip FLASH.  Then it checks for SST26 or SST25 and stores this in the DRV_SST25_OBJ boolean isSST26.  Then it checks the table for the flash size.  The next sections are switch statements that populate the geometry table based on the Flash found.  I did a case statement for each flash device so there would be no question of the parameters used.  This also provides for future expansion.  The remaining code is unchanged.  For now I left in the old code,commented out.

/* This function updates the driver object's geometry information for the flash
 * device. */
static bool DRV_SST25_UpdateGeometry
(
    DRV_SST25_OBJ *dObj,
    uint8_t deviceId
)
{
    uint32_t flashSize;
   
    if (dObj->flashId[0] != 0xBF)
    {
        return false;  // chip is not a microchip SST
    }
    dObj->isSST26 = (dObj->flashId[1] == 0x26);  // is this a SST26 type device
    deviceId = dObj->flashId[2];
    flashSize = DRV_SST25_GetFlashSize (deviceId, dObj->isSST26);  // get flash size
    if (flashSize == 0)
    {
        return false;  // chip was not in table
    }

    /* Read block size and number of blocks */
    /* All Flash memories are 1 byte in read block size */
    dObj->mediaGeometryTable[0].blockSize = 1;
    dObj->mediaGeometryTable[0].numBlocks = flashSize;

    /* Build switch statements with what each flash device needs for writing */
    if (dObj->isSST26)
    {
        dObj->flashFunctions.unlock = DRV_SST26_UnlockFlash;
        switch (deviceId)
        {
            case 0x41:  // SST26VF016B
            {
                dObj->mediaGeometryTable[1].blockSize = DRV_SST25_PAGE_SIZE;
                dObj->mediaGeometryTable[1].numBlocks = flashSize >> 8;
                dObj->flashFunctions.write = DRV_SST25_WritePageProgram;
                dObj->opCodes.write = DRV_SST25_CMD_PAGE_PROGRAM;
                break;
            }
            case 0x42:  // SST26VF032B
            {
                dObj->mediaGeometryTable[1].blockSize = DRV_SST25_PAGE_SIZE;
                dObj->mediaGeometryTable[1].numBlocks = flashSize >> 8;
                dObj->flashFunctions.write = DRV_SST25_WritePageProgram;
                dObj->opCodes.write = DRV_SST25_CMD_PAGE_PROGRAM;
                break;
            }
            case 0x43:  // SST26VF064B
            {
                dObj->mediaGeometryTable[1].blockSize = DRV_SST25_PAGE_SIZE;
                dObj->mediaGeometryTable[1].numBlocks = flashSize >> 8;
                dObj->flashFunctions.write = DRV_SST25_WritePageProgram;
                dObj->opCodes.write = DRV_SST25_CMD_PAGE_PROGRAM;
                break;
            }
            default:
                break;
        }
    }
    else // flash part is SST25
    {
        dObj->flashFunctions.unlock = DRV_SST25_UnlockFlash;
        switch (deviceId)
        {
            case 0x8C:  // SST25VF020B
            {
                dObj->mediaGeometryTable[1].blockSize = 2;
                dObj->mediaGeometryTable[1].numBlocks = flashSize >> 1;
                dObj->flashFunctions.write = DRV_SST25_WriteAutoAddressIncrement;
                dObj->opCodes.write = DRV_SST25_CMD_AAI_PROGRAM;
                break;
            }
            case 0x8D:  // SST25VF040B
            {
                dObj->mediaGeometryTable[1].blockSize = 2;
                dObj->mediaGeometryTable[1].numBlocks = flashSize >> 1;
                dObj->flashFunctions.write = DRV_SST25_WriteAutoAddressIncrement;
                dObj->opCodes.write = DRV_SST25_CMD_AAI_PROGRAM;
                break;
            }
            case 0x8E:  // SST25VF080B
            {
                dObj->mediaGeometryTable[1].blockSize = 2;
                dObj->mediaGeometryTable[1].numBlocks = flashSize >> 1;
                dObj->flashFunctions.write = DRV_SST25_WriteAutoAddressIncrement;
                dObj->opCodes.write = DRV_SST25_CMD_AAI_PROGRAM;
                break;
            }
            case 0x41:  // SST25VF016B
            {
                dObj->mediaGeometryTable[1].blockSize = 2;
                dObj->mediaGeometryTable[1].numBlocks = flashSize >> 1;
                dObj->flashFunctions.write = DRV_SST25_WriteAutoAddressIncrement;
                dObj->opCodes.write = DRV_SST25_CMD_AAI_PROGRAM;
                break;
            }
            case 0x4B:  // SST25VF064C
            {
                dObj->mediaGeometryTable[1].blockSize = DRV_SST25_PAGE_SIZE;
                dObj->mediaGeometryTable[1].numBlocks = flashSize >> 8;
                dObj->flashFunctions.write = DRV_SST25_WritePageProgram;
                dObj->opCodes.write = DRV_SST25_CMD_PAGE_PROGRAM;
                break;
            }
            default:
                break;
        }
    }

//    if (deviceId == 0x4B)
//    {
//        /* SST25VF064C */
//        /* Write block size and number of blocks */
//        dObj->mediaGeometryTable[1].blockSize = DRV_SST25_PAGE_SIZE;
//        dObj->mediaGeometryTable[1].numBlocks = flashSize >> 8;
//        dObj->flashFunctions.write = DRV_SST25_WritePageProgram;
//    }
//    else
//    {
//        /* Write block size and number of blocks */
//        dObj->mediaGeometryTable[1].blockSize = 2;
//        dObj->mediaGeometryTable[1].numBlocks = flashSize >> 1;
//
//        dObj->flashFunctions.write = DRV_SST25_WriteAutoAddressIncrement;
//        if (deviceId == 0x49)
//        {
//            /* SST25VF010A */
//            dObj->opCodes.write = DRV_SST25_CMD_AAI_PROGRAM1;
//        }
//        else
//        {
//            dObj->opCodes.write = DRV_SST25_CMD_AAI_PROGRAM;
//        }
//    }

    /* Erase block size and number of blocks */
    dObj->mediaGeometryTable[2].blockSize = DRV_SST25_ERASE_SECTOR_SIZE;
    dObj->mediaGeometryTable[2].numBlocks = flashSize >> 12;

    /* Update the Media Geometry Main Structure */
    dObj->mediaGeometryObj.mediaProperty = (SYS_FS_MEDIA_READ_IS_BLOCKING | SYS_FS_MEDIA_WRITE_IS_BLOCKING),

    /* Number of read, write and erase entries in the table */
    dObj->mediaGeometryObj.numReadRegions = 1,
    dObj->mediaGeometryObj.numWriteRegions = 1,
    dObj->mediaGeometryObj.numEraseRegions = 1,
    dObj->mediaGeometryObj.geometryTable = (SYS_FS_MEDIA_REGION_GEOMETRY *)&dObj->mediaGeometryTable;

    return true;
}

NOTE: From the above three changes it should be easy to add any flash to the table by adjust the size the of the table.  Then the GetGeometry function switch/case statements would be modified to accommodate the discovered flash. 

4.  Finally are the case statements that control the state machine. 

This function

static DRV_SPI_BUFFER_EVENT DRV_SST25_ReadFlashId
 

I changed the size of the read to 3 to conform to the JEDEC ID read.  I also changed the write to 4 just so I could see the ID come back on the logic analyzer.  It is not necessary, but it is a nice debug tool.

This function

static DRV_SPI_BUFFER_EVENT DRV_SST25_UnlockFlash

I did not change.  I had no way to check it, but it should still work with SST25 Flash.  It is selected when the state machine determines it is SST25.

This function I added and is selected when the state machine determines it is SST26

static DRV_SPI_BUFFER_EVENT DRV_SST26_UnlockFlash
(
    void *driverObj
)
{
    DRV_SPI_BUFFER_EVENT event = DRV_SPI_BUFFER_EVENT_ERROR;
    DRV_SST25_OBJ *dObj = (DRV_SST25_OBJ *)driverObj;

    switch (dObj->subState)
    {
        case DRV_SST26_WREN_CMD_SQIORST:
            {
                dObj->cmdParams[0] = dObj->opCodes.writeEnable;
                dObj->disableCs = true;
                DRV_SPI_BufferAddWrite2(dObj->spiDriverHandle, &dObj->cmdParams[0], 1, 0, dObj, &dObj->spiBufferHandle);

                if (dObj->spiBufferHandle != DRV_SPI_BUFFER_HANDLE_INVALID)
                {
                    dObj->subState = DRV_SST26_WREN_STATUS_CHECK_SQIORST;  // and fall through
                    event = DRV_SPI_BUFFER_EVENT_PENDING;
                }
                else
                {
                    /* Failed to queue the write transfer request. */
                    break;
                }
            }

        case DRV_SST26_WREN_STATUS_CHECK_SQIORST:
            {
                event = DRV_SPI_BufferStatus(dObj->spiBufferHandle);
                if (event == DRV_SPI_BUFFER_EVENT_COMPLETE)
                {
                    dObj->subState = DRV_SST26_SEND_CMD_SQIORST;  // and fall through
                    event = DRV_SPI_BUFFER_EVENT_PENDING;                   
                }
                else if (event == DRV_SPI_BUFFER_EVENT_ERROR)
                    {
                        /* Error will be handled in the caller function. */
                    }
                    else
                    {
                        /* Continue to remain in the same state. */
                        event = DRV_SPI_BUFFER_EVENT_PENDING;
                        break;
                    }
            }

        case DRV_SST26_SEND_CMD_SQIORST:
            {
                dObj->cmdParams[0] = dObj->opCodes.sqioReset;
                dObj->disableCs = true;
                DRV_SPI_BufferAddWrite2(dObj->spiDriverHandle, &dObj->cmdParams[0], 1, 0, dObj, &dObj->spiBufferHandle);
                if (dObj->spiBufferHandle != DRV_SPI_BUFFER_HANDLE_INVALID)
                {
                    dObj->subState = DRV_SST26_CMD_STATUS_CHECK_SQIORST;  // and fall through
                    event = DRV_SPI_BUFFER_EVENT_PENDING;
                }
                else
                {
                    /* Failed to queue the write transfer request. */
                    break;
                }
            }

        case DRV_SST26_CMD_STATUS_CHECK_SQIORST:
            {
                event = DRV_SPI_BufferStatus(dObj->spiBufferHandle);
                if (event == DRV_SPI_BUFFER_EVENT_COMPLETE)
                {
                    dObj->subState = DRV_SST26_WREN_CMD_UNLOCK;  // and fall through
                    event = DRV_SPI_BUFFER_EVENT_PENDING;                   
                }
                else if (event == DRV_SPI_BUFFER_EVENT_ERROR)
                    {
                        /* Error will be handled in the caller function. */
                    }
                    else
                    {
                        /* Continue to remain in the same state. */
                        event = DRV_SPI_BUFFER_EVENT_PENDING;
                        break;
                    }
            }
       
        case DRV_SST26_WREN_CMD_UNLOCK:
            {
                dObj->cmdParams[0] = dObj->opCodes.writeEnable;
                dObj->disableCs = true;
                DRV_SPI_BufferAddWrite2(dObj->spiDriverHandle, &dObj->cmdParams[0], 1, 0, dObj, &dObj->spiBufferHandle);

                if (dObj->spiBufferHandle != DRV_SPI_BUFFER_HANDLE_INVALID)
                {
                    dObj->subState = DRV_SST26_WREN_STATUS_CHECK_UNLOCK;  // and fall through
                    event = DRV_SPI_BUFFER_EVENT_PENDING;
                }
                else
                {
                    /* Failed to queue the write transfer request. */
                    break;
                }
            }

        case DRV_SST26_WREN_STATUS_CHECK_UNLOCK:
            {
                event = DRV_SPI_BufferStatus(dObj->spiBufferHandle);
                if (event == DRV_SPI_BUFFER_EVENT_COMPLETE)
                {
                    dObj->subState = DRV_SST26_SEND_CMD_UNLOCK;  // and fall through
                    event = DRV_SPI_BUFFER_EVENT_PENDING;                   
                }
                else if (event == DRV_SPI_BUFFER_EVENT_ERROR)
                    {
                        /* Error will be handled in the caller function. */
                    }
                    else
                    {
                        /* Continue to remain in the same state. */
                        event = DRV_SPI_BUFFER_EVENT_PENDING;
                        break;
                    }
            }

        case DRV_SST26_SEND_CMD_UNLOCK:
            {
                dObj->cmdParams[0] = dObj->opCodes.globalUnlock;
                dObj->disableCs = true;
                DRV_SPI_BufferAddWrite2(dObj->spiDriverHandle, &dObj->cmdParams[0], 1, 0, dObj, &dObj->spiBufferHandle);
                if (dObj->spiBufferHandle != DRV_SPI_BUFFER_HANDLE_INVALID)
                {
                    dObj->subState = DRV_SST26_CMD_STATUS_CHECK_UNLOCK;  // and fall through
                    event = DRV_SPI_BUFFER_EVENT_PENDING;
                }
                else
                {
                    /* Failed to queue the write transfer request. */
                    break;
                }
            }

        case DRV_SST26_CMD_STATUS_CHECK_UNLOCK:
            {
                event = DRV_SPI_BufferStatus(dObj->spiBufferHandle);
                break;
            }
    }

    return event;
}



So what this does differently than the SST25 unlock function.  First it executes a SQIO Reset, ensuring that the device is in SPI mode and not SQIO mode.  Then it does a Global Unlock command.  Each of these is preceded by a write enable command.

Finally this function starts it off 

void DRV_SST25_Tasks

This opens the SPI driver, then reads the flash to determine if it is SST25 or SST26.  The UpdateGeometry function picks the correct unlock sequence and this is then executed.  Then it follows the normal state machine from then on.


Hoepfully this will make it easier for others to follow how the modifications were made.  Again I did not test the SST25 portion, so I may have done something to break this without knowing.  If you find it pass it along and I will update this section.









No comments:

Post a Comment