diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index d4ac8ce750..39388257a5 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -984,3 +984,28 @@ config W25_SLOWREAD default n endif # MTD_W25 + +config MTD_GD25 + bool "SPI-based GD25 FLASH" + default n + select SPI + +if MTD_GD25 + +config GD25_SPIMODE + int "GD25 SPI Mode" + default 0 + +config GD25_SPIFREQUENCY + int "GD25 SPI Frequency" + default 20000000 + +config GD25_READONLY + bool "GD25 Read-Only FLASH" + default n + +config GD25_SLOWREAD + bool + default n + +endif # MTD_GD25 diff --git a/drivers/mtd/Make.defs b/drivers/mtd/Make.defs index 9deab19551..fa76c0444e 100644 --- a/drivers/mtd/Make.defs +++ b/drivers/mtd/Make.defs @@ -108,6 +108,10 @@ ifeq ($(CONFIG_MTD_W25),y) CSRCS += w25.c endif +ifeq ($(CONFIG_MTD_GD25),y) +CSRCS += gd25.c +endif + ifeq ($(CONFIG_MTD_AT25),y) CSRCS += at25.c endif diff --git a/drivers/mtd/gd25.c b/drivers/mtd/gd25.c new file mode 100644 index 0000000000..97bc8c5c6b --- /dev/null +++ b/drivers/mtd/gd25.c @@ -0,0 +1,932 @@ +/**************************************************************************** + * drivers/mtd/gd25.c + * Driver for SPI-based GigaDevice FLASH + * + * Copyright (C) 2017 Pinecone Inc. All rights reserved. + * Author: Wang Yanjiong + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ***************************************************************************/ + +/*************************************************************************** + * Included Files + ***************************************************************************/ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/************************************************************************** + * Configuration + **************************************************************************/ + +#ifndef CONFIG_GD25_SPIMODE +# define CONFIG_GD25_SPIMODE SPIDEV_MODE0 +#endif + +#ifndef CONFIG_GD25_SPIFREQUENCY +# define CONFIG_GD25_SPIFREQUENCY 20000000 +#endif + +/************************************************************************** + * GD25 Instructions + **************************************************************************/ + +/* Command Value Description */ +/* */ +#define GD25_WREN 0x06 /* Write enable */ +#define GD25_WRDI 0x04 /* Write Disable */ +#define GD25_RDSR 0x05 /* Read status register */ +#define GD25_WRSR 0x01 /* Write Status Register */ +#define GD25_RDDATA 0x03 /* Read data bytes */ +#define GD25_FRD 0x0b /* Higher speed read */ +#define GD25_FRDD 0x3b /* Fast read, dual output */ +#define GD25_PP 0x02 /* Program page */ +#define GD25_SE 0x20 /* Sector erase (4KB) */ +#define GD25_BE 0xd8 /* Block Erase (64KB) */ +#define GD25_CE 0xc7 /* Chip erase */ +#define GD25_PD 0xb9 /* Power down */ +#define GD25_PURDID 0xab /* Release PD, Device ID */ +#define GD25_RDMFID 0x90 /* Read Manufacturer / Device */ +#define GD25_JEDEC_ID 0x9f /* JEDEC ID read */ + +/************************************************************************** + * GD25 Registers + **************************************************************************/ + +/* JEDEC Read ID register values */ + +#define GD25_JEDEC_MANUFACTURER 0xc8 /* GigaDevice manufacturer ID */ +#define GD25L_JEDEC_MEMORY_TYPE 0x60 /* GD25L memory type, 1.8V */ +#define GD25Q_JEDEC_MEMORY_TYPE 0x40 /* GD25Q memory type, 3V */ + +#define GD25_JEDEC_CAPACITY_8MBIT 0x14 /* 256x4096 = 8Mbit memory capacity */ +#define GD25_JEDEC_CAPACITY_16MBIT 0x15 /* 512x4096 = 16Mbit memory capacity */ +#define GD25_JEDEC_CAPACITY_32MBIT 0x16 /* 1024x4096 = 32Mbit memory capacity */ +#define GD25_JEDEC_CAPACITY_64MBIT 0x17 /* 2048x4096 = 64Mbit memory capacity */ +#define GD25_JEDEC_CAPACITY_128MBIT 0x18 /* 4096x4096 = 128Mbit memory capacity */ + +#define GD25_NSECTORS_8MBIT 256 /* 256 sectors x 4096 bytes/sector = 1Mb */ +#define GD25_NSECTORS_16MBIT 512 /* 512 sectors x 4096 bytes/sector = 2Mb */ +#define GD25_NSECTORS_32MBIT 1024 /* 1024 sectors x 4096 bytes/sector = 4Mb */ +#define GD25_NSECTORS_64MBIT 2048 /* 2048 sectors x 4096 bytes/sector = 8Mb */ +#define GD25_NSECTORS_128MBIT 4096 /* 4096 sectors x 4096 bytes/sector = 16Mb */ + +/* Status register bit definitions */ + +#define GD25_SR_WIP (1 << 0) /* Bit 0: Write in Progress */ +#define GD25_SR_WEL (1 << 1) /* Bit 1: Write Enable Latch */ + +#define GD25_DUMMY 0x00 + +/************************************************************************** + * Chip Geometries + **************************************************************************/ + +/* All members of the family support uniform 4K-byte sectors and 256 byte pages */ + +#define GD25_SECTOR_SHIFT 12 /* Sector size 1 << 12 = 4Kb */ +#define GD25_SECTOR_SIZE (1 << 12) /* Sector size 1 << 12 = 4Kb */ +#define GD25_PAGE_SHIFT 8 /* Sector size 1 << 8 = 256b */ +#define GD25_PAGE_SIZE (1 << 8) /* Sector size 1 << 8 = 256b */ + +#define GD25_ERASED_STATE 0xff /* State of FLASH when erased */ + +/************************************************************************** + * Private Types + **************************************************************************/ + +/* 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 gd25_dev_s. + */ + +struct gd25_dev_s +{ + struct mtd_dev_s mtd; /* MTD interface */ + FAR struct spi_dev_s *spi; /* Saved SPI interface instance */ + uint16_t nsectors; /* Number of erase sectors */ + uint8_t prev_instr; /* Previous instruction given to GD25 device */ +}; + +/************************************************************************** + * Private Function Prototypes + **************************************************************************/ + +/* Helpers */ + +static void gd25_lock(FAR struct spi_dev_s *spi); +static inline void gd25_unlock(FAR struct spi_dev_s *spi); +static inline int gd25_readid(FAR struct gd25_dev_s *priv); +#ifndef CONFIG_GD25_READONLY +static void gd25_unprotect(FAR struct gd25_dev_s *priv); +#endif +static uint8_t gd25_waitwritecomplete(FAR struct gd25_dev_s *priv); +static inline void gd25_wren(FAR struct gd25_dev_s *priv); +static inline void gd25_wrdi(FAR struct gd25_dev_s *priv); +static bool gd25_is_erased(struct gd25_dev_s *priv, off_t address, off_t size); +static void gd25_sectorerase(FAR struct gd25_dev_s *priv, off_t offset); +static inline int gd25_chiperase(FAR struct gd25_dev_s *priv); +static void gd25_byteread(FAR struct gd25_dev_s *priv, FAR uint8_t *buffer, + off_t address, size_t nbytes); +#ifndef CONFIG_GD25_READONLY +static void gd25_pagewrite(FAR struct gd25_dev_s *priv, + FAR const uint8_t *buffer, off_t address, size_t nbytes); +#endif + +/* MTD driver methods */ + +static int gd25_erase(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks); +static ssize_t gd25_bread(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR uint8_t *buf); +static ssize_t gd25_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR const uint8_t *buf); +static ssize_t gd25_read(FAR struct mtd_dev_s *dev, off_t offset, + size_t nbytes, FAR uint8_t *buffer); +static int gd25_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg); +#ifdef CONFIG_MTD_BYTE_WRITE +static ssize_t gd25_write(FAR struct mtd_dev_s *dev, off_t offset, + size_t nbytes, FAR const uint8_t *buffer); +#endif + +/************************************************************************** + * Private Data + **************************************************************************/ + +/************************************************************************** + * Private Functions + **************************************************************************/ + +/************************************************************************** + * Name: gd25_lock + **************************************************************************/ + +static void gd25_lock(FAR struct spi_dev_s *spi) +{ + (void)SPI_LOCK(spi, true); + + SPI_SETMODE(spi, CONFIG_GD25_SPIMODE); + SPI_SETBITS(spi, 8); + (void)SPI_HWFEATURES(spi, 0); + (void)SPI_SETFREQUENCY(spi, CONFIG_GD25_SPIFREQUENCY); +} + +/************************************************************************** + * Name: gd25_unlock + **************************************************************************/ + +static inline void gd25_unlock(FAR struct spi_dev_s *spi) +{ + (void)SPI_LOCK(spi, false); +} + +/************************************************************************** + * Name: gd25_readid + **************************************************************************/ + +static inline int gd25_readid(struct gd25_dev_s *priv) +{ + uint16_t manufacturer; + uint16_t memory; + uint16_t capacity; + + /* Lock and configure the SPI bus */ + + gd25_lock(priv->spi); + + /* Select this FLASH part. */ + + SPI_SELECT(priv->spi, SPIDEV_FLASH(0), true); + + /* Send the "Read ID (RDID)" command and read the first three ID bytes */ + + (void)SPI_SEND(priv->spi, GD25_JEDEC_ID); + manufacturer = SPI_SEND(priv->spi, GD25_DUMMY); + memory = SPI_SEND(priv->spi, GD25_DUMMY); + capacity = SPI_SEND(priv->spi, GD25_DUMMY); + + /* Deselect the FLASH and unlock the bus */ + + SPI_SELECT(priv->spi, SPIDEV_FLASH(0), false); + gd25_unlock(priv->spi); + + finfo("manufacturer: %02x memory: %02x capacity: %02x\n", + manufacturer, memory, capacity); + + /* Check for a valid manufacturer and memory type */ + + if (manufacturer == GD25_JEDEC_MANUFACTURER && + (memory == GD25L_JEDEC_MEMORY_TYPE || + memory == GD25Q_JEDEC_MEMORY_TYPE)) + { + if (capacity == GD25_JEDEC_CAPACITY_8MBIT) + { + priv->nsectors = GD25_NSECTORS_8MBIT; + } + else if (capacity == GD25_JEDEC_CAPACITY_16MBIT) + { + priv->nsectors = GD25_NSECTORS_16MBIT; + } + else if (capacity == GD25_JEDEC_CAPACITY_32MBIT) + { + priv->nsectors = GD25_NSECTORS_32MBIT; + } + else if (capacity == GD25_JEDEC_CAPACITY_64MBIT) + { + priv->nsectors = GD25_NSECTORS_64MBIT; + } + else if (capacity == GD25_JEDEC_CAPACITY_128MBIT) + { + priv->nsectors = GD25_NSECTORS_128MBIT; + } + else + { + return -ENODEV; + } + + return OK; + } + + /* We don't understand the manufacturer or the memory type */ + + return -ENODEV; +} + +/************************************************************************** + * Name: gd25_unprotect + **************************************************************************/ + +#ifndef CONFIG_GD25_READONLY +static void gd25_unprotect(FAR struct gd25_dev_s *priv) +{ + /* Lock and configure the SPI bus */ + + gd25_lock(priv->spi); + + /* Wait for any preceding write or erase operation to complete. */ + + (void)gd25_waitwritecomplete(priv); + + /* Send "Write enable (WREN)" */ + + gd25_wren(priv); + + SPI_SELECT(priv->spi, SPIDEV_FLASH(0), true); + + /* Send "Write enable status (EWSR)" */ + + SPI_SEND(priv->spi, GD25_WRSR); + + /* Following by the new status value */ + + SPI_SEND(priv->spi, 0); + SPI_SEND(priv->spi, 0); + + SPI_SELECT(priv->spi, SPIDEV_FLASH(0), false); + + /* Unlock the SPI bus */ + + gd25_unlock(priv->spi); +} +#endif + +/************************************************************************** + * Name: gd25_waitwritecomplete + **************************************************************************/ + +static uint8_t gd25_waitwritecomplete(struct gd25_dev_s *priv) +{ + uint8_t status; + + do + { + SPI_SELECT(priv->spi, SPIDEV_FLASH(0), true); + + (void)SPI_SEND(priv->spi, GD25_RDSR); + + status = SPI_SEND(priv->spi, GD25_DUMMY); + + SPI_SELECT(priv->spi, SPIDEV_FLASH(0), false); + + if (priv->prev_instr != GD25_PP && (status & GD25_SR_WIP) != 0) + { + gd25_unlock(priv->spi); + usleep(1000); + gd25_lock(priv->spi); + } + } + while ((status & GD25_SR_WIP) != 0); + + return status; +} + +/************************************************************************** + * Name: gd25_wren + **************************************************************************/ + +static inline void gd25_wren(struct gd25_dev_s *priv) +{ + SPI_SELECT(priv->spi, SPIDEV_FLASH(0), true); + (void)SPI_SEND(priv->spi, GD25_WREN); + SPI_SELECT(priv->spi, SPIDEV_FLASH(0), false); +} + +/************************************************************************** + * Name: gd25_wrdi + **************************************************************************/ + +static inline void gd25_wrdi(struct gd25_dev_s *priv) +{ + SPI_SELECT(priv->spi, SPIDEV_FLASH(0), true); + (void)SPI_SEND(priv->spi, GD25_WRDI); + SPI_SELECT(priv->spi, SPIDEV_FLASH(0), false); +} + +/************************************************************************** + * Name: gd25_is_erased + **************************************************************************/ + +static bool gd25_is_erased(struct gd25_dev_s *priv, off_t address, off_t size) +{ + size_t npages = size >> GD25_PAGE_SHIFT; + uint32_t erased_32; + unsigned int i; + uint32_t buf[GD25_PAGE_SIZE / sizeof(uint32_t)]; + + DEBUGASSERT((address % GD25_PAGE_SIZE) == 0); + DEBUGASSERT((size % GD25_PAGE_SIZE) == 0); + + memset(&erased_32, GD25_ERASED_STATE, sizeof(erased_32)); + + /* Walk all pages in given area. */ + + while (npages) + { + /* Check if all bytes of page is in erased state. */ + + gd25_byteread(priv, (uint8_t *)buf, address, GD25_PAGE_SIZE); + + for (i = 0; i < GD25_PAGE_SIZE / sizeof(uint32_t); i++) + { + if (buf[i] != erased_32) + { + /* Page not in erased state! */ + + return false; + } + } + + address += GD25_PAGE_SIZE; + npages--; + } + + return true; +} + +/************************************************************************** + * Name: gd25_sectorerase + **************************************************************************/ + +static void gd25_sectorerase(struct gd25_dev_s *priv, off_t sector) +{ + off_t address = sector << GD25_SECTOR_SHIFT; + + finfo("sector: %08lx\n", (long)sector); + + /* Check if sector is already erased. */ + + if (gd25_is_erased(priv, address, GD25_SECTOR_SIZE)) + { + /* Sector already in erased state, so skip erase. */ + + return; + } + + /* Wait for any preceding write or erase operation to complete. */ + + (void)gd25_waitwritecomplete(priv); + + /* Send write enable instruction */ + + gd25_wren(priv); + + SPI_SELECT(priv->spi, SPIDEV_FLASH(0), true); + + /* Send the "Sector Erase (SE)" instruction */ + + (void)SPI_SEND(priv->spi, GD25_SE); + priv->prev_instr = GD25_SE; + + /* Send the sector address high byte first. Only the most significant + * bits (those corresponding to the sector) have any meaning. + */ + + (void)SPI_SEND(priv->spi, (address >> 16) & 0xff); + (void)SPI_SEND(priv->spi, (address >> 8) & 0xff); + (void)SPI_SEND(priv->spi, address & 0xff); + + SPI_SELECT(priv->spi, SPIDEV_FLASH(0), false); +} + +/************************************************************************** + * Name: gd25_chiperase + **************************************************************************/ + +static inline int gd25_chiperase(struct gd25_dev_s *priv) +{ + /* Wait for any preceding write or erase operation to complete. */ + + (void)gd25_waitwritecomplete(priv); + + /* Send write enable instruction */ + + gd25_wren(priv); + + SPI_SELECT(priv->spi, SPIDEV_FLASH(0), true); + + /* Send the "Chip Erase (CE)" instruction */ + + (void)SPI_SEND(priv->spi, GD25_CE); + priv->prev_instr = GD25_CE; + + SPI_SELECT(priv->spi, SPIDEV_FLASH(0), false); + return OK; +} + +/************************************************************************** + * Name: gd25_byteread + **************************************************************************/ + +static void gd25_byteread(FAR struct gd25_dev_s *priv, FAR uint8_t *buffer, + off_t address, size_t nbytes) +{ + finfo("address: %08lx nbytes: %d\n", (long)address, (int)nbytes); + + /* Wait for any preceding write or erase operation to complete. */ + + gd25_waitwritecomplete(priv); + + /* Make sure that writing is disabled */ + + gd25_wrdi(priv); + + SPI_SELECT(priv->spi, SPIDEV_FLASH(0), true); + + /* Send "Read from Memory " instruction */ + +#ifdef CONFIG_GD25_SLOWREAD + (void)SPI_SEND(priv->spi, GD25_RDDATA); + priv->prev_instr = GD25_RDDATA; +#else + (void)SPI_SEND(priv->spi, GD25_FRD); + priv->prev_instr = GD25_FRD; +#endif + + /* Send the address high byte first. */ + + (void)SPI_SEND(priv->spi, (address >> 16) & 0xff); + (void)SPI_SEND(priv->spi, (address >> 8) & 0xff); + (void)SPI_SEND(priv->spi, address & 0xff); + + /* Send a dummy byte */ + +#ifndef CONFIG_GD25_SLOWREAD + (void)SPI_SEND(priv->spi, GD25_DUMMY); +#endif + + /* Then read all of the requested bytes */ + + SPI_RECVBLOCK(priv->spi, buffer, nbytes); + + SPI_SELECT(priv->spi, SPIDEV_FLASH(0), false); +} + +/************************************************************************** + * Name: gd25_pagewrite + **************************************************************************/ + +#ifndef CONFIG_GD25_READONLY +static void gd25_pagewrite(struct gd25_dev_s *priv, FAR const uint8_t *buffer, + off_t address, size_t nbytes) +{ + finfo("address: %08lx nwords: %d\n", (long)address, (int)nbytes); + DEBUGASSERT(priv && buffer && (address & 0xff) == 0 && (nbytes & 0xff) == 0); + + for (; nbytes > 0; nbytes -= GD25_PAGE_SIZE) + { + /* Wait for any preceding write or erase operation to complete. */ + + gd25_waitwritecomplete(priv); + + /* Enable write access to the FLASH */ + + gd25_wren(priv); + + SPI_SELECT(priv->spi, SPIDEV_FLASH(0), true); + + /* Send the "Page Program (GD25_PP)" Command */ + + SPI_SEND(priv->spi, GD25_PP); + priv->prev_instr = GD25_PP; + + /* Send the address high byte first. */ + + (void)SPI_SEND(priv->spi, (address >> 16) & 0xff); + (void)SPI_SEND(priv->spi, (address >> 8) & 0xff); + (void)SPI_SEND(priv->spi, address & 0xff); + + /* Then send the page of data */ + + SPI_SNDBLOCK(priv->spi, buffer, GD25_PAGE_SIZE); + + SPI_SELECT(priv->spi, SPIDEV_FLASH(0), false); + + /* Update addresses */ + + address += GD25_PAGE_SIZE; + buffer += GD25_PAGE_SIZE; + } +} +#endif + +/************************************************************************** + * Name: gd25_bytewrite + **************************************************************************/ + +#if defined(CONFIG_MTD_BYTE_WRITE) && !defined(CONFIG_GD25_READONLY) +static inline void gd25_bytewrite(struct gd25_dev_s *priv, + FAR const uint8_t *buffer, off_t offset, uint16_t count) +{ + finfo("offset: %08lx count:%d\n", (long)offset, count); + + /* Wait for any preceding write to complete. We could simplify things by + * perform this wait at the end of each write operation (rather than at + * the beginning of ALL operations), but have the wait first will slightly + * improve performance. + */ + + gd25_waitwritecomplete(priv); + + /* Enable the write access to the FLASH */ + + gd25_wren(priv); + + SPI_SELECT(priv->spi, SPIDEV_FLASH(0), true); + + /* Send "Page Program (PP)" command */ + + (void)SPI_SEND(priv->spi, GD25_PP); + priv->prev_instr = GD25_PP; + + /* Send the page offset high byte first. */ + + (void)SPI_SEND(priv->spi, (offset >> 16) & 0xff); + (void)SPI_SEND(priv->spi, (offset >> 8) & 0xff); + (void)SPI_SEND(priv->spi, offset & 0xff); + + /* Then write the specified number of bytes */ + + SPI_SNDBLOCK(priv->spi, buffer, count); + + SPI_SELECT(priv->spi, SPIDEV_FLASH(0), false); + finfo("Written\n"); +} +#endif /* defined(CONFIG_MTD_BYTE_WRITE) && !defined(CONFIG_GD25_READONLY) */ + +/************************************************************************** + * Name: gd25_erase + **************************************************************************/ + +static int gd25_erase(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks) +{ +#ifdef CONFIG_GD25_READONLY + return -EACESS +#else + FAR struct gd25_dev_s *priv = (FAR struct gd25_dev_s *)dev; + size_t blocksleft = nblocks; + + finfo("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + /* Lock access to the SPI bus until we complete the erase */ + + gd25_lock(priv->spi); + + while (blocksleft-- > 0) + { + /* Erase each sector */ + + gd25_sectorerase(priv, startblock); + startblock++; + } + + gd25_unlock(priv->spi); + return (int)nblocks; +#endif +} + +/************************************************************************** + * Name: gd25_bread + **************************************************************************/ + +static ssize_t gd25_bread(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR uint8_t *buffer) +{ + ssize_t nbytes; + + finfo("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + nbytes = gd25_read(dev, startblock << GD25_PAGE_SHIFT, nblocks << GD25_PAGE_SHIFT, buffer); + if (nbytes > 0) + { + nbytes >>= GD25_PAGE_SHIFT; + } + + return nbytes; +} + +/************************************************************************** + * Name: gd25_bwrite + **************************************************************************/ + +static ssize_t gd25_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR const uint8_t *buffer) +{ +#ifdef CONFIG_GD25_READONLY + return -EACCESS; +#else + FAR struct gd25_dev_s *priv = (FAR struct gd25_dev_s *)dev; + + finfo("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + /* Lock the SPI bus and write all of the pages to FLASH */ + + gd25_lock(priv->spi); + gd25_pagewrite(priv, buffer, startblock << GD25_PAGE_SHIFT, + nblocks << GD25_PAGE_SHIFT); + gd25_unlock(priv->spi); + + return nblocks; +#endif +} + +/************************************************************************** + * Name: gd25_read + **************************************************************************/ + +static ssize_t gd25_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes, + FAR uint8_t *buffer) +{ + FAR struct gd25_dev_s *priv = (FAR struct gd25_dev_s *)dev; + + finfo("offset: %08lx nbytes: %d\n", (long)offset, (int)nbytes); + + /* Lock the SPI bus and select this FLASH part */ + + gd25_lock(priv->spi); + gd25_byteread(priv, buffer, offset, nbytes); + gd25_unlock(priv->spi); + + finfo("return nbytes: %d,%x,%x\n", (int)nbytes, buffer[0], buffer[1]); + return nbytes; +} + +/************************************************************************** + * Name: gd25_write + **************************************************************************/ + +#ifdef CONFIG_MTD_BYTE_WRITE +static ssize_t gd25_write(FAR struct mtd_dev_s *dev, off_t offset, + size_t nbytes, FAR const uint8_t *buffer) +{ +#ifdef CONFIG_GD25_READONLY + return -EACCESS; +#else + FAR struct gd25_dev_s *priv = (FAR struct gd25_dev_s *)dev; + int startpage; + int endpage; + int count; + int index; + int bytestowrite; + + finfo("offset: %08lx nbytes: %d\n", (long)offset, (int)nbytes); + + /* We must test if the offset + count crosses one or more pages + * and perform individual writes. The devices can only write in + * page increments. + */ + + startpage = offset / GD25_PAGE_SIZE; + endpage = (offset + nbytes) / GD25_PAGE_SIZE; + + gd25_lock(priv->spi); + if (startpage == endpage) + { + /* All bytes within one programmable page. Just do the write. */ + + gd25_bytewrite(priv, buffer, offset, nbytes); + } + else + { + /* Write the 1st partial-page */ + + count = nbytes; + bytestowrite = GD25_PAGE_SIZE - (offset & (GD25_PAGE_SIZE-1)); + gd25_bytewrite(priv, buffer, offset, bytestowrite); + + /* Update offset and count */ + + offset += bytestowrite; + count -= bytestowrite; + index = bytestowrite; + + /* Write full pages */ + + while (count >= GD25_PAGE_SIZE) + { + gd25_bytewrite(priv, &buffer[index], offset, GD25_PAGE_SIZE); + + /* Update offset and count */ + + offset += GD25_PAGE_SIZE; + count -= GD25_PAGE_SIZE; + index += GD25_PAGE_SIZE; + } + + /* Now write any partial page at the end */ + + if (count > 0) + { + gd25_bytewrite(priv, &buffer[index], offset, count); + } + } + + gd25_unlock(priv->spi); + return nbytes; +#endif +} +#endif + +/************************************************************************** + * Name: gd25_ioctl + **************************************************************************/ + +static int gd25_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg) +{ + FAR struct gd25_dev_s *priv = (FAR struct gd25_dev_s *)dev; + int ret = -EINVAL; + + finfo("cmd: %d \n", cmd); + + switch (cmd) + { + case MTDIOC_GEOMETRY: + { + FAR struct mtd_geometry_s *geo = + (FAR struct mtd_geometry_s *)((uintptr_t)arg); + if (geo) + { + geo->blocksize = GD25_PAGE_SIZE; + geo->erasesize = GD25_SECTOR_SIZE; + geo->neraseblocks = priv->nsectors; + ret = OK; + + finfo("blocksize: %d erasesize: %d neraseblocks: %d\n", + geo->blocksize, geo->erasesize, geo->neraseblocks); + } + } + break; + + case MTDIOC_BULKERASE: + { + /* Erase the entire device */ + + gd25_lock(priv->spi); + ret = gd25_chiperase(priv); + gd25_unlock(priv->spi); + } + break; + + case MTDIOC_XIPBASE: + default: + ret = -ENOTTY; + break; + } + + finfo("return %d\n", ret); + return ret; +} + +/************************************************************************** + * Public Functions + **************************************************************************/ + +/************************************************************************** + * Name: gd25_initialize + * + * Description: + * Create an initialize MTD device instance. 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 *gd25_initialize(FAR struct spi_dev_s *spi) +{ + FAR struct gd25_dev_s *priv; + int ret; + + priv = (FAR struct gd25_dev_s *)kmm_zalloc(sizeof(struct gd25_dev_s)); + if (priv) + { + /* Initialize the allocated structure (unsupported methods were + * nullified by kmm_zalloc). + */ + + priv->mtd.erase = gd25_erase; + priv->mtd.bread = gd25_bread; + priv->mtd.bwrite = gd25_bwrite; + priv->mtd.read = gd25_read; + priv->mtd.ioctl = gd25_ioctl; +#ifdef CONFIG_MTD_BYTE_WRITE + priv->mtd.write = gd25_write; +#endif + priv->spi = spi; + + /* Deselect the FLASH */ + + SPI_SELECT(spi, SPIDEV_FLASH(0), false); + + /* Identify the FLASH chip and get its capacity */ + + ret = gd25_readid(priv); + if (ret != OK) + { + ferr("ERROR: Unrecognized\n"); + kmm_free(priv); + return NULL; + } + else + { + /* Make sure that the FLASH is unprotected + * so that we can write into it + */ + +#ifndef CONFIG_GD25_READONLY + gd25_unprotect(priv); +#endif + } + } + + /* Register the MTD with the procfs system if enabled */ + +#ifdef CONFIG_MTD_REGISTRATION + mtd_register(&priv->mtd, "gd25"); +#endif + + /* Return the implementation-specific state structure as the MTD device */ + + return (FAR struct mtd_dev_s *)priv; +} diff --git a/include/nuttx/mtd/mtd.h b/include/nuttx/mtd/mtd.h index f201e9ebbd..9ea98435c8 100644 --- a/include/nuttx/mtd/mtd.h +++ b/include/nuttx/mtd/mtd.h @@ -524,6 +524,16 @@ FAR struct mtd_dev_s *sst39vf_initialize(void); FAR struct mtd_dev_s *w25_initialize(FAR struct spi_dev_s *dev); +/**************************************************************************** + * Name: gd25_initialize + * + * Description: + * Initializes the driver for SPI-based GD25 FLASH + * + ****************************************************************************/ + +FAR struct mtd_dev_s *gd25_initialize(FAR struct spi_dev_s *dev); + /**************************************************************************** * Name: s25fl1_initialize *