537 lines
14 KiB
C
537 lines
14 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#define LOG_TAG "Flash"
|
|
|
|
#include "cts_config.h"
|
|
#include "cts_platform.h"
|
|
#include "cts_core.h"
|
|
#include "cts_sfctrl.h"
|
|
#include "cts_spi_flash.h"
|
|
#include "cts_strerror.h"
|
|
|
|
/* NOTE: double check command sets and memory organization when you add
|
|
* more flash chips. This current list focusses on newer chips, which
|
|
* have been converging on command sets which including JEDEC ID.
|
|
*/
|
|
static const struct cts_flash cts_flashes[] = {
|
|
/* Winbond */
|
|
{ "Winbond,W25Q10EW",
|
|
0xEF6011, 256, 0x1000, 0x8000, 0x20000},
|
|
{ "Winbond,W25Q20EW",
|
|
0xEF6012, 256, 0x1000, 0x8000, 0x40000},
|
|
{ "Winbond,W25Q40EW",
|
|
0xEF6013, 256, 0x1000, 0x8000, 0x80000},
|
|
{ "Winbond,W25Q20BW",
|
|
0xEF5012, 256, 0x1000, 0x8000, 0x40000},
|
|
{ "Winbond,W25Q40BW",
|
|
0xEF5013, 256, 0x1000, 0x8000, 0x80000},
|
|
|
|
/* Giga device */
|
|
{ "Giga,GD25LQ10B",
|
|
0xC86011, 256, 0x1000, 0x8000, 0x20000},
|
|
{ "Giga,GD25LD20CEIGR",
|
|
0xC86012, 256, 0x1000, 0x8000, 0x40000},
|
|
{ "Giga,GD25LD40CEIGR",
|
|
0xC86013, 256, 0x1000, 0x8000, 0x80000},
|
|
|
|
/* Macronix */
|
|
{ "Macronix,MX25U1001E",
|
|
0xC22531, 32, 0x1000, 0x10000, 0x20000},
|
|
{ "Macronix,MX25R2035F",
|
|
0xC22812, 256, 0x1000, 0x8000, 0x40000},
|
|
{ "Macronix,MX25R4035F",
|
|
0xC22813, 256, 0x1000, 0x8000, 0x80000},
|
|
|
|
/* Puya-Semi */
|
|
{ "Puya-Semi,P25Q20LUVHIR",
|
|
0x856012, 256, 0x1000, 0x8000, 0x40000},
|
|
{ "Puya-Semi,P25Q40LUXHIR",
|
|
0x856013, 256, 0x1000, 0x8000, 0x80000},
|
|
{ "Puya-Semi,P25Q11L",
|
|
0x854011, 256, 0x1000, 0x8000, 0x20000},
|
|
{ "Puya-Semi,P25Q21L",
|
|
0x854012, 256, 0x1000, 0x8000, 0x40000},
|
|
{ "Puya-Semi,P25T22L",
|
|
0x854412, 256, 0x1000, 0x8000, 0x40000},
|
|
|
|
|
|
/* Boya */
|
|
{ "Boya,BY25D10",
|
|
0x684011, 256, 0x1000, 0x8000, 0x20000},
|
|
|
|
/* EON */
|
|
{ "EoN,EN25S20A",
|
|
0x1C3812, 256, 0x1000, 0x8000, 0x40000},
|
|
|
|
/* XTXTECH */
|
|
{ "XTX,XT25Q01",
|
|
0x0B6011, 256, 0x1000, 0, 0x20000},
|
|
{ "XTX,XT25Q02",
|
|
0x0B6012, 256, 0x1000, 0, 0x40000},
|
|
|
|
/* Xinxin-Semi */
|
|
{ "Xinxin-Semi,XM25QU20B",
|
|
0x205012, 256, 0x1000, 0x8000, 0x40000},
|
|
{ "Xinxin-Semi,XM25QU40B",
|
|
0x205013, 256, 0x1000, 0x8000, 0x80000},
|
|
|
|
{ "KangYong,XK25Q20T",
|
|
0xEB6012, 256, 0x1000, 0x8000, 0x40000},
|
|
|
|
/*ZBIT*/
|
|
{ "ZBIT,ZB25LD40B",
|
|
0x5E1013, 256, 0x1000, 0x8000, 0x80000},
|
|
};
|
|
|
|
static const struct cts_flash *find_flash_by_jedec_id(u32 jedec_id)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(cts_flashes); i++) {
|
|
if (cts_flashes[i].jedec_id == jedec_id) {
|
|
return &cts_flashes[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int probe_flash(struct cts_device *cts_dev)
|
|
{
|
|
int ret;
|
|
|
|
cts_info("Probe flash");
|
|
|
|
if (cts_dev->hwdata->sfctrl->ops->rdid != NULL) {
|
|
u32 id;
|
|
|
|
cts_info("Read JEDEC ID");
|
|
|
|
ret = cts_dev->hwdata->sfctrl->ops->rdid(cts_dev, &id);
|
|
if (ret) {
|
|
cts_err("Read JEDEC ID failed %d(%s)",
|
|
ret, cts_strerror(ret));
|
|
return ret;
|
|
}
|
|
|
|
cts_dev->flash = find_flash_by_jedec_id(id);
|
|
if (cts_dev->flash == NULL) {
|
|
cts_err("Unknown JEDEC ID: %06x", id);
|
|
return -ENODEV;
|
|
}
|
|
|
|
cts_info("Flash type: '%s'", cts_dev->flash->name);
|
|
return 0;
|
|
} else {
|
|
cts_err("Probe flash with sfctrl->ops->rdid == NULL");
|
|
return -ENOTSUPP;
|
|
}
|
|
}
|
|
|
|
/** Make sure sector addr is sector aligned && < flash total size */
|
|
static int erase_sector_retry(struct cts_device *cts_dev,
|
|
u32 sector_addr, int retry)
|
|
{
|
|
int ret, retries;
|
|
|
|
cts_info(" Erase sector 0x%06x", sector_addr);
|
|
|
|
retries = 0;
|
|
do {
|
|
retries++;
|
|
ret = cts_dev->hwdata->sfctrl->ops->se(cts_dev, sector_addr);
|
|
if (ret) {
|
|
cts_err("Erase sector 0x%06x failed %d(%s) retries %d",
|
|
sector_addr, ret, cts_strerror(ret), retries);
|
|
continue;
|
|
}
|
|
} while (retries < retry);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/** Make sure sector addr is sector aligned && < flash total size */
|
|
static inline int erase_sector(struct cts_device *cts_dev,
|
|
u32 sector_addr)
|
|
{
|
|
return erase_sector_retry(cts_dev, sector_addr, CTS_FLASH_ERASE_DEFAULT_RETRY);
|
|
}
|
|
|
|
/** Make sure block addr is block aligned && < flash total size */
|
|
static int erase_block_retry(struct cts_device *cts_dev,
|
|
u32 block_addr, int retry)
|
|
{
|
|
int ret, retries;
|
|
|
|
cts_info(" Erase block 0x%06x", block_addr);
|
|
|
|
retries = 0;
|
|
do {
|
|
retries++;
|
|
|
|
ret = cts_dev->hwdata->sfctrl->ops->be(cts_dev, block_addr);
|
|
if (ret) {
|
|
cts_err("Erase block 0x%06x failed %d(%s) retries %d",
|
|
block_addr, ret, cts_strerror(ret), retries);
|
|
continue;
|
|
}
|
|
} while (retries < retry);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/** Make sure block addr is block aligned && < flash total size */
|
|
static inline int erase_block(struct cts_device *cts_dev,
|
|
u32 block_addr)
|
|
{
|
|
return erase_block_retry(cts_dev, block_addr,
|
|
CTS_FLASH_ERASE_DEFAULT_RETRY);
|
|
}
|
|
|
|
int cts_prepare_flash_operation(struct cts_device *cts_dev)
|
|
{
|
|
int ret;
|
|
u8 diva = 0;
|
|
bool program_mode = cts_is_device_program_mode(cts_dev);
|
|
u32 hwid;
|
|
bool enabled = cts_is_device_enabled(cts_dev);
|
|
|
|
cts_info("Prepare for flash operation");
|
|
|
|
if (!program_mode) {
|
|
ret = cts_enter_program_mode(cts_dev);
|
|
if (ret) {
|
|
cts_err("Enter program mode failed %d(%s)",
|
|
ret, cts_strerror(ret));
|
|
goto err_start_device;
|
|
}
|
|
}
|
|
|
|
ret = cts_hw_reg_readb_retry(cts_dev, CTS_DEV_HW_REG_CLK_DIV_CFG, &diva, 5, 0);
|
|
|
|
if (ret) {
|
|
cts_warn("Read DIVA failed %d(%s)", ret, cts_strerror(ret));
|
|
} else {
|
|
cts_dbg("Device DIVA = %d", diva);
|
|
}
|
|
|
|
if (ret == 0 && diva == 0x0C) {
|
|
cts_info("DIVA is ready already");
|
|
} else {
|
|
int retries;
|
|
|
|
/* Set HCLK to 10MHz */
|
|
hwid = cts_dev->hwdata->hwid;
|
|
if (hwid == CTS_DEV_HWID_ICNL9951) {
|
|
|
|
ret = cts_hw_reg_writeb_retry(cts_dev, CTS_DEV_HW_REG_CLK_DIV_CFG, 0x0C, 5, 0);
|
|
if (ret) {
|
|
cts_err("Write DIVA failed %d(%s)",
|
|
ret, cts_strerror(ret));
|
|
goto err_enter_normal_mode;
|
|
}
|
|
}
|
|
|
|
/* Reset SFCTL */
|
|
ret = cts_hw_reg_writeb_retry(cts_dev, CTS_DEV_HW_REG_RESET_CONFIG, 0xFB, 5, 0);
|
|
if (ret) {
|
|
cts_err("Reset sfctl failed %d(%s)",
|
|
ret, cts_strerror(ret));
|
|
goto err_enter_normal_mode;
|
|
}
|
|
|
|
retries = 0;
|
|
do {
|
|
u8 state;
|
|
|
|
ret = cts_hw_reg_readb_relaxed(cts_dev, CTS_DEV_HW_REG_HW_STATUS, &state);
|
|
if (ret == 0 && (state & 0x40) != 0)
|
|
goto init_flash;
|
|
|
|
mdelay(2);
|
|
} while (++retries < 1000);
|
|
|
|
if (ret) {
|
|
cts_warn("Wait SFCTRL ready failed %d(%s)", ret, cts_strerror(ret));
|
|
}
|
|
// Go through and try
|
|
}
|
|
|
|
init_flash:
|
|
if (cts_dev->flash == NULL) {
|
|
cts_info("Flash is not initialized, try to probe...");
|
|
if ((ret = probe_flash(cts_dev)) != 0) {
|
|
cts_dev->rtdata.has_flash = false;
|
|
cts_warn("Probe flash failed %d(%s)",
|
|
ret, cts_strerror(ret));
|
|
return 0;
|
|
}
|
|
}
|
|
cts_dev->rtdata.has_flash = true;
|
|
return 0;
|
|
|
|
err_enter_normal_mode:
|
|
if (!program_mode) {
|
|
int r = cts_enter_normal_mode(cts_dev);
|
|
if (r) {
|
|
cts_err("Enter normal mode failed %d(%s)",
|
|
r, cts_strerror(r));
|
|
}
|
|
}
|
|
err_start_device:
|
|
if (enabled) {
|
|
int r = cts_start_device(cts_dev);
|
|
if (r) {
|
|
cts_err("Start device failed %d(%s)",
|
|
r, cts_strerror(r));
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int cts_post_flash_operation(struct cts_device *cts_dev)
|
|
{
|
|
u32 hwid;
|
|
cts_info("Post flash operation");
|
|
|
|
hwid = cts_dev->hwdata->hwid;
|
|
if (hwid == CTS_DEV_HWID_ICNL9951) {
|
|
return cts_hw_reg_writeb_retry(cts_dev, CTS_DEV_HW_REG_CLK_DIV_CFG, 4, 5, 0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int cts_read_flash_retry(struct cts_device *cts_dev,
|
|
u32 flash_addr, void *dst, size_t size, int retry)
|
|
{
|
|
const struct cts_sfctrl *sfctrl;
|
|
const struct cts_flash *flash;
|
|
int ret;
|
|
|
|
cts_info("Read from 0x%06x size %zu", flash_addr, size);
|
|
|
|
sfctrl = cts_dev->hwdata->sfctrl;
|
|
flash = cts_dev->flash;
|
|
|
|
if (flash == NULL ||
|
|
sfctrl == NULL ||
|
|
sfctrl->ops == NULL ||
|
|
sfctrl->ops->read == NULL) {
|
|
cts_err("Read not supported");
|
|
return -ENOTSUPP;
|
|
}
|
|
|
|
if (flash_addr > flash->total_size) {
|
|
cts_err("Read from 0x%06x > flash size 0x%06zx",
|
|
flash_addr, flash->total_size);
|
|
return -EINVAL;
|
|
}
|
|
size = min(size, flash->total_size - flash_addr);
|
|
|
|
cts_info("Read actually from 0x%06x size %zu", flash_addr, size);
|
|
|
|
while (size) {
|
|
size_t l;
|
|
|
|
l = min(sfctrl->xchg_sram_size, size);
|
|
|
|
ret = sfctrl->ops->read(cts_dev, flash_addr, dst, l);
|
|
if(ret < 0) {
|
|
cts_err("Read from 0x%06x size %zu failed %d(%s)",
|
|
flash_addr, size, ret, cts_strerror(ret));
|
|
return ret;
|
|
}
|
|
|
|
dst += l;
|
|
size -= l;
|
|
flash_addr += l;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cts_program_flash(struct cts_device *cts_dev,
|
|
u32 flash_addr, const void *src, size_t size)
|
|
{
|
|
const struct cts_sfctrl *sfctrl;
|
|
const struct cts_flash *flash;
|
|
int ret;
|
|
|
|
cts_info("Program to 0x%06x size %zu", flash_addr, size);
|
|
|
|
sfctrl = cts_dev->hwdata->sfctrl;
|
|
flash = cts_dev->flash;
|
|
|
|
if (flash == NULL ||
|
|
sfctrl == NULL ||
|
|
sfctrl->ops == NULL ||
|
|
sfctrl->ops->program == NULL) {
|
|
cts_err("Program not supported");
|
|
return -ENOTSUPP;
|
|
}
|
|
|
|
if (flash_addr >= flash->total_size) {
|
|
cts_err("Program from 0x%06x >= flash size 0x%06zx",
|
|
flash_addr, flash->total_size);
|
|
return -EINVAL;
|
|
}
|
|
size = min(size, flash->total_size - flash_addr);
|
|
|
|
cts_info("Program actually to 0x%06x size %zu", flash_addr, size);
|
|
|
|
while (size) {
|
|
size_t l, offset;
|
|
|
|
l = min(flash->page_size, size);
|
|
offset = flash_addr & (flash->page_size - 1);
|
|
|
|
if (offset) {
|
|
l = min(flash->page_size - offset, l);
|
|
}
|
|
|
|
ret = sfctrl->ops->program(cts_dev, flash_addr, src, l);
|
|
if(ret) {
|
|
cts_err("Program to 0x%06x size %zu failed %d(%s)",
|
|
flash_addr, l, ret, cts_strerror(ret));
|
|
return ret;
|
|
}
|
|
|
|
src += l;
|
|
size -= l;
|
|
flash_addr += l;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cts_program_flash_from_sram(struct cts_device *cts_dev,
|
|
u32 flash_addr, u32 sram_addr, size_t size)
|
|
{
|
|
const struct cts_sfctrl *sfctrl;
|
|
const struct cts_flash *flash;
|
|
int ret;
|
|
|
|
cts_info("Program to 0x%06x from sram 0x%06x size %zu",
|
|
flash_addr, sram_addr, size);
|
|
|
|
sfctrl = cts_dev->hwdata->sfctrl;
|
|
flash = cts_dev->flash;
|
|
|
|
if (flash == NULL ||
|
|
sfctrl == NULL ||
|
|
sfctrl->ops == NULL ||
|
|
sfctrl->ops->program_from_sram == NULL) {
|
|
cts_err("Program from sram not supported");
|
|
return -ENOTSUPP;
|
|
}
|
|
|
|
if (flash_addr >= flash->total_size) {
|
|
cts_err("Program from 0x%06x >= flash size 0x%06zx",
|
|
flash_addr, flash->total_size);
|
|
return -EINVAL;
|
|
}
|
|
size = min(size, flash->total_size - flash_addr);
|
|
|
|
cts_info("Program actually to 0x%06x from sram 0x%06x size %zu",
|
|
flash_addr, sram_addr, size);
|
|
|
|
while (size) {
|
|
size_t l, offset;
|
|
|
|
l = min(flash->page_size, size);
|
|
offset = flash_addr & (flash->page_size - 1);
|
|
|
|
if (offset) {
|
|
l = min(flash->page_size - offset, l);
|
|
}
|
|
|
|
ret = sfctrl->ops->program_from_sram(cts_dev,
|
|
flash_addr, sram_addr, l);
|
|
if(ret) {
|
|
cts_err("Program to 0x%06x from sram 0x%06x size %zu "
|
|
"failed %d(%s)",
|
|
flash_addr, sram_addr, l, ret, cts_strerror(ret));
|
|
return ret;
|
|
}
|
|
|
|
size -= l;
|
|
flash_addr += l;
|
|
sram_addr += l;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cts_erase_flash(struct cts_device *cts_dev, u32 addr, size_t size)
|
|
{
|
|
const struct cts_sfctrl *sfctrl;
|
|
const struct cts_flash *flash;
|
|
int ret;
|
|
|
|
cts_info("Erase from 0x%06x size %zu", addr, size);
|
|
|
|
sfctrl = cts_dev->hwdata->sfctrl;
|
|
flash = cts_dev->flash;
|
|
|
|
if (flash == NULL ||
|
|
sfctrl == NULL || sfctrl->ops == NULL ||
|
|
sfctrl->ops->se == NULL || sfctrl->ops->be == NULL ||
|
|
flash == NULL) {
|
|
cts_err("Oops");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Addr and size MUST sector aligned */
|
|
addr = rounddown(addr, flash->sector_size);
|
|
size = roundup(size, flash->sector_size);
|
|
|
|
if (addr > flash->total_size) {
|
|
cts_err("Erase from 0x%06x > flash size 0x%06zx",
|
|
addr, flash->total_size);
|
|
return -EINVAL;
|
|
}
|
|
size = min(size, flash->total_size - addr);
|
|
|
|
cts_info("Erase actually from 0x%06x size %zu", addr, size);
|
|
|
|
if (flash->block_size) {
|
|
while (addr != ALIGN(addr, flash->block_size) &&
|
|
size >= flash->sector_size) {
|
|
ret = erase_sector(cts_dev, addr);
|
|
if (ret) {
|
|
cts_err("Erase sector 0x%06x size 0x%04zx failed %d(%s)",
|
|
addr, flash->sector_size, ret, cts_strerror(ret));
|
|
return ret;
|
|
}
|
|
addr += flash->sector_size;
|
|
size -= flash->sector_size;
|
|
}
|
|
|
|
while (size >= flash->block_size) {
|
|
ret = erase_block(cts_dev, addr);
|
|
if (ret) {
|
|
cts_err("Erase block 0x%06x size 0x%04zx failed %d(%s)",
|
|
addr, flash->block_size, ret, cts_strerror(ret));
|
|
return ret;
|
|
}
|
|
addr += flash->block_size;
|
|
size -= flash->block_size;
|
|
}
|
|
}
|
|
|
|
while (size >= flash->sector_size) {
|
|
ret = erase_sector(cts_dev, addr);
|
|
if (ret) {
|
|
cts_err("Erase sector 0x%06x size 0x%04zx failed %d(%s)",
|
|
addr, flash->sector_size, ret, cts_strerror(ret));
|
|
return ret;
|
|
}
|
|
addr += flash->sector_size;
|
|
size -= flash->sector_size;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|