| /**************************************************************************** |
| * drivers/mtd/s25fl1.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. |
| * |
| ****************************************************************************/ |
| |
| /* Driver for QuadSPI-based S25FL116K, S25FL132K, and S25L164K */ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <sys/types.h> |
| |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <debug.h> |
| #include <inttypes.h> |
| |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/signal.h> |
| #include <nuttx/fs/ioctl.h> |
| #include <nuttx/spi/qspi.h> |
| #include <nuttx/mtd/mtd.h> |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| /* Configuration ************************************************************/ |
| |
| /* QuadSPI Mode. Per data sheet, either Mode 0 or Mode 3 may be used. */ |
| |
| #ifndef CONFIG_S25FL1_QSPIMODE |
| #define CONFIG_S25FL1_QSPIMODE QSPIDEV_MODE0 |
| #endif |
| |
| /* QuadSPI Frequency per data sheet:: |
| * |
| * – Normal Read (Serial): |
| * 50 MHz clock rate (-40°C to +85°C/105°C) |
| * 45 MHz clock rate (-40°C to +125°C) |
| * – Fast Read (Serial): |
| * 108 MHz clock rate (-40°C to +85°C/105°C) |
| * 97 MHz clock rate (-40°C to +125°C) |
| * – Dual Read: |
| * 108 MHz clock rate (-40°C to +85°C/105°C) |
| * 97 MHz clock rate (-40°C to +125°C) |
| * – Quad Read: |
| * 108 MHz clock rate (-40°C to +85°C/105°C) |
| * 97 MHz clock rate for S25FL164K (-40°C to +125°C) |
| * |
| * Table 5.8: |
| * - Clock frequency for all SPI commands except for Read Data |
| * command (0x03) and Fast Read command (0x0b): 108 MHz |
| * - Clock frequency for Read Data command (0x03): 50 MHz |
| * - Clock frequency for all Fast Read commands SIO and MIO: 108 MHz |
| * |
| * In this implementation, only "Quad" reads are performed. |
| */ |
| |
| #ifndef CONFIG_S25FL1_QSPI_FREQUENCY |
| #define CONFIG_S25FL1_QSPI_FREQUENCY 108000000 |
| #endif |
| |
| /* S25FL1 Commands **********************************************************/ |
| |
| /* Configuration, Status, Erase, Program Commands ***************************/ |
| |
| /* Command Value Description: */ |
| |
| /* Data sequence */ |
| |
| #define S25FL1_READ_STATUS1 0x05 /* Read status register 1: * |
| * 0x05 | SR1 */ |
| #define S25FL1_READ_STATUS2 0x35 /* Read status register 2: * |
| * 0x35 | SR2 */ |
| #define S25FL1_READ_STATUS3 0x33 /* Read status register 3: * |
| * 0x33 | SR3 */ |
| #define S25FL1_WRITE_ENABLE 0x06 /* Write enable: * |
| * 0x06 */ |
| #define S25FL1_VWRITE_ENABLE 0x50 /* Write enable for volatile status: * |
| * 0x50 */ |
| #define S25FL1_WRITE_DISABLE 0x04 /* Write disable command code: * |
| * 0x04 */ |
| #define S25FL1_WRITE_STATUS 0x01 /* Write status register: * |
| * 0x01 | SR1 | SR2 | SR3 */ |
| #define S25FL1_WRAP_ENABLE 0x77 /* Set Burst with Wrap: * |
| * 0x77 | xx | xx | xx | SR3 */ |
| #define S25FL1_UNPROTECT_SECTOR 0x39 /* Set Block / Pointer Protection: * |
| * 0x39 | ADDR(MS) | ADDR(MID) | xx */ |
| #define S25FL1_PAGE_PROGRAM 0x02 /* Page Program: * |
| * 0x02 | ADDR(MS) | ADDR(MID) | * |
| * ADDR(LS) | data */ |
| #define S25FL1_SECTOR_ERASE 0x20 /* Sector Erase (4 kB) * |
| * 0x02 | ADDR(MS) | ADDR(MID) | * |
| * ADDR(LS) */ |
| #define S25FL1_BLOCK_ERASE 0xd8 /* Block Erase (64 kB): * |
| * 0x02 | ADDR(MS) | ADDR(MID) | * |
| * ADDR(LS) */ |
| #define S25FL1_CHIP_ERASE_1 0x60 /* Chip Erase 1: * |
| * 0x60 */ |
| #define S25FL1_CHIP_ERASE_2 0xc7 /* Chip Erase 2: * |
| * 0xc7 */ |
| #define S25FL1_ERASE_PROG_SUSPEND 0x75 /* Erase / Program Suspend: * |
| * 0x75 */ |
| #define S25FL1_ERASE_PROG_RESUME 0x7a /* Erase / Program Resume: * |
| * 0x7a */ |
| |
| /* Read Commands ************************************************************/ |
| |
| /* Command Value Description: */ |
| |
| /* Data sequence */ |
| |
| #define S25FL1_READ_DATA 0x03 /* Read Data: * |
| * 0x03 | ADDR(MS) | ADDR(MID) | * |
| * ADDR(LS) | data... */ |
| #define S25FL1_FAST_READ 0x0b /* Fast Read: * |
| * 0x0b | ADDR(MS) | ADDR(MID) | * |
| * ADDR(LS) | dummy | data... */ |
| #define S25FL1_FAST_READ_DUAL 0x3b /* Fast Read Dual Output: * |
| * 0x3b | ADDR(MS) | ADDR(MID) | * |
| * ADDR(LS) | dummy | data... */ |
| #define S25FL1_FAST_READ_QUAD 0x6b /* Fast Read Dual Output: * |
| * 0x6b | ADDR(MS) | ADDR(MID) | * |
| * ADDR(LS) | dummy | data... */ |
| #define S25FL1_FAST_READ_DUALIO 0xbb /* Fast Read Dual I/O: * |
| * 0xbb | ADDR(MS) | ADDR(LS) | data... */ |
| #define S25FL1_FAST_READ_QUADIO 0xeb /* Fast Read Quad I/O: * |
| * 0xeb | ADDR | data... */ |
| #define S25FL1_CONT_READ_RESET 0xff /* Continuous Read Mode Reset: * |
| * 0xff | 0xff */ |
| |
| /* Reset Commands ***********************************************************/ |
| |
| /* Command Value Description: */ |
| |
| /* Data sequence */ |
| |
| #define S25FL1_SOFT_RESET_ENABLE 0x66 /* Software Reset Enable: * |
| * 0x66 */ |
| #define S25FL1_SOFT_RESET 0x99 /* Software Reset: * |
| * 0x99 */ |
| /* Continuous Read Mode Reset: * |
| * 0xff | 0xff */ |
| |
| /* ID/Security Commands *************************&***************************/ |
| |
| /* Command Value Description: */ |
| |
| /* Data sequence */ |
| |
| #define S25FL1_DEEP_PWRDOWN 0xb9 /* Deep Power-down: * |
| * 0xb9 */ |
| #define S25FL1_RELEASE_PWRDOWN 0xab /* Release Power down / Device ID: * |
| * 0xab | dummy | dummy | dummy | * |
| * DeviceID */ |
| #define S25FL1_MANUFACTURER 0x90 /* Manufacturer / Device ID: * |
| * 0x90 | dummy | dummy | 0x00 | * |
| * Manufacturer | DeviceID */ |
| #define S25FL1_JEDEC_ID 0x9f /* JEDEC ID: * |
| * 0x9f | Manufacturer | MemoryType | * |
| * Capacity */ |
| #define S25FL1_READ_SFDP 0x5a /* Read SFDP Register / Read Unique ID * |
| * Number: * |
| * 0x5a | 0x00 | 0x00 | ADDR | dummy | * |
| * data... */ |
| #define S25FL1_READ_SECURITY 0x48 /* Read Security Registers: * |
| * 0x48 | ADDR(MS) | ADDR(MID) | * |
| * ADDR(LS) | dummy | data... */ |
| #define S25FL1_ERASE_SECURITY 0x44 /* Erase Security Registers: * |
| * 0x48 | ADDR(MS) | ADDR(MID) | * |
| * ADDR(LS) */ |
| #define S25FL1_PROgRAM_SECURITY 0x42 /* Program Security Registers: * |
| * 0x42 | ADDR(MS) | ADDR(MID) | * |
| * ADDR(LS) | data... */ |
| |
| /* Flash Manufacturer JEDEC IDs */ |
| |
| #define S25FL1_JEDEC_ID_SPANSION 0x01 |
| #define S25FL1_JEDEC_ID_ATMEL 0x1f |
| #define S25FL1_JEDEC_ID_ST 0x20 |
| #define S25FL1_JEDEC_ID_SST 0xbf |
| #define S25FL1_JEDEC_ID_MACRONIX 0xc2 |
| #define S25FL1_JEDEC_ID_WINBOND 0xef |
| |
| /* S25FL1 JEDIC IDs */ |
| |
| #define S25FL1_JEDEC_DEVICE_TYPE 0x40 /* S25FL1 memory device type */ |
| #define S25FL116K_JEDEC_CAPACITY 0x15 /* S25FL116K memory capacity */ |
| #define S25FL132K_JEDEC_CAPACITY 0x16 /* S25FL132K memory capacity */ |
| #define S25FL164K_JEDEC_CAPACITY 0x17 /* S25FL164K memory capacity */ |
| |
| /* S25FL1 Registers *********************************************************/ |
| |
| /* Status register bit definitions */ |
| |
| #define STATUS1_BUSY_MASK (1 << 0) /* Bit 0: Device ready/busy status */ |
| #define STATUS1_READY (0 << 0) /* 0 = Not Busy */ |
| #define STATUS1_BUSY (1 << 0) /* 1 = Busy */ |
| #define STATUS1_WEL_MASK (1 << 1) /* Bit 1: Write enable latch status */ |
| #define STATUS1_WEL_DISABLED (0 << 1) /* 0 = Not Write Enabled */ |
| #define STATUS1_WEL_ENABLED (1 << 1) /* 1 = Write Enabled */ |
| #define STATUS1_BP_SHIFT (2) /* Bits 2-4: Block protect bits */ |
| #define STATUS1_BP_MASK (7 << STATUS1_BP_SHIFT) |
| #define STATUS1_BP_NONE (0 << STATUS1_BP_SHIFT) |
| #define STATUS1_BP_ALL (7 << STATUS1_BP_SHIFT) |
| #define STATUS1_TB_MASK (1 << 5) /* Bit 5: Top / Bottom Protect */ |
| #define STATUS1_TB_TOP (0 << 5) /* 0 = BP2-BP0 protect Top down */ |
| #define STATUS1_TB_BOTTOM (1 << 5) /* 1 = BP2-BP0 protect Bottom up */ |
| #define STATUS1_SEC_MASK (1 << 6) /* Bit 6: Sector / Block Protect */ |
| #define STATUS1_SEC_BLOCK (0 << 6) /* 0 = BP2-BP0 protect 64-kB blocks */ |
| #define STATUS1_SEC_SECTOR (1 << 6) /* 1 = BP2-BP0 protect 4-kB sectors */ |
| #define STATUS1_SRP0_MASK (1 << 7) /* Bit 7: Status register protect 0 */ |
| #define STATUS1_SRP0_UNLOCKED (0 << 7) /* 0 = WP# no effect / PS Lock Down */ |
| #define STATUS1_SRP0_LOCKED (1 << 7) /* 1 = WP# protect / OTP Lock Down */ |
| |
| #define STATUS2_SRP1_MASK (1 << 0) /* Bit 0: Status register protect 1 */ |
| #define STATUS2_SRP1_UNLOCKED (0 << 0) /* 0 = WP# no effect / PS Lock Down */ |
| #define STATUS2_SRP1_LOCKED (1 << 0) /* 1 = WP# protect / OTP Lock Down */ |
| #define STATUS2_QUAD_ENABLE_MASK (1 << 1) /* Bit 1: Quad Enable */ |
| #define STATUS2_QUAD_DISABLE (0 << 1) /* 0 = Quad Mode Not Enabled */ |
| #define STATUS2_QUAD_ENABLE (1 << 1) /* 1 = Quad Mode Enabled */ |
| #define STATUS2_LB_SHIFT (2) /* Bits 2-5: Security Register Lock */ |
| #define STATUS2_LB_MASK (15 << STATUS2_LB_SHIFT) |
| #define STATUS2_LB_NONE (0 << STATUS2_LB_SHIFT) |
| #define STATUS2_LB_ALL (15 << STATUS2_LB_SHIFT) |
| #define STATUS2_CMP_MASK (1 << 6) /* Bit 6: Complement Protect */ |
| #define STATUS2_CMP_NORMAL (0 << 6) /* 0 = Normal Protection Map */ |
| #define STATUS2_CMP_INVERTED (1 << 6) /* 1 = Inverted Protection Map */ |
| #define STATUS2_SUS_MASK (1 << 7) /* Bit 7: Suspend Status */ |
| #define STATUS2_SUS_NONE (0 << 7) /* 0 = Erase / Program not suspended */ |
| #define STATUS2_SUS_SUSPENDED (1 << 7) /* 1 = Erase / Program suspended */ |
| |
| #define STATUS3_LC_SHIFT (0) /* Bits 0-3: Latency control */ |
| #define STATUS3_LC_MASK (15 << STATUS3_LC_SHIFT) |
| #define STATUS3_W4_MASK (1 << 4) /* Bit 4: Burst Wrap Enable */ |
| #define STATUS3_W4_DISABLED (0 << 4) /* 0 = Wrap Enabled */ |
| #define STATUS3_W4_ENABLED (1 << 4) /* 1 = Wrap Disabled */ |
| #define STATUS3_W56_SHIFT (5) /* Bits 5-6: Burst Wrap Length */ |
| #define STATUS3_W56_MASK (3 << STATUS3_W56_SHIFT) |
| #define STATUS3_W56_8BYTE (0 << STATUS3_W56_SHIFT) |
| #define STATUS3_W56_16BYTE (1 << STATUS3_W56_SHIFT) |
| #define STATUS3_W56_32BYTE (2 << STATUS3_W56_SHIFT) |
| #define STATUS3_W56_63BYTE (3 << STATUS3_W56_SHIFT) |
| /* Bit 7: Reserved */ |
| |
| /* Chip Geometries **********************************************************/ |
| |
| /* All members of the family support uniform 4K-byte sectors */ |
| |
| #define S25FL116K_SECTOR_SIZE (4*1024) |
| #define S25FL116K_SECTOR_SHIFT (12) |
| #define S25FL116K_SECTOR_COUNT (512) |
| #define S25FL116K_PAGE_SIZE (256) |
| #define S25FL116K_PAGE_SHIFT (8) |
| |
| #define S25FL132K_SECTOR_SIZE (4*1024) |
| #define S25FL132K_SECTOR_SHIFT (12) |
| #define S25FL132K_SECTOR_COUNT (1024) |
| #define S25FL132K_PAGE_SIZE (256) |
| #define S25FL132K_PAGE_SHIFT (8) |
| |
| #define S25FL164K_SECTOR_SIZE (4*1024) |
| #define S25FL164K_SECTOR_SHIFT (12) |
| #define S25FL164K_SECTOR_COUNT (2048) |
| #define S25FL164K_PAGE_SIZE (256) |
| #define S25FL164K_PAGE_SHIFT (8) |
| |
| /* Cache flags **************************************************************/ |
| |
| #define S25FL1_CACHE_VALID (1 << 0) /* 1=Cache has valid data */ |
| #define S25FL1_CACHE_DIRTY (1 << 1) /* 1=Cache is dirty */ |
| #define S25FL1_CACHE_ERASED (1 << 2) /* 1=Backing FLASH is erased */ |
| |
| #define IS_VALID(p) ((((p)->flags) & S25FL1_CACHE_VALID) != 0) |
| #define IS_DIRTY(p) ((((p)->flags) & S25FL1_CACHE_DIRTY) != 0) |
| #define IS_ERASED(p) ((((p)->flags) & S25FL1_CACHE_ERASED) != 0) |
| |
| #define SET_VALID(p) do { (p)->flags |= S25FL1_CACHE_VALID; } while (0) |
| #define SET_DIRTY(p) do { (p)->flags |= S25FL1_CACHE_DIRTY; } while (0) |
| #define SET_ERASED(p) do { (p)->flags |= S25FL1_CACHE_ERASED; } while (0) |
| |
| #define CLR_VALID(p) do { (p)->flags &= ~S25FL1_CACHE_VALID; } while (0) |
| #define CLR_DIRTY(p) do { (p)->flags &= ~S25FL1_CACHE_DIRTY; } while (0) |
| #define CLR_ERASED(p) do { (p)->flags &= ~S25FL1_CACHE_ERASED; } while (0) |
| |
| /* 512 byte sector support **************************************************/ |
| |
| #define S25FL1_SECTOR512_SHIFT 9 |
| #define S25FL1_SECTOR512_SIZE (1 << 9) |
| #define S25FL1_ERASED_STATE 0xff |
| |
| /**************************************************************************** |
| * 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 |
| * s25fl1_dev_s. |
| */ |
| |
| struct s25fl1_dev_s |
| { |
| struct mtd_dev_s mtd; /* MTD interface */ |
| FAR struct qspi_dev_s *qspi; /* Saved QuadSPI interface instance */ |
| uint16_t nsectors; /* Number of erase sectors */ |
| uint8_t sectorshift; /* Log2 of sector size */ |
| uint8_t pageshift; /* Log2 of page size */ |
| FAR uint8_t *cmdbuf; /* Allocated command buffer */ |
| FAR uint8_t *readbuf; /* Allocated status read buffer */ |
| |
| #ifdef CONFIG_S25FL1_SECTOR512 |
| uint8_t flags; /* Buffered sector flags */ |
| uint16_t esectno; /* Erase sector number in the cache */ |
| FAR uint8_t *sector; /* Allocated sector data */ |
| #endif |
| }; |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| /* Locking */ |
| |
| static void s25fl1_lock(FAR struct qspi_dev_s *qspi); |
| static inline void s25fl1_unlock(FAR struct qspi_dev_s *qspi); |
| |
| /* Low-level message helpers */ |
| |
| static int s25fl1_command(FAR struct qspi_dev_s *qspi, |
| uint8_t cmd); |
| static int s25fl1_command_address(FAR struct qspi_dev_s *qspi, |
| uint8_t cmd, |
| off_t addr, |
| uint8_t addrlen); |
| static int s25fl1_command_read(FAR struct qspi_dev_s *qspi, |
| uint8_t cmd, |
| FAR void *buffer, |
| size_t buflen); |
| static int s25fl1_command_write(FAR struct qspi_dev_s *qspi, |
| uint8_t cmd, |
| FAR const void *buffer, |
| size_t buflen); |
| static uint8_t sf25fl1_read_status1(FAR struct s25fl1_dev_s *priv); |
| static uint8_t sf25fl1_read_status2(FAR struct s25fl1_dev_s *priv); |
| static uint8_t sf25fl1_read_status3(FAR struct s25fl1_dev_s *priv); |
| static void s25fl1_write_enable(FAR struct s25fl1_dev_s *priv); |
| static void s25fl1_write_disable(FAR struct s25fl1_dev_s *priv); |
| |
| static int s25fl1_readid(FAR struct s25fl1_dev_s *priv); |
| static int s25fl1_protect(FAR struct s25fl1_dev_s *priv, |
| off_t startblock, |
| size_t nblocks); |
| static int s25fl1_unprotect(FAR struct s25fl1_dev_s *priv, |
| off_t startblock, |
| size_t nblocks); |
| static bool s25fl1_isprotected(FAR struct s25fl1_dev_s *priv, |
| uint8_t status, |
| off_t address); |
| static int s25fl1_erase_sector(FAR struct s25fl1_dev_s *priv, |
| off_t offset); |
| static int s25fl1_erase_chip(FAR struct s25fl1_dev_s *priv); |
| static int s25fl1_read_byte(FAR struct s25fl1_dev_s *priv, |
| FAR uint8_t *buffer, |
| off_t address, size_t nbytes); |
| static int s25fl1_write_page(FAR struct s25fl1_dev_s *priv, |
| FAR const uint8_t *buffer, |
| off_t address, |
| size_t nbytes); |
| #ifdef CONFIG_S25FL1_SECTOR512 |
| static int s25fl1_flush_cache(struct s25fl1_dev_s *priv); |
| static FAR uint8_t *s25fl1_read_cache(struct s25fl1_dev_s *priv, |
| off_t sector); |
| static void s25fl1_erase_cache(struct s25fl1_dev_s *priv, |
| off_t sector); |
| static int s25fl1_write_cache(FAR struct s25fl1_dev_s *priv, |
| FAR const uint8_t *buffer, |
| off_t sector, |
| size_t nsectors); |
| #endif |
| |
| /* MTD driver methods */ |
| |
| static int s25fl1_erase(FAR struct mtd_dev_s *dev, |
| off_t startblock, |
| size_t nblocks); |
| static ssize_t s25fl1_bread(FAR struct mtd_dev_s *dev, |
| off_t startblock, |
| size_t nblocks, |
| FAR uint8_t *buf); |
| static ssize_t s25fl1_bwrite(FAR struct mtd_dev_s *dev, |
| off_t startblock, |
| size_t nblocks, |
| FAR const uint8_t *buf); |
| static ssize_t s25fl1_read(FAR struct mtd_dev_s *dev, |
| off_t offset, |
| size_t nbytes, |
| FAR uint8_t *buffer); |
| static int s25fl1_ioctl(FAR struct mtd_dev_s *dev, |
| int cmd, |
| unsigned long arg); |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: s25fl1_lock |
| ****************************************************************************/ |
| |
| static void s25fl1_lock(FAR struct qspi_dev_s *qspi) |
| { |
| /* On QuadSPI buses where there are multiple devices, it will be necessary |
| * to lock QuadSPI to have exclusive access to the buses for a sequence of |
| * transfers. The bus should be locked before the chip is selected. |
| * |
| * This is a blocking call and will not return until we have exclusive |
| * access to the QuadSPI bus. |
| * We will retain that exclusive access until the bus is unlocked. |
| */ |
| |
| QSPI_LOCK(qspi, true); |
| |
| /* After locking the QuadSPI bus, the we also need call the setfrequency, |
| * setbits, and setmode methods to make sure that the QuadSPI is properly |
| * configured for the device. |
| * If the QuadSPI bus is being shared, then it may have been left in an |
| * incompatible state. |
| */ |
| |
| QSPI_SETMODE(qspi, CONFIG_S25FL1_QSPIMODE); |
| QSPI_SETBITS(qspi, 8); |
| QSPI_SETFREQUENCY(qspi, CONFIG_S25FL1_QSPI_FREQUENCY); |
| } |
| |
| /**************************************************************************** |
| * Name: s25fl1_unlock |
| ****************************************************************************/ |
| |
| static inline void s25fl1_unlock(FAR struct qspi_dev_s *qspi) |
| { |
| QSPI_LOCK(qspi, false); |
| } |
| |
| /**************************************************************************** |
| * Name: s25fl1_command |
| ****************************************************************************/ |
| |
| static int s25fl1_command(FAR struct qspi_dev_s *qspi, uint8_t cmd) |
| { |
| struct qspi_cmdinfo_s cmdinfo; |
| |
| finfo("CMD: %02x\n", cmd); |
| |
| cmdinfo.flags = 0; |
| cmdinfo.addrlen = 0; |
| cmdinfo.cmd = cmd; |
| cmdinfo.buflen = 0; |
| cmdinfo.addr = 0; |
| cmdinfo.buffer = NULL; |
| |
| return QSPI_COMMAND(qspi, &cmdinfo); |
| } |
| |
| /**************************************************************************** |
| * Name: s25fl1_command_address |
| ****************************************************************************/ |
| |
| static int s25fl1_command_address(FAR struct qspi_dev_s *qspi, |
| uint8_t cmd, |
| off_t addr, |
| uint8_t addrlen) |
| { |
| struct qspi_cmdinfo_s cmdinfo; |
| |
| finfo("CMD: %02x Address: %04lx addrlen=%d\n", |
| cmd, (unsigned long)addr, addrlen); |
| |
| cmdinfo.flags = QSPICMD_ADDRESS; |
| cmdinfo.addrlen = addrlen; |
| cmdinfo.cmd = cmd; |
| cmdinfo.buflen = 0; |
| cmdinfo.addr = addr; |
| cmdinfo.buffer = NULL; |
| |
| return QSPI_COMMAND(qspi, &cmdinfo); |
| } |
| |
| /**************************************************************************** |
| * Name: s25fl1_command_read |
| ****************************************************************************/ |
| |
| static int s25fl1_command_read(FAR struct qspi_dev_s *qspi, uint8_t cmd, |
| FAR void *buffer, size_t buflen) |
| { |
| struct qspi_cmdinfo_s cmdinfo; |
| |
| finfo("CMD: %02x buflen: %lu\n", cmd, (unsigned long)buflen); |
| |
| cmdinfo.flags = QSPICMD_READDATA; |
| cmdinfo.addrlen = 0; |
| cmdinfo.cmd = cmd; |
| cmdinfo.buflen = buflen; |
| cmdinfo.addr = 0; |
| cmdinfo.buffer = buffer; |
| |
| return QSPI_COMMAND(qspi, &cmdinfo); |
| } |
| |
| /**************************************************************************** |
| * Name: s25fl1_command_write |
| ****************************************************************************/ |
| |
| static int s25fl1_command_write(FAR struct qspi_dev_s *qspi, uint8_t cmd, |
| FAR const void *buffer, size_t buflen) |
| { |
| struct qspi_cmdinfo_s cmdinfo; |
| |
| finfo("CMD: %02x buflen: %lu\n", cmd, (unsigned long)buflen); |
| |
| cmdinfo.flags = QSPICMD_WRITEDATA; |
| cmdinfo.addrlen = 0; |
| cmdinfo.cmd = cmd; |
| cmdinfo.buflen = buflen; |
| cmdinfo.addr = 0; |
| cmdinfo.buffer = (FAR void *)buffer; |
| |
| return QSPI_COMMAND(qspi, &cmdinfo); |
| } |
| |
| /**************************************************************************** |
| * Name: sf25fl1_read_status1 |
| ****************************************************************************/ |
| |
| static uint8_t sf25fl1_read_status1(FAR struct s25fl1_dev_s *priv) |
| { |
| DEBUGVERIFY(s25fl1_command_read(priv->qspi, S25FL1_READ_STATUS1, |
| (FAR void *)&priv->readbuf[0], 1)); |
| return priv->readbuf[0]; |
| } |
| |
| /**************************************************************************** |
| * Name: sf25fl1_read_status2 |
| ****************************************************************************/ |
| |
| static uint8_t sf25fl1_read_status2(FAR struct s25fl1_dev_s *priv) |
| { |
| DEBUGVERIFY(s25fl1_command_read(priv->qspi, S25FL1_READ_STATUS2, |
| (FAR void *)&priv->readbuf[0], 1)); |
| return priv->readbuf[0]; |
| } |
| |
| /**************************************************************************** |
| * Name: sf25fl1_read_status3 |
| ****************************************************************************/ |
| |
| static uint8_t sf25fl1_read_status3(FAR struct s25fl1_dev_s *priv) |
| { |
| DEBUGVERIFY(s25fl1_command_read(priv->qspi, S25FL1_READ_STATUS3, |
| (FAR void *)&priv->readbuf[0], 1)); |
| return priv->readbuf[0]; |
| } |
| |
| /**************************************************************************** |
| * Name: s25fl1_write_enable |
| ****************************************************************************/ |
| |
| static void s25fl1_write_enable(FAR struct s25fl1_dev_s *priv) |
| { |
| uint8_t status; |
| |
| do |
| { |
| s25fl1_command(priv->qspi, S25FL1_WRITE_ENABLE); |
| status = sf25fl1_read_status1(priv); |
| } |
| while ((status & STATUS1_WEL_MASK) != STATUS1_WEL_ENABLED); |
| } |
| |
| /**************************************************************************** |
| * Name: s25fl1_write_disable |
| ****************************************************************************/ |
| |
| static void s25fl1_write_disable(FAR struct s25fl1_dev_s *priv) |
| { |
| uint8_t status; |
| |
| do |
| { |
| s25fl1_command(priv->qspi, S25FL1_WRITE_DISABLE); |
| status = sf25fl1_read_status1(priv); |
| } |
| while ((status & STATUS1_WEL_MASK) != STATUS1_WEL_DISABLED); |
| } |
| |
| /**************************************************************************** |
| * Name: s25fl1_write_status |
| ****************************************************************************/ |
| |
| static void s25fl1_write_status(FAR struct s25fl1_dev_s *priv) |
| { |
| s25fl1_write_enable(priv); |
| s25fl1_command_write(priv->qspi, S25FL1_WRITE_STATUS, |
| (FAR const void *)priv->cmdbuf, 3); |
| s25fl1_write_disable(priv); |
| } |
| |
| /**************************************************************************** |
| * Name: s25fl1_readid |
| ****************************************************************************/ |
| |
| static inline int s25fl1_readid(struct s25fl1_dev_s *priv) |
| { |
| /* Lock the QuadSPI bus and configure the bus. */ |
| |
| s25fl1_lock(priv->qspi); |
| |
| /* Read the JEDEC ID */ |
| |
| s25fl1_command_read(priv->qspi, S25FL1_JEDEC_ID, priv->cmdbuf, 3); |
| |
| /* Unlock the bus */ |
| |
| s25fl1_unlock(priv->qspi); |
| |
| finfo("Manufacturer: %02x Device Type %02x, Capacity: %02x", |
| priv->cmdbuf[0], priv->cmdbuf[1], priv->cmdbuf[2]); |
| |
| /* Check for a recognized memory device type */ |
| |
| if (priv->cmdbuf[1] != S25FL1_JEDEC_DEVICE_TYPE) |
| { |
| ferr("ERROR: Unrecognized device type: %02x\n", priv->cmdbuf[1]); |
| return -ENODEV; |
| } |
| |
| /* Check for a supported capacity */ |
| |
| switch (priv->cmdbuf[2]) |
| { |
| case S25FL116K_JEDEC_CAPACITY: |
| priv->sectorshift = S25FL116K_SECTOR_SHIFT; |
| priv->pageshift = S25FL116K_PAGE_SHIFT; |
| priv->nsectors = S25FL116K_SECTOR_COUNT; |
| break; |
| |
| case S25FL132K_JEDEC_CAPACITY: |
| priv->sectorshift = S25FL132K_SECTOR_SHIFT; |
| priv->pageshift = S25FL116K_PAGE_SHIFT; |
| priv->nsectors = S25FL132K_SECTOR_COUNT; |
| break; |
| |
| case S25FL164K_JEDEC_CAPACITY: |
| priv->sectorshift = S25FL164K_SECTOR_SHIFT; |
| priv->pageshift = S25FL116K_PAGE_SHIFT; |
| priv->nsectors = S25FL164K_SECTOR_COUNT; |
| break; |
| |
| /* Support for this part is not implemented yet */ |
| |
| default: |
| ferr("ERROR: Unsupported memory capacity: %02x\n", priv->cmdbuf[2]); |
| return -ENODEV; |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: s25fl1_protect |
| ****************************************************************************/ |
| |
| static int s25fl1_protect(FAR struct s25fl1_dev_s *priv, |
| off_t startblock, size_t nblocks) |
| { |
| /* Get the status register value to check the current protection */ |
| |
| priv->cmdbuf[0] = sf25fl1_read_status1(priv); |
| priv->cmdbuf[1] = sf25fl1_read_status2(priv); |
| priv->cmdbuf[2] = sf25fl1_read_status3(priv); |
| |
| if ((priv->cmdbuf[0] & STATUS1_BP_MASK) == STATUS1_BP_NONE) |
| { |
| /* Protection already disabled */ |
| |
| return 0; |
| } |
| |
| /* Check if sector protection registers are locked */ |
| |
| if ((priv->cmdbuf[0] & STATUS1_SRP0_MASK) == STATUS1_SRP0_LOCKED) |
| { |
| /* Yes.. unprotect section protection registers */ |
| |
| priv->cmdbuf[0] &= ~STATUS1_SRP0_MASK; |
| s25fl1_write_status(priv); |
| } |
| |
| /* Set the protection mask to zero. |
| * REVISIT: This logic should really just set the BP bits as |
| * necessary to protect the range of sectors. |
| */ |
| |
| priv->cmdbuf[0] |= STATUS1_BP_MASK; |
| s25fl1_write_status(priv); |
| |
| /* Check the new status */ |
| |
| priv->cmdbuf[0] = sf25fl1_read_status1(priv); |
| if ((priv->cmdbuf[0] & STATUS1_BP_MASK) != STATUS1_BP_MASK) |
| { |
| return -EACCES; |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: s25fl1_unprotect |
| ****************************************************************************/ |
| |
| static int s25fl1_unprotect(FAR struct s25fl1_dev_s *priv, |
| off_t startblock, size_t nblocks) |
| { |
| /* Get the status register value to check the current protection */ |
| |
| priv->cmdbuf[0] = sf25fl1_read_status1(priv); |
| priv->cmdbuf[1] = sf25fl1_read_status2(priv); |
| priv->cmdbuf[2] = sf25fl1_read_status3(priv); |
| |
| if ((priv->cmdbuf[0] & STATUS1_BP_MASK) == STATUS1_BP_NONE && |
| (priv->cmdbuf[1] & STATUS2_CMP_MASK) == 0) |
| { |
| /* Protection already disabled */ |
| |
| return 0; |
| } |
| |
| /* Check if sector protection registers are locked */ |
| |
| if ((priv->cmdbuf[0] & STATUS1_SRP0_MASK) == STATUS1_SRP0_LOCKED) |
| { |
| /* Yes.. unprotect section protection registers */ |
| |
| priv->cmdbuf[0] &= ~STATUS1_SRP0_MASK; |
| s25fl1_write_status(priv); |
| } |
| |
| /* Set the protection mask to zero (and not complemented). |
| * REVISIT: This logic should really just re-write the BP bits as |
| * necessary to unprotect the range of sectors. |
| */ |
| |
| priv->cmdbuf[0] &= ~STATUS1_BP_MASK; |
| priv->cmdbuf[1] &= ~STATUS2_CMP_MASK; |
| s25fl1_write_status(priv); |
| |
| /* Check the new status */ |
| |
| priv->cmdbuf[0] = sf25fl1_read_status1(priv); |
| if ((priv->cmdbuf[0] & (STATUS1_SRP0_MASK | STATUS1_BP_MASK)) != 0) |
| { |
| return -EACCES; |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: s25fl1_isprotected |
| ****************************************************************************/ |
| |
| static bool s25fl1_isprotected(FAR struct s25fl1_dev_s *priv, uint8_t status, |
| off_t address) |
| { |
| off_t protstart; |
| off_t protend; |
| off_t protsize; |
| unsigned int bp; |
| |
| /* What is protected? 64 Kb blocks? Or 4Kb sectors? */ |
| |
| if ((status & STATUS1_SEC_MASK) == STATUS1_SEC_BLOCK) |
| { |
| /* 64 Kb block */ |
| |
| protsize = 0x00010000; |
| } |
| else |
| { |
| /* 4 Kb sector */ |
| |
| protsize = 0x00001000; |
| } |
| |
| /* The BP field is the essentially a multiplier on this protection size */ |
| |
| bp = (status & STATUS1_BP_MASK) >> STATUS1_BP_SHIFT; |
| switch (bp) |
| { |
| case 0: |
| return false; |
| |
| case 1: |
| break; |
| |
| case 6: |
| case 7: |
| return true; |
| |
| default: |
| protsize <<= (protsize << (bp - 1)); |
| break; |
| } |
| |
| /* The final protection range then depends on if the protection region is |
| * configured top-down or bottom up (assuming CMP=0). |
| */ |
| |
| if ((status & STATUS1_TB_MASK) != 0) |
| { |
| protstart = 0x00000000; |
| protend = protstart + protsize; |
| } |
| else |
| { |
| protend = 0x00200000; |
| protstart = protend - protsize; |
| } |
| |
| return (address >= protstart && address < protend); |
| } |
| |
| /**************************************************************************** |
| * Name: s25fl1_erase_sector |
| ****************************************************************************/ |
| |
| static int s25fl1_erase_sector(struct s25fl1_dev_s *priv, off_t sector) |
| { |
| off_t address; |
| uint8_t status; |
| |
| finfo("sector: %08lx\n", (unsigned long)sector); |
| |
| /* Check that the flash is ready and unprotected */ |
| |
| status = sf25fl1_read_status1(priv); |
| if ((status & STATUS1_BUSY_MASK) != STATUS1_READY) |
| { |
| ferr("ERROR: Flash busy: %02x", status); |
| return -EBUSY; |
| } |
| |
| /* Get the address associated with the sector */ |
| |
| address = (off_t)sector << priv->sectorshift; |
| |
| if ((status & STATUS1_BP_MASK) != 0 && |
| s25fl1_isprotected(priv, status, address)) |
| { |
| ferr("ERROR: Flash protected: %02x", status); |
| return -EACCES; |
| } |
| |
| /* Send the sector erase command */ |
| |
| s25fl1_write_enable(priv); |
| s25fl1_command_address(priv->qspi, S25FL1_SECTOR_ERASE, address, 3); |
| |
| /* Wait for erasure to finish */ |
| |
| while ((sf25fl1_read_status1(priv) & STATUS1_BUSY_MASK) != 0); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: s25fl1_erase_chip |
| ****************************************************************************/ |
| |
| static int s25fl1_erase_chip(struct s25fl1_dev_s *priv) |
| { |
| uint8_t status; |
| |
| /* Check if the FLASH is protected */ |
| |
| status = sf25fl1_read_status1(priv); |
| if ((status & STATUS1_BP_MASK) != 0) |
| { |
| ferr("ERROR: FLASH is Protected: %02x", status); |
| return -EACCES; |
| } |
| |
| /* Erase the whole chip */ |
| |
| s25fl1_write_enable(priv); |
| s25fl1_command(priv->qspi, S25FL1_CHIP_ERASE_2); |
| |
| /* Wait for the erasure to complete */ |
| |
| status = sf25fl1_read_status1(priv); |
| while ((status & STATUS1_BUSY_MASK) != 0) |
| { |
| nxsig_usleep(200 * 1000); |
| status = sf25fl1_read_status1(priv); |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: s25fl1_read_byte |
| ****************************************************************************/ |
| |
| static int s25fl1_read_byte(FAR struct s25fl1_dev_s *priv, |
| FAR uint8_t *buffer, |
| off_t address, |
| size_t buflen) |
| { |
| struct qspi_meminfo_s meminfo; |
| |
| finfo("address: %08lx nbytes: %d\n", (long)address, (int)buflen); |
| |
| #ifdef CONFIG_S25FL1_SCRAMBLE |
| meminfo.flags = QSPIMEM_READ | QSPIMEM_QUADIO | QSPIMEM_SCRAMBLE; |
| #else |
| meminfo.flags = QSPIMEM_READ | QSPIMEM_QUADIO; |
| #endif |
| meminfo.addrlen = 3; |
| meminfo.dummies = 6; |
| meminfo.buflen = buflen; |
| meminfo.cmd = S25FL1_FAST_READ_QUADIO; |
| meminfo.addr = address; |
| #ifdef CONFIG_S25FL1_SCRAMBLE |
| meminfo.key = CONFIG_S25FL1_SCRAMBLE_KEY; |
| #endif |
| meminfo.buffer = buffer; |
| |
| return QSPI_MEMORY(priv->qspi, &meminfo); |
| } |
| |
| /**************************************************************************** |
| * Name: s25fl1_write_page |
| ****************************************************************************/ |
| |
| static int s25fl1_write_page(struct s25fl1_dev_s *priv, |
| FAR const uint8_t *buffer, |
| off_t address, |
| size_t buflen) |
| { |
| struct qspi_meminfo_s meminfo; |
| unsigned int pagesize; |
| unsigned int npages; |
| int ret; |
| int i; |
| |
| finfo("address: %08lx buflen: %u\n", |
| (unsigned long)address, (unsigned)buflen); |
| |
| npages = (buflen >> priv->pageshift); |
| pagesize = (1 << priv->pageshift); |
| |
| /* Set up non-varying parts of transfer description */ |
| |
| #ifdef CONFIG_S25FL1_SCRAMBLE |
| meminfo.flags = QSPIMEM_WRITE | QSPIMEM_SCRAMBLE; |
| #else |
| meminfo.flags = QSPIMEM_WRITE; |
| #endif |
| meminfo.cmd = S25FL1_PAGE_PROGRAM; |
| meminfo.addrlen = 3; |
| meminfo.buflen = pagesize; |
| #ifdef CONFIG_S25FL1_SCRAMBLE |
| meminfo.key = CONFIG_S25FL1_SCRAMBLE_KEY; |
| #endif |
| meminfo.dummies = 0; |
| |
| /* Then write each page */ |
| |
| for (i = 0; i < npages; i++) |
| { |
| /* Set up varying parts of the transfer description */ |
| |
| meminfo.addr = address; |
| meminfo.buffer = (void *)buffer; |
| |
| /* Write one page */ |
| |
| s25fl1_write_enable(priv); |
| ret = QSPI_MEMORY(priv->qspi, &meminfo); |
| s25fl1_write_disable(priv); |
| |
| if (ret < 0) |
| { |
| ferr("ERROR: QSPI_MEMORY failed writing address=%06" PRIxOFF "\n", |
| address); |
| return ret; |
| } |
| |
| /* Update for the next time through the loop */ |
| |
| buffer += pagesize; |
| address += pagesize; |
| buflen -= pagesize; |
| } |
| |
| /* The transfer should always be an even number of sectors and hence also |
| * pages. There should be no remainder. |
| */ |
| |
| DEBUGASSERT(buflen == 0); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: s25fl1_flush_cache |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_S25FL1_SECTOR512 |
| static int s25fl1_flush_cache(struct s25fl1_dev_s *priv) |
| { |
| int ret = OK; |
| |
| /* If the cached is dirty (meaning that it no longer matches the old FLASH |
| * contents) or was erased (with the cache containing the correct FLASH |
| * contents), then write the cached erase block to FLASH. |
| */ |
| |
| if (IS_DIRTY(priv) || IS_ERASED(priv)) |
| { |
| off_t address; |
| |
| /* Convert the erase sector numuber into a FLASH address */ |
| |
| address = (off_t)priv->esectno << priv->sectorshift; |
| |
| /* Write entire erase block to FLASH */ |
| |
| ret = s25fl1_write_page(priv, |
| priv->sector, |
| address, |
| 1 << priv->sectorshift); |
| if (ret < 0) |
| { |
| ferr("ERROR: s25fl1_write_page failed: %d\n", ret); |
| } |
| |
| /* The case is no long dirty and the FLASH is no longer erased */ |
| |
| CLR_DIRTY(priv); |
| CLR_ERASED(priv); |
| } |
| |
| return ret; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: s25fl1_read_cache |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_S25FL1_SECTOR512 |
| static FAR uint8_t *s25fl1_read_cache(struct s25fl1_dev_s *priv, |
| off_t sector) |
| { |
| off_t esectno; |
| int shift; |
| int index; |
| int ret; |
| |
| /* Convert from the 512 byte sector to the erase sector size of the device. |
| * For exmample, if the actual erase sector size if 4Kb (1 << 12), then we |
| * first shift to the right by 3 to get the sector number in 4096 |
| * increments. |
| */ |
| |
| shift = priv->sectorshift - S25FL1_SECTOR512_SHIFT; |
| esectno = sector >> shift; |
| finfo("sector: %ld esectno: %d shift=%d\n", sector, esectno, shift); |
| |
| /* Check if the requested erase block is already in the cache */ |
| |
| if (!IS_VALID(priv) || esectno != priv->esectno) |
| { |
| /* No.. Flush any dirty erase block currently in the cache */ |
| |
| ret = s25fl1_flush_cache(priv); |
| if (ret < 0) |
| { |
| ferr("ERROR: s25fl1_flush_cache failed: %d\n", ret); |
| return NULL; |
| } |
| |
| /* Read the erase block into the cache */ |
| |
| ret = s25fl1_read_byte(priv, priv->sector, |
| (esectno << priv->sectorshift), |
| (1 << priv->sectorshift)); |
| if (ret < 0) |
| { |
| ferr("ERROR: s25fl1_read_byte failed: %d\n", ret); |
| return NULL; |
| } |
| |
| /* Mark the sector as cached */ |
| |
| priv->esectno = esectno; |
| |
| SET_VALID(priv); /* The data in the cache is valid */ |
| CLR_DIRTY(priv); /* It should match the FLASH contents */ |
| CLR_ERASED(priv); /* The underlying FLASH has not been erased */ |
| } |
| |
| /* Get the index to the 512 sector in the erase block that holds the |
| * argument |
| */ |
| |
| index = sector & ((1 << shift) - 1); |
| |
| /* Return the address in the cache that holds this sector */ |
| |
| return &priv->sector[index << S25FL1_SECTOR512_SHIFT]; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: s25fl1_erase_cache |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_S25FL1_SECTOR512 |
| static void s25fl1_erase_cache(struct s25fl1_dev_s *priv, off_t sector) |
| { |
| FAR uint8_t *dest; |
| |
| /* First, make sure that the erase block containing the 512 byte sector is |
| * in the cache. |
| */ |
| |
| dest = s25fl1_read_cache(priv, sector); |
| |
| /* Erase the block containing this sector if it is not already erased. |
| * The erased indicated will be cleared when the data from the erase |
| * sector is read into the cache and set here when we erase the block. |
| */ |
| |
| if (!IS_ERASED(priv)) |
| { |
| off_t esectno = sector >> |
| (priv->sectorshift - S25FL1_SECTOR512_SHIFT); |
| finfo("sector: %ld esectno: %d\n", sector, esectno); |
| |
| DEBUGVERIFY(s25fl1_erase_sector(priv, esectno)); |
| SET_ERASED(priv); |
| } |
| |
| /* Put the cached sector data into the erase state and mart the cache as |
| * dirty (but don't update the FLASH yet. The caller will do that at a |
| * more optimal time). |
| */ |
| |
| memset(dest, S25FL1_ERASED_STATE, S25FL1_SECTOR512_SIZE); |
| SET_DIRTY(priv); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: s25fl1_write_cache |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_S25FL1_SECTOR512 |
| static int s25fl1_write_cache(FAR struct s25fl1_dev_s *priv, |
| FAR const uint8_t *buffer, off_t sector, |
| size_t nsectors) |
| { |
| FAR uint8_t *dest; |
| int ret; |
| |
| for (; nsectors > 0; nsectors--) |
| { |
| /* First, make sure that the erase block containing 512 byte sector is |
| * in memory. |
| */ |
| |
| dest = s25fl1_read_cache(priv, sector); |
| |
| /* Erase the block containing this sector if it is not already erased. |
| * The erased indicated will be cleared when the data from the erase |
| * sector is read into the cache and set here when we erase the sector. |
| */ |
| |
| if (!IS_ERASED(priv)) |
| { |
| off_t esectno = sector >> |
| (priv->sectorshift - S25FL1_SECTOR512_SHIFT); |
| finfo("sector: %ld esectno: %d\n", sector, esectno); |
| |
| ret = s25fl1_erase_sector(priv, esectno); |
| if (ret < 0) |
| { |
| ferr("ERROR: s25fl1_erase_sector failed: %d\n", ret); |
| return ret; |
| } |
| |
| SET_ERASED(priv); |
| } |
| |
| /* Copy the new sector data into cached erase block */ |
| |
| memcpy(dest, buffer, S25FL1_SECTOR512_SIZE); |
| SET_DIRTY(priv); |
| |
| /* Set up for the next 512 byte sector */ |
| |
| buffer += S25FL1_SECTOR512_SIZE; |
| sector++; |
| } |
| |
| /* Flush the last erase block left in the cache */ |
| |
| return s25fl1_flush_cache(priv); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: s25fl1_erase |
| ****************************************************************************/ |
| |
| static int s25fl1_erase(FAR struct mtd_dev_s *dev, |
| off_t startblock, |
| size_t nblocks) |
| { |
| FAR struct s25fl1_dev_s *priv = (FAR struct s25fl1_dev_s *)dev; |
| size_t blocksleft = nblocks; |
| #ifdef CONFIG_S25FL1_SECTOR512 |
| int ret; |
| #endif |
| |
| finfo("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); |
| |
| /* Lock access to the SPI bus until we complete the erase */ |
| |
| s25fl1_lock(priv->qspi); |
| |
| while (blocksleft-- > 0) |
| { |
| /* Erase each sector */ |
| |
| #ifdef CONFIG_S25FL1_SECTOR512 |
| s25fl1_erase_cache(priv, startblock); |
| #else |
| s25fl1_erase_sector(priv, startblock); |
| #endif |
| startblock++; |
| } |
| |
| #ifdef CONFIG_S25FL1_SECTOR512 |
| /* Flush the last erase block left in the cache */ |
| |
| ret = s25fl1_flush_cache(priv); |
| if (ret < 0) |
| { |
| nblocks = ret; |
| } |
| #endif |
| |
| s25fl1_unlock(priv->qspi); |
| return (int)nblocks; |
| } |
| |
| /**************************************************************************** |
| * Name: s25fl1_bread |
| ****************************************************************************/ |
| |
| static ssize_t s25fl1_bread(FAR struct mtd_dev_s *dev, off_t startblock, |
| size_t nblocks, FAR uint8_t *buffer) |
| { |
| #ifndef CONFIG_S25FL1_SECTOR512 |
| FAR struct s25fl1_dev_s *priv = (FAR struct s25fl1_dev_s *)dev; |
| #endif |
| ssize_t nbytes; |
| |
| finfo("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); |
| |
| /* On this device, we can handle the block read just like the byte-oriented |
| * read |
| */ |
| |
| #ifdef CONFIG_S25FL1_SECTOR512 |
| nbytes = s25fl1_read(dev, startblock << S25FL1_SECTOR512_SHIFT, |
| nblocks << S25FL1_SECTOR512_SHIFT, buffer); |
| if (nbytes > 0) |
| { |
| nbytes >>= S25FL1_SECTOR512_SHIFT; |
| } |
| #else |
| nbytes = s25fl1_read(dev, startblock << priv->pageshift, |
| nblocks << priv->pageshift, buffer); |
| if (nbytes > 0) |
| { |
| nbytes >>= priv->pageshift; |
| } |
| #endif |
| |
| return nbytes; |
| } |
| |
| /**************************************************************************** |
| * Name: s25fl1_bwrite |
| ****************************************************************************/ |
| |
| static ssize_t s25fl1_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, |
| size_t nblocks, FAR const uint8_t *buffer) |
| { |
| FAR struct s25fl1_dev_s *priv = (FAR struct s25fl1_dev_s *)dev; |
| int ret = (int)nblocks; |
| |
| finfo("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); |
| |
| /* Lock the QuadSPI bus and write all of the pages to FLASH */ |
| |
| s25fl1_lock(priv->qspi); |
| |
| #if defined(CONFIG_S25FL1_SECTOR512) |
| ret = s25fl1_write_cache(priv, buffer, startblock, nblocks); |
| if (ret < 0) |
| { |
| ferr("ERROR: s25fl1_write_cache failed: %d\n", ret); |
| } |
| |
| #else |
| ret = s25fl1_write_page(priv, buffer, startblock << priv->pageshift, |
| nblocks << priv->pageshift); |
| if (ret < 0) |
| { |
| ferr("ERROR: s25fl1_write_page failed: %d\n", ret); |
| } |
| #endif |
| |
| s25fl1_unlock(priv->qspi); |
| |
| return ret < 0 ? ret : nblocks; |
| } |
| |
| /**************************************************************************** |
| * Name: s25fl1_read |
| ****************************************************************************/ |
| |
| static ssize_t s25fl1_read(FAR struct mtd_dev_s *dev, |
| off_t offset, |
| size_t nbytes, |
| FAR uint8_t *buffer) |
| { |
| FAR struct s25fl1_dev_s *priv = (FAR struct s25fl1_dev_s *)dev; |
| int ret; |
| |
| finfo("offset: %08lx nbytes: %d\n", (long)offset, (int)nbytes); |
| |
| /* Lock the QuadSPI bus and select this FLASH part */ |
| |
| s25fl1_lock(priv->qspi); |
| ret = s25fl1_read_byte(priv, buffer, offset, nbytes); |
| s25fl1_unlock(priv->qspi); |
| |
| if (ret < 0) |
| { |
| ferr("ERROR: s25fl1_read_byte returned: %d\n", ret); |
| return (ssize_t)ret; |
| } |
| |
| finfo("return nbytes: %d\n", (int)nbytes); |
| return (ssize_t)nbytes; |
| } |
| |
| /**************************************************************************** |
| * Name: s25fl1_ioctl |
| ****************************************************************************/ |
| |
| static int s25fl1_ioctl(FAR struct mtd_dev_s *dev, |
| int cmd, |
| unsigned long arg) |
| { |
| FAR struct s25fl1_dev_s *priv = (FAR struct s25fl1_dev_s *)dev; |
| int ret = -EINVAL; /* Assume good command with bad parameters */ |
| |
| 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) |
| { |
| memset(geo, 0, sizeof(*geo)); |
| |
| /* Populate the geometry structure with information need to |
| * know the capacity and how to access the device. |
| * |
| * NOTE: |
| * that the device is treated as though it where just an array |
| * of fixed size blocks. That is most likely not true, but |
| * the client will expect the device logic to do whatever is |
| * necessary to make it appear so. |
| */ |
| |
| #ifdef CONFIG_S25FL1_SECTOR512 |
| geo->blocksize = (1 << S25FL1_SECTOR512_SHIFT); |
| geo->erasesize = (1 << S25FL1_SECTOR512_SHIFT); |
| geo->neraseblocks = priv->nsectors << |
| (priv->sectorshift - S25FL1_SECTOR512_SHIFT); |
| #else |
| geo->blocksize = (1 << priv->pageshift); |
| geo->erasesize = (1 << priv->sectorshift); |
| geo->neraseblocks = priv->nsectors; |
| #endif |
| ret = OK; |
| |
| finfo("blocksize: %" PRIu32 " erasesize: %" PRIu32 |
| " neraseblocks: %" PRIu32 "\n", |
| geo->blocksize, geo->erasesize, geo->neraseblocks); |
| } |
| } |
| break; |
| |
| case BIOC_PARTINFO: |
| { |
| FAR struct partition_info_s *info = |
| (FAR struct partition_info_s *)arg; |
| if (info != NULL) |
| { |
| #ifdef CONFIG_S25FL1_SECTOR512 |
| info->numsectors = priv->nsectors << |
| (priv->sectorshift - S25FL1_SECTOR512_SHIFT); |
| info->sectorsize = 1 << S25FL1_SECTOR512_SHIFT; |
| #else |
| info->numsectors = priv->nsectors << |
| (priv->sectorshift - priv->pageshift); |
| info->sectorsize = 1 << priv->pageshift; |
| #endif |
| info->startsector = 0; |
| info->parent[0] = '\0'; |
| ret = OK; |
| } |
| } |
| break; |
| |
| case MTDIOC_BULKERASE: |
| { |
| /* Erase the entire device */ |
| |
| s25fl1_lock(priv->qspi); |
| ret = s25fl1_erase_chip(priv); |
| s25fl1_unlock(priv->qspi); |
| } |
| break; |
| |
| case MTDIOC_PROTECT: |
| { |
| FAR const struct mtd_protect_s *prot = |
| (FAR const struct mtd_protect_s *)((uintptr_t)arg); |
| |
| DEBUGASSERT(prot); |
| ret = s25fl1_protect(priv, prot->startblock, prot->nblocks); |
| } |
| break; |
| |
| case MTDIOC_UNPROTECT: |
| { |
| FAR const struct mtd_protect_s *prot = |
| (FAR const struct mtd_protect_s *)((uintptr_t)arg); |
| |
| DEBUGASSERT(prot); |
| ret = s25fl1_unprotect(priv, prot->startblock, prot->nblocks); |
| } |
| break; |
| |
| case MTDIOC_ERASESTATE: |
| { |
| FAR uint8_t *result = (FAR uint8_t *)arg; |
| *result = S25FL1_ERASED_STATE; |
| |
| ret = OK; |
| } |
| break; |
| |
| default: |
| ret = -ENOTTY; /* Bad/unsupported command */ |
| break; |
| } |
| |
| finfo("return %d\n", ret); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: s25fl1_initialize |
| * |
| * Description: |
| * Create an initialize MTD device instance for the QuadSPI-based ST24FL1 |
| * FLASH part. |
| * |
| * 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 *s25fl1_initialize(FAR struct qspi_dev_s *qspi, |
| bool unprotect) |
| { |
| FAR struct s25fl1_dev_s *priv; |
| int ret; |
| |
| finfo("qspi: %p\n", qspi); |
| DEBUGASSERT(qspi != NULL); |
| |
| /* Allocate a state structure (we allocate the structure instead of using |
| * a fixed, static allocation so that we can handle multiple FLASH devices. |
| * The current implementation would handle only one FLASH part per QuadSPI |
| * device (only because of the QSPIDEV_FLASH(0) definition) and so would |
| * have to be extended to handle multiple FLASH parts on the same QuadSPI |
| * bus. |
| */ |
| |
| priv = kmm_zalloc(sizeof(struct s25fl1_dev_s)); |
| if (priv) |
| { |
| /* Initialize the allocated structure (unsupported methods were |
| * nullified by kmm_zalloc). |
| */ |
| |
| priv->mtd.erase = s25fl1_erase; |
| priv->mtd.bread = s25fl1_bread; |
| priv->mtd.bwrite = s25fl1_bwrite; |
| priv->mtd.read = s25fl1_read; |
| priv->mtd.ioctl = s25fl1_ioctl; |
| priv->mtd.name = "s25fl1"; |
| priv->qspi = qspi; |
| |
| /* Allocate a 4-byte buffer to support DMA command data */ |
| |
| priv->cmdbuf = (FAR uint8_t *)QSPI_ALLOC(qspi, 4); |
| if (priv->cmdbuf == NULL) |
| { |
| ferr("ERROR Failed to allocate command buffer\n"); |
| goto errout_with_priv; |
| } |
| |
| /* Allocate a one-byte buffer to support DMA status read data */ |
| |
| priv->readbuf = (FAR uint8_t *)QSPI_ALLOC(qspi, 1); |
| if (priv->readbuf == NULL) |
| { |
| ferr("ERROR Failed to allocate read buffer\n"); |
| goto errout_with_cmdbuf; |
| } |
| |
| /* Identify the FLASH chip and get its capacity */ |
| |
| ret = s25fl1_readid(priv); |
| if (ret != OK) |
| { |
| /* Unrecognized! Discard all of that work we just did and return |
| * NULL |
| */ |
| |
| ferr("ERROR Unrecognized QSPI device\n"); |
| goto errout_with_readbuf; |
| } |
| |
| /* Enable quad mode */ |
| |
| priv->cmdbuf[0] = sf25fl1_read_status1(priv); |
| priv->cmdbuf[1] = sf25fl1_read_status2(priv); |
| priv->cmdbuf[2] = sf25fl1_read_status3(priv); |
| |
| while ((priv->cmdbuf[1] & STATUS2_QUAD_ENABLE_MASK) == 0) |
| { |
| priv->cmdbuf[1] |= STATUS2_QUAD_ENABLE; |
| s25fl1_write_status(priv); |
| priv->cmdbuf[1] = sf25fl1_read_status2(priv); |
| nxsig_usleep(50 * 1000); |
| } |
| |
| /* Unprotect FLASH sectors if so requested. */ |
| |
| if (unprotect) |
| { |
| ret = s25fl1_unprotect(priv, 0, priv->nsectors - 1); |
| if (ret < 0) |
| { |
| ferr("ERROR: Sector unprotect failed\n"); |
| } |
| } |
| |
| #ifdef CONFIG_S25FL1_SECTOR512 /* Simulate a 512 byte sector */ |
| /* Allocate a buffer for the erase block cache */ |
| |
| priv->sector = (FAR uint8_t *)QSPI_ALLOC(qspi, 1 << priv->sectorshift); |
| if (priv->sector == NULL) |
| { |
| /* Allocation failed! |
| * Discard all of that work we just did and return NULL |
| */ |
| |
| ferr("ERROR: Sector allocation failed\n"); |
| goto errout_with_readbuf; |
| } |
| #endif |
| } |
| |
| /* Return the implementation-specific state structure as the MTD device */ |
| |
| finfo("Return %p\n", priv); |
| return (FAR struct mtd_dev_s *)priv; |
| |
| errout_with_readbuf: |
| QSPI_FREE(qspi, priv->readbuf); |
| |
| errout_with_cmdbuf: |
| QSPI_FREE(qspi, priv->cmdbuf); |
| |
| errout_with_priv: |
| kmm_free(priv); |
| return NULL; |
| } |