2021-01-27 16:48:40 +01:00
|
|
|
/****************************************************************************
|
2017-08-06 18:51:17 +02:00
|
|
|
* drivers/mtd/mx25rxx.c
|
|
|
|
*
|
2021-05-27 11:12:43 +02:00
|
|
|
* 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
|
2017-08-06 18:51:17 +02:00
|
|
|
*
|
2021-05-27 11:12:43 +02:00
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
2017-08-06 18:51:17 +02:00
|
|
|
*
|
2021-05-27 11:12:43 +02:00
|
|
|
* 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.
|
2017-08-06 18:51:17 +02:00
|
|
|
*
|
2021-01-27 16:48:40 +01:00
|
|
|
****************************************************************************/
|
2017-08-06 18:51:17 +02:00
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
/****************************************************************************
|
2017-08-06 18:51:17 +02:00
|
|
|
* Included Files
|
2021-01-27 16:48:40 +01:00
|
|
|
****************************************************************************/
|
2017-08-06 18:51:17 +02:00
|
|
|
|
|
|
|
#include <nuttx/config.h>
|
2021-05-18 08:59:14 +02:00
|
|
|
#include <assert.h>
|
2017-08-06 18:51:17 +02:00
|
|
|
#include <errno.h>
|
|
|
|
#include <debug.h>
|
2020-12-01 03:29:50 +01:00
|
|
|
#include <inttypes.h>
|
2017-08-06 18:51:17 +02:00
|
|
|
#include <stdbool.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
|
2019-07-26 17:17:56 +02:00
|
|
|
#ifdef CONFIG_MX25RXX_SECTOR512
|
|
|
|
# include <stdlib.h>
|
|
|
|
# include <string.h>
|
|
|
|
#endif
|
|
|
|
|
2017-08-06 18:51:17 +02:00
|
|
|
#include <nuttx/kmalloc.h>
|
2017-10-06 18:15:01 +02:00
|
|
|
#include <nuttx/signal.h>
|
|
|
|
#include <nuttx/signal.h>
|
2017-08-06 18:51:17 +02:00
|
|
|
#include <nuttx/fs/ioctl.h>
|
|
|
|
#include <nuttx/spi/qspi.h>
|
|
|
|
#include <nuttx/mtd/mtd.h>
|
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
/****************************************************************************
|
2017-08-06 18:51:17 +02:00
|
|
|
* Pre-processor Definitions
|
2021-01-27 16:48:40 +01:00
|
|
|
****************************************************************************/
|
2017-08-06 18:51:17 +02:00
|
|
|
|
|
|
|
/* MX25RXX Commands */
|
|
|
|
|
|
|
|
#define MX25R_READ 0x03 /* Read data bytes */
|
|
|
|
#define MX25R_FAST_READ 0x0b /* Higher speed read */
|
|
|
|
#define MX25R_2READ 0xbb /* 2 x I/O read command */
|
|
|
|
#define MX25R_DREAD 0x3b /* 1I / 2O read command */
|
|
|
|
#define MX25R_4READ 0xeb /* 4 x I/O read command */
|
|
|
|
#define MX25R_QREAD 0x6b /* 1I / 4O read command */
|
|
|
|
#define MX25R_PP 0x02 /* Page program */
|
|
|
|
#define MX25R_4PP 0x38 /* Quad page program */
|
|
|
|
|
|
|
|
#define MX25R_SE 0x20 /* 4Kb Sector erase */
|
|
|
|
#define MX25R_BE32 0x52 /* 32Kbit block Erase */
|
|
|
|
#define MX25R_BE64 0xd8 /* 64Kbit block Erase */
|
|
|
|
#define MX25R_CE 0xc7 /* Chip erase */
|
|
|
|
#define MX25R_CE_ALT 0x60 /* Chip erase (alternate) */
|
|
|
|
|
|
|
|
#define MX25R_WREN 0x06 /* Write Enable */
|
|
|
|
#define MX25R_WRDI 0x04 /* Write Disable */
|
|
|
|
#define MX25R_RDSR 0x05 /* Read status register */
|
|
|
|
#define MX25R_RDCR 0x15 /* Read config register */
|
|
|
|
#define MX25R_WRSR 0x01 /* Write stat/conf register */
|
|
|
|
|
|
|
|
#define MX25R_RDID 0x9f /* Read identification */
|
|
|
|
#define MX25R_RES 0xab /* Read electronic ID */
|
|
|
|
#define MX25R_REMS 0x90 /* Read manufacture and ID */
|
|
|
|
|
|
|
|
#define MX25R_DP 0xb9 /* Deep power down */
|
|
|
|
#define MX25R_RDP 0xab /* Release deep power down */
|
|
|
|
#define MX25R_PGM_SUSPEND 0x75 /* Suspends program */
|
|
|
|
#define MX25R_ERS_SUSPEND 0xb0 /* Suspends erase */
|
|
|
|
#define MX25R_PGM_RESUME 0x7A /* Resume program */
|
|
|
|
#define MX25R_ERS_RESUME 0x30 /* Resume erase */
|
|
|
|
#define MX25R_ENSO 0xb1 /* Enter secured OTP */
|
|
|
|
#define MX25R_EXSO 0xc1 /* Exit secured OTP */
|
|
|
|
#define MX25R_RDSCUR 0x2b /* Read security register */
|
|
|
|
#define MX25R_WRSCUR 0x2f /* Write security register */
|
|
|
|
#define MX25R_RSTEN 0x66 /* Reset Enable */
|
|
|
|
#define MX25R_RST 0x99 /* Reset Memory */
|
|
|
|
#define MX25R_RDSFDP 0x5a /* read out until CS# high */
|
|
|
|
#define MX25R_SBL 0xc0 /* Set Burst Length */
|
|
|
|
#define MX25R_SBL_ALT 0x77 /* Set Burst Length */
|
|
|
|
#define MX25R_NOP 0x00 /* No Operation */
|
|
|
|
|
|
|
|
/* MX25Rxx Registers */
|
|
|
|
|
|
|
|
/* Read ID (RDID) register values */
|
|
|
|
|
|
|
|
#define MX25R_MANUFACTURER 0xc2 /* Macronix manufacturer ID */
|
|
|
|
#define MX25R6435F_DEVID 0x17 /* MX25R6435F device ID */
|
|
|
|
|
|
|
|
/* JEDEC Read ID register values */
|
|
|
|
|
|
|
|
#define MX25R_JEDEC_MANUFACTURER 0xc2 /* Macronix manufacturer ID */
|
2022-07-22 10:20:39 +02:00
|
|
|
#ifdef CONFIG_MX25RXX_LXX
|
2022-07-22 17:10:22 +02:00
|
|
|
# define MX25R_JEDEC_MEMORY_TYPE 0x20 /* MX25Lx memory type */
|
2022-07-22 10:20:39 +02:00
|
|
|
#else
|
2022-07-22 17:10:22 +02:00
|
|
|
# define MX25R_JEDEC_MEMORY_TYPE 0x28 /* MX25Rx memory type */
|
2022-07-22 10:20:39 +02:00
|
|
|
#endif
|
2023-10-19 13:38:23 +02:00
|
|
|
#define MX25R_JEDEC_MX25L25673G_CAPACITY 0x19 /* MX25L25673G memory capacity */
|
2017-08-06 18:51:17 +02:00
|
|
|
#define MX25R_JEDEC_MX25R6435F_CAPACITY 0x17 /* MX25R6435F memory capacity */
|
|
|
|
#define MX25R_JEDEC_MX25R8035F_CAPACITY 0x14 /* MX25R8035F memory capacity */
|
|
|
|
|
|
|
|
/* Supported chips parameters */
|
|
|
|
|
|
|
|
/* MX25R6435F (64 MB) memory capacity */
|
|
|
|
|
|
|
|
#define MX25R6435F_SECTOR_SIZE (4*1024)
|
|
|
|
#define MX25R6435F_SECTOR_SHIFT (12)
|
2019-07-26 17:17:56 +02:00
|
|
|
#define MX25R6435F_SECTOR_COUNT (2048)
|
2017-08-06 18:51:17 +02:00
|
|
|
#define MX25R6435F_PAGE_SIZE (256)
|
2022-07-22 10:20:39 +02:00
|
|
|
|
2023-10-19 13:38:23 +02:00
|
|
|
/* MX25L25673G (256 MB) memory capacity */
|
|
|
|
|
|
|
|
#define MX25L25673G_SECTOR_SIZE (4*1024)
|
|
|
|
#define MX25L25673G_SECTOR_SHIFT (12)
|
|
|
|
#define MX25L25673G_SECTOR_COUNT (8192)
|
|
|
|
#define MX25L25673G_PAGE_SIZE (256)
|
|
|
|
|
2022-07-22 10:20:39 +02:00
|
|
|
#ifdef CONFIG_MX25RXX_PAGE128
|
2023-10-19 13:38:23 +02:00
|
|
|
# define MX25R6435F_PAGE_SHIFT (7)
|
|
|
|
# define MX25L25673G_PAGE_SHIFT (7)
|
2022-07-22 10:20:39 +02:00
|
|
|
#else
|
2023-10-19 13:38:23 +02:00
|
|
|
# define MX25R6435F_PAGE_SHIFT (8)
|
|
|
|
# define MX25L25673G_PAGE_SHIFT (8)
|
2022-07-22 10:20:39 +02:00
|
|
|
#endif
|
2017-08-06 18:51:17 +02:00
|
|
|
|
|
|
|
/* Status register bit definitions */
|
|
|
|
|
|
|
|
#define MX25R_SR_WIP (1 << 0) /* Bit 0: Write in progress */
|
|
|
|
#define MX25R_SR_WEL (1 << 1) /* Bit 1: Write enable latch */
|
|
|
|
#define MX25R_SR_BP_SHIFT (2) /* Bits 2-5: Block protect bits */
|
|
|
|
#define MX25R_SR_BP_MASK (15 << MX25R_SR_BP_SHIFT)
|
|
|
|
#define MX25R_SR_QE (1 << 6) /* Bit 6: Quad enable */
|
|
|
|
#define MX25R_SR_SRWD (1 << 7) /* Bit 7: Status register write protect */
|
|
|
|
|
2023-10-19 13:38:23 +02:00
|
|
|
/* Configuration register bit definitions */
|
2017-08-06 18:51:17 +02:00
|
|
|
|
|
|
|
#define MX25R_CR_LH (1 << 9) /* Bit 9: Power mode */
|
|
|
|
#define MX25R_CR_TB (1 << 3) /* Bit 3: Top/bottom selected */
|
|
|
|
#define MX25R_CR_DC (1 << 6) /* Bit 6: Dummy cycle */
|
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
/* Cache flags **************************************************************/
|
2019-07-26 17:17:56 +02:00
|
|
|
|
|
|
|
#define MX25RXX_CACHE_VALID (1 << 0) /* 1=Cache has valid data */
|
|
|
|
#define MX25RXX_CACHE_DIRTY (1 << 1) /* 1=Cache is dirty */
|
|
|
|
#define MX25RXX_CACHE_ERASED (1 << 2) /* 1=Backing FLASH is erased */
|
|
|
|
|
|
|
|
#define IS_VALID(p) ((((p)->flags) & MX25RXX_CACHE_VALID) != 0)
|
|
|
|
#define IS_DIRTY(p) ((((p)->flags) & MX25RXX_CACHE_DIRTY) != 0)
|
|
|
|
#define IS_ERASED(p) ((((p)->flags) & MX25RXX_CACHE_ERASED) != 0)
|
|
|
|
|
|
|
|
#define SET_VALID(p) do { (p)->flags |= MX25RXX_CACHE_VALID; } while (0)
|
|
|
|
#define SET_DIRTY(p) do { (p)->flags |= MX25RXX_CACHE_DIRTY; } while (0)
|
|
|
|
#define SET_ERASED(p) do { (p)->flags |= MX25RXX_CACHE_ERASED; } while (0)
|
|
|
|
|
|
|
|
#define CLR_VALID(p) do { (p)->flags &= ~MX25RXX_CACHE_VALID; } while (0)
|
|
|
|
#define CLR_DIRTY(p) do { (p)->flags &= ~MX25RXX_CACHE_DIRTY; } while (0)
|
|
|
|
#define CLR_ERASED(p) do { (p)->flags &= ~MX25RXX_CACHE_ERASED; } while (0)
|
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
/* 512 byte sector support **************************************************/
|
2019-07-26 17:17:56 +02:00
|
|
|
|
|
|
|
#define MX25RXX_SECTOR512_SHIFT 9
|
|
|
|
#define MX25RXX_SECTOR512_SIZE (1 << 9)
|
|
|
|
#define MX25RXX_ERASED_STATE 0xff
|
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
/****************************************************************************
|
2017-08-06 18:51:17 +02:00
|
|
|
* Private Types
|
2021-01-27 16:48:40 +01:00
|
|
|
****************************************************************************/
|
2017-08-06 18:51:17 +02:00
|
|
|
|
|
|
|
/* Internal state of the MTD device */
|
|
|
|
|
|
|
|
struct mx25rxx_dev_s
|
|
|
|
{
|
|
|
|
struct mtd_dev_s mtd; /* MTD interface */
|
|
|
|
FAR struct qspi_dev_s *qspi; /* QuadSPI interface */
|
|
|
|
|
|
|
|
FAR uint8_t *cmdbuf; /* Allocated command buffer */
|
|
|
|
|
2019-07-26 17:17:56 +02:00
|
|
|
uint8_t sectorshift; /* Log2 of sector size */
|
|
|
|
uint8_t pageshift; /* Log2 of page size */
|
|
|
|
uint16_t nsectors; /* Number of erase sectors */
|
|
|
|
|
|
|
|
#ifdef CONFIG_MX25RXX_SECTOR512
|
|
|
|
uint8_t flags; /* Buffered sector flags */
|
|
|
|
uint16_t esectno; /* Erase sector number in the cache */
|
|
|
|
FAR uint8_t *sector; /* Allocated sector data */
|
|
|
|
#endif
|
2017-08-06 18:51:17 +02:00
|
|
|
};
|
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
/****************************************************************************
|
2017-08-06 18:51:17 +02:00
|
|
|
* Private Function Prototypes
|
2021-01-27 16:48:40 +01:00
|
|
|
****************************************************************************/
|
2017-08-06 18:51:17 +02:00
|
|
|
|
|
|
|
/* MTD driver methods */
|
|
|
|
|
|
|
|
static int mx25rxx_erase(FAR struct mtd_dev_s *dev, off_t startblock,
|
|
|
|
size_t nblocks);
|
|
|
|
static ssize_t mx25rxx_bread(FAR struct mtd_dev_s *dev, off_t startblock,
|
|
|
|
size_t nblocks, FAR uint8_t *buf);
|
|
|
|
static ssize_t mx25rxx_bwrite(FAR struct mtd_dev_s *dev, off_t startblock,
|
|
|
|
size_t nblocks, FAR const uint8_t *buf);
|
|
|
|
static ssize_t mx25rxx_read(FAR struct mtd_dev_s *dev, off_t offset,
|
|
|
|
size_t nbytes, FAR uint8_t *buffer);
|
2020-12-01 03:26:36 +01:00
|
|
|
static int mx25rxx_ioctl(FAR struct mtd_dev_s *dev, int cmd,
|
|
|
|
unsigned long arg);
|
2017-08-06 18:51:17 +02:00
|
|
|
|
|
|
|
/* Internal driver methods */
|
|
|
|
|
|
|
|
static void mx25rxx_lock(FAR struct qspi_dev_s *qspi, bool read);
|
|
|
|
static void mx25rxx_unlock(FAR struct qspi_dev_s *qspi);
|
|
|
|
static int mx25rxx_command_read(FAR struct qspi_dev_s *qspi, uint8_t cmd,
|
|
|
|
FAR void *buffer, size_t buflen);
|
|
|
|
static int mx25rxx_command_write(FAR struct qspi_dev_s *qspi, uint8_t cmd,
|
|
|
|
FAR const void *buffer, size_t buflen);
|
|
|
|
static int mx25rxx_command(FAR struct qspi_dev_s *qspi, uint8_t cmd);
|
|
|
|
static int mx25rxx_command_address(FAR struct qspi_dev_s *qspi, uint8_t cmd,
|
|
|
|
off_t addr, uint8_t addrlen);
|
|
|
|
|
2024-08-25 01:21:12 +02:00
|
|
|
static int mx25rxx_readid(FAR struct mx25rxx_dev_s *dev);
|
2017-08-06 18:51:17 +02:00
|
|
|
static int mx25rxx_read_byte(FAR struct mx25rxx_dev_s *dev,
|
2020-12-01 03:26:36 +01:00
|
|
|
FAR uint8_t *buffer, off_t address,
|
|
|
|
size_t buflen);
|
2017-08-06 18:51:17 +02:00
|
|
|
static int mx25rxx_read_status(FAR struct mx25rxx_dev_s *dev);
|
|
|
|
static int mx25rxx_read_configuration(FAR struct mx25rxx_dev_s *dev);
|
|
|
|
static void mx25rxx_write_status_config(FAR struct mx25rxx_dev_s *dev,
|
|
|
|
uint8_t status, uint16_t config);
|
|
|
|
static void mx25rxx_write_enable(FAR struct mx25rxx_dev_s *dev, bool enable);
|
|
|
|
|
2024-08-25 01:21:12 +02:00
|
|
|
static int mx25rxx_write_page(FAR struct mx25rxx_dev_s *priv,
|
2021-01-27 16:48:40 +01:00
|
|
|
FAR const uint8_t *buffer,
|
|
|
|
off_t address,
|
|
|
|
size_t buflen);
|
2024-08-25 01:21:12 +02:00
|
|
|
static int mx25rxx_erase_sector(FAR struct mx25rxx_dev_s *priv,
|
|
|
|
off_t sector);
|
2019-07-29 01:50:57 +02:00
|
|
|
#if 0 /* FIXME: Not used */
|
2024-08-25 01:21:12 +02:00
|
|
|
static int mx25rxx_erase_block(FAR struct mx25rxx_dev_s *priv, off_t block);
|
2019-07-29 01:50:57 +02:00
|
|
|
#endif
|
2024-08-25 01:21:12 +02:00
|
|
|
static int mx25rxx_erase_chip(FAR struct mx25rxx_dev_s *priv);
|
2017-08-06 18:51:17 +02:00
|
|
|
|
2019-07-26 17:17:56 +02:00
|
|
|
#ifdef CONFIG_MX25RXX_SECTOR512
|
2024-08-25 01:21:12 +02:00
|
|
|
static int mx25rxx_flush_cache(FAR struct mx25rxx_dev_s *priv);
|
|
|
|
static FAR uint8_t *mx25rxx_read_cache(FAR struct mx25rxx_dev_s *priv,
|
2021-01-27 16:48:40 +01:00
|
|
|
off_t sector);
|
2024-08-25 01:21:12 +02:00
|
|
|
static void mx25rxx_erase_cache(FAR struct mx25rxx_dev_s *priv,
|
|
|
|
off_t sector);
|
2019-07-26 17:17:56 +02:00
|
|
|
static int mx25rxx_write_cache(FAR struct mx25rxx_dev_s *priv,
|
2024-08-25 01:21:12 +02:00
|
|
|
FAR const uint8_t *buffer, off_t sector,
|
|
|
|
size_t nsectors);
|
2019-07-26 17:17:56 +02:00
|
|
|
#endif
|
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
/****************************************************************************
|
2017-08-06 18:51:17 +02:00
|
|
|
* Private Functions
|
2021-01-27 16:48:40 +01:00
|
|
|
****************************************************************************/
|
2017-08-06 18:51:17 +02:00
|
|
|
|
|
|
|
void mx25rxx_lock(FAR struct qspi_dev_s *qspi, bool read)
|
|
|
|
{
|
2020-02-23 09:50:23 +01:00
|
|
|
/* On SPI buses where there are multiple devices, it will be necessary to
|
|
|
|
* lock SPI to have exclusive access to the buses for a sequence of
|
2017-08-06 18:51:17 +02:00
|
|
|
* transfers. The bus should be locked before the chip is selected.
|
|
|
|
*
|
2021-01-27 16:48:40 +01:00
|
|
|
* This is a blocking call and will not return until we have exclusive
|
|
|
|
* access to the SPI bus. We will retain that exclusive access until the
|
|
|
|
* bus is unlocked.
|
2017-08-06 18:51:17 +02:00
|
|
|
*/
|
|
|
|
|
2020-01-02 17:49:34 +01:00
|
|
|
QSPI_LOCK(qspi, true);
|
2017-08-06 18:51:17 +02:00
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
/* After locking the SPI bus, the we also need call the setfrequency,
|
|
|
|
* setbits and setmode methods to make sure that the SPI is properly
|
|
|
|
* configured for the device. If the SPI bus is being shared, then it
|
|
|
|
* may have been left in an incompatible state.
|
2017-08-06 18:51:17 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
QSPI_SETMODE(qspi, CONFIG_MX25RXX_QSPIMODE);
|
|
|
|
QSPI_SETBITS(qspi, 8);
|
2020-01-02 17:49:34 +01:00
|
|
|
QSPI_SETFREQUENCY(qspi,
|
2021-01-27 16:48:40 +01:00
|
|
|
read ? CONFIG_MX25RXX_QSPI_READ_FREQUENCY :
|
|
|
|
CONFIG_MX25RXX_QSPI_FREQUENCY);
|
2017-08-06 18:51:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void mx25rxx_unlock(FAR struct qspi_dev_s *qspi)
|
|
|
|
{
|
2020-01-02 17:49:34 +01:00
|
|
|
QSPI_LOCK(qspi, false);
|
2017-08-06 18:51:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int mx25rxx_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);
|
|
|
|
}
|
|
|
|
|
|
|
|
int mx25rxx_command_write(FAR struct qspi_dev_s *qspi, uint8_t cmd,
|
|
|
|
FAR const void *buffer, size_t buflen)
|
|
|
|
{
|
|
|
|
struct qspi_cmdinfo_s cmdinfo;
|
|
|
|
|
2020-12-01 03:29:50 +01:00
|
|
|
finfo("CMD: %02x buflen: %lu 0x%" PRIx32 "\n",
|
2019-07-26 17:17:56 +02:00
|
|
|
cmd, (unsigned long)buflen, *(FAR uint32_t *)buffer);
|
2017-08-06 18:51:17 +02:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
int mx25rxx_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);
|
|
|
|
}
|
|
|
|
|
|
|
|
int mx25rxx_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);
|
|
|
|
}
|
|
|
|
|
|
|
|
int mx25rxx_read_byte(FAR struct mx25rxx_dev_s *dev, 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);
|
|
|
|
|
|
|
|
meminfo.flags = QSPIMEM_READ | QSPIMEM_QUADIO;
|
|
|
|
meminfo.addrlen = 3;
|
|
|
|
|
2020-02-23 09:50:23 +01:00
|
|
|
/* Ignore performance enhanced mode => 2+4 dummies */
|
2017-08-06 18:51:17 +02:00
|
|
|
|
|
|
|
meminfo.dummies = 6;
|
|
|
|
meminfo.buflen = buflen;
|
|
|
|
meminfo.cmd = MX25R_4READ;
|
|
|
|
meminfo.addr = address;
|
|
|
|
meminfo.buffer = buffer;
|
|
|
|
|
|
|
|
return QSPI_MEMORY(dev->qspi, &meminfo);
|
|
|
|
}
|
|
|
|
|
2024-08-25 01:21:12 +02:00
|
|
|
int mx25rxx_write_page(FAR struct mx25rxx_dev_s *priv,
|
|
|
|
FAR const uint8_t *buffer,
|
2017-08-06 18:51:17 +02:00
|
|
|
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 */
|
|
|
|
|
|
|
|
meminfo.flags = QSPIMEM_WRITE | QSPIMEM_QUADIO;
|
|
|
|
meminfo.cmd = MX25R_4PP;
|
|
|
|
meminfo.addrlen = 3;
|
|
|
|
meminfo.buflen = pagesize;
|
|
|
|
meminfo.dummies = 0;
|
|
|
|
|
|
|
|
/* Then write each page */
|
|
|
|
|
|
|
|
for (i = 0; i < npages; i++)
|
|
|
|
{
|
|
|
|
/* Set up varying parts of the transfer description */
|
|
|
|
|
|
|
|
meminfo.addr = address;
|
2024-08-25 01:21:12 +02:00
|
|
|
meminfo.buffer = (FAR void *)buffer;
|
2017-08-06 18:51:17 +02:00
|
|
|
|
|
|
|
/* Write one page */
|
|
|
|
|
|
|
|
mx25rxx_write_enable(priv, true);
|
|
|
|
ret = QSPI_MEMORY(priv->qspi, &meminfo);
|
|
|
|
mx25rxx_write_enable(priv, false);
|
|
|
|
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
2020-12-01 03:29:50 +01:00
|
|
|
ferr("ERROR: QSPI_MEMORY failed writing address=%06jx\n",
|
|
|
|
(intmax_t)address);
|
2017-08-06 18:51:17 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Update for the next time through the loop */
|
|
|
|
|
|
|
|
buffer += pagesize;
|
|
|
|
address += pagesize;
|
|
|
|
}
|
|
|
|
|
2019-07-26 17:17:56 +02:00
|
|
|
/* Wait for write operation to finish */
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
mx25rxx_read_status(priv);
|
|
|
|
ret = priv->cmdbuf[0];
|
|
|
|
}
|
|
|
|
while ((ret & MX25R_SR_WIP) != 0);
|
|
|
|
|
2017-08-06 18:51:17 +02:00
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
2024-08-25 01:21:12 +02:00
|
|
|
int mx25rxx_erase_sector(FAR struct mx25rxx_dev_s *priv, off_t sector)
|
2017-08-06 18:51:17 +02:00
|
|
|
{
|
|
|
|
off_t address;
|
|
|
|
uint8_t status;
|
|
|
|
|
|
|
|
finfo("sector: %08lx\n", (unsigned long)sector);
|
|
|
|
|
|
|
|
/* Get the address associated with the sector */
|
|
|
|
|
|
|
|
address = (off_t)sector << priv->sectorshift;
|
|
|
|
|
|
|
|
/* Send the sector erase command */
|
|
|
|
|
|
|
|
mx25rxx_write_enable(priv, true);
|
|
|
|
mx25rxx_command_address(priv->qspi, MX25R_SE, address, 3);
|
|
|
|
|
|
|
|
/* Wait for erasure to finish */
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
2019-07-26 17:17:56 +02:00
|
|
|
nxsig_usleep(50 * 1000);
|
2017-08-06 18:51:17 +02:00
|
|
|
mx25rxx_read_status(priv);
|
|
|
|
status = priv->cmdbuf[0];
|
|
|
|
}
|
|
|
|
while ((status & MX25R_SR_WIP) != 0);
|
|
|
|
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
2019-07-29 01:50:57 +02:00
|
|
|
#if 0 /* FIXME: Not used */
|
2024-08-25 01:21:12 +02:00
|
|
|
int mx25rxx_erase_block(FAR struct mx25rxx_dev_s *priv, off_t block)
|
2017-08-06 18:51:17 +02:00
|
|
|
{
|
|
|
|
uint8_t status;
|
|
|
|
|
|
|
|
finfo("block: %08lx\n", (unsigned long)block);
|
|
|
|
|
|
|
|
/* Send the 64k block erase command */
|
|
|
|
|
|
|
|
mx25rxx_write_enable(priv, true);
|
|
|
|
mx25rxx_command_address(priv->qspi, MX25R_BE64, block << 16, 3);
|
|
|
|
|
|
|
|
/* Wait for erasure to finish */
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
2019-07-26 17:17:56 +02:00
|
|
|
nxsig_usleep(300 * 1000);
|
2017-08-06 18:51:17 +02:00
|
|
|
mx25rxx_read_status(priv);
|
|
|
|
status = priv->cmdbuf[0];
|
|
|
|
}
|
|
|
|
while ((status & MX25R_SR_WIP) != 0);
|
|
|
|
|
|
|
|
return OK;
|
|
|
|
}
|
2019-07-29 01:50:57 +02:00
|
|
|
#endif
|
2017-08-06 18:51:17 +02:00
|
|
|
|
2024-08-25 01:21:12 +02:00
|
|
|
int mx25rxx_erase_chip(FAR struct mx25rxx_dev_s *priv)
|
2017-08-06 18:51:17 +02:00
|
|
|
{
|
|
|
|
uint8_t status;
|
|
|
|
|
|
|
|
/* Erase the whole chip */
|
|
|
|
|
|
|
|
mx25rxx_write_enable(priv, true);
|
|
|
|
mx25rxx_command(priv->qspi, MX25R_CE);
|
|
|
|
|
|
|
|
/* Wait for the erasure to complete */
|
|
|
|
|
|
|
|
mx25rxx_read_status(priv);
|
|
|
|
status = priv->cmdbuf[0];
|
|
|
|
|
|
|
|
while ((status & MX25R_SR_WIP) != 0)
|
|
|
|
{
|
2017-10-06 18:15:01 +02:00
|
|
|
nxsig_sleep(2);
|
2017-08-06 18:51:17 +02:00
|
|
|
mx25rxx_read_status(priv);
|
|
|
|
status = priv->cmdbuf[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
void mx25rxx_write_enable(FAR struct mx25rxx_dev_s *dev, bool enable)
|
|
|
|
{
|
|
|
|
uint8_t status;
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
mx25rxx_command(dev->qspi, enable ? MX25R_WREN : MX25R_WRDI);
|
|
|
|
mx25rxx_read_status(dev);
|
|
|
|
status = dev->cmdbuf[0];
|
|
|
|
}
|
|
|
|
while ((status & MX25R_SR_WEL) ^ (enable ? MX25R_SR_WEL : 0));
|
|
|
|
}
|
|
|
|
|
|
|
|
int mx25rxx_read_status(FAR struct mx25rxx_dev_s *dev)
|
|
|
|
{
|
|
|
|
return mx25rxx_command_read(dev->qspi, MX25R_RDSR, dev->cmdbuf, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
int mx25rxx_read_configuration(FAR struct mx25rxx_dev_s *dev)
|
|
|
|
{
|
|
|
|
return mx25rxx_command_read(dev->qspi, MX25R_RDCR, dev->cmdbuf, 4);
|
|
|
|
}
|
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
void mx25rxx_write_status_config(FAR struct mx25rxx_dev_s *dev,
|
|
|
|
uint8_t status,
|
2017-08-06 18:51:17 +02:00
|
|
|
uint16_t config)
|
|
|
|
{
|
|
|
|
mx25rxx_write_enable(dev, true);
|
|
|
|
|
|
|
|
/* take care to mask of the SRP bit; it is one-time-programmable */
|
|
|
|
|
|
|
|
config &= ~MX25R_CR_TB;
|
|
|
|
|
|
|
|
dev->cmdbuf[0] = status | 2;
|
|
|
|
dev->cmdbuf[1] = config & 0xff;
|
|
|
|
dev->cmdbuf[2] = config >> 8;
|
|
|
|
|
2022-07-22 10:20:39 +02:00
|
|
|
#ifdef CONFIG_MX25RXX_LXX
|
|
|
|
mx25rxx_command_write(dev->qspi, MX25R_WRSR, dev->cmdbuf, 2);
|
|
|
|
#else
|
2017-08-06 18:51:17 +02:00
|
|
|
mx25rxx_command_write(dev->qspi, MX25R_WRSR, dev->cmdbuf, 3);
|
2022-07-22 10:20:39 +02:00
|
|
|
#endif
|
2017-08-06 18:51:17 +02:00
|
|
|
mx25rxx_write_enable(dev, false);
|
|
|
|
}
|
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
int mx25rxx_erase(FAR struct mtd_dev_s *dev,
|
|
|
|
off_t startblock,
|
|
|
|
size_t nblocks)
|
2017-08-06 18:51:17 +02:00
|
|
|
{
|
|
|
|
FAR struct mx25rxx_dev_s *priv = (FAR struct mx25rxx_dev_s *)dev;
|
|
|
|
size_t blocksleft = nblocks;
|
2019-07-26 17:17:56 +02:00
|
|
|
#ifdef CONFIG_MX25RXX_SECTOR512
|
|
|
|
int ret;
|
|
|
|
#endif
|
2017-08-06 18:51:17 +02:00
|
|
|
|
|
|
|
finfo("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks);
|
|
|
|
|
|
|
|
/* Lock access to the SPI bus until we complete the erase */
|
|
|
|
|
|
|
|
mx25rxx_lock(priv->qspi, false);
|
|
|
|
|
2019-07-26 17:17:56 +02:00
|
|
|
while (blocksleft-- > 0)
|
|
|
|
{
|
|
|
|
/* Erase each sector */
|
|
|
|
|
|
|
|
#ifdef CONFIG_MX25RXX_SECTOR512
|
|
|
|
mx25rxx_erase_cache(priv, startblock);
|
|
|
|
#else
|
|
|
|
mx25rxx_erase_sector(priv, startblock);
|
|
|
|
#endif
|
|
|
|
startblock++;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_MX25RXX_SECTOR512
|
|
|
|
/* Flush the last erase block left in the cache */
|
|
|
|
|
|
|
|
ret = mx25rxx_flush_cache(priv);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
nblocks = ret;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if 0
|
2020-12-01 03:26:36 +01:00
|
|
|
/* FIXME: use mx25rxx_erase_block in case CONFIG_MX25RXX_SECTOR512 is
|
|
|
|
* not configured to speed up block erase.
|
2019-07-26 17:17:56 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
unsigned int sectorsperblock = (64 * 1024) >> priv->sectorshift;
|
2017-08-06 18:51:17 +02:00
|
|
|
while (blocksleft > 0)
|
|
|
|
{
|
|
|
|
/* Check if current block is aligned on 64k block to speed up erase */
|
|
|
|
|
2019-07-26 17:17:56 +02:00
|
|
|
if (((startblock & (sectorsperblock - 1)) == 0) &&
|
|
|
|
(blocksleft >= sectorsperblock))
|
2017-08-06 18:51:17 +02:00
|
|
|
{
|
|
|
|
/* Erase 64k block */
|
|
|
|
|
2019-07-26 17:17:56 +02:00
|
|
|
mx25rxx_erase_block(priv, startblock >> (16 - priv->sectorshift));
|
|
|
|
startblock += sectorsperblock;
|
|
|
|
blocksleft -= sectorsperblock;
|
2017-08-06 18:51:17 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Erase each sector */
|
|
|
|
|
|
|
|
mx25rxx_erase_sector(priv, startblock);
|
2021-12-26 23:18:22 +01:00
|
|
|
startblock++;
|
|
|
|
blocksleft--;
|
2017-08-06 18:51:17 +02:00
|
|
|
}
|
|
|
|
}
|
2019-07-26 17:17:56 +02:00
|
|
|
#endif
|
2017-08-06 18:51:17 +02:00
|
|
|
|
|
|
|
mx25rxx_unlock(priv->qspi);
|
|
|
|
|
|
|
|
return (int)nblocks;
|
|
|
|
}
|
|
|
|
|
|
|
|
ssize_t mx25rxx_bread(FAR struct mtd_dev_s *dev, off_t startblock,
|
|
|
|
size_t nblocks, FAR uint8_t *buf)
|
|
|
|
{
|
2019-07-26 17:17:56 +02:00
|
|
|
#ifndef CONFIG_MX25RXX_SECTOR512
|
|
|
|
FAR struct mx25rxx_dev_s *priv = (FAR struct mx25rxx_dev_s *)dev;
|
|
|
|
#endif
|
2017-08-06 18:51:17 +02:00
|
|
|
ssize_t nbytes;
|
|
|
|
|
|
|
|
finfo("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks);
|
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
/* On this device, we can handle the block read just like the
|
|
|
|
* byte-oriented read
|
|
|
|
*/
|
2019-07-26 17:17:56 +02:00
|
|
|
|
|
|
|
#ifdef CONFIG_MX25RXX_SECTOR512
|
|
|
|
nbytes = mx25rxx_read(dev, startblock << MX25RXX_SECTOR512_SHIFT,
|
|
|
|
nblocks << MX25RXX_SECTOR512_SHIFT, buf);
|
2017-08-06 18:51:17 +02:00
|
|
|
if (nbytes > 0)
|
|
|
|
{
|
2019-07-26 17:17:56 +02:00
|
|
|
nbytes >>= MX25RXX_SECTOR512_SHIFT;
|
2017-08-06 18:51:17 +02:00
|
|
|
}
|
2019-07-26 17:17:56 +02:00
|
|
|
#else
|
|
|
|
nbytes = mx25rxx_read(dev, startblock << priv->pageshift,
|
|
|
|
nblocks << priv->pageshift, buf);
|
|
|
|
if (nbytes > 0)
|
|
|
|
{
|
|
|
|
nbytes >>= priv->pageshift;
|
|
|
|
}
|
|
|
|
#endif
|
2017-08-06 18:51:17 +02:00
|
|
|
|
|
|
|
return nbytes;
|
|
|
|
}
|
|
|
|
|
|
|
|
ssize_t mx25rxx_bwrite(FAR struct mtd_dev_s *dev, off_t startblock,
|
|
|
|
size_t nblocks, FAR const uint8_t *buf)
|
|
|
|
{
|
|
|
|
FAR struct mx25rxx_dev_s *priv = (FAR struct mx25rxx_dev_s *)dev;
|
2019-07-26 17:17:56 +02:00
|
|
|
int ret;
|
2017-08-06 18:51:17 +02:00
|
|
|
|
|
|
|
finfo("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks);
|
|
|
|
|
|
|
|
/* Lock the QuadSPI bus and write all of the pages to FLASH */
|
|
|
|
|
|
|
|
mx25rxx_lock(priv->qspi, false);
|
|
|
|
|
2019-07-26 17:17:56 +02:00
|
|
|
#if defined(CONFIG_MX25RXX_SECTOR512)
|
|
|
|
ret = mx25rxx_write_cache(priv, buf, startblock, nblocks);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
ferr("ERROR: mx25rxx_write_cache failed: %d\n", ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
2017-08-06 18:51:17 +02:00
|
|
|
ret = mx25rxx_write_page(priv, buf, startblock << priv->pageshift,
|
|
|
|
nblocks << priv->pageshift);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
ferr("ERROR: mx25rxx_write_page failed: %d\n", ret);
|
|
|
|
}
|
2019-07-26 17:17:56 +02:00
|
|
|
#endif
|
2017-08-06 18:51:17 +02:00
|
|
|
|
|
|
|
mx25rxx_unlock(priv->qspi);
|
|
|
|
|
|
|
|
return ret < 0 ? ret : nblocks;
|
|
|
|
}
|
|
|
|
|
|
|
|
ssize_t mx25rxx_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes,
|
|
|
|
FAR uint8_t *buffer)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
FAR struct mx25rxx_dev_s *priv = (FAR struct mx25rxx_dev_s *)dev;
|
|
|
|
|
|
|
|
finfo("offset: %08lx nbytes: %d\n", (long)offset, (int)nbytes);
|
|
|
|
|
|
|
|
/* Lock the QuadSPI bus and select this FLASH part */
|
|
|
|
|
|
|
|
mx25rxx_lock(priv->qspi, true);
|
|
|
|
ret = mx25rxx_read_byte(priv, buffer, offset, nbytes);
|
|
|
|
mx25rxx_unlock(priv->qspi);
|
|
|
|
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
ferr("ERROR: mx25rxx_read_byte returned: %d\n", ret);
|
|
|
|
return (ssize_t)ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
finfo("return nbytes: %d\n", (int)nbytes);
|
|
|
|
return (ssize_t)nbytes;
|
|
|
|
}
|
|
|
|
|
|
|
|
int mx25rxx_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg)
|
|
|
|
{
|
|
|
|
FAR struct mx25rxx_dev_s *priv = (FAR struct mx25rxx_dev_s *)dev;
|
|
|
|
int ret = -EINVAL; /* Assume good command with bad parameters */
|
|
|
|
|
2021-12-26 23:18:22 +01:00
|
|
|
finfo("cmd: %d\n", cmd);
|
2017-08-06 18:51:17 +02:00
|
|
|
|
|
|
|
switch (cmd)
|
|
|
|
{
|
|
|
|
case MTDIOC_GEOMETRY:
|
|
|
|
{
|
|
|
|
FAR struct mtd_geometry_s *geo =
|
|
|
|
(FAR struct mtd_geometry_s *)((uintptr_t)arg);
|
|
|
|
|
|
|
|
if (geo)
|
|
|
|
{
|
2021-08-11 06:21:39 +02:00
|
|
|
memset(geo, 0, sizeof(*geo));
|
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
/* Populate the geometry structure with information need to
|
|
|
|
* know the capacity and how to access the device.
|
2017-08-06 18:51:17 +02:00
|
|
|
*
|
2021-01-27 16:48:40 +01:00
|
|
|
* 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.
|
2017-08-06 18:51:17 +02:00
|
|
|
*/
|
|
|
|
|
2019-07-26 17:17:56 +02:00
|
|
|
#ifdef CONFIG_MX25RXX_SECTOR512
|
|
|
|
geo->blocksize = (1 << MX25RXX_SECTOR512_SHIFT);
|
|
|
|
geo->erasesize = (1 << MX25RXX_SECTOR512_SHIFT);
|
|
|
|
geo->neraseblocks = priv->nsectors <<
|
2021-01-27 16:48:40 +01:00
|
|
|
(priv->sectorshift -
|
|
|
|
MX25RXX_SECTOR512_SHIFT);
|
2019-07-26 17:17:56 +02:00
|
|
|
#else
|
2017-08-06 18:51:17 +02:00
|
|
|
geo->blocksize = (1 << priv->pageshift);
|
|
|
|
geo->erasesize = (1 << priv->sectorshift);
|
|
|
|
geo->neraseblocks = priv->nsectors;
|
2019-07-26 17:17:56 +02:00
|
|
|
#endif
|
2017-08-06 18:51:17 +02:00
|
|
|
ret = OK;
|
|
|
|
|
2020-12-01 03:29:50 +01:00
|
|
|
finfo("blocksize: %" PRId32
|
|
|
|
" erasesize: %" PRId32
|
|
|
|
" neraseblocks: %" PRId32 "\n",
|
2017-08-06 18:51:17 +02:00
|
|
|
geo->blocksize, geo->erasesize, geo->neraseblocks);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2021-08-15 20:10:23 +02:00
|
|
|
case BIOC_PARTINFO:
|
|
|
|
{
|
|
|
|
FAR struct partition_info_s *info =
|
|
|
|
(FAR struct partition_info_s *)arg;
|
|
|
|
if (info != NULL)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_MX25RXX_SECTOR512
|
|
|
|
info->numsectors = priv->nsectors <<
|
|
|
|
(priv->sectorshift - MX25RXX_SECTOR512_SHIFT);
|
|
|
|
info->sectorsize = 1 << MX25RXX_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;
|
|
|
|
|
2017-08-06 18:51:17 +02:00
|
|
|
case MTDIOC_BULKERASE:
|
|
|
|
{
|
|
|
|
/* Erase the entire device */
|
|
|
|
|
|
|
|
mx25rxx_lock(priv->qspi, false);
|
|
|
|
ret = mx25rxx_erase_chip(priv);
|
|
|
|
mx25rxx_unlock(priv->qspi);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2021-07-16 19:16:41 +02:00
|
|
|
case MTDIOC_ERASESTATE:
|
|
|
|
{
|
|
|
|
FAR uint8_t *result = (FAR uint8_t *)arg;
|
|
|
|
*result = MX25RXX_ERASED_STATE;
|
|
|
|
|
|
|
|
ret = OK;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2017-08-06 18:51:17 +02:00
|
|
|
default:
|
|
|
|
ret = -ENOTTY; /* Bad/unsupported command */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
finfo("return %d\n", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2024-08-25 01:21:12 +02:00
|
|
|
int mx25rxx_readid(FAR struct mx25rxx_dev_s *dev)
|
2017-08-06 18:51:17 +02:00
|
|
|
{
|
|
|
|
/* Lock the QuadSPI bus and configure the bus. */
|
|
|
|
|
|
|
|
mx25rxx_lock(dev->qspi, false);
|
|
|
|
|
|
|
|
/* Read the JEDEC ID */
|
|
|
|
|
|
|
|
mx25rxx_command_read(dev->qspi, MX25R_RDID, dev->cmdbuf, 3);
|
|
|
|
|
|
|
|
/* Unlock the bus */
|
|
|
|
|
|
|
|
mx25rxx_unlock(dev->qspi);
|
|
|
|
|
|
|
|
finfo("Manufacturer: %02x Device Type %02x, Capacity: %02x\n",
|
|
|
|
dev->cmdbuf[0], dev->cmdbuf[1], dev->cmdbuf[2]);
|
|
|
|
|
|
|
|
/* Check for Macronix MX25Rxx chip */
|
|
|
|
|
|
|
|
if (dev->cmdbuf[0] != MX25R_JEDEC_MANUFACTURER ||
|
|
|
|
dev->cmdbuf[1] != MX25R_JEDEC_MEMORY_TYPE)
|
|
|
|
{
|
|
|
|
ferr("ERROR: Unrecognized device type: 0x%02x 0x%02x\n",
|
|
|
|
dev->cmdbuf[0], dev->cmdbuf[1]);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check for a supported capacity */
|
|
|
|
|
|
|
|
switch (dev->cmdbuf[2])
|
|
|
|
{
|
|
|
|
case MX25R_JEDEC_MX25R6435F_CAPACITY:
|
|
|
|
dev->sectorshift = MX25R6435F_SECTOR_SHIFT;
|
|
|
|
dev->pageshift = MX25R6435F_PAGE_SHIFT;
|
|
|
|
dev->nsectors = MX25R6435F_SECTOR_COUNT;
|
|
|
|
break;
|
|
|
|
|
2023-10-19 13:38:23 +02:00
|
|
|
case MX25R_JEDEC_MX25L25673G_CAPACITY:
|
|
|
|
dev->sectorshift = MX25L25673G_SECTOR_SHIFT;
|
|
|
|
dev->pageshift = MX25L25673G_PAGE_SHIFT;
|
|
|
|
dev->nsectors = MX25L25673G_SECTOR_COUNT;
|
|
|
|
break;
|
|
|
|
|
2017-08-06 18:51:17 +02:00
|
|
|
default:
|
|
|
|
ferr("ERROR: Unsupported memory capacity: %02x\n", dev->cmdbuf[2]);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
/****************************************************************************
|
2019-07-26 17:17:56 +02:00
|
|
|
* Name: mx25rxx_flush_cache
|
2021-01-27 16:48:40 +01:00
|
|
|
****************************************************************************/
|
2019-07-26 17:17:56 +02:00
|
|
|
|
|
|
|
#ifdef CONFIG_MX25RXX_SECTOR512
|
2024-08-25 01:21:12 +02:00
|
|
|
static int mx25rxx_flush_cache(FAR struct mx25rxx_dev_s *priv)
|
2019-07-26 17:17:56 +02:00
|
|
|
{
|
|
|
|
int ret = OK;
|
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
/* If the cache 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.
|
2019-07-26 17:17:56 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
if (IS_DIRTY(priv) || IS_ERASED(priv))
|
|
|
|
{
|
|
|
|
off_t address;
|
|
|
|
|
|
|
|
/* Convert the erase sector number into a FLASH address */
|
|
|
|
|
|
|
|
address = (off_t)priv->esectno << priv->sectorshift;
|
|
|
|
|
|
|
|
/* Write entire erase block to FLASH */
|
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
ret = mx25rxx_write_page(priv,
|
|
|
|
priv->sector,
|
|
|
|
address,
|
|
|
|
1 << priv->sectorshift);
|
2019-07-26 17:17:56 +02:00
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
ferr("ERROR: mx25rxx_write_page failed: %d\n", ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The cache is no long dirty and the FLASH is no longer erased */
|
|
|
|
|
|
|
|
CLR_DIRTY(priv);
|
|
|
|
CLR_ERASED(priv);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
#endif /* CONFIG_MX25RXX_SECTOR512 */
|
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
/****************************************************************************
|
2019-07-26 17:17:56 +02:00
|
|
|
* Name: mx25rxx_read_cache
|
2021-01-27 16:48:40 +01:00
|
|
|
****************************************************************************/
|
2019-07-26 17:17:56 +02:00
|
|
|
|
|
|
|
#ifdef CONFIG_MX25RXX_SECTOR512
|
2024-08-25 01:21:12 +02:00
|
|
|
static FAR uint8_t *mx25rxx_read_cache(FAR struct mx25rxx_dev_s *priv,
|
2021-01-27 16:48:40 +01:00
|
|
|
off_t sector)
|
2019-07-26 17:17:56 +02:00
|
|
|
{
|
|
|
|
off_t esectno;
|
|
|
|
int shift;
|
|
|
|
int index;
|
|
|
|
int ret;
|
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
/* Convert from the 512 byte sector to the erase sector size of the device.
|
|
|
|
* For example, if the actual erase sector size is 4Kb (1 << 12), then we
|
|
|
|
* first shift to the right by 3 to get the sector number in 4096
|
|
|
|
* increments.
|
2019-07-26 17:17:56 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
shift = priv->sectorshift - MX25RXX_SECTOR512_SHIFT;
|
|
|
|
esectno = sector >> shift;
|
2020-12-01 03:29:50 +01:00
|
|
|
finfo("sector: %jd esectno: %jd (%d) shift=%d\n",
|
|
|
|
(intmax_t)sector, (intmax_t)esectno, priv->esectno, shift);
|
2019-07-26 17:17:56 +02:00
|
|
|
|
|
|
|
/* 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 = mx25rxx_flush_cache(priv);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
ferr("ERROR: mx25rxx_flush_cache failed: %d\n", ret);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Read the erase block into the cache */
|
|
|
|
|
|
|
|
ret = mx25rxx_read_byte(priv, priv->sector,
|
|
|
|
(esectno << priv->sectorshift),
|
|
|
|
(1 << priv->sectorshift));
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
ferr("ERROR: mx25rxx_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 */
|
|
|
|
}
|
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
/* Get the index to the 512 sector in the erase block that holds the
|
|
|
|
* argument
|
|
|
|
*/
|
2019-07-26 17:17:56 +02:00
|
|
|
|
|
|
|
index = sector & ((1 << shift) - 1);
|
|
|
|
|
|
|
|
/* Return the address in the cache that holds this sector */
|
|
|
|
|
|
|
|
return &priv->sector[index << MX25RXX_SECTOR512_SHIFT];
|
|
|
|
}
|
|
|
|
#endif /* CONFIG_MX25RXX_SECTOR512 */
|
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
/****************************************************************************
|
2019-07-26 17:17:56 +02:00
|
|
|
* Name: mx25rxx_erase_cache
|
2021-01-27 16:48:40 +01:00
|
|
|
****************************************************************************/
|
2019-07-26 17:17:56 +02:00
|
|
|
|
|
|
|
#ifdef CONFIG_MX25RXX_SECTOR512
|
2024-08-25 01:21:12 +02:00
|
|
|
static void mx25rxx_erase_cache(FAR struct mx25rxx_dev_s *priv, off_t sector)
|
2019-07-26 17:17:56 +02:00
|
|
|
{
|
|
|
|
FAR uint8_t *dest;
|
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
/* First, make sure that the erase block containing the 512 byte sector is
|
|
|
|
* in the cache.
|
2019-07-26 17:17:56 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
dest = mx25rxx_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))
|
|
|
|
{
|
2021-01-27 16:48:40 +01:00
|
|
|
off_t esectno = sector >>
|
|
|
|
(priv->sectorshift - MX25RXX_SECTOR512_SHIFT);
|
2020-12-01 03:29:50 +01:00
|
|
|
finfo("sector: %jd esectno: %jd\n",
|
|
|
|
(intmax_t)sector, (intmax_t)esectno);
|
2019-07-26 17:17:56 +02:00
|
|
|
|
|
|
|
DEBUGVERIFY(mx25rxx_erase_sector(priv, esectno));
|
|
|
|
SET_ERASED(priv);
|
|
|
|
}
|
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
/* Put the cached sector data into the erase state and mark the cache as
|
|
|
|
* dirty (but don't update the FLASH yet. The caller will do that at a
|
|
|
|
* more optimal time).
|
2019-07-26 17:17:56 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
memset(dest, MX25RXX_ERASED_STATE, MX25RXX_SECTOR512_SIZE);
|
|
|
|
SET_DIRTY(priv);
|
|
|
|
}
|
|
|
|
#endif /* CONFIG_MX25RXX_SECTOR512 */
|
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
/****************************************************************************
|
2019-07-26 17:17:56 +02:00
|
|
|
* Name: mx25rxx_write_cache
|
2021-01-27 16:48:40 +01:00
|
|
|
****************************************************************************/
|
2019-07-26 17:17:56 +02:00
|
|
|
|
|
|
|
#ifdef CONFIG_MX25RXX_SECTOR512
|
|
|
|
static int mx25rxx_write_cache(FAR struct mx25rxx_dev_s *priv,
|
2024-08-25 01:21:12 +02:00
|
|
|
FAR const uint8_t *buffer, off_t sector,
|
|
|
|
size_t nsectors)
|
2019-07-26 17:17:56 +02:00
|
|
|
{
|
|
|
|
FAR uint8_t *dest;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
for (; nsectors > 0; nsectors--)
|
|
|
|
{
|
2021-01-27 16:48:40 +01:00
|
|
|
/* First, make sure that the erase block containing 512 byte sector is
|
|
|
|
* in memory.
|
2019-07-26 17:17:56 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
dest = mx25rxx_read_cache(priv, sector);
|
|
|
|
|
|
|
|
/* Erase the block containing this sector if it is not already erased.
|
2021-01-27 16:48:40 +01:00
|
|
|
* 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.
|
2019-07-26 17:17:56 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
if (!IS_ERASED(priv))
|
|
|
|
{
|
2021-01-27 16:48:40 +01:00
|
|
|
off_t esectno = sector >>
|
|
|
|
(priv->sectorshift - MX25RXX_SECTOR512_SHIFT);
|
2020-12-01 03:29:50 +01:00
|
|
|
finfo("sector: %jd esectno: %jd\n",
|
|
|
|
(intmax_t)sector, (intmax_t)esectno);
|
2019-07-26 17:17:56 +02:00
|
|
|
|
|
|
|
ret = mx25rxx_erase_sector(priv, esectno);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
ferr("ERROR: mx25rxx_erase_sector failed: %d\n", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
SET_ERASED(priv);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Copy the new sector data into cached erase block */
|
|
|
|
|
|
|
|
memcpy(dest, buffer, MX25RXX_SECTOR512_SIZE);
|
|
|
|
SET_DIRTY(priv);
|
|
|
|
|
|
|
|
/* Set up for the next 512 byte sector */
|
|
|
|
|
2020-12-01 03:29:50 +01:00
|
|
|
finfo("address: %08jx nbytes: %d 0x%04" PRIx32 "\n",
|
|
|
|
(intmax_t)(sector << MX25RXX_SECTOR512_SHIFT),
|
|
|
|
MX25RXX_SECTOR512_SIZE,
|
2019-07-26 17:17:56 +02:00
|
|
|
*(FAR uint32_t *)buffer);
|
|
|
|
buffer += MX25RXX_SECTOR512_SIZE;
|
|
|
|
sector++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Flush the last erase block left in the cache */
|
|
|
|
|
|
|
|
return mx25rxx_flush_cache(priv);
|
|
|
|
}
|
|
|
|
#endif /* CONFIG_MX25RXX_SECTOR512 */
|
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
/****************************************************************************
|
2017-08-06 18:51:17 +02:00
|
|
|
* Public Functions
|
2021-01-27 16:48:40 +01:00
|
|
|
****************************************************************************/
|
2017-08-06 18:51:17 +02:00
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
/****************************************************************************
|
2017-08-06 18:51:17 +02:00
|
|
|
* Name: mx25rxx_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).
|
|
|
|
*
|
2021-01-27 16:48:40 +01:00
|
|
|
****************************************************************************/
|
2017-08-06 18:51:17 +02:00
|
|
|
|
2021-01-27 16:48:40 +01:00
|
|
|
FAR struct mtd_dev_s *mx25rxx_initialize(FAR struct qspi_dev_s *qspi,
|
|
|
|
bool unprotect)
|
2017-08-06 18:51:17 +02:00
|
|
|
{
|
|
|
|
FAR struct mx25rxx_dev_s *dev;
|
|
|
|
int ret;
|
|
|
|
uint8_t status;
|
|
|
|
uint16_t config;
|
|
|
|
|
|
|
|
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
|
2021-01-27 16:48:40 +01:00
|
|
|
* 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.
|
2017-08-06 18:51:17 +02:00
|
|
|
*/
|
|
|
|
|
2023-08-28 09:39:47 +02:00
|
|
|
dev = kmm_zalloc(sizeof(*dev));
|
2017-08-06 18:51:17 +02:00
|
|
|
|
|
|
|
if (dev == NULL)
|
|
|
|
{
|
|
|
|
ferr("Failed to allocate mtd device\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev->mtd.erase = mx25rxx_erase;
|
|
|
|
dev->mtd.bread = mx25rxx_bread;
|
|
|
|
dev->mtd.bwrite = mx25rxx_bwrite;
|
|
|
|
dev->mtd.read = mx25rxx_read;
|
|
|
|
dev->mtd.ioctl = mx25rxx_ioctl;
|
2018-11-08 16:46:11 +01:00
|
|
|
dev->mtd.name = "mx25rxx";
|
2017-08-06 18:51:17 +02:00
|
|
|
dev->qspi = qspi;
|
|
|
|
|
|
|
|
/* Allocate a 4-byte buffer to support DMA-able command data */
|
|
|
|
|
|
|
|
dev->cmdbuf = (FAR uint8_t *)QSPI_ALLOC(qspi, 4);
|
|
|
|
if (dev->cmdbuf == NULL)
|
|
|
|
{
|
|
|
|
ferr("Failed to allocate command buffer\n");
|
|
|
|
goto exit_free_dev;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Identify the FLASH chip and get its capacity */
|
|
|
|
|
|
|
|
ret = mx25rxx_readid(dev);
|
|
|
|
if (ret != OK)
|
|
|
|
{
|
|
|
|
/* Unrecognized! Discard all of that work we just did and return NULL */
|
|
|
|
|
|
|
|
ferr("Unrecognized QSPI device\n");
|
|
|
|
goto exit_free_cmdbuf;
|
|
|
|
}
|
|
|
|
|
2019-07-26 17:17:56 +02:00
|
|
|
#ifdef CONFIG_MX25RXX_SECTOR512 /* Simulate a 512 byte sector */
|
|
|
|
/* Allocate a buffer for the erase block cache */
|
|
|
|
|
|
|
|
dev->sector = (FAR uint8_t *)QSPI_ALLOC(qspi, 1 << dev->sectorshift);
|
|
|
|
if (dev->sector == NULL)
|
|
|
|
{
|
2021-01-27 16:48:40 +01:00
|
|
|
/* Allocation failed! Discard all of that work we just did and
|
|
|
|
* return NULL
|
|
|
|
*/
|
2019-07-26 17:17:56 +02:00
|
|
|
|
|
|
|
ferr("ERROR: Sector allocation failed\n");
|
|
|
|
goto exit_free_cmdbuf;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2017-08-06 18:51:17 +02:00
|
|
|
mx25rxx_lock(dev->qspi, false);
|
|
|
|
|
|
|
|
/* Set MTD device in low power mode, with minimum dummy cycles */
|
|
|
|
|
|
|
|
mx25rxx_write_status_config(dev, MX25R_SR_QE, 0x0000);
|
|
|
|
|
|
|
|
mx25rxx_read_status(dev);
|
|
|
|
status = dev->cmdbuf[0];
|
|
|
|
mx25rxx_read_configuration(dev);
|
2019-07-26 17:17:56 +02:00
|
|
|
config = *(FAR uint16_t *)(dev->cmdbuf);
|
|
|
|
|
|
|
|
/* Avoid compiler warnings in case info logs are disabled */
|
2017-08-06 18:51:17 +02:00
|
|
|
|
2019-07-26 17:17:56 +02:00
|
|
|
UNUSED(status);
|
|
|
|
UNUSED(config);
|
2017-08-06 18:51:17 +02:00
|
|
|
|
|
|
|
finfo("device ready 0x%02x 0x%04x\n", status, config);
|
|
|
|
|
|
|
|
mx25rxx_unlock(dev->qspi);
|
|
|
|
|
|
|
|
/* Return the implementation-specific state structure as the MTD device */
|
|
|
|
|
|
|
|
return &dev->mtd;
|
|
|
|
|
|
|
|
exit_free_cmdbuf:
|
|
|
|
QSPI_FREE(qspi, dev->cmdbuf);
|
|
|
|
exit_free_dev:
|
|
|
|
kmm_free(dev);
|
|
|
|
return NULL;
|
|
|
|
}
|