|  | /**************************************************************************** | 
|  | * 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) | 
|  | { | 
|  | 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 = nbytes; | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } |