/**************************************************************************** * drivers/mtd/sst39vf.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* Configuration */ #ifndef CONFIG_SST39VF_BASE_ADDRESS # error "The FLASH base address was not provided (CONFIG_SST39VF_BASE_ADDRESS)" #endif /* MAP SST39VF address to a 16-bit bus address */ #define SST39VF_ADDR(addr) \ (volatile FAR uint16_t *)(CONFIG_SST39VF_BASE_ADDRESS | (addr << 1)) /* Timing */ #define SST39VF_TBP_USEC 10 /* Word-Program Time (max); 7uS typical */ #define SST39VF_TIDA_NSEC 150 /* Software ID Access and Exit Time (max) */ #define SST39VF_TSE_MSEC 25 /* Sector-Erase 25 ms (max); 18 ms typical */ #define SST39VF_TBE_MSEC 25 /* Block-Erase 25 ms (max); 18 ms typical */ #define SST39VF_TSCE_MSEC 50 /* Chip-Erase 50 ms (max); */ #define WORDWRITE_TIMEOUT 0x080000000 /* IDs */ #define SST_MANUFACTURER_ID 0xbf /**************************************************************************** * Private Types ****************************************************************************/ /* This describes one chip in the SST39VF family */ struct sst39vf_chip_s { uint16_t chipid; /* ID of the chip */ uint16_t nsectors; /* Number of erase-ablesectors */ uint32_t sectorsize; /* Size of one sector */ }; /* This type holds one FLASH address and one 16-bit FLASH data value */ struct sst39vf_wrinfo_s { uintptr_t address; uint16_t data; }; /* This type represents the state of the MTD device. The struct mtd_dev_s * must appear at the beginning of the definition so that you can freely * cast between pointers to struct mtd_dev_s and struct sst39vf_dev_s. */ struct sst39vf_dev_s { struct mtd_dev_s mtd; FAR const struct sst39vf_chip_s *chip; }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* Low Level Helpers */ static inline void sst39vf_flashwrite(FAR const struct sst39vf_wrinfo_s *wrinfo); static inline uint16_t sst39vf_flashread(uintptr_t address); static void sst39vf_writeseq(FAR const struct sst39vf_wrinfo_s *wrinfo, int nseq); static int sst39vf_chiperase(FAR struct sst39vf_dev_s *priv); static int sst39vf_sectorerase(FAR struct sst39vf_dev_s *priv, uintptr_t sectaddr); static int sst39vf_writeword(FAR const struct sst39vf_wrinfo_s *wrinfo); /* MTD driver methods */ static int sst39vf_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks); static ssize_t sst39vf_bread(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, FAR uint8_t *buf); static ssize_t sst39vf_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, FAR const uint8_t *buf); static ssize_t sst39vf_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes, FAR uint8_t *buffer); static int sst39vf_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg); /**************************************************************************** * Private Data ****************************************************************************/ static const struct sst39vf_chip_s g_sst39vf1601 = { 0x234b, /* chipid */ 512, /* nsectors */ 4 * 1024 /* sectorsize */ }; static const struct sst39vf_chip_s g_sst39vf1602 = { 0x234a, /* chipid */ 512, /* nsectors */ 4 * 1024 /* sectorsize */ }; static const struct sst39vf_chip_s g_sst39vf3201 = { 0x235b, /* chipid */ 1024, /* nsectors */ 4 * 1024 /* sectorsize */ }; static const struct sst39vf_chip_s g_sst39vf3202 = { 0x235a, /* chipid */ 1024, /* nsectors */ 4 * 1024 /* sectorsize */ }; /* This structure holds the state of the MTD driver */ static struct sst39vf_dev_s g_sst39vf = { { sst39vf_erase, /* erase method */ sst39vf_bread, /* bread method */ sst39vf_bwrite, /* bwrte method */ sst39vf_read, /* read method */ #ifdef CONFIG_MTD_BYTE_WRITE NULL, /* write method */ #endif sst39vf_ioctl, /* ioctl method */ "sst39vf", }, NULL /* Chip */ }; /* Command sequences */ static const struct sst39vf_wrinfo_s g_wordprogram[3] = { { 0x5555, 0x00aa }, { 0x2aaa, 0x0055 }, { 0x5555, 0x00a0 } }; static const struct sst39vf_wrinfo_s g_sectorerase[5] = { { 0x5555, 0x00aa }, { 0x2aaa, 0x0055 }, { 0x5555, 0x0080 }, { 0x5555, 0x00aa }, { 0x2aaa, 0x0055 } }; static const struct sst39vf_wrinfo_s g_chiperase[6] = { { 0x5555, 0x00aa }, { 0x2aaa, 0x0055 }, { 0x5555, 0x0080 }, { 0x5555, 0x00aa }, { 0x2aaa, 0x0055 }, { 0x5555, 0x0010 } }; static const struct sst39vf_wrinfo_s g_swid_entry[3] = { { 0x5555, 0x00aa }, { 0x2aaa, 0x0055 }, { 0x5555, 0x0090 } }; static const struct sst39vf_wrinfo_s g_swid_exit[3] = { { 0x5555, 0x00aa }, { 0x2aaa, 0x0055 }, { 0x5555, 0x00f0 } }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: sst39vf_flashwrite * * Description: * Write one value to FLASH * ****************************************************************************/ static inline void sst39vf_flashwrite(FAR const struct sst39vf_wrinfo_s *wrinfo) { volatile uint16_t *addr = SST39VF_ADDR(wrinfo->address); *addr = wrinfo->data; } /**************************************************************************** * Name: sst39vf_flashread * * Description: * Read one value from FLASH * ****************************************************************************/ static inline uint16_t sst39vf_flashread(uintptr_t address) { return *SST39VF_ADDR(address); } /**************************************************************************** * Name: sst39vf_writeseq * * Description: * Write a sequence of values to FLASH * ****************************************************************************/ static void sst39vf_writeseq(FAR const struct sst39vf_wrinfo_s *wrinfo, int nseq) { while (nseq--) { sst39vf_flashwrite(wrinfo); wrinfo++; } } /**************************************************************************** * Name: sst39vf_checktoggle * * Description: * Check for bit toggle * * "Toggle Bits (DQ6 and DQ2). During the internal Program or Erase * operation, any consecutive attempts to read DQ6 will produce * alternating 1s and 0s, i.e., toggling between 1 and 0. When * the internal Program or Erase operation is completed, the DQ6 bit * will stop toggling. The device is then ready for the next operation. * For Sector-, Block-, or Chip-Erase, the toggle bit (DQ6) is valid * after the rising edge of sixth WE# (or CE#) pulse. DQ6 will be set to * 1 if a Read operation is attempted on an Erase-Suspended * Sector/Block. If Program operation is initiated in a sector/block not * selected in Erase-Suspend mode, DQ6 will toggle. * * "An additional Toggle Bit is available on DQ2, which can be used in * conjunction with DQ6 to check whether a particular sector is being * actively erased or erase-suspended. ... The Toggle Bit (DQ2) is valid * after the rising edge of the last WE# (or CE#) pulse of Write * operation." * ****************************************************************************/ static bool sst39vf_checktoggle(FAR const struct sst39vf_wrinfo_s *wrinfo) { uint16_t value1; uint16_t value2; value1 = sst39vf_flashread(wrinfo->address); value2 = sst39vf_flashread(wrinfo->address); return (value1 == value2); } /**************************************************************************** * Name: sst39vf_waittoggle * * Description: * Wait until the data is no longer toggling. * ****************************************************************************/ static int sst39vf_waittoggle(FAR const struct sst39vf_wrinfo_s *wrinfo, uint32_t retries) { while (retries-- > 0) { if (sst39vf_checktoggle(wrinfo)) { return OK; } } return -ETIMEDOUT; } /**************************************************************************** * Name: sst39vf_chiperase * * Description: * Erase the entire chip * * "The SST39VF160x/320x provide a Chip-Erase operation, which allows the * user to erase the entire memory array to the 1 state. This is * useful when the entire device must be quickly erased. The Chip-Erase * operation is initiated by executing a six-byte command sequence with * Chip-Erase command (10H) at address 5555H in the last byte sequence. * The Erase operation begins with the rising edge of the sixth WE# or * CE#, whichever occurs first. During the Erase operation, the only valid * read is Toggle Bit or Data# Polling... Any commands issued during the * Chip-Erase operation are ignored. When WP# is low, any attempt to * Chip-Erase will be ignored. During the command sequence, WP# should * be statically held high or low." * ****************************************************************************/ static int sst39vf_chiperase(FAR struct sst39vf_dev_s *priv) { #if 0 struct sst39vf_wrinfo_s wrinfo; clock_t start; clock_t elapsed; #endif /* Send the sequence to erase the chip */ sst39vf_writeseq(g_chiperase, 6); /* Use the data toggle delay method. The typical delay is 40 MSec. The * maximum is 50 MSec. So using the data toggle delay method should give * better chip erase performance by about 10MS. */ #if 0 wrinfo.address = CONFIG_SST39VF_BASE_ADDRESS; wrinfo.data = 0xffff; start = clock_systime_ticks(); while (delay < MSEC2TICK(SST39VF_TSCE_MSEC)) { /* Check if the erase is complete */ if (sst39vf_checktoggle(&wrinfo)) { return OK; } /* No, check if the timeout has elapsed */ elapsed = clock_systime_ticks() - start; if (elapsed > MSEC2TICK(SST39VF_TSCE_MSEC)) { return -ETIMEDOUT; } /* No, wait one system clock tick */ nxsig_usleep(USEC_PER_TICK); } #else /* Delay the maximum amount of time for the chip erase to complete. */ nxsig_usleep(SST39VF_TSCE_MSEC * USEC_PER_MSEC); #endif return OK; } /**************************************************************************** * Name: sst39vf_sectorerase * * Description: * Erase the entire chip * * "... The Sector-Erase operation is initiated by executing a six-byte * command sequence with Sector-Erase command (30H) and sector address * (SA) in the last bus cycle. * * The sector ... address is latched on the falling edge of the sixth * WE# pulse, while the command (30H or 50H) is latched on the rising edge * of the sixth WE# pulse. The internal Erase operation begins after the * sixth WE# pulse. The End-of-Erase operation can be determined using * either Data# Polling or Toggle Bit methods." * ****************************************************************************/ static int sst39vf_sectorerase(FAR struct sst39vf_dev_s *priv, uintptr_t sectaddr) { struct sst39vf_wrinfo_s wrinfo; #if 0 clock_t start; clock_t elapsed; #endif /* Set up the sector address */ wrinfo.address = sectaddr; wrinfo.data = 0x0030; /* Send the sequence to erase the chip */ sst39vf_writeseq(g_sectorerase, 5); sst39vf_flashwrite(&wrinfo); /* Use the data toggle delay method. The typical delay is 18 MSec. The * maximum is 25 MSec. With a 10 MS system timer resolution, this is * the difference of waiting 20MS vs. 20MS. So using the data toggle * delay method should give better write performance by about 10MS per * block. */ #if 0 start = clock_systime_ticks(); while (delay < MSEC2TICK(SST39VF_TSE_MSEC)) { /* Check if the erase is complete */ if (sst39vf_checktoggle(&wrinfo)) { return OK; } /* No, check if the timeout has elapsed */ elapsed = clock_systime_ticks() - start; if (elapsed > MSEC2TICK(SST39VF_TSE_MSEC)) { return -ETIMEDOUT; } /* No, wait one system clock tick */ nxsig_usleep(USEC_PER_TICK); } #else /* Delay the maximum amount of time for the sector erase to complete. */ nxsig_usleep(SST39VF_TSE_MSEC * USEC_PER_MSEC); #endif return OK; } /**************************************************************************** * Name: sst39vf_writeword * * Description: * Write one 16-bit word to FLASH * * "The SST39VF160x/320x are programmed on a word-by-word basis. Before * programming, the sector where the word exists must be fully erased. The * rogram operation is accomplished in three steps. The first step is the * three-byte load sequence for Software Data Protection. The second step * is to load word address and word data. During the Word-Program operation * , the addresses are latched on the falling edge of either CE# or WE#, * whichever occurs last. The data is latched on the rising edge of either * CE# or WE#, whichever occurs first. The third step is the internal * Program operation which is initiated after the rising edge of the * fourth WE# or CE#, whichever occurs first. The Program operation, once * initiated, will be completed within 10s. .... During the Program * operation, the only valid reads are Data# Polling and Toggle Bit. * During the internal Program operation, the host is free to perform * additional tasks. Any commands issued during the internal Program * operation are ignored. During the command sequence, WP# should be * statically held high or low." * ****************************************************************************/ static int sst39vf_writeword(FAR const struct sst39vf_wrinfo_s *wrinfo) { /* Send the sequence to write the word to the chip */ sst39vf_writeseq(g_wordprogram, 3); sst39vf_flashwrite(wrinfo); /* Use the data toggle delay method. The typical delay is 7 usec; the * maximum is 10 usec. */ return sst39vf_waittoggle(wrinfo, WORDWRITE_TIMEOUT); } /**************************************************************************** * Name: sst39vf_erase * * Description: * Erase several blocks, each of the size previously reported (i.e., one * SST39VF sector). * ****************************************************************************/ static int sst39vf_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks) { FAR struct sst39vf_dev_s *priv = (FAR struct sst39vf_dev_s *)dev; uintptr_t address; int ret; DEBUGASSERT(priv && priv->chip && startblock < priv->chip->nsectors); for (address = startblock * priv->chip->sectorsize; nblocks > 0; nblocks--, address += priv->chip->sectorsize) { /* Clear the sector */ ret = sst39vf_sectorerase(priv, address >> 1); if (ret < 0) { return ret; } } return OK; } /**************************************************************************** * Name: sst39vf_bread * * Description: * Read the specified number of blocks into the user provided buffer. * ****************************************************************************/ static ssize_t sst39vf_bread(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, FAR uint8_t *buf) { FAR struct sst39vf_dev_s *priv = (FAR struct sst39vf_dev_s *)dev; FAR const uint8_t *source; size_t nbytes; DEBUGASSERT(priv && priv->chip && startblock < priv->chip->nsectors); /* Get the source address and the size of the transfer */ source = (FAR const uint8_t *) SST39VF_ADDR(startblock * priv->chip->sectorsize >> 1); nbytes = nblocks * priv->chip->sectorsize; /* Copy the data to the user buffer */ memcpy(buf, source, nbytes); return nblocks; } /**************************************************************************** * Name: sst39vf_bwrite * * Description: * Write the specified number of blocks from the user provided buffer. * ****************************************************************************/ static ssize_t sst39vf_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, FAR const uint8_t *buf) { FAR struct sst39vf_dev_s *priv = (FAR struct sst39vf_dev_s *)dev; struct sst39vf_wrinfo_s wrinfo; FAR const uint16_t *source = (FAR const uint16_t *)buf; size_t nwords; int ret; DEBUGASSERT(priv && priv->chip && ((uintptr_t)buf & 1) == 0 && startblock < priv->chip->nsectors); /* Get the destination address and the size of the transfer */ wrinfo.address = (uintptr_t)(startblock * priv->chip->sectorsize >> 1); nwords = nblocks * (priv->chip->sectorsize >> 1); /* Copy the data to the user buffer */ while (nwords-- > 0) { wrinfo.data = *source++; ret = sst39vf_writeword(&wrinfo); if (ret < 0) { return ret; } wrinfo.address += sizeof(uint8_t); } return nblocks; } /**************************************************************************** * Name: sst39vf_read * * Description: * Read the specified number of bytes to the user provided buffer. * ****************************************************************************/ static ssize_t sst39vf_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes, FAR uint8_t *buffer) { #ifdef CONFIG_DEBUG_FEATURES FAR struct sst39vf_dev_s *priv = (FAR struct sst39vf_dev_s *)dev; #endif FAR const uint8_t *source; DEBUGASSERT(priv && priv->chip && offset < priv->chip->nsectors * priv->chip->sectorssize); /* Get the source address and the size of the transfer */ source = (FAR const uint8_t *)SST39VF_ADDR(offset >> 1); /* Copy the data to the user buffer */ memcpy(buffer, source, nbytes); return nbytes; } /**************************************************************************** * Name: sst39vf_ioctl ****************************************************************************/ static int sst39vf_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg) { FAR struct sst39vf_dev_s *priv = (FAR struct sst39vf_dev_s *)dev; int ret = -ENOTTY; DEBUGASSERT(priv && priv->chip); switch (cmd) { case MTDIOC_GEOMETRY: { FAR struct mtd_geometry_s *geo = (FAR struct mtd_geometry_s *)arg; if (geo) { memset(geo, 0, sizeof(*geo)); /* Populate the geometry structure with information need to * know the capacity and how to access the device. */ geo->blocksize = priv->chip->sectorsize; geo->erasesize = priv->chip->sectorsize; geo->neraseblocks = priv->chip->nsectors; ret = OK; } } break; case BIOC_XIPBASE: { FAR void **ppv = (FAR void **)arg; if (ppv) { /* Return the base address of FLASH memory */ *ppv = (FAR void *)CONFIG_SST39VF_BASE_ADDRESS; ret = OK; } } break; case BIOC_PARTINFO: { FAR struct partition_info_s *info = (FAR struct partition_info_s *)arg; if (info != NULL) { info->numsectors = priv->chip->nsectors; info->sectorsize = priv->chip->sectorsize; info->startsector = 0; info->parent[0] = '\0'; ret = OK; } } break; case MTDIOC_BULKERASE: { /* Erase the entire chip */ return sst39vf_chiperase(priv); } break; default: ret = -ENOTTY; /* Bad command */ break; } return ret; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: sst39vf_initialize * * Description: * Create and initialize an MTD device instance assuming an SST39VF NOR * FLASH device at the configured address in memory. MTD devices are not * registered in the file system, but are created as instances that can * be bound to other functions (such as a block or character driver front * end). * ****************************************************************************/ FAR struct mtd_dev_s *sst39vf_initialize(void) { uint16_t manufacturer; uint16_t chipid; DEBUGASSERT(g_sst39vf.chip == NULL); /* Issue the software entry command sequence */ sst39vf_writeseq(g_swid_entry, 3); up_udelay(10); /* Read the manufacturer and chip ID */ manufacturer = sst39vf_flashread(0x0000); chipid = sst39vf_flashread(0x0001); /* Issue the software exit sequence */ sst39vf_writeseq(g_swid_exit, 3); up_udelay(10); /* Now see if we can support the part */ finfo("Manufacturer: %02x\n", manufacturer); finfo("Chip ID: %04x\n", chipid); if (manufacturer != SST_MANUFACTURER_ID) { ferr("ERROR: Unrecognized manufacturer: %02x\n", manufacturer); return NULL; } else if (chipid == g_sst39vf1601.chipid) { g_sst39vf.chip = &g_sst39vf1601; } else if (chipid == g_sst39vf1602.chipid) { g_sst39vf.chip = &g_sst39vf1602; } else if (chipid == g_sst39vf3201.chipid) { g_sst39vf.chip = &g_sst39vf3201; } else if (chipid == g_sst39vf3202.chipid) { g_sst39vf.chip = &g_sst39vf3202; } else { ferr("ERROR: Unrecognized chip ID: %04x\n", chipid); return NULL; } /* Return the state structure as the MTD device */ return (FAR struct mtd_dev_s *)&g_sst39vf; }