Files
rt-thread/components/drivers/mtd/mtd-cfi.c
GuEe-GUI cd1d47b87c [DM][MTD] Add common MTD drivers
1. CFI-Nor flash DM driver.
2. SPI-Nor flash DM driver.

Signed-off-by: GuEe-GUI <2991707448@qq.com>
2025-12-08 17:06:12 +08:00

1337 lines
34 KiB
C

/*
* Copyright (c) 2006-2023, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2023-02-25 GuEe-GUI the first version
*/
#include <rtthread.h>
#include <rtdevice.h>
#include <drivers/byteorder.h>
#define DBG_TAG "mtd.cfi"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
#include "mtd-cfi.h"
struct cfi_flash_device
{
struct rt_mtd_nor_device parent;
struct rt_mutex rw_lock;
rt_ubase_t sect[CFI_FLASH_SECT_MAX];
rt_ubase_t protect[CFI_FLASH_SECT_MAX];
rt_size_t sect_count;
rt_uint8_t portwidth;
rt_uint8_t chipwidth;
rt_uint8_t chip_lsb;
rt_uint8_t cmd_reset;
rt_uint8_t cmd_erase_sector;
rt_uint8_t sr_supported;
rt_uint16_t ext_addr;
rt_uint16_t version;
rt_uint16_t offset;
rt_uint16_t vendor;
rt_uint16_t device_id;
rt_uint16_t device_ext_id;
rt_uint16_t manufacturer_id;
rt_uint16_t interface;
rt_ubase_t addr_unlock1;
rt_ubase_t addr_unlock2;
rt_ubase_t write_tout;
rt_ubase_t erase_blk_tout;
rt_size_t size;
};
#define raw_to_cfi_flash_device(raw) rt_container_of(raw, struct cfi_flash_device, parent)
struct cfi_flash
{
int count;
struct cfi_flash_device dev[];
};
#define __get_unaligned_t(type, ptr) \
({ \
const rt_packed(struct { type x; }) *_ptr = (typeof(_ptr))(ptr); \
_ptr->x; \
})
#define __put_unaligned_t(type, val, ptr) \
do { \
rt_packed(struct { type x; }) *_ptr = (typeof(_ptr))(ptr); \
_ptr->x = (val); \
} while (0)
#define get_unaligned(ptr) __get_unaligned_t(typeof(*(ptr)), (ptr))
#define put_unaligned(val, ptr) __put_unaligned_t(typeof(*(ptr)), (val), (ptr))
static rt_uint32_t cfi_flash_offset[2] = { FLASH_OFFSET_CFI, FLASH_OFFSET_CFI_ALT };
rt_inline void *cfi_flash_map(struct cfi_flash_device *fdev, rt_off_t sect, rt_off_t offset)
{
rt_off_t byte_offset = offset * fdev->portwidth;
return (void *)(fdev->sect[sect] + (byte_offset << fdev->chip_lsb));
}
rt_inline void cfi_flash_write8(void *addr, rt_uint8_t value)
{
HWREG8(addr) = value;
}
rt_inline void cfi_flash_write16(void *addr, rt_uint16_t value)
{
HWREG16(addr) = value;
}
rt_inline void cfi_flash_write32(void *addr, rt_uint32_t value)
{
HWREG32(addr) = value;
}
rt_inline void cfi_flash_write64(void *addr, rt_uint64_t value)
{
HWREG64(addr) = value;
}
rt_inline rt_uint8_t cfi_flash_read8(void *addr)
{
return HWREG8(addr);
}
rt_inline rt_uint16_t cfi_flash_read16(void *addr)
{
return HWREG16(addr);
}
rt_inline rt_uint32_t cfi_flash_read32(void *addr)
{
return HWREG32(addr);
}
rt_inline rt_uint64_t cfi_flash_read64(void *addr)
{
return HWREG64(addr);
}
rt_inline rt_uint8_t cfi_flash_read_byte(struct cfi_flash_device *fdev, rt_off_t offset)
{
unsigned char *cp, value;
cp = cfi_flash_map(fdev, 0, offset);
#ifndef ARCH_CPU_BIG_ENDIAN
value = cfi_flash_read8(cp);
#else
value = cfi_flash_read8(cp + fdev->portwidth - 1);
#endif
return value;
}
rt_inline rt_uint16_t cfi_flash_read_word(struct cfi_flash_device *fdev, rt_off_t offset)
{
rt_uint16_t *addr, value;
addr = cfi_flash_map(fdev, 0, offset);
value = cfi_flash_read16(addr);
return value;
}
static void cfi_flash_make_cmd(struct cfi_flash_device *fdev, rt_uint32_t cmd, void *cmdbuf)
{
int cword_offset, cp_offset;
rt_uint8_t val, *cp = (rt_uint8_t *)cmdbuf;
#ifndef ARCH_CPU_BIG_ENDIAN
cmd = rt_cpu_to_le32(cmd);
#endif
for (int i = fdev->portwidth; i > 0; --i)
{
cword_offset = (fdev->portwidth - i) % fdev->chipwidth;
#ifndef ARCH_CPU_BIG_ENDIAN
cp_offset = fdev->portwidth - i;
val = *((rt_uint8_t *)&cmd + cword_offset);
#else
cp_offset = i - 1;
val = *((rt_uint8_t *)&cmd + sizeof(rt_uint32_t) - cword_offset - 1);
#endif
cp[cp_offset] = (cword_offset >= sizeof(rt_uint32_t)) ? 0x00 : val;
}
}
static void cfi_flash_write_cmd(struct cfi_flash_device *fdev,
rt_off_t sect, rt_off_t offset, rt_uint32_t cmd)
{
void *addr;
union cfi_word word;
addr = cfi_flash_map(fdev, sect, offset);
cfi_flash_make_cmd(fdev, cmd, &word);
switch (fdev->portwidth)
{
case FLASH_CFI_8BIT:
cfi_flash_write8(addr, word.w8);
break;
case FLASH_CFI_16BIT:
cfi_flash_write16(addr, word.w16);
break;
case FLASH_CFI_32BIT:
cfi_flash_write32(addr, word.w32);
break;
case FLASH_CFI_64BIT:
cfi_flash_write64(addr, word.w64);
break;
}
rt_hw_isb();
}
static void cfi_flash_unlock_seq(struct cfi_flash_device *fdev, rt_off_t sect)
{
cfi_flash_write_cmd(fdev, sect, fdev->addr_unlock1, AMD_CMD_UNLOCK_START);
cfi_flash_write_cmd(fdev, sect, fdev->addr_unlock2, AMD_CMD_UNLOCK_ACK);
}
static rt_off_t cfi_flash_find(struct cfi_flash_device *fdev, rt_ubase_t addr)
{
rt_off_t sect = 0;
while (sect < fdev->sect_count - 1 && fdev->sect[sect] < addr)
{
sect++;
}
while (fdev->sect[sect] > addr && sect > 0)
{
sect--;
}
return sect;
}
static rt_bool_t cfi_flash_isequal(struct cfi_flash_device *fdev,
rt_off_t sect, rt_off_t offset, rt_uint32_t cmd)
{
void *addr;
union cfi_word word;
addr = cfi_flash_map(fdev, sect, offset);
cfi_flash_make_cmd(fdev, cmd, &word);
switch (fdev->portwidth)
{
case FLASH_CFI_8BIT:
return cfi_flash_read8(addr) == word.w8;
case FLASH_CFI_16BIT:
return cfi_flash_read16(addr) == word.w16;
case FLASH_CFI_32BIT:
return cfi_flash_read32(addr) == word.w32;
case FLASH_CFI_64BIT:
return cfi_flash_read64(addr) == word.w64;
default:
break;
}
return RT_FALSE;
}
static void cfi_flash_add_byte(struct cfi_flash_device *fdev,
union cfi_word *word, rt_uint8_t c)
{
#ifndef ARCH_CPU_BIG_ENDIAN
rt_uint16_t w;
rt_uint32_t l;
rt_uint64_t ll;
#endif
switch (fdev->portwidth)
{
case FLASH_CFI_8BIT:
word->w8 = c;
break;
case FLASH_CFI_16BIT:
#ifndef ARCH_CPU_BIG_ENDIAN
w = c;
w <<= 8;
word->w16 = (word->w16 >> 8) | w;
#else
word->w16 = (word->w16 << 8) | c;
#endif
break;
case FLASH_CFI_32BIT:
#ifndef ARCH_CPU_BIG_ENDIAN
l = c;
l <<= 24;
word->w32 = (word->w32 >> 8) | l;
#else
word->w32 = (word->w32 << 8) | c;
#endif
break;
case FLASH_CFI_64BIT:
#ifndef ARCH_CPU_BIG_ENDIAN
ll = c;
ll <<= 56;
word->w64 = (word->w64 >> 8) | ll;
#else
word->w64 = (word->w64 << 8) | c;
#endif
break;
}
}
static rt_bool_t cfi_flash_isset(struct cfi_flash_device *fdev,
rt_off_t sect, rt_off_t offset, rt_uint8_t cmd)
{
rt_bool_t res;
rt_uint8_t *addr;
union cfi_word word;
addr = cfi_flash_map(fdev, sect, offset);
cfi_flash_make_cmd(fdev, cmd, &word);
switch (fdev->portwidth)
{
case FLASH_CFI_8BIT:
res = (cfi_flash_read8(addr) & word.w8) == word.w8;
break;
case FLASH_CFI_16BIT:
res = (cfi_flash_read16(addr) & word.w16) == word.w16;
break;
case FLASH_CFI_32BIT:
res = (cfi_flash_read32(addr) & word.w32) == word.w32;
break;
case FLASH_CFI_64BIT:
res = (cfi_flash_read64(addr) & word.w64) == word.w64;
break;
default:
res = RT_FALSE;
break;
}
return res;
}
static rt_bool_t cfi_flash_toggle(struct cfi_flash_device *fdev,
rt_off_t sect, rt_off_t offset, rt_uint8_t cmd)
{
rt_bool_t res;
rt_uint8_t *addr;
union cfi_word word;
addr = cfi_flash_map(fdev, sect, offset);
cfi_flash_make_cmd(fdev, cmd, &word);
switch (fdev->portwidth)
{
case FLASH_CFI_8BIT:
res = cfi_flash_read8(addr) != cfi_flash_read8(addr);
break;
case FLASH_CFI_16BIT:
res = cfi_flash_read16(addr) != cfi_flash_read16(addr);
break;
case FLASH_CFI_32BIT:
res = cfi_flash_read32(addr) != cfi_flash_read32(addr);
break;
case FLASH_CFI_64BIT:
res = cfi_flash_read32(addr) != cfi_flash_read32(addr) ||
cfi_flash_read32(addr + 4) != cfi_flash_read32(addr + 4);
break;
default:
res = RT_FALSE;
break;
}
return res;
}
static rt_bool_t cfi_flash_is_busy(struct cfi_flash_device *fdev, rt_off_t sect)
{
switch (fdev->vendor)
{
case CFI_CMDSET_INTEL_PROG_REGIONS:
case CFI_CMDSET_INTEL_STANDARD:
case CFI_CMDSET_INTEL_EXTENDED:
return !cfi_flash_isset(fdev, sect, 0, FLASH_STATUS_DONE);
case CFI_CMDSET_AMD_STANDARD:
case CFI_CMDSET_AMD_EXTENDED:
if (fdev->sr_supported)
{
cfi_flash_write_cmd(fdev, sect, fdev->addr_unlock1, FLASH_CMD_READ_STATUS);
return !cfi_flash_isset(fdev, sect, 0, FLASH_STATUS_DONE);
}
else
{
return cfi_flash_toggle(fdev, sect, 0, AMD_STATUS_TOGGLE);
}
default:
break;
}
return RT_FALSE;
}
static rt_err_t cfi_flash_status_check(struct cfi_flash_device *fdev,
rt_off_t sect, rt_ubase_t tout)
{
rt_tick_t tick;
tick = rt_tick_from_millisecond(rt_max_t(rt_ubase_t, tout / 1000, 1));
tick += rt_tick_get();
while (cfi_flash_is_busy(fdev, sect))
{
if (rt_tick_get() > tick)
{
cfi_flash_write_cmd(fdev, sect, 0, fdev->cmd_reset);
rt_hw_us_delay(1);
return -RT_ETIMEOUT;
}
rt_hw_us_delay(1);
}
return RT_EOK;
}
static rt_err_t cfi_flash_full_status_check(struct cfi_flash_device *fdev,
rt_off_t sect, rt_ubase_t tout)
{
rt_err_t err;
err = cfi_flash_status_check(fdev, sect, tout);
switch (fdev->vendor)
{
case CFI_CMDSET_INTEL_PROG_REGIONS:
case CFI_CMDSET_INTEL_EXTENDED:
case CFI_CMDSET_INTEL_STANDARD:
cfi_flash_write_cmd(fdev, sect, 0, fdev->cmd_reset);
rt_hw_us_delay(1);
break;
default:
break;
}
return err;
}
static rt_err_t cfi_flash_write_word(struct cfi_flash_device *fdev,
rt_ubase_t dest, union cfi_word word)
{
int flag;
void *dstaddr;
rt_off_t sect = 0;
rt_bool_t sect_found = RT_FALSE;
dstaddr = (void *)dest;
/* Check if Flash is (sufficiently) erased */
switch (fdev->portwidth)
{
case FLASH_CFI_8BIT:
flag = (cfi_flash_read8(dstaddr) & word.w8) == word.w8;
break;
case FLASH_CFI_16BIT:
flag = (cfi_flash_read16(dstaddr) & word.w16) == word.w16;
break;
case FLASH_CFI_32BIT:
flag = (cfi_flash_read32(dstaddr) & word.w32) == word.w32;
break;
case FLASH_CFI_64BIT:
flag = (cfi_flash_read64(dstaddr) & word.w64) == word.w64;
break;
default:
flag = 0;
break;
}
if (!flag)
{
return -RT_EIO;
}
switch (fdev->vendor)
{
case CFI_CMDSET_INTEL_PROG_REGIONS:
case CFI_CMDSET_INTEL_EXTENDED:
case CFI_CMDSET_INTEL_STANDARD:
cfi_flash_write_cmd(fdev, 0, 0, FLASH_CMD_CLEAR_STATUS);
cfi_flash_write_cmd(fdev, 0, 0, FLASH_CMD_WRITE);
break;
case CFI_CMDSET_AMD_EXTENDED:
case CFI_CMDSET_AMD_STANDARD:
sect = cfi_flash_find(fdev, dest);
cfi_flash_unlock_seq(fdev, sect);
cfi_flash_write_cmd(fdev, sect, fdev->addr_unlock1, AMD_CMD_WRITE);
sect_found = RT_TRUE;
break;
}
switch (fdev->portwidth)
{
case FLASH_CFI_8BIT:
cfi_flash_write8(dstaddr, word.w8);
break;
case FLASH_CFI_16BIT:
cfi_flash_write16(dstaddr, word.w16);
break;
case FLASH_CFI_32BIT:
cfi_flash_write32(dstaddr, word.w32);
break;
case FLASH_CFI_64BIT:
cfi_flash_write64(dstaddr, word.w64);
break;
}
if (!sect_found)
{
sect = cfi_flash_find(fdev, dest);
}
return cfi_flash_full_status_check(fdev, sect, fdev->write_tout);
}
static rt_err_t cfi_flash_read_id(struct rt_mtd_nor_device *dev)
{
struct cfi_flash_device *fdev = raw_to_cfi_flash_device(dev);
return fdev->device_id;
}
static rt_ssize_t cfi_flash_read(struct rt_mtd_nor_device *dev, rt_off_t offset, rt_uint8_t *data, rt_size_t length)
{
struct cfi_flash_device *fdev = raw_to_cfi_flash_device(dev);
rt_mutex_take(&fdev->rw_lock, RT_WAITING_FOREVER);
rt_memcpy(data, (void *)fdev->sect[0] + offset, length);
rt_mutex_release(&fdev->rw_lock);
return length;
}
static rt_ssize_t cfi_flash_write(struct rt_mtd_nor_device *dev, rt_off_t offset,
const rt_uint8_t *data, rt_size_t length)
{
int i;
rt_ubase_t wp;
rt_uint8_t *ptr;
rt_ssize_t res;
rt_size_t size, written = 0, tail_written = 0;
union cfi_word word;
struct cfi_flash_device *fdev = raw_to_cfi_flash_device(dev);
rt_mutex_take(&fdev->rw_lock, RT_WAITING_FOREVER);
offset += fdev->sect[0];
wp = offset & ~(fdev->portwidth - 1);
size = offset - wp;
if (size)
{
rt_size_t head_written = 0;
word.w32 = 0;
ptr = (rt_uint8_t *)wp;
for (i = 0; i < size; ++i)
{
cfi_flash_add_byte(fdev, &word, cfi_flash_read8(ptr + i));
}
for (; i < fdev->portwidth && length > 0; ++i)
{
cfi_flash_add_byte(fdev, &word, *data++);
--length;
++head_written;
}
for (; !length && i < fdev->portwidth; ++i)
{
cfi_flash_add_byte(fdev, &word, cfi_flash_read8(ptr + i));
}
if ((res = cfi_flash_write_word(fdev, wp, word)))
{
goto _out_lock;
}
written += head_written;
wp += i;
}
/* Handle the aligned part */
while (length >= fdev->portwidth)
{
word.w32 = 0;
for (i = 0; i < fdev->portwidth; ++i)
{
cfi_flash_add_byte(fdev, &word, *data++);
}
if ((res = cfi_flash_write_word(fdev, wp, word)))
{
goto _out_lock;
}
wp += fdev->portwidth;
length -= fdev->portwidth;
written += fdev->portwidth; /* new: accumulate bytes */
}
if (!length)
{
res = written;
goto _out_lock;
}
/* Handle unaligned tail bytes */
word.w32 = 0;
ptr = (rt_uint8_t *)wp;
for (i = 0; i < fdev->portwidth && length > 0; ++i)
{
cfi_flash_add_byte(fdev, &word, *data++);
--length;
++tail_written;
}
for (; i < fdev->portwidth; ++i)
{
cfi_flash_add_byte(fdev, &word, cfi_flash_read8(ptr + i));
}
if ((res = cfi_flash_write_word(fdev, wp, word)))
{
goto _out_lock;
}
written += tail_written;
res = written;
_out_lock:
rt_mutex_release(&fdev->rw_lock);
return res;
}
static rt_err_t cfi_flash_erase_block(struct rt_mtd_nor_device *dev,
rt_off_t offset, rt_size_t length)
{
rt_err_t err = RT_EOK;
rt_ubase_t sect, sect_end;
struct cfi_flash_device *fdev = raw_to_cfi_flash_device(dev);
rt_mutex_take(&fdev->rw_lock, RT_WAITING_FOREVER);
sect = cfi_flash_find(fdev, fdev->sect[0] + offset);
sect_end = cfi_flash_find(fdev, fdev->sect[0] + offset + length);
for (; sect <= sect_end; ++sect)
{
if (fdev->protect[sect])
{
continue;
}
switch (fdev->vendor)
{
case CFI_CMDSET_INTEL_PROG_REGIONS:
case CFI_CMDSET_INTEL_STANDARD:
case CFI_CMDSET_INTEL_EXTENDED:
cfi_flash_write_cmd(fdev, sect, 0, FLASH_CMD_CLEAR_STATUS);
cfi_flash_write_cmd(fdev, sect, 0, FLASH_CMD_BLOCK_ERASE);
cfi_flash_write_cmd(fdev, sect, 0, FLASH_CMD_ERASE_CONFIRM);
break;
case CFI_CMDSET_AMD_STANDARD:
case CFI_CMDSET_AMD_EXTENDED:
cfi_flash_unlock_seq(fdev, sect);
cfi_flash_write_cmd(fdev, sect, fdev->addr_unlock1, AMD_CMD_ERASE_START);
cfi_flash_unlock_seq(fdev, sect);
cfi_flash_write_cmd(fdev, sect, 0, fdev->cmd_erase_sector);
break;
default:
break;
}
err = cfi_flash_full_status_check(fdev, sect, fdev->erase_blk_tout);
}
rt_mutex_release(&fdev->rw_lock);
return err;
}
const static struct rt_mtd_nor_driver_ops cfi_flash_ops =
{
.read_id = cfi_flash_read_id,
.read = cfi_flash_read,
.write = cfi_flash_write,
.erase_block = cfi_flash_erase_block,
};
static rt_bool_t cfi_flash_detect_info(struct cfi_flash_device *fdev, struct cfi_query *query)
{
rt_uint8_t *buffer;
/* Reset, unknown reset's cmd now, so try Intel and AMD both. */
cfi_flash_write_cmd(fdev, 0, 0, AMD_CMD_RESET);
rt_hw_us_delay(1);
cfi_flash_write_cmd(fdev, 0, 0, FLASH_CMD_RESET);
for (int i = 0; i < RT_ARRAY_SIZE(cfi_flash_offset); ++i)
{
cfi_flash_write_cmd(fdev, 0, cfi_flash_offset[i], FLASH_CMD_CFI);
if (cfi_flash_isequal(fdev, 0, FLASH_OFFSET_CFI_RESP, 'Q') &&
cfi_flash_isequal(fdev, 0, FLASH_OFFSET_CFI_RESP + 1, 'R') &&
cfi_flash_isequal(fdev, 0, FLASH_OFFSET_CFI_RESP + 2, 'Y'))
{
buffer = (void *)query;
for (int byte = 0; byte < sizeof(*query); ++byte)
{
buffer[byte] = cfi_flash_read_byte(fdev, FLASH_OFFSET_CFI_RESP + byte);
}
fdev->interface = rt_le16_to_cpu(query->interface_desc);
/*
* Some flash chips can support multiple bus widths.
* In this case, override the interface width and
* limit it to the port width.
*/
if (fdev->interface == FLASH_CFI_X8X16 &&
fdev->portwidth == FLASH_CFI_8BIT)
{
fdev->interface = FLASH_CFI_X8;
}
else if (fdev->interface == FLASH_CFI_X16X32 &&
fdev->portwidth == FLASH_CFI_16BIT)
{
fdev->interface = FLASH_CFI_X16;
}
fdev->offset = cfi_flash_offset[i];
fdev->addr_unlock1 = 0x555;
fdev->addr_unlock2 = 0x2aa;
/* modify the unlock address if we are in compatibility mode */
if ((fdev->chipwidth == FLASH_CFI_BY8 && /* x8/x16 in x8 mode */
fdev->interface == FLASH_CFI_X8X16) ||
(fdev->chipwidth == FLASH_CFI_BY16 && /* x16/x32 in x16 mode */
fdev->interface == FLASH_CFI_X16X32))
{
fdev->addr_unlock1 = 0xaaa;
fdev->addr_unlock2 = 0x555;
}
return RT_TRUE;
}
}
return RT_FALSE;
}
static rt_bool_t cfi_flash_detect(struct cfi_flash_device *fdev, struct cfi_query *query)
{
for (fdev->portwidth = FLASH_CFI_8BIT;
fdev->portwidth <= FLASH_CFI_64BIT;
fdev->portwidth <<= 1)
{
for (fdev->chipwidth = FLASH_CFI_BY8;
fdev->chipwidth <= fdev->portwidth;
fdev->chipwidth <<= 1)
{
/*
* First, try detection without shifting the addresses
* for 8bit devices (16bit wide connection)
*/
fdev->chip_lsb = 0;
if (cfi_flash_detect_info(fdev, query))
{
return RT_TRUE;
}
/* Not detected, so let's try with shifting for 8bit devices */
fdev->chip_lsb = 1;
if (cfi_flash_detect_info(fdev, query))
{
return RT_TRUE;
}
}
}
return RT_FALSE;
}
static void cfi_cmdset_intel_init(struct cfi_flash_device *fdev, struct cfi_query *query)
{
fdev->cmd_reset = FLASH_CMD_RESET;
/* Intel read jedec IDs */
cfi_flash_write_cmd(fdev, 0, 0, FLASH_CMD_RESET);
rt_hw_us_delay(1);
cfi_flash_write_cmd(fdev, 0, 0, FLASH_CMD_READ_ID);
rt_hw_us_delay(1000);
fdev->manufacturer_id = cfi_flash_read_byte(fdev, FLASH_OFFSET_MANUFACTURER_ID);
fdev->device_id = (fdev->chipwidth == FLASH_CFI_16BIT) ?
cfi_flash_read_word(fdev, FLASH_OFFSET_DEVICE_ID) :
cfi_flash_read_byte(fdev, FLASH_OFFSET_DEVICE_ID);
cfi_flash_write_cmd(fdev, 0, 0, FLASH_CMD_RESET);
/* Read end */
cfi_flash_write_cmd(fdev, 0, fdev->offset, FLASH_CMD_CFI);
}
static void cfi_cmdset_amd_init(struct cfi_flash_device *fdev, struct cfi_query *query)
{
rt_uint16_t bank_id = 0;
rt_uint8_t manu_id, feature;
fdev->cmd_reset = AMD_CMD_RESET;
fdev->cmd_erase_sector = AMD_CMD_ERASE_SECTOR;
/* AMD read jedec IDs */
cfi_flash_write_cmd(fdev, 0, 0, AMD_CMD_RESET);
cfi_flash_unlock_seq(fdev, 0);
cfi_flash_write_cmd(fdev, 0, fdev->addr_unlock1, FLASH_CMD_READ_ID);
rt_hw_us_delay(1000);
manu_id = cfi_flash_read_byte(fdev, FLASH_OFFSET_MANUFACTURER_ID);
/* JEDEC JEP106Z specifies ID codes up to bank 7 */
while (manu_id == FLASH_CONTINUATION_CODE && bank_id < 0x800)
{
bank_id += 0x100;
manu_id = cfi_flash_read_byte(fdev, bank_id | FLASH_OFFSET_MANUFACTURER_ID);
}
fdev->manufacturer_id = manu_id;
if (fdev->ext_addr && fdev->version >= 0x3134)
{
/* Read software feature (at 0x53) */
feature = cfi_flash_read_byte(fdev, fdev->ext_addr + 0x13);
fdev->sr_supported = feature & 0x1;
}
switch (fdev->chipwidth)
{
case FLASH_CFI_8BIT:
fdev->device_id = cfi_flash_read_byte(fdev, FLASH_OFFSET_DEVICE_ID);
if (fdev->device_id == 0x7e)
{
/* AMD 3-byte (expanded) device ids */
fdev->device_ext_id = cfi_flash_read_byte(fdev, FLASH_OFFSET_DEVICE_ID2);
fdev->device_ext_id <<= 8;
fdev->device_ext_id |= cfi_flash_read_byte(fdev, FLASH_OFFSET_DEVICE_ID3);
}
break;
case FLASH_CFI_16BIT:
fdev->device_id = cfi_flash_read_word(fdev, FLASH_OFFSET_DEVICE_ID);
if ((fdev->device_id & 0xff) == 0x7e)
{
/* AMD 3-byte (expanded) device ids */
fdev->device_ext_id = cfi_flash_read_byte(fdev, FLASH_OFFSET_DEVICE_ID2);
fdev->device_ext_id <<= 8;
fdev->device_ext_id |= cfi_flash_read_byte(fdev, FLASH_OFFSET_DEVICE_ID3);
}
break;
default:
break;
}
cfi_flash_write_cmd(fdev, 0, 0, AMD_CMD_RESET);
rt_hw_us_delay(1);
/* Read end */
cfi_flash_write_cmd(fdev, 0, fdev->offset, FLASH_CMD_CFI);
}
static void cfi_reverse_geometry(struct cfi_query *query)
{
rt_uint32_t info;
for (int i = 0, j = query->num_erase_regions - 1; i < j; ++i, --j)
{
info = get_unaligned(&query->erase_region_info[i]);
put_unaligned(get_unaligned(&query->erase_region_info[j]),
&query->erase_region_info[i]);
put_unaligned(info, &query->erase_region_info[j]);
}
}
static void cfi_flash_fixup_amd(struct cfi_flash_device *fdev, struct cfi_query *query)
{
/* check if flash geometry needs reversal */
if (query->num_erase_regions > 1)
{
/* reverse geometry if top boot part */
if (fdev->version < 0x3131)
{
/* CFI < 1.1, try to guess from device id */
if ((fdev->device_id & 0x80) != 0)
{
cfi_reverse_geometry(query);
}
}
else if (cfi_flash_read_byte(fdev, fdev->ext_addr + 0xf) == 3)
{
/*
* CFI >= 1.1, deduct from top/bottom flag,
* ext_addr is valid since cfi version > 0.
*/
cfi_reverse_geometry(query);
}
}
}
static void cfi_flash_fixup_atmel(struct cfi_flash_device *fdev, struct cfi_query *query)
{
int reverse_geometry = 0;
/* Check the "top boot" bit in the PRI */
if (fdev->ext_addr && !(cfi_flash_read_byte(fdev, fdev->ext_addr + 6) & 1))
{
reverse_geometry = 1;
}
/*
* AT49BV6416(T) list the erase regions in the wrong order.
* However, the device ID is identical with the non-broken
* AT49BV642D they differ in the high byte.
*/
if (fdev->device_id == 0xd6 || fdev->device_id == 0xd2)
{
reverse_geometry = !reverse_geometry;
}
if (reverse_geometry)
{
cfi_reverse_geometry(query);
}
}
static void cfi_flash_fixup_stm(struct cfi_flash_device *fdev, struct cfi_query *query)
{
/* Check if flash geometry needs reversal */
if (query->num_erase_regions > 1)
{
/* Reverse geometry if top boot part */
if (fdev->version < 0x3131)
{
/*
* CFI < 1.1, guess by device id:
* M29W320DT, M29W320ET, M29W800DT
*/
if (fdev->device_id == 0x22ca ||
fdev->device_id == 0x2256 ||
fdev->device_id == 0x22d7)
{
cfi_reverse_geometry(query);
}
}
else if (cfi_flash_read_byte(fdev, fdev->ext_addr + 0xf) == 3)
{
/*
* CFI >= 1.1, deduct from top/bottom flag,
* ext_addr is valid since cfi version > 0.
*/
cfi_reverse_geometry(query);
}
}
}
static void cfi_flash_fixup_sst(struct cfi_flash_device *fdev, struct cfi_query *query)
{
/*
* SST, for many recent nor parallel flashes, says they are
* CFI-conformant. This is not true, since qry struct.
* reports a std. AMD command set (0x0002), while SST allows to
* erase two different sector sizes for the same memory.
* 64KB sector (SST call it block) needs 0x30 to be erased.
* 4KB sector (SST call it sector) needs 0x50 to be erased.
* Since CFI query detect the 4KB number of sectors, users expects
* a sector granularity of 4KB, and it is here set.
*/
/* SST39VF3201B, SST39VF3202B */
if (fdev->device_id == 0x5d23 || fdev->device_id == 0x5c23)
{
/* Set sector granularity to 4KB */
fdev->cmd_erase_sector = 0x50;
}
}
static void cfi_flash_fixup_num(struct cfi_flash_device *fdev, struct cfi_query *query)
{
/*
* The M29EW devices seem to report the CFI information wrong
* when it's in 8 bit mode.
* There's an app note from Numonyx on this issue.
* So adjust the buffer size for M29EW while operating in 8-bit mode
*/
if (query->max_buf_write_size > 0x8 && fdev->device_id == 0x7e &&
(fdev->device_ext_id == 0x2201 || fdev->device_ext_id == 0x2301 ||
fdev->device_ext_id == 0x2801 || fdev->device_ext_id == 0x4801))
{
query->max_buf_write_size = 0x8;
}
}
static rt_err_t cfi_flash_dev_probe(struct rt_device *dev, struct cfi_flash_device *fdev, int index)
{
rt_err_t err;
int size_ratio;
const char *name;
rt_uint64_t addr, size;
rt_ubase_t sect, value;
rt_size_t max_size, sect_count;
int num_erase_regions, erase_region_count, erase_region_size;
struct cfi_query *query = rt_malloc(sizeof(*query));
if (!query)
{
return -RT_ENOMEM;
}
if (rt_dm_dev_get_address(dev, index, &addr, &size) < 0)
{
err = -RT_EIO;
goto _fail;
}
fdev->sect[0] = (rt_ubase_t)rt_ioremap((void *)addr, size);
if (!cfi_flash_detect(fdev, query))
{
err = -RT_EEMPTY;
goto _fail;
}
fdev->vendor = rt_le16_to_cpu(get_unaligned(&query->primary_id));
fdev->ext_addr = rt_le16_to_cpu(get_unaligned(&query->primary_address));
num_erase_regions = query->num_erase_regions;
if (fdev->ext_addr)
{
fdev->version = cfi_flash_read_byte(fdev, fdev->ext_addr + 3) << 8;
fdev->version |= cfi_flash_read_byte(fdev, fdev->ext_addr + 4);
}
switch (fdev->vendor)
{
case CFI_CMDSET_INTEL_PROG_REGIONS:
case CFI_CMDSET_INTEL_STANDARD:
case CFI_CMDSET_INTEL_EXTENDED:
cfi_cmdset_intel_init(fdev, query);
break;
case CFI_CMDSET_AMD_STANDARD:
case CFI_CMDSET_AMD_EXTENDED:
cfi_cmdset_amd_init(fdev, query);
break;
default:
/* Try an Intel-style reset*/
cfi_flash_write_cmd(fdev, 0, 0, FLASH_CMD_RESET);
break;
}
switch (fdev->manufacturer_id)
{
case 0x0001: /* AMD */
case 0x0037: /* AMIC */
cfi_flash_fixup_amd(fdev, query);
break;
case 0x001f:
cfi_flash_fixup_atmel(fdev, query);
break;
case 0x0020:
cfi_flash_fixup_stm(fdev, query);
break;
case 0x00bf: /* SST */
cfi_flash_fixup_sst(fdev, query);
break;
case 0x0089: /* Numonyx */
cfi_flash_fixup_num(fdev, query);
break;
}
size_ratio = fdev->portwidth / fdev->chipwidth;
if (fdev->interface == FLASH_CFI_X8X16 && fdev->chipwidth == FLASH_CFI_BY8)
{
size_ratio >>= 1;
}
fdev->size = 1 << query->dev_size;
/* Multiply the size by the number of chips */
fdev->size *= size_ratio;
max_size = size;
if (max_size && fdev->size > max_size)
{
fdev->size = max_size;
}
sect_count = 0;
sect = fdev->sect[0];
for (int i = 0; i < num_erase_regions; ++i)
{
if (i > RT_ARRAY_SIZE(query->erase_region_info))
{
LOG_E("Too many %d (> %d) erase regions found",
num_erase_regions, RT_ARRAY_SIZE(query->erase_region_info));
break;
}
value = rt_le32_to_cpu(get_unaligned(&query->erase_region_info[i]));
erase_region_count = (value & 0xffff) + 1;
value >>= 16;
erase_region_size = (value & 0xffff) ? ((value & 0xffff) * 256) : 128;
for (int j = 0; j < erase_region_count; ++j)
{
if (sect - fdev->sect[0] >= fdev->size)
{
break;
}
if (sect_count > RT_ARRAY_SIZE(fdev->sect))
{
LOG_E("Too many %d (> %d) sector found",
sect_count, RT_ARRAY_SIZE(fdev->sect));
break;
}
fdev->sect[sect_count] = sect;
sect += (erase_region_size * size_ratio);
switch (fdev->vendor)
{
case CFI_CMDSET_INTEL_PROG_REGIONS:
case CFI_CMDSET_INTEL_EXTENDED:
case CFI_CMDSET_INTEL_STANDARD:
/*
* Set flash to read-id mode. Otherwise
* reading protected status is not guaranteed.
*/
cfi_flash_write_cmd(fdev, sect_count, 0, FLASH_CMD_READ_ID);
fdev->protect[sect_count] = cfi_flash_isset(fdev,
sect_count, FLASH_OFFSET_PROTECT, FLASH_STATUS_PROTECT);
cfi_flash_write_cmd(fdev, sect_count, 0, FLASH_CMD_RESET);
break;
case CFI_CMDSET_AMD_EXTENDED:
case CFI_CMDSET_AMD_STANDARD:
default:
/* Default: not protected */
fdev->protect[sect_count] = RT_NULL;
}
++sect_count;
}
fdev->sect_count = sect_count;
}
if (fdev->interface == FLASH_CFI_X8X16 && fdev->chipwidth == FLASH_CFI_BY8)
{
/* Need to test on x8/x16 in parallel. */
fdev->portwidth >>= 1;
}
value = 1 << query->block_erase_timeout_type;
fdev->erase_blk_tout = value * (1 << query->block_erase_timeout_max);
value = (1 << query->word_write_timeout_type) * (1 << query->word_write_timeout_max);
/* Round up when converting to ms */
fdev->write_tout = (value + 999) / 1000;
cfi_flash_write_cmd(fdev, 0, 0, fdev->cmd_reset);
fdev->parent.ops = &cfi_flash_ops;
fdev->parent.block_start = 0;
fdev->parent.block_end = fdev->sect_count;
fdev->parent.block_size = size / fdev->sect_count;
if ((err = rt_dm_dev_set_name_auto(&fdev->parent.parent, "nor")) < 0)
{
goto _fail;
}
name = rt_dm_dev_get_name(&fdev->parent.parent);
if ((err = rt_mutex_init(&fdev->rw_lock, name, RT_IPC_FLAG_PRIO)))
{
goto _fail;
}
if ((err = rt_mtd_nor_register_device(name, &fdev->parent)))
{
goto _fail;
}
rt_free(query);
return RT_EOK;
_fail:
if (fdev->sect[0])
{
rt_iounmap((void *)fdev->sect[0]);
}
rt_free(query);
return err;
}
static void cfi_flash_dev_remove(struct rt_device *dev, struct cfi_flash_device *fdev)
{
if (fdev->rw_lock.parent.parent.name[0])
{
rt_mutex_detach(&fdev->rw_lock);
}
if (fdev->sect[0])
{
rt_iounmap((void *)fdev->sect[0]);
}
}
static rt_err_t cfi_flash_probe(struct rt_platform_device *pdev)
{
rt_err_t err;
rt_size_t count;
struct cfi_flash *cfi;
struct rt_device *dev = &pdev->parent;
if ((count = rt_dm_dev_get_address_count(dev)) <= 0)
{
return -RT_EEMPTY;
}
cfi = rt_calloc(1, sizeof(*cfi) + count * sizeof(cfi->dev[0]));
if (!cfi)
{
return -RT_ENOMEM;
}
cfi->count = count;
for (int i = 0; i < cfi->count; ++i)
{
if ((err = cfi_flash_dev_probe(dev, &cfi->dev[i], i)))
{
goto _fail;
}
}
dev->user_data = cfi;
return RT_EOK;
_fail:
for (int i = 0; i < cfi->count; ++i)
{
cfi_flash_dev_remove(dev, &cfi->dev[i]);
}
rt_free(cfi);
return err;
}
static rt_err_t cfi_flash_remove(struct rt_platform_device *pdev)
{
struct rt_device *dev = &pdev->parent;
struct cfi_flash *cfi = pdev->parent.user_data;
for (int i = 0; i < cfi->count; ++i)
{
cfi_flash_dev_remove(dev, &cfi->dev[i]);
}
rt_free(cfi);
return RT_EOK;
}
static const struct rt_ofw_node_id cfi_flash_ofw_ids[] =
{
{ .compatible = "cfi-flash" },
{ .compatible = "jedec-flash" },
{ /* sentinel */ }
};
static struct rt_platform_driver cfi_flash_driver =
{
.name = "cfi-flash",
.ids = cfi_flash_ofw_ids,
.probe = cfi_flash_probe,
.remove = cfi_flash_remove,
};
RT_PLATFORM_DRIVER_EXPORT(cfi_flash_driver);