mirror of https://github.com/OpenIPC/firmware.git
				
				
				
			
		
			
				
	
	
		
			1203 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			Diff
		
	
	
			
		
		
	
	
			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
 | |
| +
 |