gk7205v200-uboot/drivers/mtd/spi/fmc100/fmc100.c
2025-08-07 17:13:54 +08:00

1415 lines
37 KiB
C
Executable File

/*
* Copyright (c) Hunan Goke,Chengdu Goke,Shandong Goke. 2021. All rights reserved.
*/
/*****************************************************************************/
#include "fmc100.h"
#include <common.h>
#include <malloc.h>
#include <asm/io.h>
#include <linux/mtd/mtd.h>
#include <errno.h>
#include <asm/arch/platform.h>
/*****************************************************************************/
int write_bp_area_check(struct fmc_host *host, u_int to, size_t len)
{
#ifdef CONFIG_SPI_BLOCK_PROTECT
if (host->level) {
if ((host->cmp == BP_CMP_TOP)
&& ((to + len) > host->start_addr)) {
puts("\n");
db_msg("Reg write area[%#x => %#x] was locked\n",
host->start_addr, (to + len));
return -EINVAL;
}
if ((host->cmp == BP_CMP_BOTTOM) && (to < host->end_addr)) {
unsigned end = ((to + len) > host->end_addr) ?
host->end_addr : (to + len);
puts("\n");
db_msg("Reg write area[%#x => %#x] was locked\n", to,
end);
return -EINVAL;
}
}
#endif
return 0;
}
/*****************************************************************************/
static void fmc100_dma_transfer(struct fmc_spi *spi,
unsigned int spi_start_addr, unsigned char *dma_buffer,
unsigned char rw_op, unsigned int size)
{
unsigned char if_type = 0;
unsigned char dummy = 0;
unsigned char w_cmd = 0;
unsigned char r_cmd = 0;
unsigned int regval;
struct fmc_host *host = spi->host;
fmc_pr(DMA_DB, "\t\t *-Start dma transfer => [%#x], len[%#x], buf = %p\n",
spi_start_addr, size, dma_buffer);
regval = FMC_INT_CLR_ALL;
fmc_write(host, FMC_INT_CLR, regval);
fmc_pr(DMA_DB, "\t\t Set INT_CLR[%#x]%#x\n", FMC_INT_CLR, regval);
regval = spi_start_addr;
fmc_write(host, FMC_ADDRL, regval);
fmc_pr(DMA_DB, "\t\t Set ADDRL[%#x]%#x\n", FMC_ADDRL, regval);
if (rw_op == RW_OP_WRITE) {
if_type = spi->write->iftype;
dummy = spi->write->dummy;
w_cmd = spi->write->cmd;
} else if (rw_op == RW_OP_READ) {
if_type = spi->read->iftype;
dummy = spi->read->dummy;
r_cmd = spi->read->cmd;
}
regval = op_cfg_fm_cs(spi->chipselect) | OP_CFG_OEN_EN |
op_cfg_mem_if_type(if_type) | op_cfg_addr_num(spi->addrcycle) |
op_cfg_dummy_num(dummy);
fmc_write(host, FMC_OP_CFG, regval);
fmc_pr(DMA_DB, "\t\t Set OP_CFG[%#x]%#x\n", FMC_OP_CFG, regval);
regval = fmc_dma_len_set(size);
fmc_write(host, FMC_DMA_LEN, regval);
fmc_pr(DMA_DB, "\t\t Set DMA_LEN[%#x]%#x\n", FMC_DMA_LEN, regval);
/* get hight 32 bits */
regval = (((uintptr_t)dma_buffer & FMC_DMA_SADDRH_MASK) >> 32);
fmc_write(host, FMC_DMA_SADDRH_D0, regval);
fmc_pr(DMA_DB, "\t\t Set DMA_SADDRH_D0[%#x]%#x\n", FMC_DMA_SADDRH_D0,
regval);
regval = (unsigned int)((uintptr_t)dma_buffer);
fmc_write(host, FMC_DMA_SADDR_D0, regval);
fmc_pr(DMA_DB, "\t\t Set DMA_SADDR_D0[%#x]%#x\n", FMC_DMA_SADDR_D0,
regval);
regval = op_ctrl_rd_opcode(r_cmd) | op_ctrl_wr_opcode(w_cmd) |
op_ctrl_rw_op(rw_op) | OP_CTRL_DMA_OP_READY;
fmc_write(host, FMC_OP_CTRL, regval);
fmc_pr(DMA_DB, "\t\t Set OP_CTRL[%#x]%#x\n", FMC_OP_CTRL, regval);
fmc_dma_wait_int_finish(host);
fmc_pr(DMA_DB, "\t\t *-End dma transfer.\n");
}
/*****************************************************************************/
#ifdef FMC100_SPI_NOR_SUPPORT_REG_READ
static char *fmc100_reg_read_buf(struct fmc_host *host,
struct fmc_spi *spi,
unsigned int spi_start_addr,
unsigned int size,
unsigned char *buffer)
{
unsigned int regval;
fmc_pr(DMA_DB, "* Start reg read, from:%#x len:%#x\n", spi_start_addr,
size);
if (size > FMC100_REG_RD_MAX_SIZE)
db_bug("reg read out of reg range.\n");
fmc_write(host, FMC_ADDRL, spi_start_addr);
fmc_pr(DMA_DB, " Set ADDRL[%#x]%#x\n", FMC_ADDRL, spi_start_addr);
regval = fmc_data_num_cnt(size);
fmc_write(host, FMC_DATA_NUM, regval);
fmc_pr(DMA_DB, " Set DATA_NUM[%#x]%#x\n", FMC_DATA_NUM, regval);
regval = op_ctrl_rd_opcode(spi->read->cmd) |
op_ctrl_dma_op(OP_TYPE_REG) |
op_ctrl_rw_op(RW_OP_READ) |
OP_CTRL_DMA_OP_READY;
fmc_write(host, FMC_OP_CTRL, regval);
fmc_pr(DMA_DB, " Set OP_CTRL[%#x]%#x\n", FMC_OP_CTRL, regval);
fmc_cmd_wait_cpu_finish(host);
memcpy(buffer, host->iobase, size);
fmc_pr(DMA_DB, "*-End reg page read.\n");
return (char*)buffer;
}
/*****************************************************************************/
static int fmc100_reg_read(struct spi_flash *spiflash, u_int from,
size_t len, char *buf)
{
int num;
int chip_idx = 0;
int result = -EIO;
unsigned char *ptr = (u_char *)buf;
struct fmc_host *host = spiflash_to_host(spiflash);
struct fmc_spi *spi = host->spi;
unsigned char *fmc_ip;
if (((from + len) > spiflash->size) || (!len)) {
db_msg("read area out of range or len is zero\n");
return -EINVAL;
}
fmc_ip = get_fmc_ip();
if (*fmc_ip) {
printf("Warning: Fmc IP is busy, Please try again.\n");
udelay(100); /* delay 100 us */
return;
} else {
fmc_dev_type_switch(FLASH_TYPE_SPI_NOR);
(*fmc_ip)++;
}
if (spi->driver->wait_ready(spi)) {
db_msg("Error: Dma read wait ready fail!\n");
result = -EBUSY;
goto fail;
}
host->set_system_clock(spi->read, ENABLE);
while (len > 0) {
while (from >= spi->chipsize) {
from -= spi->chipsize;
chip_idx++;
spi++;
if ((chip_idx == CONFIG_SPI_NOR_MAX_CHIP_NUM) || (!spi->name)) {
db_bug("read memory out of range.\n");
goto fail;
}
if (spi->driver->wait_ready(spi))
goto fail;
host->set_system_clock(spi->read, ENABLE);
}
num = ((from + len) >= spi->chipsize) ? (spi->chipsize - from) : len;
while (num >= FMC100_REG_RD_MAX_SIZE) {
fmc100_reg_read_buf(host, spi,
from, FMC100_REG_RD_MAX_SIZE, ptr);
ptr += FMC100_REG_RD_MAX_SIZE;
from += FMC100_REG_RD_MAX_SIZE;
len -= FMC100_REG_RD_MAX_SIZE;
num -= FMC100_REG_RD_MAX_SIZE;
}
if (num) {
fmc100_reg_read_buf(host, spi, from, num, ptr);
from += num;
ptr += num;
len -= num;
}
}
result = 0;
fail:
(*fmc_ip)--;
return result;
}
#endif
static int dma_cycle_op(struct spi_flash *spiflash, unsigned char rw_op,
u_int from, size_t len, const void *buf)
{
int op_len = 0;
size_t num = 0;
int chip_idx = 0;
struct spi_op *op = NULL;
struct fmc_host *host = spiflash_to_host(spiflash);
struct fmc_spi *spi = host->spi;
if (rw_op == RW_OP_READ) {
op_len = FMC100_DMA_RD_MAX_SIZE;
op = spi->read;
} else {
op_len = FMC100_DMA_WR_MAX_SIZE;
op = spi->write;
}
while (len) {
num = ((len >= op_len) ? op_len : len);
while (from >= spi->chipsize) {
from -= spi->chipsize;
chip_idx++;
spi++;
if ((chip_idx == CONFIG_SPI_NOR_MAX_CHIP_NUM) || (!spi->name)) {
db_bug("read memory out of range.\n");
return -1;
}
if (spi->driver->wait_ready(spi)) {
db_msg("Error: Dma op wait ready fail!!\n");
return -EBUSY;
}
host->set_system_clock(op, ENABLE);
}
if (from + num > spi->chipsize)
num = spi->chipsize - from;
fmc100_dma_transfer(spi, from, (u_char *)buf,
rw_op, num);
from += num;
buf += num;
len -= num;
}
return 0;
}
/*****************************************************************************/
#ifndef FMC100_SPI_NOR_SUPPORT_REG_READ
static int fmc100_dma_read(struct spi_flash *spiflash, u_int from, size_t len,
unsigned char *buf)
{
int result = -EIO;
struct fmc_host *host = spiflash_to_host(spiflash);
struct fmc_spi *spi = host->spi;
unsigned char *fmc_ip = NULL;
fmc_pr(RD_DBG, "\t|*-Start dma read, from:%#x len:%#lx\n", from, (long)len);
if (((from + len) > spiflash->size) || (!len)) {
db_msg("read area out of range[%#x].\n", spiflash->size);
return -EINVAL;
}
fmc_ip = get_fmc_ip();
if (*fmc_ip) {
printf("Warning: Fmc IP is busy, Please try again.\n");
udelay(100); /* delay 100 us */
return -EBUSY;
} else {
fmc_dev_type_switch(FLASH_TYPE_SPI_NOR);
(*fmc_ip)++;
}
if (spi->driver->wait_ready(spi)) {
db_msg("Error: Dma read wait ready fail!\n");
result = -EBUSY;
goto fail;
}
host->set_system_clock(spi->read, ENABLE);
#ifndef CONFIG_SYS_DCACHE_OFF
invalidate_dcache_range((uintptr_t)buf, (uintptr_t)(buf + len));
#endif
if (dma_cycle_op(spiflash, RW_OP_READ, from, len, buf))
goto fail;
#ifndef CONFIG_SYS_DCACHE_OFF
invalidate_dcache_range((uintptr_t)buf, (uintptr_t)(buf + len));
#endif
result = 0;
fail:
(*fmc_ip)--;
fmc_pr(RD_DBG, "\t|*-End dma read.\n");
return result;
}
#endif
/*****************************************************************************/
void fmc100_read_ids(struct fmc_spi *spi, u_char cs, u_char *id)
{
unsigned int reg;
struct fmc_host *host = spi->host;
fmc_pr(BT_DBG, "\t|||*-Start Read SPI(cs:%d) ID.\n", cs);
reg = fmc_cmd_cmd1(SPI_CMD_RDID);
fmc_write(host, FMC_CMD, reg);
fmc_pr(BT_DBG, "\t||||-Set CMD[%#x]%#x\n", FMC_CMD, reg);
reg = op_cfg_fm_cs(cs) | OP_CFG_OEN_EN;
fmc_write(host, FMC_OP_CFG, reg);
fmc_pr(BT_DBG, "\t||||-Set OP_CFG[%#x]%#x\n", FMC_OP_CFG, reg);
reg = fmc_data_num_cnt(MAX_SPI_NOR_ID_LEN);
fmc_write(host, FMC_DATA_NUM, reg);
fmc_pr(BT_DBG, "\t||||-Set DATA_NUM[%#x]%#x\n", FMC_DATA_NUM, reg);
reg = fmc_op_cmd1_en(ENABLE) |
fmc_op_read_data_en(ENABLE) |
FMC_OP_REG_OP_START;
fmc_write(host, FMC_OP, reg);
fmc_pr(BT_DBG, "\t||||-Set OP[%#x]%#x\n", FMC_OP, reg);
fmc_cmd_wait_cpu_finish(host);
memcpy(id, host->iobase, MAX_SPI_NOR_ID_LEN);
fmc_pr(BT_DBG, "\t|||*-Read CS: %d ID: %#X %#X %#X %#X %#X %#X\n",
cs, id[0], id[1], id[2], id[3], id[4], id[5]); /* id 1 2 3 4 5 6 */
}
/*****************************************************************************/
static int fmc100_reg_erase_one_block(struct fmc_host *host,
struct fmc_spi *spi, unsigned int offset)
{
unsigned int regval;
fmc_pr(OP_DBG, "\t\t * Start erase one block, offset:%#x\n", offset);
regval = spi->driver->wait_ready(spi);
if (regval) {
db_msg("Error: Erase wait ready fail! reg:%#x\n", regval);
return 1;
}
spi->driver->write_enable(spi);
host->set_system_clock(spi->erase, ENABLE);
regval = fmc_cmd_cmd1(spi->erase->cmd);
fmc_write(host, FMC_CMD, regval);
fmc_pr(OP_DBG, "\t\t Set CMD[%#x]%#x\n", FMC_CMD, regval);
regval = offset;
fmc_write(host, FMC_ADDRL, regval);
fmc_pr(OP_DBG, "\t\t Set ADDRL[%#x]%#x\n", FMC_ADDRL, regval);
regval = op_cfg_fm_cs(spi->chipselect) |
OP_CFG_OEN_EN |
op_cfg_mem_if_type(spi->erase->iftype) |
op_cfg_addr_num(spi->addrcycle) |
op_cfg_dummy_num(spi->erase->dummy);
fmc_write(host, FMC_OP_CFG, regval);
fmc_pr(OP_DBG, "\t\t Set OP_CFG[%#x]%#x\n", FMC_OP_CFG, regval);
regval = fmc_op_cmd1_en(ENABLE) |
fmc_op_addr_en(ENABLE) |
FMC_OP_REG_OP_START;
fmc_write(host, FMC_OP, regval);
fmc_pr(OP_DBG, "\t\t Set OP[%#x]%#x\n", FMC_OP, regval);
fmc_cmd_wait_cpu_finish(host);
fmc_pr(OP_DBG, "\t\t * End erase one block.\n");
return 0;
}
/*****************************************************************************/
#ifndef FMC100_SPI_NOR_SUPPORT_REG_WRITE
static int fmc100_dma_write(struct spi_flash *spiflash, u_int to, size_t len,
const unsigned char *buf)
{
int result = -EIO;
struct fmc_host *host = spiflash_to_host(spiflash);
struct fmc_spi *spi = host->spi;
unsigned char *fmc_ip = NULL;
fmc_pr(WR_DBG, "\n\t\t* Start dma write, to:%#x len:%#lx\n", to, (long)len);
if (((to + len) > spiflash->size) || (!len)) {
db_msg("write data out of range or len is zero.\n");
return -EINVAL;
}
#ifdef CONFIG_SPI_BLOCK_PROTECT
if (host->level && (to < host->end_addr)) {
printf("\nERROR: The DMA write area was locked.\n");
return -EINVAL;
}
#endif
fmc_ip = get_fmc_ip();
if (*fmc_ip) {
printf("Warning: Fmc IP is busy, Please try again.\n");
udelay(100); /* delay 100 us */
return -EBUSY;
} else {
fmc_dev_type_switch(FLASH_TYPE_SPI_NOR);
(*fmc_ip)++;
}
if (spi->driver->wait_ready(spi))
goto fail;
spi->driver->write_enable(spi);
host->set_system_clock(spi->write, ENABLE);
#ifndef CONFIG_SYS_DCACHE_OFF
flush_dcache_range((uintptr_t)buf, (uintptr_t)(buf + len));
#endif
if (dma_cycle_op(spiflash, RW_OP_WRITE, to, len, buf))
goto fail;
result = 0;
fail:
(*fmc_ip)--;
fmc_pr(WR_DBG, "\t\t* End dma write.\n");
return result;
}
#endif
/*****************************************************************************/
#ifdef FMC100_SPI_NOR_SUPPORT_REG_WRITE
static int fmc100_reg_write_buf(struct fmc_host *host,
struct fmc_spi *spi, u_int spi_start_addr,
size_t size, unsigned char *buffer)
{
if (size > FMC100_DMA_WR_MAX_SIZE)
db_bug("reg read out of reg range.\n");
if (spi->driver->wait_ready(spi))
return 1;
memcpy((char *)host->iobase, buffer, size);
spi->driver->write_enable(spi);
fmc_write(host, FMC_INT_CLR, FMC_INT_CLR_ALL);
fmc_write(host, FMC_CMD, fmc_cmd_cmd1(spi->write->cmd));
fmc_write(host, FMC_OP_CFG, op_cfg_fm_cs(spi->chipselect) |
op_cfg_mem_if_type(spi->write->iftype) | OP_CFG_OEN_EN);
fmc_write(host, FMC_ADDRL, spi_start_addr);
fmc_write(host, FMC_OP_CTRL, (op_ctrl_wr_opcode(spi->write->cmd) |
op_ctrl_dma_op(OP_TYPE_REG) |
op_ctrl_rw_op(RW_OP_WRITE) |
OP_CTRL_DMA_OP_READY));
FMC_DMA_WAIT_CPU_FINISH(host);
return 0;
}
static int cycle_write(struct fmc_spi *spi, u_int to, size_t len,
unsigned char *ptr, int num)
{
int chip_idx = 0;
while (to >= spi->chipsize) {
to -= spi->chipsize;
chip_idx++;
spi++;
if ((chip_idx == CONFIG_SPI_NOR_MAX_CHIP_NUM) || (!spi->name)) {
db_bug("write memory out of range.\n");
return -1;
}
if (spi->driver->wait_ready(spi))
return -1;
host->set_system_clock(spi->write, ENABLE);
}
if (fmc100_reg_write_buf(host, spi, to, num, ptr))
return -1;
to += num;
ptr += num;
len -= num;
return 0;
}
/*****************************************************************************/
static int fmc100_reg_write(struct spi_flash *spiflash, u_int to, size_t len,
const unsigned char *buf)
{
int num;
int result = -EIO;
unsigned char *ptr = (unsigned char *)buf;
struct fmc_host *host = spiflash_to_host(spiflash);
struct fmc_spi *spi = host->spi;
unsigned char *fmc_ip;
if ((to + len) > spiflash->size) {
db_msg("write data out of range.\n");
return -EINVAL;
}
if (!len) {
db_msg("write length is 0.\n");
return 0;
}
if (write_bp_area_check(host, to, len))
return -EINVAL;
fmc_ip = get_fmc_ip();
if (*fmc_ip) {
printf("Warning: Fmc IP is busy, Please try again.\n");
udelay(100); /* delay 100 us */
return;
} else {
fmc_dev_type_switch(FLASH_TYPE_SPI_NOR);
(*fmc_ip)++;
}
if (spi->driver->wait_ready(spi))
goto fail;
host->set_system_clock(spi->write, ENABLE);
if (to & FMC100_DMA_WR_MASK) {
num = FMC100_DMA_WR_MAX_SIZE - (to & FMC100_DMA_WR_MASK);
if (num > (int)len)
num = (int)len;
if (cycle_write(spi, to, len, ptr, num)) {
result = -1;
goto fail;
}
}
while (len > 0) {
num = ((len >= FMC100_DMA_WR_MAX_SIZE) ?
FMC100_DMA_WR_MAX_SIZE : len);
if (cycle_write(spi, to, len, ptr, num)) {
result = -1;
goto fail;
}
}
result = 0;
fail:
(*fmc_ip)--;
return result;
}
#endif /* FMC100_SPI_NOR_SUPPORT_REG_WRITE */
/*****************************************************************************/
static int fmc100_reg_erase(struct spi_flash *spiflash, u_int offset,
size_t length)
{
int chip_idx = 0;
struct fmc_host *host = spiflash_to_host(spiflash);
struct fmc_spi *spi = host->spi;
unsigned char *fmc_ip = NULL;
if (offset + length > spiflash->size) {
db_msg("Error: Erase area out of range of mtd.\n");
return -EINVAL;
}
if (offset & (spi->erasesize - 1)) {
db_msg("Error: Erase start address is not alignment.\n");
return -EINVAL;
}
if (length & (spi->erasesize - 1)) {
db_msg("Error: Erase length is not alignment.\n");
return -EINVAL;
}
#ifdef CONFIG_SPI_BLOCK_PROTECT
if ((host->level) && (offset < host->end_addr)) {
printf("\nERROR: The erase area was locked.\n");
return -EINVAL;
}
#endif
fmc_ip = get_fmc_ip();
if (*fmc_ip) {
printf("Warning: Fmc IP is busy, Please try again.\n");
udelay(100); /* delay 100 us */
return -EBUSY;
} else {
fmc_dev_type_switch(FLASH_TYPE_SPI_NOR);
(*fmc_ip)++;
}
while (length) {
if (spi->chipsize <= offset) {
offset -= spi->chipsize;
chip_idx++;
spi++;
if ((chip_idx == CONFIG_SPI_NOR_MAX_CHIP_NUM) || (!spi->name)) {
db_bug("Error: Erase memory out of range.\n");
(*fmc_ip)--;
return -1;
}
}
if (fmc100_reg_erase_one_block(host, spi, offset)) {
(*fmc_ip)--;
return -1;
}
offset += spi->erase->size;
length -= spi->erase->size;
}
(*fmc_ip)--;
return 0;
}
/*****************************************************************************/
static void spi_nor_func_hook(struct fmc_host *host)
{
struct spi_flash *spi_nor_flash = host->spi_nor_flash;
#ifdef CONFIG_SPI_BLOCK_PROTECT
host->cmp = BP_CMP_UPDATE_FLAG;
fmc100_get_bp_lock_level(host);
spi_nor_flash->lock = fmc100_spi_flash_lock;
#endif
fmc_pr(BT_DBG, "\t||-Initial spi_flash structure\n");
spi_nor_flash->name = "bsp_fmc";
spi_nor_flash->erase = fmc100_reg_erase;
#ifdef FMC100_SPI_NOR_SUPPORT_REG_WRITE
spi_nor_flash->write = fmc100_reg_write;
#else
spi_nor_flash->write = fmc100_dma_write;
#endif
#ifdef FMC100_SPI_NOR_SUPPORT_REG_READ
spi_nor_flash->read = fmc100_reg_read;
#else
spi_nor_flash->read = fmc100_dma_read;
#endif
}
/*****************************************************************************/
struct spi_flash *fmc100_spi_nor_scan(struct fmc_host *host)
{
unsigned char cs;
struct spi_flash *spi_nor_flash = host->spi_nor_flash;
struct fmc_spi *spi = host->spi;
struct mtd_info_ex *spi_nor_info = host->spi_nor_info;
fmc_pr(BT_DBG, "\t|*-Start Scan SPI nor flash\n");
spi_nor_flash->size = 0;
for (cs = 0; cs < CONFIG_SPI_NOR_MAX_CHIP_NUM; cs++)
host->spi[cs].host = host;
fmc_pr(BT_DBG, "\t||-Initial mtd_info_ex structure\n");
memset(spi_nor_info, 0, sizeof(struct mtd_info_ex));
if (!fmc_spi_nor_probe(spi_nor_info, spi)) {
#ifndef CONFIG_SPI_NOR_QUIET_TEST
printf("Can't find a valid spi nor flash chip.\n");
#endif
return NULL;
}
if (spi->addrcycle == SPI_NOR_4BYTE_ADDR_LEN) {
host->set_host_addr_mode(host, ENABLE);
fmc_pr(AC_DBG, "\t* Controller entry 4-byte mode.\n");
}
spi_nor_func_hook(host);
#ifdef CONFIG_DTR_MODE_SUPPORT
if (spi->dtr_mode_support) {
int ret;
host->dtr_training_flag = 0;
/* setting DTR mode dummy and traning */
ret = spi_dtr_dummy_training_set(host, ENABLE);
/* If training setting fail, must reset back to SDR mode,
* Note: logic auto turn on DTR when read
* auto turn off DTR when write and erase */
if (ret) {
printf("Reset to STR mode.\n");
/* switch DTR mode to SDR mode */
fmc_dtr_mode_ctrl(spi, DISABLE);
spi_dtr_to_sdr_switch(spi);
spi_dtr_dummy_training_set(host, DISABLE);
}
}
#endif
fmc_pr(BT_DBG, "\t|*-Found SPI nor flash: %s\n", spi_nor_info->name);
return spi_nor_flash;
}
/*****************************************************************************/
static void fmc100_set_host_addr_mode(struct fmc_host *host, int enable)
{
unsigned int regval = fmc_read(host, FMC_CFG);
/* 1: SPI_NOR_ADDR_MODE_4_BYTES 0: SPI_NOR_ADDR_MODE_3_BYTES */
if (enable)
regval |= SPI_NOR_ADDR_MODE_MASK;
else
regval &= ~SPI_NOR_ADDR_MODE_MASK;
fmc_write(host, FMC_CFG, regval);
}
/*****************************************************************************/
static int fmc100_host_init(struct fmc_host *host)
{
unsigned int reg, flash_type;
fmc_pr(BT_DBG, "\t|||*-Start SPI Nor host init\n");
host->regbase = (void *)CONFIG_FMC_REG_BASE;
host->iobase = (void *)CONFIG_FMC_BUFFER_BASE;
reg = fmc_read(host, FMC_CFG);
fmc_pr(BT_DBG, "\t||||-Get CFG[%#x]%#x\n", FMC_CFG, reg);
flash_type = (reg & FLASH_SEL_MASK) >> FLASH_SEL_SHIFT;
if (flash_type != FLASH_TYPE_SPI_NOR) {
db_msg("Error: Flash type isn't SPI Nor. reg: %#x\n", reg);
return -ENODEV;
}
if ((reg & OP_MODE_MASK) == OP_MODE_BOOT) {
reg |= fmc_cfg_op_mode(OP_MODE_NORMAL);
fmc_pr(BT_DBG, "\t||||-Controller enter normal mode\n");
fmc_write(host, FMC_CFG, reg);
fmc_pr(BT_DBG, "\t||||-Set CFG[%#x]%#x\n", FMC_CFG, reg);
}
host->set_system_clock = fmc_set_fmc_system_clock;
host->set_host_addr_mode = fmc100_set_host_addr_mode;
reg = timing_cfg_tcsh(CS_HOLD_TIME) |
timing_cfg_tcss(CS_SETUP_TIME) |
timing_cfg_tshsl(CS_DESELECT_TIME);
fmc_write(host, FMC_SPI_TIMING_CFG, reg);
fmc_pr(BT_DBG, "\t||||-Set TIMING[%#x]%#x\n", FMC_SPI_TIMING_CFG, reg);
fmc_pr(BT_DBG, "\t|||*-End SPI Nor host init\n");
return 0;
}
/*****************************************************************************/
int fmc100_spi_nor_init(struct fmc_host *host)
{
int ret;
fmc_pr(BT_DBG, "\t||*-Start fmc100 SPI Nor init\n");
fmc_pr(BT_DBG, "\t|||-fmc100 host structure init\n");
ret = fmc100_host_init(host);
if (ret) {
db_msg("Error: SPI Nor host init failed, result: %d\n", ret);
return ret;
}
fmc_pr(BT_DBG, "\t|||-Set default system clock, Enable controller\n");
if (host->set_system_clock)
host->set_system_clock(NULL, ENABLE);
fmc_pr(BT_DBG, "\t||*-End fmc100 SPI Nor init\n");
return ret;
}
#ifdef CONFIG_SPI_BLOCK_PROTECT
/*****************************************************************************/
void spi_lock_update_address(struct fmc_host *host)
{
unsigned int lock_level_max, erasesize, chipsize;
unsigned char mid = host->spi_nor_info->ids[0];
struct fmc_spi *spi = host->spi;
if (!host->level) {
host->end_addr = 0;
fmc_pr(BP_DBG, "all blocks is unlocked.\n");
return;
}
chipsize = spi->chipsize;
erasesize = spi->erasesize;
lock_level_max = host->spi_nor_flash[0].bp_level_max;
switch (mid) {
case MID_MXIC:
if (spi->chipsize == _2M) {
if ((host->level != lock_level_max) &&
(host->level != 1))
host->end_addr = chipsize - (erasesize <<
(lock_level_max - host->level - 1));
else
host->end_addr = chipsize;
return;
}
if (spi->chipsize != _8M)
break;
/* general case */
case MID_ESMT:
/* this case is for ESMT and MXIC 8M devices */
if (host->level != lock_level_max)
host->end_addr = chipsize - (erasesize <<
(lock_level_max - host->level));
else
host->end_addr = chipsize;
return;
case MID_CFEON:
if (host->level != lock_level_max)
host->end_addr = chipsize - (erasesize <<
(host->level - 1));
else
host->end_addr = chipsize;
return;
default:
break;
}
/* general case */
host->end_addr = chipsize >> (lock_level_max - host->level);
}
/*****************************************************************************/
void fmc100_get_bp_lock_level(struct fmc_host *host)
{
struct fmc_spi *spi = host->spi;
unsigned char mid = host->spi_nor_info->ids[0];
unsigned int lock_level_max;
fmc_pr(BP_DBG, "Get manufacturer ID: [%#x]\n", mid);
/* match the manufacture ID to get the block protect info */
switch (mid) {
case MID_GD:
case MID_ESMT:
case MID_CFEON:
case MID_SPANSION:
host->bp_num = BP_NUM_3;
host->level = fmc100_bp_to_level(host);
break;
case MID_WINBOND:
if (spi->chipsize <= _16M) {
host->bp_num = BP_NUM_3;
host->level = fmc100_bp_to_level(host);
} else {
host->bp_num = BP_NUM_4;
host->level = fmc100_bp_to_level(host);
}
break;
case MID_MXIC:
if (spi->chipsize <= _8M) {
host->bp_num = BP_NUM_3;
host->level = fmc100_bp_to_level(host);
} else {
host->bp_num = BP_NUM_4;
host->level = fmc100_bp_to_level(host);
}
break;
case MID_MICRON:
case MID_PARAGON:
return;
default:
goto usage;
}
/* this branch only for initialization */
if (host->cmp == BP_CMP_UPDATE_FLAG) {
/* get the max block protect level of current manufacture ID */
if (host->bp_num == BP_NUM_4) {
lock_level_max = lock_level_max(host->bp_num) - BP_NUM_5;
/* just for MXIC(16M), the max lock level is 9 */
if ((mid == MID_MXIC) && (spi->chipsize == _16M))
lock_level_max--;
} else {
lock_level_max = lock_level_max(host->bp_num);
}
host->spi_nor_flash[0].bp_level_max = lock_level_max;
fmc_pr(BP_DBG, "Get the max bp level: [%d]\n", lock_level_max);
spi_lock_update_address(host);
if (host->end_addr)
printf("Spi is locked. lock address[0 => %#x]\n",
host->end_addr);
}
return;
usage:
db_msg("Error:The ID: %#x isn't in the BP table,\n", mid);
db_msg("Current device can't not protect\n");
}
/*****************************************************************************/
unsigned short fmc100_set_spi_lock_info(struct fmc_host *host)
{
unsigned short val;
unsigned char mid = host->spi_nor_info->ids[0];
struct fmc_spi *spi = host->spi;
fmc_pr(BP_DBG, "Get manufacturer ID: [%#x]\n", mid);
/* match the manufacture ID to get the block protect set info */
switch (mid) {
case MID_SPANSION:
val = fmc100_handle_bp_rdcr_info(host, SPI_CMD_RDCR);
break;
case MID_MXIC:
if (spi->chipsize < _16M)
val = fmc100_handle_bp_rdsr_info(host, SPI_CMD_RDSR);
else
val = fmc100_handle_bp_rdcr_info(host,
SPI_CMD_RDCR_MX);
break;
case MID_GD:
case MID_ESMT:
case MID_CFEON:
case MID_WINBOND:
val = fmc100_handle_bp_rdsr_info(host, SPI_CMD_RDSR);
break;
default:
goto usage;
}
return val;
usage:
db_msg("Error: The manufacture ID is change,\n Pleaer check!!\n");
db_msg("Error: The ID: %#x isn't in the BP table,\n", mid);
return 1;
}
/*****************************************************************************/
unsigned short fmc100_handle_bp_rdcr_info(struct fmc_host *host, u_char cmd)
{
unsigned char status, config;
struct fmc_spi *spi = host->spi;
unsigned char mid = host->spi_nor_info->ids[0];
/* this macro definition is for determining the writing length */
host->bt_loc = BT_LOC_RDCR;
spi->driver->wait_ready(spi);
/* get the block protect B/P info in config register */
config = spi_general_get_flash_register(spi, cmd);
fmc_pr(BP_DBG, "Get Config Register[%#x]\n", config);
/* the location of T/B bit in config register is different.
SPANSION is 5th and the MXIC(16/32M) is 3th */
if (mid == MID_SPANSION) {
config = spi_bp_bottom_rdcr_set_s(config);
fmc_pr(BP_DBG, "Set Config Register[%#x]\n", config);
} else {
config = spi_bp_bottom_rdcr_set(config);
fmc_pr(BP_DBG, "Set Config Register[%#x]\n", config);
}
/* get the block protect level info in status register */
status = spi_general_get_flash_register(spi, SPI_CMD_RDSR);
fmc_pr(BP_DBG, "Get Status Register[%#x]\n", status);
return ((unsigned short)config << SPI_NOR_CR_SHIFT) | status;
}
/*****************************************************************************/
unsigned short fmc100_handle_bp_rdsr_info(struct fmc_host *host,
u_char cmd)
{
unsigned char val;
struct fmc_spi *spi = host->spi;
unsigned char mid = host->spi_nor_info->ids[0];
/* this macro definition is for determining the writing length */
host->bt_loc = BT_LOC_RDSR;
spi->driver->wait_ready(spi);
/* get the block protect level and B/T info in status register */
val = spi_general_get_flash_register(spi, cmd);
fmc_pr(BP_DBG, "Get Status Register[%#x]\n", val);
if (mid == MID_CFEON)
val &= spi_bp_bottom_rdsr_set_0(host->bp_num);
else
val |= spi_bp_bottom_rdsr_set_1(host->bp_num);
fmc_pr(BP_DBG, "Set Config Register[%#x]\n", val);
return val;
}
/*****************************************************************************/
static void fmc100_set_bp_val(struct fmc_host *host, unsigned short val)
{
unsigned int reg;
struct fmc_spi *spi = host->spi;
reg = fmc_read(host, FMC_GLOBAL_CFG);
if (reg & FMC_GLOBAL_CFG_WP_ENABLE) {
fmc_pr(BP_DBG, " Hardware protected enable!, reg[%#x]\n", reg);
reg &= ~FMC_GLOBAL_CFG_WP_ENABLE;
fmc_write(host, FMC_GLOBAL_CFG, reg);
val &= ~SPI_NOR_SR_SRWD_MASK;
fmc_pr(BP_DBG, "Disable SR[%d]:SRWD and WP#\n",
SPI_NOR_SR_SRWD_SHIFT);
}
if (host->bt_loc == BT_LOC_RDSR) {
writeb(val, host->iobase);
fmc_pr(BP_DBG, "Write IO[%p]%#x\n", host->iobase,
*(unsigned char *)host->iobase);
} else {
writew(val, host->iobase);
fmc_pr(BP_DBG, "Write IO[%p]%#x\n", host->iobase,
*(unsigned short *)host->iobase);
}
reg = fmc_cmd_cmd1(SPI_CMD_WRSR);
fmc_write(host, FMC_CMD, reg);
fmc_pr(BP_DBG, " Set CMD[%#x]%#x\n", FMC_CMD, reg);
reg = op_cfg_fm_cs(spi->chipselect) | OP_CFG_OEN_EN;
fmc_write(host, FMC_OP_CFG, reg);
fmc_pr(BP_DBG, " Set OP_CFG[%#x]%#x\n", FMC_OP_CFG, reg);
if (host->bt_loc == BT_LOC_RDSR)
reg = fmc_data_num_cnt(SPI_NOR_SR_LEN);
else
reg = fmc_data_num_cnt(SPI_NOR_SR_LEN + SPI_NOR_CR_LEN);
fmc_write(host, FMC_DATA_NUM, reg);
fmc_pr(BP_DBG, " Set DATA_NUM[%#x]%#x\n", FMC_DATA_NUM, reg);
reg = fmc_op_cmd1_en(ENABLE) |
fmc_op_write_data_en(ENABLE) |
FMC_OP_REG_OP_START;
fmc_write(host, FMC_OP, reg);
fmc_pr(BP_DBG, " Set OP[%#x]%#x\n", FMC_OP, reg);
}
/*****************************************************************************/
static void fmc100_set_bp_level(struct fmc_host *host, unsigned char level)
{
unsigned char old_level;
unsigned short val;
struct fmc_spi *spi = host->spi;
unsigned char mid = host->spi_nor_info->ids[0];
fmc_pr(BP_DBG, "* Start BP bottom level %d\n", level);
val = fmc100_set_spi_lock_info(host);
old_level = host->level;
fmc_pr(BP_DBG, " Read CR:SR[%#x]\n", val);
if (old_level != level) {
if (host->bp_num == BP_NUM_3)
val &= ~SPI_NOR_SR_BP_MASK_3;
else
val &= ~SPI_NOR_SR_BP_MASK_4;
val |= level << SPI_NOR_SR_BP0_SHIFT;
fmc_pr(BP_DBG, "Set Status Register[%#x]\n", val);
} else {
fmc_pr(BP_DBG, "NOTES: old_level[%#x] = level[%#x]\n",
old_level, level);
return;
}
if (((mid == MID_ESMT) || ((mid == MID_MXIC)
&& (spi->chipsize < _16M))) && (level == 0)) {
val &= spi_bp_bottom_rdsr_set_0(host->bp_num);
fmc_pr(BP_DBG, "set level = 0[PB3 = 0]:[%#x]\n", val);
}
spi->driver->write_enable(spi);
fmc100_set_bp_val(host, val);
fmc_cmd_wait_cpu_finish(host);
fmc_pr(BP_DBG, "* Set BP level end.\n");
}
/*****************************************************************************/
void fmc100_spi_lock(struct fmc_host *host, unsigned char level)
{
unsigned char current_level;
fmc100_set_bp_level(host, level);
/* check if we have set successfully or not */
current_level = fmc100_bp_to_level(host);
if (current_level != level) {
db_msg("Error: Current lock level: %d, but set value: %d\n",
current_level, level);
return;
}
host->level = level;
spi_lock_update_address(host);
if (host->end_addr)
printf("Spi is locked. lock address[0 => %#x]\n",
host->end_addr);
return;
}
/*****************************************************************************/
unsigned char fmc100_bp_to_level(struct fmc_host *host)
{
unsigned char val;
unsigned char level;
struct fmc_spi *spi = host->spi;
spi->driver->wait_ready(spi);
val = spi_general_get_flash_register(spi, SPI_CMD_RDSR);
fmc_pr(BP_DBG, "Get Status Register[%#x]\n", val);
fmc_pr(BP_DBG, "the bp_num[%d]\n", host->bp_num);
if (host->bp_num == BP_NUM_3)
level = (val & SPI_NOR_SR_BP_MASK_3) >> SPI_NOR_SR_BP0_SHIFT;
else
level = (val & SPI_NOR_SR_BP_MASK_4) >> SPI_NOR_SR_BP0_SHIFT;
fmc_pr(BP_DBG, "the current level[%d]\n", level);
return level;
}
/*****************************************************************************/
#endif /* CONFIG_SPI_BLOCK_PROTECT */
#ifdef CONFIG_DTR_MODE_SUPPORT
/*****************************************************************************/
int spi_dtr_dummy_training_set(struct fmc_host *host, int dtr_en)
{
struct fmc_spi *spi = host->spi;
int ret;
switch (spi->dtr_cookie) {
case DTR_MODE_SET_ODS:
if (spi->driver->dtr_set_device)
spi->driver->dtr_set_device(spi, dtr_en);
break;
case DTR_MODE_SET_NONE:
default:
break;
}
/* disable DTR mode without training */
/* dtr dummy training is done, return it */
if ((host->dtr_training_flag == 1) || (dtr_en == DISABLE))
return 0;
/* enable DTR mode and set sample point */
fmc_dtr_mode_ctrl(spi, ENABLE);
/* set training */
ret = spi_dtr_training(host);
if (ret) {
fmc_pr(DTR_DB, " * Set dtr training fail.\n");
return 1;
}
fmc_pr(DTR_DB, "* Set dtr and dummy end.\n");
return 0;
}
/*****************************************************************************/
static int select_sample_point(unsigned char *status, int len)
{
int ix = 0;
unsigned int p_count = 0;
unsigned int p_temp = 0;
unsigned int reg = 0;
if (len <= 0)
return 0;
/* select the best smaple point */
for (ix = 0; ix < len;) {
if (status[ix] == 1) {
p_count++;
ix++;
if ((status[ix] == 0) && (p_count > p_temp)) {
p_temp = p_count;
p_count = 0;
reg = ix - ((p_temp + 1) >> 1);
fmc_pr(DTR_DB, "the sample point choice: %#x\n",
reg);
break;
}
continue;
}
ix++;
}
return reg;
}
/*****************************************************************************/
static int dtr_training_handle(struct fmc_host *host)
{
int ret = 0;
int ix = 0;
unsigned int reg = 0;
unsigned int regval;
unsigned char *buf = NULL;
unsigned char status[DTR_TRAINING_POINT_NUM] = {0};
struct fmc_spi *spi = host->spi;
spi->driver->wait_ready(spi);
/* set div 4 clock */
host->set_system_clock(spi->read, ENABLE);
buf = memalign(CONFIG_SYS_CACHELINE_SIZE, DTR_TRAINING_CMP_LEN);
if (!buf)
return -1;
/* start training to check every sample point */
regval = fmc_read(host, FMC_GLOBAL_CFG);
for (ix = 0; ix < DTR_TRAINING_POINT_NUM; ix++) {
regval = dtr_training_point_clr(regval);
regval |= (ix << DTR_TRAINING_POINT_MASK);
fmc_pr(DTR_DB, " setting the dtr training point:%d\n", ix);
fmc_write(host, FMC_GLOBAL_CFG, regval);
fmc_pr(DTR_DB, " Set dtr_training[%#x]%#x\n",
FMC_GLOBAL_CFG, regval);
/* read */
#ifndef CONFIG_SYS_DCACHE_OFF
invalidate_dcache_range((uintptr_t)buf,
(uintptr_t)(buf + DTR_TRAINING_CMP_LEN));
#endif
fmc100_dma_transfer(spi, DTR_TRAINING_CMP_ADDR_SHIFT, buf,
RW_OP_READ, DTR_TRAINING_CMP_LEN);
#ifndef CONFIG_SYS_DCACHE_OFF
invalidate_dcache_range((uintptr_t)buf,
(uintptr_t)(buf + DTR_TRAINING_CMP_LEN));
#endif
ret = memcmp((const void *)buf,
(const void *)DTR_TRAINING_CMP_ADDR_S,
DTR_TRAINING_CMP_LEN);
if (ret == 0) {
/* Just to reduce the use of variables, no other reasion */
reg = 1;
status[ix] = 1; /* like */
fmc_pr(DTR_DB, " status[%d] = 1\n", ix);
}
if (!reg && (ix == DTR_TRAINING_POINT_NUM - 1))
goto fail_training;
}
kfree(buf);
/* select the best smaple point */
reg = 0;
reg = select_sample_point(status, DTR_TRAINING_POINT_NUM);
/* to set the best sample point */
regval = dtr_training_point_clr(regval);
regval |= (reg << DTR_TRAINING_POINT_MASK);
fmc_pr(DTR_DB, " set the sample point[%#x]%#x\n",
FMC_GLOBAL_CFG, regval);
fmc_write(host, FMC_GLOBAL_CFG, regval);
/* training handle end */
return regval;
fail_training:
printf("Cannot find an useful sample point.\n");
kfree(buf);
return -1;
}
/*****************************************************************************/
unsigned int spi_dtr_training(struct fmc_host *host)
{
int reg, cur_reg;
fmc_pr(DTR_DB, "DTR traiining start ...\n");
/* DTR traiining start */
reg = dtr_training_handle(host);
if (reg == -1) {
host->dtr_training_flag = 0;
printf("DTR traiining fail.\n");
return 1;
}
fmc_pr(DTR_DB, "* DTR traiining end.\n");
cur_reg = fmc_read(host, FMC_GLOBAL_CFG);
/* to check whether training is done */
if (cur_reg == reg) {
host->dtr_training_flag = 1;
fmc_pr(DTR_DB, "* Set dtr_training seccess.\n");
return 0;
}
return 1;
}
/*****************************************************************************/
void fmc_dtr_mode_ctrl(struct fmc_spi *spi, int dtr_en)
{
unsigned int regval;
struct fmc_host *host = (struct fmc_host *)spi->host;
host->dtr_mode_en = dtr_en;
regval = fmc_read(host, FMC_GLOBAL_CFG);
if (dtr_en == ENABLE) {
/* enable DTR mode and set the DC value */
regval |= (1 << DTR_MODE_REQUEST_SHIFT);
fmc_write(host, FMC_GLOBAL_CFG, regval);
fmc_pr(DTR_DB, " enable dtr mode[%#x]%#x\n",
FMC_GLOBAL_CFG, regval);
} else {
/* disable DTR mode */
regval &= (~(1 << DTR_MODE_REQUEST_SHIFT));
fmc_write(host, FMC_GLOBAL_CFG, regval);
fmc_pr(DTR_DB, " disable dtr mode[%#x]%#x\n",
FMC_GLOBAL_CFG, regval);
}
}
/*****************************************************************************/
void fmc_check_spi_dtr_support(struct fmc_spi *spi, u_char *ids, int len)
{
unsigned char manu_id = 0;
unsigned char dev_id = 0;
if(len < 2){
return;
}
manu_id = ids[0];
dev_id = ids[1];
spi->dtr_mode_support = 0;
spi->dtr_cookie = DTR_MODE_SET_NONE;
switch (manu_id) {
case MID_MXIC:
if (spi_mxic_check_spi_dtr_support(spi)) {
spi->dtr_cookie = DTR_MODE_SET_ODS;
goto dtr_on;
}
break;
case MID_WINBOND:
/* Device ID: 0x70 means support DTR Mode for Winbond */
if (dev_id == DEVICE_ID_SUPPORT_DTR_WINBOND) {
spi->dtr_mode_support = 1;
goto dtr_on;
}
break;
default:
break;
}
fmc_pr(DTR_DB, "The Double Transfer Rate Read Mode isn't supported.\n");
return;
dtr_on:
fmc_pr(FMC_INFO, "The Double Transfer Rate Read Mode is supported.\n");
}
#endif /* CONFIG_DTR_MODE_SUPPORT */
/*****************************************************************************/
void fmc100_op_reg(struct fmc_spi *spi, unsigned char opcode,
unsigned int len, unsigned char optype)
{
struct fmc_host *host = (struct fmc_host *)spi->host;
u32 regval;
regval = fmc_cmd_cmd1(opcode);
fmc_write(host, FMC_CMD, regval);
regval = fmc_data_num_cnt(len);
fmc_write(host, FMC_DATA_NUM, regval);
regval = op_cfg_fm_cs(spi->chipselect) | OP_CFG_OEN_EN;
fmc_write(host, FMC_OP_CFG, regval);
regval = fmc_op_cmd1_en(ENABLE) | FMC_OP_REG_OP_START | optype;
fmc_write(host, FMC_OP, regval);
fmc_cmd_wait_cpu_finish(host);
}