mirror of
https://github.com/apache/nuttx.git
synced 2026-05-19 03:03:37 +08:00
0a381a3ff7
1. In cfi_write_unalign(): Add additional check to ensure nbytes is at least bankwidth size before skipping unaligned start handling. This prevents incorrect behavior when writing small amounts of data that are less than bankwidth. 2. In cfi_write(): Align down the write size to bankwidth boundary when the remaining nbytes is less than the buffer write size. This ensures the buffer write operation always works with properly aligned data size, preventing write failures.)) Signed-off-by: dongjiuzhu1 <dongjiuzhu1@xiaomi.com>
1149 lines
33 KiB
C
1149 lines
33 KiB
C
/****************************************************************************
|
|
* drivers/mtd/cfi.c
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
#include <stdint.h>
|
|
#include <errno.h>
|
|
#include <debug.h>
|
|
|
|
#include <nuttx/fs/ioctl.h>
|
|
#include <nuttx/mtd/mtd.h>
|
|
#include <nuttx/fs/fs.h>
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/nuttx.h>
|
|
|
|
#include "cfi.h"
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
/* type of port */
|
|
|
|
#define CFI_PORT_8BIT 0x01
|
|
#define CFI_PORT_16BIT 0x02
|
|
#define CFI_PORT_32BIT 0X04
|
|
#define CFI_PORT_64BIT 0x08
|
|
|
|
/* Common CFI value */
|
|
|
|
#define CFI_CMD 0x98
|
|
#define CFI_QRY_OFFSET 0x10
|
|
#define CFI_ERASE_BLOCK_REGION_INFO_OFFSET 0x2D
|
|
|
|
/* CFI command set number */
|
|
|
|
#define CFI_CMDSET_INTEL_EXTENDED 0x0001
|
|
#define CFI_CMDSET_AMD_STANDARD 0x0002
|
|
#define CFI_CMDSET_INTEL_STANDARD 0x0003
|
|
#define CFI_CMDSET_AMD_EXTENDED 0x0004
|
|
#define CFI_CMDSET_MITS_STANDARD 0x0100
|
|
#define CFI_CMDSET_MITS_EXTENDED 0x0101
|
|
|
|
/* Intel command value */
|
|
|
|
#define CFI_INTEL_CMD_RESET 0xFF
|
|
#define CFI_INTEL_CMD_CLEAR_STATUS 0x50
|
|
#define CFI_INTEL_CMD_BLOCK_ERASE 0x20
|
|
#define CFI_INTEL_CMD_ERASE_CONFIRM 0xD0
|
|
#define CFI_INTEL_CMD_WRITE 0x40
|
|
#define CFI_INTEL_CMD_WRITE_BUFFER 0xE8
|
|
#define CFI_INTEL_CMD_WRITE_CONFIRM 0xD0
|
|
|
|
/* Intel status value */
|
|
|
|
#define CFI_INTEL_STATUS_DONE 0x80
|
|
#define CFI_INTEL_STATUS_ESS 0x40 /* erase suspended */
|
|
#define CFI_INTEL_STATUS_ECLBS 0x20 /* clear block lock bit
|
|
* or erase error */
|
|
#define CFI_INTEL_STATUS_PSLBS 0X10 /* write or block lock bit
|
|
* set error */
|
|
#define CFI_INTEL_STATUS_VPENS 0x08 /* voltage range error */
|
|
#define CFI_INTEL_STATUS_PSS 0x04 /* write is suspended */
|
|
#define CFI_INTEL_STATUS_DPS 0x02 /* device protected error */
|
|
|
|
/* Amd command value */
|
|
|
|
#define CFI_AMD_CMD_RESET 0xF0
|
|
#define CFI_AMD_CMD_UNLOCK1 0xAA
|
|
#define CFI_AMD_CMD_UNLOCK2 0x55
|
|
#define CFI_AMD_CMD_ERASE_START 0x80
|
|
#define CFI_AMD_CMD_ERASE_SECTOR 0x30
|
|
#define CFI_AMD_CMD_WRITE 0xA0
|
|
#define CFI_AMD_CMD_WRITE_BUFFER 0x25
|
|
#define CFI_AMD_CMD_WRITE_CONFIRM 0x29
|
|
|
|
/* Amd status value */
|
|
|
|
#define CFI_AMD_STATUS_TOGGLE 0x40 /* DQ6 will toggle after a
|
|
* reading cycle during
|
|
* erasing */
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
/* Supports up to 64bits bank width */
|
|
|
|
typedef uint64_t cfiword_t;
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
/* the offset of CFI query address is usually 0x55
|
|
* Spansion S29WS-N CFI query fix is to try 0x555 if 0x55 fails
|
|
*/
|
|
|
|
static const uint32_t g_cfi_query_address[2] =
|
|
{
|
|
0x55, 0x555
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_get_block_addr
|
|
*
|
|
* Description:
|
|
* Get the start address of the block.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uintptr_t cfi_get_block_addr(FAR struct cfi_dev_s *cfi,
|
|
blkcnt_t block)
|
|
{
|
|
uint8_t i;
|
|
uintptr_t base = cfi->base_addr;
|
|
|
|
for (i = 0; i < cfi->info.erase_region_num; i++)
|
|
{
|
|
blkcnt_t block_num = cfi_get_blocknum(cfi, i);
|
|
size_t block_size = cfi_get_blocksize(cfi, i);
|
|
|
|
if (block >= block_num)
|
|
{
|
|
block -= block_num;
|
|
base += block_num * block_size;
|
|
continue;
|
|
}
|
|
|
|
base += block * block_size;
|
|
break;
|
|
}
|
|
|
|
return base;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_addr_map
|
|
*
|
|
* Description:
|
|
* Get the address according to block number and offset, and needs to
|
|
* convert it according to bankwidth.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uintptr_t cfi_addr_map(FAR struct cfi_dev_s *cfi, blkcnt_t block,
|
|
off_t offset)
|
|
{
|
|
return cfi_get_block_addr(cfi, block) + cfi->bankwidth * offset;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_make_cmd
|
|
*
|
|
* Description:
|
|
* Make command value according to bankwidth and device width. For example,
|
|
* a command 0x98 should be 0x00980098 for a flash with bankwidth = 4 and
|
|
* dev_width = 2.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static cfiword_t cfi_make_cmd(FAR struct cfi_dev_s *cfi, uint32_t cmd)
|
|
{
|
|
cfiword_t result = 0;
|
|
uint8_t i = cfi->bankwidth / cfi->dev_width;
|
|
|
|
while (i--)
|
|
{
|
|
result = (result << (8 * cfi->dev_width)) | cmd;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_add_byte
|
|
*
|
|
* Description:
|
|
* Add a byte to cfiword, which will be written into cfi-flash later.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void cfi_add_byte(FAR struct cfi_dev_s *cfi, FAR cfiword_t *word,
|
|
uint8_t c)
|
|
{
|
|
switch (cfi->bankwidth)
|
|
{
|
|
case CFI_PORT_8BIT:
|
|
*word = c;
|
|
break;
|
|
case CFI_PORT_16BIT:
|
|
*word = (*word >> 8) | (uint16_t)c << 8;
|
|
break;
|
|
case CFI_PORT_32BIT:
|
|
*word = (*word >> 8) | (uint32_t)c << 24;
|
|
break;
|
|
default:
|
|
*word = (*word >> 8) | (uint64_t)c << 56;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_write_data
|
|
*
|
|
* Description:
|
|
* Write a value to specified address.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void cfi_write_data(FAR struct cfi_dev_s *cfi, uintptr_t addr,
|
|
FAR const void *buffer)
|
|
{
|
|
switch (cfi->bankwidth)
|
|
{
|
|
case CFI_PORT_8BIT:
|
|
*(FAR volatile uint8_t *)addr = *(FAR const uint8_t *)buffer;
|
|
break;
|
|
case CFI_PORT_16BIT:
|
|
*(FAR volatile uint16_t *)addr = *(FAR const uint16_t *)buffer;
|
|
break;
|
|
case CFI_PORT_32BIT:
|
|
*(FAR volatile uint32_t *)addr = *(FAR const uint32_t *)buffer;
|
|
break;
|
|
default:
|
|
*(FAR volatile uint64_t *)addr = *(FAR const uint64_t *)buffer;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_write_block_cmd
|
|
*
|
|
* Description:
|
|
* Write a command about specified block.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void cfi_write_block_cmd(FAR struct cfi_dev_s *cfi, blkcnt_t block,
|
|
uint32_t cmd)
|
|
{
|
|
cfiword_t val = cfi_make_cmd(cfi, cmd);
|
|
|
|
cfi_write_data(cfi, cfi_addr_map(cfi, block, 0), &val);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_write_address_cmd
|
|
*
|
|
* Description:
|
|
* Write a command to specified address.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void cfi_write_address_cmd(FAR struct cfi_dev_s *cfi, uintptr_t addr,
|
|
uint8_t cmd)
|
|
{
|
|
cfiword_t val = cfi_make_cmd(cfi, cmd);
|
|
|
|
cfi_write_data(cfi, cfi_addr_map(cfi, 0, addr), &val);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_read_byte
|
|
*
|
|
* Description:
|
|
* Read an 8-bit value from addr.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline uint8_t cfi_read_byte(uintptr_t addr)
|
|
{
|
|
return *(FAR volatile uint8_t *)addr;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_read_data
|
|
*
|
|
* Description:
|
|
* Read a value to specified address.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline cfiword_t cfi_read_data(FAR struct cfi_dev_s *cfi,
|
|
uintptr_t addr)
|
|
{
|
|
switch (cfi->bankwidth)
|
|
{
|
|
case CFI_PORT_8BIT:
|
|
return cfi_read_byte(addr);
|
|
case CFI_PORT_16BIT:
|
|
return *(FAR const volatile uint16_t *)addr;
|
|
case CFI_PORT_32BIT:
|
|
return *(FAR const volatile uint32_t *)addr;
|
|
default:
|
|
return *(FAR const volatile uint64_t *)addr;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_is_equal
|
|
*
|
|
* Description:
|
|
* Check whether the value corresponding to addr is equal to cmd.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool cfi_is_equal(FAR struct cfi_dev_s *cfi, uintptr_t addr,
|
|
uint8_t cmd)
|
|
{
|
|
cfiword_t val = cfi_make_cmd(cfi, cmd);
|
|
|
|
addr = cfi_addr_map(cfi, 0, addr);
|
|
return cfi_read_data(cfi, addr) == val;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_is_set
|
|
*
|
|
* Description:
|
|
* Check the status bit, which is set by cmd.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool cfi_is_set(FAR struct cfi_dev_s *cfi, uint8_t cmd)
|
|
{
|
|
cfiword_t val = cfi_make_cmd(cfi, cmd);
|
|
uintptr_t addr = cfi->base_addr;
|
|
|
|
return (cfi_read_data(cfi, addr) & val) == val;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_get_time_us
|
|
*
|
|
* Description:
|
|
* Check whether the value corresponding to offset is equal to cmd.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline uint32_t cfi_get_time_us(void)
|
|
{
|
|
struct timespec ts;
|
|
|
|
clock_systime_timespec(&ts);
|
|
return 1000000 * ts.tv_sec + ts.tv_nsec / 1000;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_is_timeout
|
|
*
|
|
* Description:
|
|
* Check whether the operation has timed out.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool cfi_is_timeout(uint32_t start_us, uint32_t tout_us)
|
|
{
|
|
return (int32_t)(cfi_get_time_us() - start_us) >= tout_us;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_intel_is_busy
|
|
*
|
|
* Description:
|
|
* Function of intel cfi command set.
|
|
* Check whether the cfi-flash is busy.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool cfi_intel_is_busy(FAR struct cfi_dev_s *cfi, blkcnt_t block)
|
|
{
|
|
return !cfi_is_set(cfi, CFI_INTEL_STATUS_DONE);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_intel_status_check
|
|
*
|
|
* Description:
|
|
* Function of intel cfi command set.
|
|
* Check the status register of the cfi-flash.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int cfi_intel_status_check(FAR struct cfi_dev_s *cfi,
|
|
blkcnt_t block, uint32_t tout)
|
|
{
|
|
uint32_t start;
|
|
|
|
/* wait for command completion */
|
|
|
|
start = cfi_get_time_us();
|
|
while (cfi_intel_is_busy(cfi, block))
|
|
{
|
|
if (!cfi_is_timeout(start, tout))
|
|
{
|
|
up_udelay(1);
|
|
continue;
|
|
}
|
|
|
|
ferr("CFI flash operation has timed out.\n");
|
|
|
|
/* Check each status bit */
|
|
|
|
if (!cfi_is_equal(cfi, 0, CFI_INTEL_STATUS_DONE))
|
|
{
|
|
ferr("CFI flash operation failed.\n");
|
|
if (cfi_is_set(cfi, CFI_INTEL_STATUS_ECLBS |
|
|
CFI_INTEL_STATUS_PSLBS))
|
|
{
|
|
ferr("CFI: command sequence error\n");
|
|
}
|
|
else if (cfi_is_set(cfi, CFI_INTEL_STATUS_ECLBS))
|
|
{
|
|
ferr("CFI: block erase error!\n");
|
|
}
|
|
else if (cfi_is_set(cfi, CFI_INTEL_STATUS_PSLBS))
|
|
{
|
|
ferr("CFI: locking error!\n");
|
|
}
|
|
else if (cfi_is_set(cfi, CFI_INTEL_STATUS_DPS))
|
|
{
|
|
ferr("CFI: block locked!\n");
|
|
}
|
|
else if (cfi_is_set(cfi, CFI_INTEL_STATUS_VPENS))
|
|
{
|
|
ferr("CFI: Vpp Low error!\n");
|
|
}
|
|
}
|
|
|
|
cfi_reset(cfi);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_intel_write_word
|
|
*
|
|
* Description:
|
|
* Function of intel cfi command set.
|
|
* Write data with the width of cfiword_t into cfi-flash.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int cfi_intel_write_word(FAR struct cfi_dev_s *cfi,
|
|
uintptr_t addr, cfiword_t word)
|
|
{
|
|
off_t offset = addr - cfi->base_addr;
|
|
|
|
cfi_write_address_cmd(cfi, 0, CFI_INTEL_CMD_CLEAR_STATUS);
|
|
cfi_write_address_cmd(cfi, offset / cfi->bankwidth, CFI_INTEL_CMD_WRITE);
|
|
cfi_write_data(cfi, addr, &word);
|
|
|
|
return cfi_intel_status_check(cfi, cfi_find_block(cfi, offset),
|
|
(1 << cfi->info.single_write_timeout_typ) *
|
|
(1 << cfi->info.single_write_timeout_max));
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_intel_write_buffer
|
|
*
|
|
* Description:
|
|
* Function of intel cfi command set.
|
|
* Write data from buffer into cfi-flash.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int cfi_intel_write_buffer(FAR struct cfi_dev_s *cfi,
|
|
uintptr_t addr, size_t nbytes,
|
|
FAR const uint8_t *buffer)
|
|
{
|
|
int ret;
|
|
blkcnt_t block;
|
|
off_t offset = addr - cfi->base_addr;
|
|
|
|
block = cfi_find_block(cfi, offset);
|
|
cfi_write_address_cmd(cfi, 0, CFI_INTEL_CMD_CLEAR_STATUS);
|
|
cfi_write_block_cmd(cfi, block, CFI_INTEL_CMD_WRITE_BUFFER);
|
|
ret = cfi_intel_status_check(cfi, block,
|
|
(1 << cfi->info.buffer_write_timeout_typ) *
|
|
(1 << cfi->info.buffer_write_timeout_max));
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
addr = cfi->base_addr + offset;
|
|
nbytes = nbytes / cfi->bankwidth;
|
|
|
|
cfi_write_block_cmd(cfi, block, nbytes - 1);
|
|
while (nbytes-- > 0)
|
|
{
|
|
cfi_write_data(cfi, addr, buffer);
|
|
addr += cfi->bankwidth;
|
|
buffer += cfi->bankwidth;
|
|
}
|
|
|
|
cfi_write_address_cmd(cfi, 0, CFI_INTEL_CMD_WRITE_CONFIRM);
|
|
|
|
return cfi_intel_status_check(cfi, block,
|
|
(1 << cfi->info.buffer_write_timeout_typ) *
|
|
(1 << cfi->info.buffer_write_timeout_max));
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_intel_erase
|
|
*
|
|
* Description:
|
|
* Function of intel cfi command set.
|
|
* Erase one block.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int cfi_intel_erase(FAR struct cfi_dev_s *cfi, blkcnt_t block)
|
|
{
|
|
cfi_write_address_cmd(cfi, 0, CFI_INTEL_CMD_CLEAR_STATUS);
|
|
cfi_write_block_cmd(cfi, block, CFI_INTEL_CMD_BLOCK_ERASE);
|
|
cfi_write_address_cmd(cfi, 0, CFI_INTEL_CMD_ERASE_CONFIRM);
|
|
return cfi_intel_status_check(cfi, block, 1000 *
|
|
(1 << cfi->info.block_erase_timeout_typ) *
|
|
(1 << cfi->info.block_erase_timeout_max));
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_amd_fix
|
|
*
|
|
* Description:
|
|
* Set the variables specific to AMD.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void cfi_amd_fix(FAR struct cfi_dev_s *cfi)
|
|
{
|
|
/* x8/x16 in x8 mode or x16/x32 in x16 mode */
|
|
|
|
if ((cfi->dev_width == 1 && cfi->info.interface_desc == 0x02) ||
|
|
(cfi->dev_width == 2 && cfi->info.interface_desc == 0x04))
|
|
{
|
|
cfi->unlock_addr1 = 0xaaa;
|
|
cfi->unlock_addr2 = 0x555;
|
|
}
|
|
else
|
|
{
|
|
cfi->unlock_addr1 = 0x555;
|
|
cfi->unlock_addr2 = 0x2aa;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_amd_unlock
|
|
*
|
|
* Description:
|
|
* Flash unlock command sequence specific to AMD.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void cfi_amd_unlock(FAR struct cfi_dev_s *cfi)
|
|
{
|
|
cfi_write_address_cmd(cfi, cfi->unlock_addr1, CFI_AMD_CMD_UNLOCK1);
|
|
cfi_write_address_cmd(cfi, cfi->unlock_addr2, CFI_AMD_CMD_UNLOCK2);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_amd_is_busy
|
|
*
|
|
* Description:
|
|
* Function of amd cfi command set.
|
|
* Check whether the cfi-flash is busy.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool cfi_amd_is_busy(FAR struct cfi_dev_s *cfi, blkcnt_t block)
|
|
{
|
|
uintptr_t addr;
|
|
|
|
addr = cfi_addr_map(cfi, block, 0);
|
|
|
|
/* status bit DQ6 will toggle after a reading cycle during erasing */
|
|
|
|
return ((cfi_read_byte(addr) ^ cfi_read_byte(addr)) &
|
|
CFI_AMD_STATUS_TOGGLE) != 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_amd_status_check
|
|
*
|
|
* Description:
|
|
* Function of amd cfi command set.
|
|
* Check the status register of the cfi-flash.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int cfi_amd_status_check(FAR struct cfi_dev_s *cfi,
|
|
blkcnt_t block, uint32_t tout)
|
|
{
|
|
uint32_t start;
|
|
|
|
/* wait for command completion */
|
|
|
|
start = cfi_get_time_us();
|
|
while (cfi_amd_is_busy(cfi, block))
|
|
{
|
|
if (cfi_is_timeout(start, tout))
|
|
{
|
|
ferr("CFI flash operation has timed out.\n");
|
|
cfi_reset(cfi);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
up_udelay(1);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_amd_write_word
|
|
*
|
|
* Description:
|
|
* Function of amd cfi command set.
|
|
* Write data with the width of cfiword_t into cfi-flash.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int cfi_amd_write_word(FAR struct cfi_dev_s *cfi,
|
|
uintptr_t addr, cfiword_t word)
|
|
{
|
|
off_t offset = addr - cfi->base_addr;
|
|
|
|
cfi_amd_unlock(cfi);
|
|
cfi_write_address_cmd(cfi, cfi->unlock_addr1, CFI_AMD_CMD_WRITE);
|
|
cfi_write_data(cfi, addr, &word);
|
|
|
|
return cfi_amd_status_check(cfi, cfi_find_block(cfi, offset),
|
|
(1 << cfi->info.single_write_timeout_typ) *
|
|
(1 << cfi->info.single_write_timeout_max));
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_amd_write_buffer
|
|
*
|
|
* Description:
|
|
* Function of amd cfi command set.
|
|
* Write data from buffer into cfi-flash.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int cfi_amd_write_buffer(FAR struct cfi_dev_s *cfi, uintptr_t addr,
|
|
size_t nbytes, FAR const uint8_t *buffer)
|
|
{
|
|
blkcnt_t block;
|
|
off_t offset = addr - cfi->base_addr;
|
|
|
|
block = cfi_find_block(cfi, offset);
|
|
cfi_amd_unlock(cfi);
|
|
cfi_write_block_cmd(cfi, block, CFI_AMD_CMD_WRITE_BUFFER);
|
|
|
|
nbytes = nbytes / cfi->bankwidth;
|
|
cfi_write_block_cmd(cfi, block, nbytes - 1);
|
|
|
|
while (nbytes-- > 0)
|
|
{
|
|
cfi_write_data(cfi, addr, buffer);
|
|
addr += cfi->bankwidth;
|
|
buffer += cfi->bankwidth;
|
|
}
|
|
|
|
cfi_write_address_cmd(cfi, 0, CFI_AMD_CMD_WRITE_CONFIRM);
|
|
return cfi_amd_status_check(cfi, block,
|
|
(1 << cfi->info.buffer_write_timeout_max) *
|
|
(1 << cfi->info.buffer_write_timeout_max));
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_amd_erase
|
|
*
|
|
* Description:
|
|
* Function of amd cfi command set.
|
|
* Erase one block.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int cfi_amd_erase(FAR struct cfi_dev_s *cfi, blkcnt_t block)
|
|
{
|
|
cfi_amd_unlock(cfi);
|
|
cfi_write_address_cmd(cfi, cfi->unlock_addr1, CFI_AMD_CMD_ERASE_START);
|
|
cfi_amd_unlock(cfi);
|
|
cfi_write_block_cmd(cfi, block, CFI_AMD_CMD_ERASE_SECTOR);
|
|
return cfi_amd_status_check(cfi, block, 1000 *
|
|
(1 << cfi->info.block_erase_timeout_typ) *
|
|
(1 << cfi->info.block_erase_timeout_max));
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_write_word
|
|
*
|
|
* Description:
|
|
* Write data with the width of cfiword_t into cfi-flash.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int cfi_write_word(FAR struct cfi_dev_s *cfi, uintptr_t addr,
|
|
cfiword_t word)
|
|
{
|
|
switch (cfi->info.p_id)
|
|
{
|
|
case CFI_CMDSET_INTEL_EXTENDED:
|
|
case CFI_CMDSET_INTEL_STANDARD:
|
|
return cfi_intel_write_word(cfi, addr, word);
|
|
case CFI_CMDSET_AMD_STANDARD:
|
|
case CFI_CMDSET_AMD_EXTENDED:
|
|
return cfi_amd_write_word(cfi, addr, word);
|
|
case CFI_CMDSET_MITS_STANDARD:
|
|
case CFI_CMDSET_MITS_EXTENDED:
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_write_buffer
|
|
*
|
|
* Description:
|
|
* Write data from buffer into cfi-flash.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int cfi_write_buffer(FAR struct cfi_dev_s *cfi, uintptr_t addr,
|
|
size_t nbytes, FAR const uint8_t *buffer)
|
|
{
|
|
switch (cfi->info.p_id)
|
|
{
|
|
case CFI_CMDSET_INTEL_EXTENDED:
|
|
case CFI_CMDSET_INTEL_STANDARD:
|
|
return cfi_intel_write_buffer(cfi, addr, nbytes, buffer);
|
|
case CFI_CMDSET_AMD_STANDARD:
|
|
case CFI_CMDSET_AMD_EXTENDED:
|
|
return cfi_amd_write_buffer(cfi, addr, nbytes, buffer);
|
|
case CFI_CMDSET_MITS_STANDARD:
|
|
case CFI_CMDSET_MITS_EXTENDED:
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_erase_one
|
|
*
|
|
* Description:
|
|
* Erase one block.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int cfi_erase_one(FAR struct cfi_dev_s *cfi, blkcnt_t block)
|
|
{
|
|
switch (cfi->info.p_id)
|
|
{
|
|
case CFI_CMDSET_INTEL_EXTENDED:
|
|
case CFI_CMDSET_INTEL_STANDARD:
|
|
return cfi_intel_erase(cfi, block);
|
|
case CFI_CMDSET_AMD_STANDARD:
|
|
case CFI_CMDSET_AMD_EXTENDED:
|
|
return cfi_amd_erase(cfi, block);
|
|
case CFI_CMDSET_MITS_STANDARD:
|
|
case CFI_CMDSET_MITS_EXTENDED:
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static ssize_t cfi_write_unalign(FAR struct cfi_dev_s *cfi, off_t offset,
|
|
size_t nbytes, FAR const uint8_t *buffer)
|
|
{
|
|
uint8_t i;
|
|
int ret;
|
|
size_t delta;
|
|
uintptr_t wp;
|
|
cfiword_t word = 0;
|
|
|
|
/* get lower aligned address */
|
|
|
|
wp = offset & ~(cfi->bankwidth - 1);
|
|
|
|
/* handle unaligned start */
|
|
|
|
if ((delta = offset - wp) == 0 && nbytes >= cfi->bankwidth)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < delta; i++)
|
|
{
|
|
cfi_add_byte(cfi, &word, cfi_read_byte(wp + i));
|
|
}
|
|
|
|
delta = 0;
|
|
for (; i < cfi->bankwidth && nbytes > 0; i++)
|
|
{
|
|
cfi_add_byte(cfi, &word, *buffer++);
|
|
nbytes--;
|
|
delta++;
|
|
}
|
|
|
|
for (; i < cfi->bankwidth; i++)
|
|
{
|
|
cfi_add_byte(cfi, &word, cfi_read_byte(wp + i));
|
|
}
|
|
|
|
ret = cfi_write_word(cfi, wp, word);
|
|
return ret < 0 ? ret : delta;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_get_blocksize
|
|
*
|
|
* Description:
|
|
* Get the size of block in the specified region.
|
|
*
|
|
****************************************************************************/
|
|
|
|
size_t cfi_get_blocksize(FAR struct cfi_dev_s *cfi, uint8_t region)
|
|
{
|
|
return cfi->dev_num * (cfi->info.erase_region_info[region] >> 16) * 256;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_get_blocknum
|
|
*
|
|
* Description:
|
|
* Get the number of blocks in the specified region.
|
|
*
|
|
****************************************************************************/
|
|
|
|
blkcnt_t cfi_get_blocknum(FAR struct cfi_dev_s *cfi, uint8_t region)
|
|
{
|
|
return ((uint16_t)(cfi->info.erase_region_info[region])) + 1;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_find_block
|
|
*
|
|
* Description:
|
|
* Get the block number corresponding to offset.
|
|
*
|
|
****************************************************************************/
|
|
|
|
blkcnt_t cfi_find_block(FAR struct cfi_dev_s *cfi, off_t offset)
|
|
{
|
|
uint8_t i;
|
|
blkcnt_t ret = 0;
|
|
|
|
for (i = 0; i < cfi->info.erase_region_num; i++)
|
|
{
|
|
blkcnt_t block_num = cfi_get_blocknum(cfi, i);
|
|
size_t block_size = cfi_get_blocksize(cfi, i);
|
|
|
|
if (block_num * block_size <= offset)
|
|
{
|
|
offset -= block_num * block_size;
|
|
ret += block_num;
|
|
}
|
|
else
|
|
{
|
|
ret += offset / block_size;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_get_total_blocknum
|
|
*
|
|
* Description:
|
|
* Get total number of erase blocks, assuming that all erase blocks are the
|
|
* same size.
|
|
*
|
|
****************************************************************************/
|
|
|
|
blkcnt_t cfi_get_total_blocknum(FAR struct cfi_dev_s *cfi)
|
|
{
|
|
uint8_t i;
|
|
blkcnt_t blocknum = 0;
|
|
|
|
for (i = 0; i < cfi->info.erase_region_num; i++)
|
|
{
|
|
blocknum += cfi_get_blocknum(cfi, i) * cfi_get_blocksize(cfi, i) /
|
|
cfi_get_blocksize(cfi, 0);
|
|
}
|
|
|
|
return blocknum;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_check
|
|
*
|
|
* Description:
|
|
* Check whether the device is a cfi device, if so, then get the cfi data.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int cfi_check(FAR struct cfi_dev_s *cfi)
|
|
{
|
|
uint8_t i;
|
|
uint8_t j;
|
|
|
|
/* try different single device width which we don't know now */
|
|
|
|
for (cfi->dev_width = 1; cfi->dev_width <= cfi->bankwidth;
|
|
cfi->dev_width <<= 1)
|
|
{
|
|
cfi_reset(cfi);
|
|
for (i = 0; i < nitems(g_cfi_query_address); i++)
|
|
{
|
|
cfi_write_address_cmd(cfi, g_cfi_query_address[i], CFI_CMD);
|
|
|
|
/* try to get into cfi mode and read cfi data */
|
|
|
|
if (cfi_is_equal(cfi, CFI_QRY_OFFSET, 'Q') &&
|
|
cfi_is_equal(cfi, CFI_QRY_OFFSET + 1, 'R') &&
|
|
cfi_is_equal(cfi, CFI_QRY_OFFSET + 2, 'Y'))
|
|
{
|
|
FAR struct cfi_info_s *info = &(cfi->info);
|
|
FAR uint8_t *ptr = (FAR uint8_t *)info;
|
|
|
|
for (j = CFI_QRY_OFFSET;
|
|
j < CFI_ERASE_BLOCK_REGION_INFO_OFFSET; j++)
|
|
{
|
|
*ptr++ = cfi_read_byte(cfi_addr_map(cfi, 0, j));
|
|
}
|
|
|
|
while (j < CFI_ERASE_BLOCK_REGION_INFO_OFFSET + 4 *
|
|
info->erase_region_num)
|
|
{
|
|
*ptr++ = cfi_read_byte(cfi_addr_map(cfi, 0, j++));
|
|
}
|
|
|
|
cfi->cfi_offset = g_cfi_query_address[i];
|
|
cfi->dev_num = cfi->bankwidth / cfi->dev_width;
|
|
cfi->page_size = (1 << info->max_write_bytes_num) *
|
|
cfi->dev_num;
|
|
|
|
/* fix amd feature */
|
|
|
|
if (info->p_id == CFI_CMDSET_AMD_STANDARD ||
|
|
info->p_id == CFI_CMDSET_AMD_EXTENDED)
|
|
{
|
|
cfi_amd_fix(cfi);
|
|
}
|
|
|
|
/* exit cfi mode, enter into read array mode */
|
|
|
|
cfi_reset(cfi);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* cfi flash not found */
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_reset
|
|
*
|
|
* Description:
|
|
* Get into read array mode.
|
|
*
|
|
****************************************************************************/
|
|
|
|
void cfi_reset(FAR struct cfi_dev_s *cfi)
|
|
{
|
|
switch (cfi->info.p_id)
|
|
{
|
|
case CFI_CMDSET_INTEL_EXTENDED:
|
|
case CFI_CMDSET_INTEL_STANDARD:
|
|
cfi_write_address_cmd(cfi, 0, CFI_INTEL_CMD_CLEAR_STATUS);
|
|
cfi_write_address_cmd(cfi, 0, CFI_INTEL_CMD_RESET);
|
|
break;
|
|
case CFI_CMDSET_AMD_STANDARD:
|
|
case CFI_CMDSET_AMD_EXTENDED:
|
|
cfi_write_address_cmd(cfi, 0, CFI_AMD_CMD_RESET);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_erase
|
|
*
|
|
* Description:
|
|
* Erase multi blocks.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int cfi_erase(FAR struct cfi_dev_s *cfi, blkcnt_t startblock,
|
|
blkcnt_t blockcnt)
|
|
{
|
|
blkcnt_t i;
|
|
int ret = 0;
|
|
|
|
for (i = startblock; i < startblock + blockcnt; i++)
|
|
{
|
|
ret = cfi_erase_one(cfi, i);
|
|
if (ret < 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
cfi_reset(cfi);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_read
|
|
*
|
|
* Description:
|
|
* Read data from cfi-flash to buffer.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int cfi_read(FAR struct cfi_dev_s *cfi, off_t offset, size_t nbytes,
|
|
FAR uint8_t *buffer)
|
|
{
|
|
uintptr_t base = cfi->base_addr;
|
|
|
|
memcpy(buffer, (FAR const uint8_t *)base + offset, nbytes);
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cfi_write
|
|
*
|
|
* Description:
|
|
* Write data from buffer to cfi-flash.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int cfi_write(FAR struct cfi_dev_s *cfi, off_t offset, size_t nbytes,
|
|
FAR const uint8_t *buffer)
|
|
{
|
|
ssize_t ret;
|
|
|
|
offset = cfi->base_addr + offset;
|
|
ret = cfi_write_unalign(cfi, offset, nbytes, buffer);
|
|
if (ret < 0)
|
|
{
|
|
goto out;
|
|
}
|
|
|
|
offset = ALIGN_UP(offset, cfi->bankwidth);
|
|
nbytes -= ret;
|
|
buffer += ret;
|
|
|
|
/* handle the aligned part */
|
|
|
|
/* if the device support buffer write */
|
|
|
|
if (cfi->info.max_write_bytes_num != 0)
|
|
{
|
|
while (nbytes >= cfi->bankwidth)
|
|
{
|
|
size_t size = cfi->page_size - offset % cfi->page_size;
|
|
|
|
if (size > nbytes)
|
|
{
|
|
size = ALIGN_DOWN(nbytes, cfi->bankwidth);
|
|
}
|
|
|
|
ret = cfi_write_buffer(cfi, offset, size, buffer);
|
|
if (ret < 0)
|
|
{
|
|
goto out;
|
|
}
|
|
|
|
offset += size;
|
|
buffer += size;
|
|
nbytes -= size;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (nbytes >= cfi->bankwidth)
|
|
{
|
|
cfiword_t word = 0;
|
|
uint8_t i;
|
|
|
|
for (i = 0; i < cfi->bankwidth; i++)
|
|
{
|
|
cfi_add_byte(cfi, &word, *buffer++);
|
|
}
|
|
|
|
ret = cfi_write_word(cfi, offset, word);
|
|
if (ret < 0)
|
|
{
|
|
goto out;
|
|
}
|
|
|
|
offset += cfi->bankwidth;
|
|
nbytes -= cfi->bankwidth;
|
|
}
|
|
}
|
|
|
|
if (nbytes == 0)
|
|
{
|
|
goto out;
|
|
}
|
|
|
|
/* handle unaligned tail bytes */
|
|
|
|
ret = cfi_write_unalign(cfi, offset, nbytes, buffer);
|
|
|
|
out:
|
|
cfi_reset(cfi);
|
|
return ret < 0 ? ret : 0;
|
|
}
|