firmware/br-ext-chip-goke/board/gk7205v200/kernel/patches/00_drivers-mtd-nand-gkfmc10...

1203 lines
33 KiB
Diff

--- linux-4.9.37/drivers/mtd/nand/gkfmc100/fmc100.c 1970-01-01 03:00:00.000000000 +0300
+++ linux-4.9.y/drivers/mtd/nand/gkfmc100/fmc100.c 2021-06-07 13:01:33.000000000 +0300
@@ -0,0 +1,1199 @@
+/*
+ * Copyright (c) Hunan Goke,Chengdu Goke,Shandong Goke. 2021. All rights reserved.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/mtd/nand.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <asm/setup.h>
+
+#include "../nfc_gen.h"
+#include "fmc100.h"
+
+/*****************************************************************************/
+static void fmc100_switch_to_spi_nand(struct fmc_host *host)
+{
+ u32 reg;
+
+ reg = fmc_readl(host, FMC_CFG);
+ reg &= ~FLASH_TYPE_SEL_MASK;
+ reg |= FMC_CFG_FLASH_SEL(FLASH_TYPE_SPI_NAND);
+ fmc_writel(host, FMC_CFG, reg);
+}
+
+/*****************************************************************************/
+static void fmc100_set_str_mode(struct fmc_host *host)
+{
+ u32 reg;
+
+ reg = fmc_readl(host, FMC_GLOBAL_CFG);
+ reg &= (~FMC_GLOBAL_CFG_DTR_MODE);
+ fmc_writel(host, FMC_GLOBAL_CFG, reg);
+}
+
+/*****************************************************************************/
+static void fmc100_operation_config(struct fmc_host *host, int op)
+{
+ int ret, clkrate = 0;
+ struct fmc_spi *spi = host->spi;
+
+ fmc100_switch_to_spi_nand(host);
+ clk_prepare_enable(host->clk);
+ switch (op) {
+ case OP_STYPE_WRITE:
+ clkrate = min((u_long)host->clkrate,
+ (u_long)CLK_FMC_TO_CRG_MHZ(spi->write->clock));
+ break;
+ case OP_STYPE_READ:
+ clkrate = min((u_long)host->clkrate,
+ (u_long)CLK_FMC_TO_CRG_MHZ(spi->read->clock));
+ break;
+ case OP_STYPE_ERASE:
+ clkrate = min((u_long)host->clkrate,
+ (u_long)CLK_FMC_TO_CRG_MHZ(spi->erase->clock));
+ break;
+ default:
+ break;
+ }
+
+ ret = clk_set_rate(host->clk, clkrate);
+ if (WARN_ON(ret)) {
+ pr_err("clk_set_rate failed: %d\n", ret);
+ }
+}
+
+/*****************************************************************************/
+static void fmc100_send_cmd_write(struct fmc_host *host)
+{
+ unsigned char pages_per_block_shift;
+ unsigned int reg, block_num, block_num_h, page_num;
+ struct fmc_spi *spi = host->spi;
+ struct nand_chip *chip = host->chip;
+#ifdef FMC100_SPI_NAND_SUPPORT_REG_WRITE
+ const char *op = "Reg";
+#else
+ const char *op = "Dma";
+#endif
+
+ if (WR_DBG) {
+ pr_info("\n");
+ }
+ FMC_PR(WR_DBG, "*-Start send %s page write command\n", op);
+
+ mutex_lock(host->lock);
+ fmc100_operation_config(host, OP_STYPE_WRITE);
+
+ reg = spi->driver->wait_ready(spi);
+ if (reg) {
+ DB_MSG("Error: %s program wait ready failed! status: %#x\n",
+ op, reg);
+ goto end;
+ }
+
+ reg = spi->driver->write_enable(spi);
+ if (reg) {
+ DB_MSG("Error: %s program write enable failed! reg: %#x\n",
+ op, reg);
+ goto end;
+ }
+
+ reg = FMC_INT_CLR_ALL;
+ fmc_writel(host, FMC_INT_CLR, reg);
+ FMC_PR(WR_DBG, "|-Set INT_CLR[%#x]%#x\n", FMC_INT_CLR, reg);
+
+ reg = OP_CFG_FM_CS(host->cmd_op.cs)
+ | OP_CFG_MEM_IF_TYPE(spi->write->iftype)
+ | OP_CFG_OEN_EN;
+ fmc_writel(host, FMC_OP_CFG, reg);
+ FMC_PR(WR_DBG, "|-Set OP_CFG[%#x]%#x\n", FMC_OP_CFG, reg);
+
+ pages_per_block_shift = chip->phys_erase_shift - chip->page_shift;
+ block_num = host->addr_value[1] >> pages_per_block_shift;
+ block_num_h = block_num >> REG_CNT_HIGH_BLOCK_NUM_SHIFT;
+ reg = FMC_ADDRH_SET(block_num_h);
+ fmc_writel(host, FMC_ADDRH, reg);
+ FMC_PR(WR_DBG, "|-Set ADDRH[%#x]%#x\n", FMC_ADDRH, reg);
+
+ page_num = host->addr_value[1] - (block_num << pages_per_block_shift);
+ reg = ((block_num & REG_CNT_BLOCK_NUM_MASK) << REG_CNT_BLOCK_NUM_SHIFT)
+ | ((page_num & REG_CNT_PAGE_NUM_MASK) << REG_CNT_PAGE_NUM_SHIFT);
+ fmc_writel(host, FMC_ADDRL, reg);
+ FMC_PR(WR_DBG, "|-Set ADDRL[%#x]%#x\n", FMC_ADDRL, reg);
+
+ *host->epm = 0x0000;
+
+#ifndef FMC100_SPI_NAND_SUPPORT_REG_WRITE
+ reg = host->dma_buffer;
+ fmc_writel(host, FMC_DMA_SADDR_D0, reg);
+ FMC_PR(WR_DBG, "|-Set DMA_SADDR_D[0x40]%#x\n", reg);
+
+#ifdef CONFIG_64BIT
+ reg = (host->dma_buffer & FMC_DMA_SADDRH_MASK) >> 32;
+ fmc_writel(host, FMC_DMA_SADDRH_D0, reg);
+ FMC_PR(WR_DBG, "\t|-Set DMA_SADDRH_D0[%#x]%#x\n", FMC_DMA_SADDRH_D0, reg);
+#endif
+
+ reg = host->dma_oob;
+ fmc_writel(host, FMC_DMA_SADDR_OOB, reg);
+ FMC_PR(WR_DBG, "|-Set DMA_SADDR_OOB[%#x]%#x\n", FMC_DMA_SADDR_OOB, reg);
+#ifdef CONFIG_64BIT
+ reg = (host->dma_oob & FMC_DMA_SADDRH_MASK) >> 32;
+ fmc_writel(host, FMC_DMA_SADDRH_OOB, reg);
+ FMC_PR(WR_DBG, "\t|-Set DMA_SADDRH_OOB[%#x]%#x\n", FMC_DMA_SADDRH_OOB,
+ reg);
+#endif
+#endif
+
+ reg = OP_CTRL_WR_OPCODE(spi->write->cmd)
+#ifdef FMC100_SPI_NAND_SUPPORT_REG_WRITE
+ | OP_CTRL_DMA_OP(OP_TYPE_REG)
+#else
+ | OP_CTRL_DMA_OP(OP_TYPE_DMA)
+#endif
+ | OP_CTRL_RW_OP(RW_OP_WRITE)
+ | OP_CTRL_DMA_OP_READY;
+ fmc_writel(host, FMC_OP_CTRL, reg);
+ FMC_PR(WR_DBG, "|-Set OP_CTRL[%#x]%#x\n", FMC_OP_CTRL, reg);
+
+ FMC_DMA_WAIT_INT_FINISH(host);
+
+end:
+ mutex_unlock(host->lock);
+ FMC_PR(WR_DBG, "*-End %s page program!\n", op);
+}
+
+/*****************************************************************************/
+static void fmc100_send_cmd_status(struct fmc_host *host)
+{
+ unsigned char status, addr = STATUS_ADDR;
+ struct fmc_spi *spi = host->spi;
+
+ if (host->cmd_op.l_cmd == NAND_CMD_GET_FEATURES) {
+ addr = PROTECT_ADDR;
+ }
+
+ status = spi_nand_feature_op(spi, GET_OP, addr, 0);
+ FMC_PR((ER_DBG || WR_DBG), "\t*-Get status[%#x]: %#x\n", addr, status);
+}
+
+/*****************************************************************************/
+static void fmc100_send_cmd_read(struct fmc_host *host)
+{
+ unsigned char pages_per_block_shift, only_oob = 0;
+ unsigned short wrap = 0;
+ unsigned int reg, block_num, block_num_h, page_num, addr_of = 0;
+ struct fmc_spi *spi = host->spi;
+ struct nand_chip *chip = host->chip;
+#ifdef FMC100_SPI_NAND_SUPPORT_REG_READ
+ char *op = "Reg";
+#else
+ char *op = "Dma";
+#endif
+
+ if (RD_DBG) {
+ pr_info("\n");
+ }
+ FMC_PR(RD_DBG, "\t*-Start %s page read\n", op);
+
+ if ((host->addr_value[0] == host->cache_addr_value[0])
+ && (host->addr_value[1] == host->cache_addr_value[1])) {
+ FMC_PR(RD_DBG, "\t*-%s read cache hit, addr[%#x %#x]\n",
+ op, host->addr_value[1], host->addr_value[0]);
+ return;
+ }
+
+ mutex_lock(host->lock);
+ fmc100_operation_config(host, OP_STYPE_READ);
+
+ FMC_PR(RD_DBG, "\t|-Wait ready before %s page read\n", op);
+ reg = spi->driver->wait_ready(spi);
+ if (reg) {
+ DB_MSG("Error: %s read wait ready fail! reg: %#x\n", op, reg);
+ goto end;
+ }
+
+ reg = FMC_INT_CLR_ALL;
+ fmc_writel(host, FMC_INT_CLR, reg);
+ FMC_PR(RD_DBG, "\t|-Set INT_CLR[%#x]%#x\n", FMC_INT_CLR, reg);
+
+ if (host->cmd_op.l_cmd == NAND_CMD_READOOB) {
+ only_oob = 1;
+ host->cmd_op.op_cfg = OP_CTRL_RD_OP_SEL(RD_OP_READ_OOB);
+ } else {
+ host->cmd_op.op_cfg = OP_CTRL_RD_OP_SEL(RD_OP_READ_ALL_PAGE);
+ }
+
+ reg = OP_CFG_FM_CS(host->cmd_op.cs)
+ | OP_CFG_MEM_IF_TYPE(spi->read->iftype)
+ | OP_CFG_DUMMY_NUM(spi->read->dummy)
+ | OP_CFG_OEN_EN;
+ fmc_writel(host, FMC_OP_CFG, reg);
+ FMC_PR(RD_DBG, "\t|-Set OP_CFG[%#x]%#x\n", FMC_OP_CFG, reg);
+
+ pages_per_block_shift = chip->phys_erase_shift - chip->page_shift;
+ block_num = host->addr_value[1] >> pages_per_block_shift;
+ block_num_h = block_num >> REG_CNT_HIGH_BLOCK_NUM_SHIFT;
+
+ reg = FMC_ADDRH_SET(block_num_h);
+ fmc_writel(host, FMC_ADDRH, reg);
+ FMC_PR(RD_DBG, "\t|-Set ADDRH[%#x]%#x\n", FMC_ADDRH, reg);
+
+ page_num = host->addr_value[1] - (block_num << pages_per_block_shift);
+ if (only_oob)
+ switch (host->ecctype) {
+ case NAND_ECC_8BIT:
+ addr_of = REG_CNT_ECC_8BIT_OFFSET;
+ break;
+ case NAND_ECC_16BIT:
+ addr_of = REG_CNT_ECC_16BIT_OFFSET;
+ break;
+ case NAND_ECC_24BIT:
+ addr_of = REG_CNT_ECC_24BIT_OFFSET;
+ break;
+ case NAND_ECC_0BIT:
+ default:
+ break;
+ }
+
+ reg = ((block_num & REG_CNT_BLOCK_NUM_MASK) << REG_CNT_BLOCK_NUM_SHIFT)
+ | ((page_num & REG_CNT_PAGE_NUM_MASK) << REG_CNT_PAGE_NUM_SHIFT)
+ | ((wrap & REG_CNT_WRAP_MASK) << REG_CNT_WRAP_SHIFT)
+ | (addr_of & REG_CNT_ECC_OFFSET_MASK);
+ fmc_writel(host, FMC_ADDRL, reg);
+ FMC_PR(RD_DBG, "\t|-Set ADDRL[%#x]%#x\n", FMC_ADDRL, reg);
+
+#ifndef FMC100_SPI_NAND_SUPPORT_REG_READ
+ reg = host->dma_buffer;
+ fmc_writel(host, FMC_DMA_SADDR_D0, reg);
+ FMC_PR(RD_DBG, "\t|-Set DMA_SADDR_D0[%#x]%#x\n", FMC_DMA_SADDR_D0, reg);
+
+#ifdef CONFIG_64BIT
+ reg = (host->dma_buffer & FMC_DMA_SADDRH_MASK) >> 32;
+ fmc_writel(host, FMC_DMA_SADDRH_D0, reg);
+ FMC_PR(RD_DBG, "\t|-Set DMA_SADDRH_D0[%#x]%#x\n", FMC_DMA_SADDRH_D0, reg);
+#endif
+
+ reg = host->dma_oob;
+ fmc_writel(host, FMC_DMA_SADDR_OOB, reg);
+ FMC_PR(RD_DBG, "\t|-Set DMA_SADDR_OOB[%#x]%#x\n", FMC_DMA_SADDR_OOB,
+ reg);
+
+#ifdef CONFIG_64BIT
+ reg = (host->dma_oob & FMC_DMA_SADDRH_MASK) >> 32;
+ fmc_writel(host, FMC_DMA_SADDRH_OOB, reg);
+ FMC_PR(RD_DBG, "\t|-Set DMA_SADDRH_OOB[%#x]%#x\n", FMC_DMA_SADDRH_OOB,
+ reg);
+#endif
+#endif
+
+ reg = OP_CTRL_RD_OPCODE(spi->read->cmd) | host->cmd_op.op_cfg
+#ifdef FMC100_SPI_NAND_SUPPORT_REG_READ
+ | OP_CTRL_DMA_OP(OP_TYPE_REG)
+#else
+ | OP_CTRL_DMA_OP(OP_TYPE_DMA)
+#endif
+ | OP_CTRL_RW_OP(RW_OP_READ) | OP_CTRL_DMA_OP_READY;
+ fmc_writel(host, FMC_OP_CTRL, reg);
+ FMC_PR(RD_DBG, "\t|-Set OP_CTRL[%#x]%#x\n", FMC_OP_CTRL, reg);
+
+ FMC_DMA_WAIT_INT_FINISH(host);
+
+ host->cache_addr_value[0] = host->addr_value[0];
+ host->cache_addr_value[1] = host->addr_value[1];
+
+end:
+ mutex_unlock(host->lock);
+ FMC_PR(RD_DBG, "\t*-End %s page read\n", op);
+}
+
+/*****************************************************************************/
+static void fmc100_send_cmd_erase(struct fmc_host *host)
+{
+ unsigned int reg;
+ struct fmc_spi *spi = host->spi;
+
+ if (ER_DBG) {
+ pr_info("\n");
+ }
+ FMC_PR(ER_DBG, "\t*-Start send cmd erase!\n");
+
+ mutex_lock(host->lock);
+ fmc100_operation_config(host, OP_STYPE_ERASE);
+
+ reg = spi->driver->wait_ready(spi);
+ FMC_PR(ER_DBG, "\t|-Erase wait ready, reg: %#x\n", reg);
+ if (reg) {
+ DB_MSG("Error: Erase wait ready fail! status: %#x\n", reg);
+ goto end;
+ }
+
+ reg = spi->driver->write_enable(spi);
+ if (reg) {
+ DB_MSG("Error: Erase write enable failed! reg: %#x\n", reg);
+ goto end;
+ }
+
+ reg = FMC_INT_CLR_ALL;
+ fmc_writel(host, FMC_INT_CLR, reg);
+ FMC_PR(ER_DBG, "\t|-Set INT_CLR[%#x]%#x\n", FMC_INT_CLR, reg);
+
+ reg = spi->erase->cmd;
+ fmc_writel(host, FMC_CMD, FMC_CMD_CMD1(reg));
+ FMC_PR(ER_DBG, "\t|-Set CMD[%#x]%#x\n", FMC_CMD, reg);
+
+ reg = FMC_ADDRL_BLOCK_H_MASK(host->addr_value[1])
+ | FMC_ADDRL_BLOCK_L_MASK(host->addr_value[0]);
+ fmc_writel(host, FMC_ADDRL, reg);
+ FMC_PR(ER_DBG, "\t|-Set ADDRL[%#x]%#x\n", FMC_ADDRL, reg);
+
+ reg = OP_CFG_FM_CS(host->cmd_op.cs)
+ | OP_CFG_MEM_IF_TYPE(spi->erase->iftype)
+ | OP_CFG_ADDR_NUM(STD_OP_ADDR_NUM)
+ | OP_CFG_DUMMY_NUM(spi->erase->dummy)
+ | OP_CFG_OEN_EN;
+ fmc_writel(host, FMC_OP_CFG, reg);
+ FMC_PR(ER_DBG, "\t|-Set OP_CFG[%#x]%#x\n", FMC_OP_CFG, reg);
+
+ reg = FMC_OP_CMD1_EN
+ | FMC_OP_ADDR_EN
+ | FMC_OP_REG_OP_START;
+ fmc_writel(host, FMC_OP, reg);
+ FMC_PR(ER_DBG, "\t|-Set OP[%#x]%#x\n", FMC_OP, reg);
+
+ FMC_CMD_WAIT_CPU_FINISH(host);
+
+end:
+ mutex_unlock(host->lock);
+ FMC_PR(ER_DBG, "\t*-End send cmd erase!\n");
+}
+
+/*****************************************************************************/
+void fmc100_ecc0_switch(struct fmc_host *host, unsigned char op)
+{
+ unsigned int config;
+#if EC_DBG
+ unsigned int cmp_cfg;
+
+ config = fmc_readl(host, FMC_CFG);
+ FMC_PR(EC_DBG, "\t *-Get CFG[%#x]%#x\n", FMC_CFG, config);
+
+ if (op) {
+ cmp_cfg = host->fmc_cfg;
+ } else {
+ cmp_cfg = host->fmc_cfg_ecc0;
+ }
+
+ if (cmp_cfg != config)
+ DB_MSG("Warning: FMC config[%#x] is different.\n",
+ cmp_cfg);
+#endif
+
+ if (op == ENABLE) {
+ config = host->fmc_cfg_ecc0;
+ } else if (op == DISABLE) {
+ config = host->fmc_cfg;
+ } else {
+ DB_MSG("Error: Invalid opcode: %d\n", op);
+ return;
+ }
+
+ fmc_writel(host, FMC_CFG, config);
+ FMC_PR(EC_DBG, "\t *-Set CFG[%#x]%#x\n", FMC_CFG, config);
+}
+
+/*****************************************************************************/
+static void fmc100_send_cmd_readid(struct fmc_host *host)
+{
+ unsigned int reg;
+
+ FMC_PR(BT_DBG, "\t|*-Start send cmd read ID\n");
+
+ fmc100_ecc0_switch(host, ENABLE);
+
+ reg = FMC_CMD_CMD1(SPI_CMD_RDID);
+ fmc_writel(host, FMC_CMD, reg);
+ FMC_PR(BT_DBG, "\t||-Set CMD[%#x]%#x\n", FMC_CMD, reg);
+
+ reg = READ_ID_ADDR;
+ fmc_writel(host, FMC_ADDRL, reg);
+ FMC_PR(BT_DBG, "\t||-Set ADDRL[%#x]%#x\n", FMC_ADDRL, reg);
+
+ reg = OP_CFG_FM_CS(host->cmd_op.cs)
+ | OP_CFG_ADDR_NUM(READ_ID_ADDR_NUM)
+ | OP_CFG_OEN_EN;
+ fmc_writel(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_NAND_ID_LEN);
+ fmc_writel(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
+ | FMC_OP_ADDR_EN
+ | FMC_OP_READ_DATA_EN
+ | FMC_OP_REG_OP_START;
+ fmc_writel(host, FMC_OP, reg);
+ FMC_PR(BT_DBG, "\t||-Set OP[%#x]%#x\n", FMC_OP, reg);
+
+ host->addr_cycle = 0x0;
+
+ FMC_CMD_WAIT_CPU_FINISH(host);
+
+ fmc100_ecc0_switch(host, DISABLE);
+
+ FMC_PR(BT_DBG, "\t|*-End read flash ID\n");
+}
+
+/*****************************************************************************/
+static void fmc100_send_cmd_reset(struct fmc_host *host)
+{
+ unsigned int reg;
+
+ FMC_PR(BT_DBG, "\t|*-Start send cmd reset\n");
+
+ reg = FMC_CMD_CMD1(SPI_CMD_RESET);
+ fmc_writel(host, FMC_CMD, reg);
+ FMC_PR(BT_DBG, "\t||-Set CMD[%#x]%#x\n", FMC_CMD, reg);
+
+ reg = OP_CFG_FM_CS(host->cmd_op.cs) | OP_CFG_OEN_EN;
+ fmc_writel(host, FMC_OP_CFG, reg);
+ FMC_PR(BT_DBG, "\t||-Set OP_CFG[%#x]%#x\n", FMC_OP_CFG, reg);
+
+ reg = FMC_OP_CMD1_EN | FMC_OP_REG_OP_START;
+ fmc_writel(host, FMC_OP, reg);
+ FMC_PR(BT_DBG, "\t||-Set OP[%#x]%#x\n", FMC_OP, reg);
+
+ FMC_CMD_WAIT_CPU_FINISH(host);
+
+ FMC_PR(BT_DBG, "\t|*-End send cmd reset\n");
+}
+
+/*****************************************************************************/
+static void fmc100_host_init(struct fmc_host *host)
+{
+ unsigned int reg;
+
+ FMC_PR(BT_DBG, "\t||*-Start SPI Nand host init\n");
+
+ reg = fmc_readl(host, FMC_CFG);
+ if ((reg & FMC_CFG_OP_MODE_MASK) == FMC_CFG_OP_MODE_BOOT) {
+ reg |= FMC_CFG_OP_MODE(FMC_CFG_OP_MODE_NORMAL);
+ fmc_writel(host, FMC_CFG, reg);
+ FMC_PR(BT_DBG, "\t|||-Set CFG[%#x]%#x\n", FMC_CFG, reg);
+ }
+
+ host->fmc_cfg = reg;
+ host->fmc_cfg_ecc0 = (reg & ~ECC_TYPE_MASK) | ECC_TYPE_0BIT;
+
+ reg = fmc_readl(host, FMC_GLOBAL_CFG);
+ if (reg & FMC_GLOBAL_CFG_WP_ENABLE) {
+ reg &= ~FMC_GLOBAL_CFG_WP_ENABLE;
+ fmc_writel(host, FMC_GLOBAL_CFG, reg);
+ }
+
+ host->addr_cycle = 0;
+ host->addr_value[0] = 0;
+ host->addr_value[1] = 0;
+ host->cache_addr_value[0] = ~0;
+ host->cache_addr_value[1] = ~0;
+
+ host->send_cmd_write = fmc100_send_cmd_write;
+ host->send_cmd_status = fmc100_send_cmd_status;
+ host->send_cmd_read = fmc100_send_cmd_read;
+ host->send_cmd_erase = fmc100_send_cmd_erase;
+ host->send_cmd_readid = fmc100_send_cmd_readid;
+ host->send_cmd_reset = fmc100_send_cmd_reset;
+#ifdef CONFIG_PM
+ host->suspend = fmc100_suspend;
+ host->resume = fmc100_resume;
+#endif
+
+ reg = TIMING_CFG_TCSH(CS_HOLD_TIME)
+ | TIMING_CFG_TCSS(CS_SETUP_TIME)
+ | TIMING_CFG_TSHSL(CS_DESELECT_TIME);
+ fmc_writel(host, FMC_SPI_TIMING_CFG, reg);
+
+ reg = ALL_BURST_ENABLE;
+ fmc_writel(host, FMC_DMA_AHB_CTRL, reg);
+
+ FMC_PR(BT_DBG, "\t||*-End SPI Nand host init\n");
+}
+
+/*****************************************************************************/
+static unsigned char fmc100_read_byte(struct mtd_info *mtd)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct fmc_host *host = chip->priv;
+ unsigned char value, ret_val = 0;
+
+ if (host->cmd_op.l_cmd == NAND_CMD_READID) {
+ value = fmc_readb(host->iobase + host->offset);
+ host->offset++;
+ if (host->cmd_op.data_no == host->offset) {
+ host->cmd_op.l_cmd = 0;
+ }
+ return value;
+ }
+
+ if (host->cmd_op.cmd == NAND_CMD_STATUS) {
+ value = fmc_readl(host, FMC_STATUS);
+ if (host->cmd_op.l_cmd == NAND_CMD_GET_FEATURES) {
+ FMC_PR((ER_DBG || WR_DBG), "\t\tRead BP status:%#x\n",
+ value);
+ if (ANY_BP_ENABLE(value)) {
+ ret_val |= NAND_STATUS_WP;
+ }
+
+ host->cmd_op.l_cmd = NAND_CMD_STATUS;
+ }
+
+ if (!(value & STATUS_OIP_MASK)) {
+ ret_val |= NAND_STATUS_READY;
+ }
+
+ if (value & STATUS_E_FAIL_MASK) {
+ FMC_PR(ER_DBG, "\t\tGet erase status: %#x\n", value);
+ ret_val |= NAND_STATUS_FAIL;
+ }
+
+ if (value & STATUS_P_FAIL_MASK) {
+ FMC_PR(WR_DBG, "\t\tGet write status: %#x\n", value);
+ ret_val |= NAND_STATUS_FAIL;
+ }
+
+ return ret_val;
+ }
+
+ if (host->cmd_op.l_cmd == NAND_CMD_READOOB) {
+ value = fmc_readb(host->buffer + host->pagesize + host->offset);
+ host->offset++;
+ return value;
+ }
+
+ host->offset++;
+
+ return fmc_readb(host->buffer + host->column + host->offset - 1);
+}
+
+/*****************************************************************************/
+static unsigned short fmc100_read_word(struct mtd_info *mtd)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct fmc_host *host = chip->priv;
+
+ host->offset += 2;
+ return fmc_readw(host->buffer + host->column + host->offset - 2);
+}
+
+/*****************************************************************************/
+static void fmc100_write_buf(struct mtd_info *mtd,
+ const u_char *buf, int len)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct fmc_host *host = chip->priv;
+
+#ifdef FMC100_SPI_NAND_SUPPORT_REG_WRITE
+ if (buf == chip->oob_poi) {
+ memcpy((char *)host->iobase + host->pagesize, buf, len);
+ } else {
+ memcpy((char *)host->iobase, buf, len);
+ }
+#else
+ if (buf == chip->oob_poi) {
+ memcpy((char *)(host->buffer + host->pagesize), buf, len);
+ } else {
+ memcpy((char *)host->buffer, buf, len);
+ }
+#endif
+ return;
+}
+
+/*****************************************************************************/
+static void fmc100_read_buf(struct mtd_info *mtd, u_char *buf, int len)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct fmc_host *host = chip->priv;
+
+#ifdef FMC100_SPI_NAND_SUPPORT_REG_READ
+ if (buf == chip->oob_poi) {
+ memcpy(buf, (char *)host->iobase + host->pagesize, len);
+ } else {
+ memcpy(buf, (char *)host->iobase, len);
+ }
+#else
+ if (buf == chip->oob_poi) {
+ memcpy(buf, (char *)host->buffer + host->pagesize, len);
+ } else {
+ memcpy(buf, (char *)host->buffer, len);
+ }
+#endif
+
+#ifdef CONFIG_GOKE_NAND_ECC_STATUS_REPORT
+ if (buf != chip->oob_poi) {
+ u_int reg, ecc_step = host->pagesize >> 10;
+
+ reg = fmc_readl(host, FMC100_ECC_ERR_NUM0_BUF0);
+ while (ecc_step) {
+ u_char err_num;
+
+ err_num = GET_ECC_ERR_NUM(--ecc_step, reg);
+ if (err_num == 0xff) {
+ mtd->ecc_stats.failed++;
+ } else {
+ mtd->ecc_stats.corrected += err_num;
+ }
+ }
+ }
+#endif
+
+ return;
+}
+
+/*****************************************************************************/
+static void fmc100_select_chip(struct mtd_info *mtd, int chipselect)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct fmc_host *host = chip->priv;
+
+ if (chipselect < 0) {
+ mutex_unlock(&fmc_switch_mutex);
+ return;
+ }
+
+ mutex_lock(&fmc_switch_mutex);
+
+ if (chipselect > CONFIG_SPI_NAND_MAX_CHIP_NUM) {
+ DB_BUG("Error: Invalid chipselect: %d\n", chipselect);
+ }
+
+ if (host->mtd != mtd) {
+ host->mtd = mtd;
+ host->cmd_op.cs = chipselect;
+ }
+
+ if (!(chip->options & NAND_BROKEN_XD)) {
+ if ((chip->state == FL_ERASING) || (chip->state == FL_WRITING)) {
+ host->cmd_op.l_cmd = NAND_CMD_GET_FEATURES;
+ }
+ }
+}
+
+/*****************************************************************************/
+static void fmc100_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned ctrl)
+{
+ unsigned char cmd;
+ int is_cache_invalid = 1;
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct fmc_host *host = chip->priv;
+ unsigned int udat = (unsigned int)dat;
+
+ if (ctrl & NAND_ALE) {
+ unsigned int addr_value = 0;
+ unsigned int addr_offset = 0;
+
+ if (ctrl & NAND_CTRL_CHANGE) {
+ host->addr_cycle = 0x0;
+ host->addr_value[0] = 0x0;
+ host->addr_value[1] = 0x0;
+ }
+ addr_offset = host->addr_cycle << 3;
+
+ if (host->addr_cycle >= FMC100_ADDR_CYCLE_MASK) {
+ addr_offset = (host->addr_cycle -
+ FMC100_ADDR_CYCLE_MASK) << 3;
+ addr_value = 1;
+ }
+ host->addr_value[addr_value] |=
+ ((udat & 0xff) << addr_offset);
+
+ host->addr_cycle++;
+ }
+
+ if ((ctrl & NAND_CLE) && (ctrl & NAND_CTRL_CHANGE)) {
+ cmd = udat & 0xff;
+ host->cmd_op.cmd = cmd;
+ switch (cmd) {
+ case NAND_CMD_PAGEPROG:
+ host->offset = 0;
+ host->send_cmd_write(host);
+ break;
+
+ case NAND_CMD_READSTART:
+ is_cache_invalid = 0;
+ if (host->addr_value[0] == host->pagesize) {
+ host->cmd_op.l_cmd = NAND_CMD_READOOB;
+ }
+ host->send_cmd_read(host);
+ break;
+
+ case NAND_CMD_ERASE2:
+ host->send_cmd_erase(host);
+ break;
+
+ case NAND_CMD_READID:
+ memset((u_char *)(host->iobase), 0,
+ MAX_SPI_NAND_ID_LEN);
+ host->cmd_op.l_cmd = cmd;
+ host->cmd_op.data_no = MAX_SPI_NAND_ID_LEN;
+ host->send_cmd_readid(host);
+ break;
+
+ case NAND_CMD_STATUS:
+ host->send_cmd_status(host);
+ break;
+
+ case NAND_CMD_READ0:
+ host->cmd_op.l_cmd = cmd;
+ break;
+
+ case NAND_CMD_RESET:
+ host->send_cmd_reset(host);
+ break;
+
+ case NAND_CMD_SEQIN:
+ case NAND_CMD_ERASE1:
+ default:
+ break;
+ }
+ }
+
+ if ((dat == NAND_CMD_NONE) && host->addr_cycle) {
+ if (host->cmd_op.cmd == NAND_CMD_SEQIN
+ || host->cmd_op.cmd == NAND_CMD_READ0
+ || host->cmd_op.cmd == NAND_CMD_READID) {
+ host->offset = 0x0;
+ host->column = (host->addr_value[0] & 0xffff);
+ }
+ }
+
+ if (is_cache_invalid) {
+ host->cache_addr_value[0] = ~0;
+ host->cache_addr_value[1] = ~0;
+ }
+}
+
+/*****************************************************************************/
+static int fmc100_dev_ready(struct mtd_info *mtd)
+{
+ unsigned int reg;
+ unsigned long deadline = jiffies + FMC_MAX_READY_WAIT_JIFFIES;
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct fmc_host *host = chip->priv;
+
+ do {
+ reg = OP_CFG_FM_CS(host->cmd_op.cs) | OP_CFG_OEN_EN;
+ fmc_writel(host, FMC_OP_CFG, reg);
+
+ reg = FMC_OP_READ_STATUS_EN | FMC_OP_REG_OP_START;
+ fmc_writel(host, FMC_OP, reg);
+
+ FMC_CMD_WAIT_CPU_FINISH(host);
+
+ reg = fmc_readl(host, FMC_STATUS);
+
+ if (!(reg & STATUS_OIP_MASK)) {
+ return NAND_STATUS_READY;
+ }
+
+ cond_resched();
+
+ } while (!time_after_eq(jiffies, deadline));
+
+ if (!(chip->options & NAND_SCAN_SILENT_NODEV)) {
+ pr_warn("Wait SPI nand ready timeout, status: %#x\n", reg);
+ }
+
+ return 0;
+}
+
+/*****************************************************************************/
+/*
+ * 'host->epm' only use the first oobfree[0] field, it looks very simple, But...
+ */
+/* Default OOB area layout */
+static int fmc_ooblayout_ecc_default(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *oobregion)
+{
+ if (section) {
+ return -ERANGE;
+ }
+
+ oobregion->length = 32;
+ oobregion->offset = 32;
+
+ return 0;
+}
+
+static int fmc_ooblayout_free_default(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *oobregion)
+{
+ if (section) {
+ return -ERANGE;
+ }
+
+ oobregion->length = 30;
+ oobregion->offset = 2;
+
+ return 0;
+}
+
+static struct mtd_ooblayout_ops fmc_ooblayout_default_ops = {
+ .ecc = fmc_ooblayout_ecc_default,
+ .free = fmc_ooblayout_free_default,
+};
+
+#ifdef CONFIG_GOKE_NAND_FS_MAY_NO_YAFFS2
+static int fmc_ooblayout_ecc_4k16bit(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *oobregion)
+{
+ if (section) {
+ return -ERANGE;
+ }
+
+ oobregion->length = 14;
+ oobregion->offset = 14;
+
+ return 0;
+}
+
+static int fmc_ooblayout_free_4k16bit(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *oobregion)
+{
+ if (section) {
+ return -ERANGE;
+ }
+
+ oobregion->length = 14;
+ oobregion->offset = 2;
+
+ return 0;
+}
+
+static struct mtd_ooblayout_ops fmc_ooblayout_4k16bit_ops = {
+ .ecc = fmc_ooblayout_ecc_4k16bit,
+ .free = fmc_ooblayout_free_4k16bit,
+};
+
+static int fmc_ooblayout_ecc_2k16bit(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *oobregion)
+{
+ if (section) {
+ return -ERANGE;
+ }
+
+ oobregion->length = 6;
+ oobregion->offset = 6;
+
+ return 0;
+}
+
+static int fmc_ooblayout_free_2k16bit(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *oobregion)
+{
+ if (section) {
+ return -ERANGE;
+ }
+
+ oobregion->length = 6;
+ oobregion->offset = 2;
+
+ return 0;
+}
+
+static struct mtd_ooblayout_ops fmc_ooblayout_2k16bit_ops = {
+ .ecc = fmc_ooblayout_ecc_2k16bit,
+ .free = fmc_ooblayout_free_2k16bit,
+};
+#endif
+
+/*****************************************************************************/
+static struct nand_config_info fmc_spi_nand_config_table[] = {
+ {NAND_PAGE_4K, NAND_ECC_24BIT, 24, 200, &fmc_ooblayout_default_ops},
+#ifdef CONFIG_GOKE_NAND_FS_MAY_NO_YAFFS2
+ {NAND_PAGE_4K, NAND_ECC_16BIT, 16, 128, &fmc_ooblayout_4k16bit_ops},
+#endif
+ {NAND_PAGE_4K, NAND_ECC_8BIT, 8, 128, &fmc_ooblayout_default_ops},
+ {NAND_PAGE_4K, NAND_ECC_0BIT, 0, 32, &fmc_ooblayout_default_ops},
+
+ {NAND_PAGE_2K, NAND_ECC_24BIT, 24, 128, &fmc_ooblayout_default_ops},
+#ifdef CONFIG_GOKE_NAND_FS_MAY_NO_YAFFS2
+ {NAND_PAGE_2K, NAND_ECC_16BIT, 16, 64, &fmc_ooblayout_2k16bit_ops},
+#endif
+ {NAND_PAGE_2K, NAND_ECC_8BIT, 8, 64, &fmc_ooblayout_default_ops},
+ {NAND_PAGE_2K, NAND_ECC_0BIT, 0, 32, &fmc_ooblayout_default_ops},
+
+ {0, 0, 0, 0, NULL},
+};
+
+/*
+ * Auto-sensed the page size and ecc type value. driver will try each of page
+ * size and ecc type one by one till flash can be read and wrote accurately.
+ * so the page size and ecc type is match adaptively without switch on the board
+ */
+static struct nand_config_info *fmc100_get_config_type_info(
+ struct mtd_info *mtd, struct nand_dev_t *nand_dev)
+{
+ struct nand_config_info *best = NULL;
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct nand_config_info *info = fmc_spi_nand_config_table;
+
+ nand_dev->start_type = "Auto";
+
+ for (; info->ooblayout_ops; info++) {
+ if (match_page_type_to_size(info->pagetype) != mtd->writesize) {
+ continue;
+ }
+
+ if (mtd->oobsize < info->oobsize) {
+ continue;
+ }
+
+ if (!best || (best->ecctype < info->ecctype)) {
+ best = info;
+ }
+ }
+
+ /* All SPI NAND are small-page, SLC */
+ chip->bits_per_cell = 1;
+
+ return best;
+}
+
+/*****************************************************************************/
+static void fmc100_chip_init(struct nand_chip *chip)
+{
+ chip->read_byte = fmc100_read_byte;
+ chip->read_word = fmc100_read_word;
+ chip->write_buf = fmc100_write_buf;
+ chip->read_buf = fmc100_read_buf;
+
+ chip->select_chip = fmc100_select_chip;
+
+ chip->cmd_ctrl = fmc100_cmd_ctrl;
+ chip->dev_ready = fmc100_dev_ready;
+
+ chip->chip_delay = FMC_CHIP_DELAY;
+
+ chip->options = NAND_SKIP_BBTSCAN | NAND_BROKEN_XD
+ | NAND_SCAN_SILENT_NODEV;
+
+ chip->ecc.mode = NAND_ECC_NONE;
+}
+
+/*****************************************************************************/
+static void fmc100_set_oob_info(struct mtd_info *mtd,
+ struct nand_config_info *info, struct nand_dev_t *nand_dev)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct fmc_host *host = chip->priv;
+ struct mtd_oob_region fmc_oobregion = {0, 0};
+
+ if (info->ecctype != NAND_ECC_0BIT) {
+ mtd->oobsize = info->oobsize;
+ }
+
+ host->oobsize = mtd->oobsize;
+ nand_dev->oobsize = host->oobsize;
+
+ host->dma_oob = host->dma_buffer + host->pagesize;
+ host->bbm = (u_char *)(host->buffer + host->pagesize
+ + FMC_BAD_BLOCK_POS);
+
+ info->ooblayout_ops->free(mtd, 0, &fmc_oobregion);
+
+ mtd_set_ooblayout(mtd, info->ooblayout_ops);
+
+ /* EB bits locate in the bottom two of CTRL(30) */
+ host->epm = (u_short *)(host->buffer + host->pagesize
+ + fmc_oobregion.offset + 28);
+
+#ifdef CONFIG_GOKE_NAND_FS_MAY_NO_YAFFS2
+ if (best->ecctype == NAND_ECC_16BIT) {
+ if (host->pagesize == _2K) {
+ /* EB bits locate in the bottom two of CTRL(4) */
+ host->epm = (u_short *)(host->buffer + host->pagesize
+ + fmc_oobregion.offset + 4);
+ } else if (host->pagesize == _4K) {
+ /* EB bit locate in the bottom two of CTRL(14) */
+ host->epm = (u_short *)(host->buffer + host->pagesize
+ + fmc_oobregion.offset + 12);
+ }
+ }
+#endif
+}
+
+/*****************************************************************************/
+static unsigned int fmc100_get_ecc_reg(struct fmc_host *host,
+ struct nand_config_info *info, struct nand_dev_t *nand_dev)
+{
+ host->ecctype = info->ecctype;
+ nand_dev->ecctype = host->ecctype;
+
+ return FMC_CFG_ECC_TYPE(match_ecc_type_to_reg(info->ecctype));
+}
+
+/*****************************************************************************/
+static unsigned int fmc100_get_page_reg(struct fmc_host *host,
+ struct nand_config_info *info)
+{
+ host->pagesize = match_page_type_to_size(info->pagetype);
+
+ return FMC_CFG_PAGE_SIZE(match_page_type_to_reg(info->pagetype));
+}
+
+/*****************************************************************************/
+static unsigned int fmc100_get_block_reg(struct fmc_host *host,
+ struct nand_config_info *info)
+{
+ unsigned int block_reg = 0, page_per_block;
+ struct mtd_info *mtd = host->mtd;
+
+ host->block_page_mask = ((mtd->erasesize / mtd->writesize) - 1);
+ page_per_block = mtd->erasesize / match_page_type_to_size(info->pagetype);
+ switch (page_per_block) {
+ case 64:
+ block_reg = BLOCK_SIZE_64_PAGE;
+ break;
+ case 128:
+ block_reg = BLOCK_SIZE_128_PAGE;
+ break;
+ case 256:
+ block_reg = BLOCK_SIZE_256_PAGE;
+ break;
+ case 512:
+ block_reg = BLOCK_SIZE_512_PAGE;
+ break;
+ default:
+ DB_MSG("Can't support block %#x and page %#x size\n",
+ mtd->erasesize, mtd->writesize);
+ }
+
+ return FMC_CFG_BLOCK_SIZE(block_reg);
+}
+
+/*****************************************************************************/
+static void fmc100_set_fmc_cfg_reg(struct fmc_host *host,
+ struct nand_config_info *type_info, struct nand_dev_t *nand_dev)
+{
+ unsigned int page_reg, ecc_reg, block_reg, reg_fmc_cfg;
+
+ ecc_reg = fmc100_get_ecc_reg(host, type_info, nand_dev);
+ page_reg = fmc100_get_page_reg(host, type_info);
+ block_reg = fmc100_get_block_reg(host, type_info);
+
+ reg_fmc_cfg = fmc_readl(host, FMC_CFG);
+ reg_fmc_cfg &= ~(PAGE_SIZE_MASK | ECC_TYPE_MASK | BLOCK_SIZE_MASK);
+ reg_fmc_cfg |= ecc_reg | page_reg | block_reg;
+ fmc_writel(host, FMC_CFG, reg_fmc_cfg);
+
+ /* Save value of FMC_CFG and FMC_CFG_ECC0 to turn on/off ECC */
+ host->fmc_cfg = reg_fmc_cfg;
+ host->fmc_cfg_ecc0 = (host->fmc_cfg & ~ECC_TYPE_MASK) | ECC_TYPE_0BIT;
+ FMC_PR(BT_DBG, "\t|-Save FMC_CFG[%#x]: %#x and FMC_CFG_ECC0: %#x\n",
+ FMC_CFG, host->fmc_cfg, host->fmc_cfg_ecc0);
+}
+
+/*****************************************************************************/
+static int fmc100_set_config_info(struct mtd_info *mtd,
+ struct nand_chip *chip, struct nand_dev_t *nand_dev)
+{
+ struct fmc_host *host = chip->priv;
+ struct nand_config_info *type_info = NULL;
+
+ FMC_PR(BT_DBG, "\t*-Start config Block Page OOB and Ecc\n");
+
+ type_info = fmc100_get_config_type_info(mtd, nand_dev);
+ BUG_ON(!type_info);
+
+ FMC_PR(BT_DBG, "\t|-%s Config, PageSize %s EccType %s OOBSize %d\n",
+ nand_dev->start_type, nand_page_name(type_info->pagetype),
+ nand_ecc_name(type_info->ecctype), type_info->oobsize);
+
+ /* Set the page_size, ecc_type, block_size of FMC_CFG[0x0] register */
+ fmc100_set_fmc_cfg_reg(host, type_info, nand_dev);
+
+ fmc100_set_oob_info(mtd, type_info, nand_dev);
+
+ FMC_PR(BT_DBG, "\t*-End config Block Page Oob and Ecc\n");
+
+ return 0;
+}
+
+/*****************************************************************************/
+int fmc100_spi_nand_init(struct nand_chip *chip)
+{
+ struct fmc_host *host = chip->priv;
+
+ FMC_PR(BT_DBG, "\t|*-Start fmc100 SPI Nand init\n");
+
+ /* Set system clock and enable controller */
+ clk_prepare_enable(host->clk);
+
+ /* Switch SPI type to SPI nand */
+ fmc100_switch_to_spi_nand(host);
+
+ /* hold on STR mode */
+ fmc100_set_str_mode(host);
+
+ /* fmc host init */
+ fmc100_host_init(host);
+ host->chip = chip;
+
+ /* fmc nand_chip struct init */
+ fmc100_chip_init(chip);
+
+ fmc_spi_nand_ids_register();
+ nfc_param_adjust = fmc100_set_config_info;
+
+ FMC_PR(BT_DBG, "\t|*-End fmc100 SPI Nand init\n");
+
+ return 0;
+}
+#ifdef CONFIG_PM
+/*****************************************************************************/
+int fmc100_suspend(struct platform_device *pltdev, pm_message_t state)
+{
+ unsigned int ret;
+ struct fmc_host *host = platform_get_drvdata(pltdev);
+ struct fmc_spi *spi = host->spi;
+
+ mutex_lock(host->lock);
+ fmc100_switch_to_spi_nand(host);
+
+ ret = spi->driver->wait_ready(spi);
+ if (ret)
+ DB_MSG("Error: wait ready failed!");
+
+ clk_disable_unprepare(host->clk);
+ mutex_unlock(host->lock);
+
+ return 0;
+}
+/*****************************************************************************/
+int fmc100_resume(struct platform_device *pltdev)
+{
+ int cs;
+ struct fmc_host *host = platform_get_drvdata(pltdev);
+ struct nand_chip *chip = host->chip;
+
+ mutex_lock(host->lock);
+ fmc100_switch_to_spi_nand(host);
+ clk_prepare_enable(host->clk);
+
+ for (cs = 0; cs < chip->numchips; cs++) {
+ host->send_cmd_reset(host);
+ }
+
+ fmc100_spi_nand_config(host);
+
+ mutex_unlock(host->lock);
+ return 0;
+}
+#endif
+