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;
}