mirror of
				https://xff.cz/git/u-boot/
				synced 2025-10-31 02:15:45 +01:00 
			
		
		
		
	Upstream linux commit 7bb427990ee364. Rename the function to match this new behavior. NOTE: fix nand_detect/nand_get_flash_type parameters in mxs_nand_spl. This code seems never executed by any board as alternative for nand detect Signed-off-by: Michael Trimarchi <michael@amarulasolutions.com> Reviewed-by: Dario Binacchi <dario.binacchi@amarulasolutions.com> Signed-off-by: Dario Binacchi <dario.binacchi@amarulasolutions.com>
		
			
				
	
	
		
			1205 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1205 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| /*
 | |
|  * Copyright (C) 2022 MediaTek Inc. All rights reserved.
 | |
|  *
 | |
|  * Author: Weijie Gao <weijie.gao@mediatek.com>
 | |
|  */
 | |
| 
 | |
| #include <log.h>
 | |
| #include <nand.h>
 | |
| #include <malloc.h>
 | |
| #include <asm/addrspace.h>
 | |
| #include <linux/io.h>
 | |
| #include <linux/iopoll.h>
 | |
| #include <linux/sizes.h>
 | |
| #include <linux/bitops.h>
 | |
| #include <linux/bitfield.h>
 | |
| #include "mt7621_nand.h"
 | |
| 
 | |
| /* NFI core registers */
 | |
| #define NFI_CNFG			0x000
 | |
| #define   CNFG_OP_MODE			GENMASK(14, 12)
 | |
| #define     CNFG_OP_CUSTOM		6
 | |
| #define   CNFG_AUTO_FMT_EN		BIT(9)
 | |
| #define   CNFG_HW_ECC_EN		BIT(8)
 | |
| #define   CNFG_BYTE_RW			BIT(6)
 | |
| #define   CNFG_READ_MODE		BIT(1)
 | |
| 
 | |
| #define NFI_PAGEFMT			0x004
 | |
| #define   PAGEFMT_FDM_ECC		GENMASK(15, 12)
 | |
| #define   PAGEFMT_FDM			GENMASK(11, 8)
 | |
| #define   PAGEFMT_SPARE			GENMASK(5, 4)
 | |
| #define   PAGEFMT_PAGE			GENMASK(1, 0)
 | |
| 
 | |
| #define NFI_CON				0x008
 | |
| #define   CON_NFI_SEC			GENMASK(15, 12)
 | |
| #define   CON_NFI_BWR			BIT(9)
 | |
| #define   CON_NFI_BRD			BIT(8)
 | |
| #define   CON_NFI_RST			BIT(1)
 | |
| #define   CON_FIFO_FLUSH		BIT(0)
 | |
| 
 | |
| #define NFI_ACCCON			0x00c
 | |
| #define   ACCCON_POECS			GENMASK(31, 28)
 | |
| #define   ACCCON_POECS_DEF		3
 | |
| #define   ACCCON_PRECS			GENMASK(27, 22)
 | |
| #define   ACCCON_PRECS_DEF		3
 | |
| #define   ACCCON_C2R			GENMASK(21, 16)
 | |
| #define   ACCCON_C2R_DEF		7
 | |
| #define   ACCCON_W2R			GENMASK(15, 12)
 | |
| #define   ACCCON_W2R_DEF		7
 | |
| #define   ACCCON_WH			GENMASK(11, 8)
 | |
| #define   ACCCON_WH_DEF			15
 | |
| #define   ACCCON_WST			GENMASK(7, 4)
 | |
| #define   ACCCON_WST_DEF		15
 | |
| #define   ACCCON_WST_MIN		3
 | |
| #define   ACCCON_RLT			GENMASK(3, 0)
 | |
| #define   ACCCON_RLT_DEF		15
 | |
| #define   ACCCON_RLT_MIN		3
 | |
| 
 | |
| #define NFI_CMD				0x020
 | |
| 
 | |
| #define NFI_ADDRNOB			0x030
 | |
| #define   ADDR_ROW_NOB			GENMASK(6, 4)
 | |
| #define   ADDR_COL_NOB			GENMASK(2, 0)
 | |
| 
 | |
| #define NFI_COLADDR			0x034
 | |
| #define NFI_ROWADDR			0x038
 | |
| 
 | |
| #define NFI_STRDATA			0x040
 | |
| #define   STR_DATA			BIT(0)
 | |
| 
 | |
| #define NFI_CNRNB			0x044
 | |
| #define   CB2R_TIME			GENMASK(7, 4)
 | |
| #define   STR_CNRNB			BIT(0)
 | |
| 
 | |
| #define NFI_DATAW			0x050
 | |
| #define NFI_DATAR			0x054
 | |
| 
 | |
| #define NFI_PIO_DIRDY			0x058
 | |
| #define   PIO_DIRDY			BIT(0)
 | |
| 
 | |
| #define NFI_STA				0x060
 | |
| #define   STA_NFI_FSM			GENMASK(19, 16)
 | |
| #define     STA_FSM_CUSTOM_DATA		14
 | |
| #define   STA_BUSY			BIT(8)
 | |
| #define   STA_ADDR			BIT(1)
 | |
| #define   STA_CMD			BIT(0)
 | |
| 
 | |
| #define NFI_ADDRCNTR			0x070
 | |
| #define   SEC_CNTR			GENMASK(15, 12)
 | |
| #define   SEC_ADDR			GENMASK(9, 0)
 | |
| 
 | |
| #define NFI_CSEL			0x090
 | |
| #define   CSEL				GENMASK(1, 0)
 | |
| 
 | |
| #define NFI_FDM0L			0x0a0
 | |
| #define NFI_FDML(n)			(0x0a0 + ((n) << 3))
 | |
| 
 | |
| #define NFI_FDM0M			0x0a4
 | |
| #define NFI_FDMM(n)			(0x0a4 + ((n) << 3))
 | |
| 
 | |
| #define NFI_MASTER_STA			0x210
 | |
| #define   MAS_ADDR			GENMASK(11, 9)
 | |
| #define   MAS_RD			GENMASK(8, 6)
 | |
| #define   MAS_WR			GENMASK(5, 3)
 | |
| #define   MAS_RDDLY			GENMASK(2, 0)
 | |
| 
 | |
| /* ECC engine registers */
 | |
| #define ECC_ENCCON			0x000
 | |
| #define   ENC_EN			BIT(0)
 | |
| 
 | |
| #define ECC_ENCCNFG			0x004
 | |
| #define   ENC_CNFG_MSG			GENMASK(28, 16)
 | |
| #define   ENC_MODE			GENMASK(5, 4)
 | |
| #define     ENC_MODE_NFI		1
 | |
| #define   ENC_TNUM			GENMASK(2, 0)
 | |
| 
 | |
| #define ECC_ENCIDLE			0x00c
 | |
| #define   ENC_IDLE			BIT(0)
 | |
| 
 | |
| #define ECC_DECCON			0x100
 | |
| #define   DEC_EN			BIT(0)
 | |
| 
 | |
| #define ECC_DECCNFG			0x104
 | |
| #define   DEC_EMPTY_EN			BIT(31)
 | |
| #define   DEC_CS			GENMASK(28, 16)
 | |
| #define   DEC_CON			GENMASK(13, 12)
 | |
| #define     DEC_CON_EL			2
 | |
| #define   DEC_MODE			GENMASK(5, 4)
 | |
| #define     DEC_MODE_NFI		1
 | |
| #define   DEC_TNUM			GENMASK(2, 0)
 | |
| 
 | |
| #define ECC_DECIDLE			0x10c
 | |
| #define   DEC_IDLE			BIT(1)
 | |
| 
 | |
| #define ECC_DECENUM			0x114
 | |
| #define   ERRNUM_S			2
 | |
| #define   ERRNUM_M			GENMASK(3, 0)
 | |
| 
 | |
| #define ECC_DECDONE			0x118
 | |
| #define   DEC_DONE7			BIT(7)
 | |
| #define   DEC_DONE6			BIT(6)
 | |
| #define   DEC_DONE5			BIT(5)
 | |
| #define   DEC_DONE4			BIT(4)
 | |
| #define   DEC_DONE3			BIT(3)
 | |
| #define   DEC_DONE2			BIT(2)
 | |
| #define   DEC_DONE1			BIT(1)
 | |
| #define   DEC_DONE0			BIT(0)
 | |
| 
 | |
| #define ECC_DECEL(n)			(0x11c + (n) * 4)
 | |
| #define   DEC_EL_ODD_S			16
 | |
| #define   DEC_EL_M			0x1fff
 | |
| #define   DEC_EL_BYTE_POS_S		3
 | |
| #define   DEC_EL_BIT_POS_M		GENMASK(2, 0)
 | |
| 
 | |
| #define ECC_FDMADDR			0x13c
 | |
| 
 | |
| /* ENCIDLE and DECIDLE */
 | |
| #define   ECC_IDLE			BIT(0)
 | |
| 
 | |
| #define ACCTIMING(tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt) \
 | |
| 	(FIELD_PREP(ACCCON_POECS, tpoecs) | \
 | |
| 	 FIELD_PREP(ACCCON_PRECS, tprecs) | \
 | |
| 	 FIELD_PREP(ACCCON_C2R, tc2r) | \
 | |
| 	 FIELD_PREP(ACCCON_W2R, tw2r) | \
 | |
| 	 FIELD_PREP(ACCCON_WH, twh) | \
 | |
| 	 FIELD_PREP(ACCCON_WST, twst) | \
 | |
| 	 FIELD_PREP(ACCCON_RLT, trlt))
 | |
| 
 | |
| #define MASTER_STA_MASK			(MAS_ADDR | MAS_RD | MAS_WR | \
 | |
| 					 MAS_RDDLY)
 | |
| #define NFI_RESET_TIMEOUT		1000000
 | |
| #define NFI_CORE_TIMEOUT		500000
 | |
| #define ECC_ENGINE_TIMEOUT		500000
 | |
| 
 | |
| #define ECC_SECTOR_SIZE			512
 | |
| #define ECC_PARITY_BITS			13
 | |
| 
 | |
| #define NFI_FDM_SIZE			8
 | |
| 
 | |
| /* Register base */
 | |
| #define NFI_BASE			0x1e003000
 | |
| #define NFI_ECC_BASE			0x1e003800
 | |
| 
 | |
| static struct mt7621_nfc nfc_dev;
 | |
| 
 | |
| static const u16 mt7621_nfi_page_size[] = { SZ_512, SZ_2K, SZ_4K };
 | |
| static const u8 mt7621_nfi_spare_size[] = { 16, 26, 27, 28 };
 | |
| static const u8 mt7621_ecc_strength[] = { 4, 6, 8, 10, 12 };
 | |
| 
 | |
| static inline u32 nfi_read32(struct mt7621_nfc *nfc, u32 reg)
 | |
| {
 | |
| 	return readl(nfc->nfi_regs + reg);
 | |
| }
 | |
| 
 | |
| static inline void nfi_write32(struct mt7621_nfc *nfc, u32 reg, u32 val)
 | |
| {
 | |
| 	writel(val, nfc->nfi_regs + reg);
 | |
| }
 | |
| 
 | |
| static inline u16 nfi_read16(struct mt7621_nfc *nfc, u32 reg)
 | |
| {
 | |
| 	return readw(nfc->nfi_regs + reg);
 | |
| }
 | |
| 
 | |
| static inline void nfi_write16(struct mt7621_nfc *nfc, u32 reg, u16 val)
 | |
| {
 | |
| 	writew(val, nfc->nfi_regs + reg);
 | |
| }
 | |
| 
 | |
| static inline void ecc_write16(struct mt7621_nfc *nfc, u32 reg, u16 val)
 | |
| {
 | |
| 	writew(val, nfc->ecc_regs + reg);
 | |
| }
 | |
| 
 | |
| static inline u32 ecc_read32(struct mt7621_nfc *nfc, u32 reg)
 | |
| {
 | |
| 	return readl(nfc->ecc_regs + reg);
 | |
| }
 | |
| 
 | |
| static inline void ecc_write32(struct mt7621_nfc *nfc, u32 reg, u32 val)
 | |
| {
 | |
| 	return writel(val, nfc->ecc_regs + reg);
 | |
| }
 | |
| 
 | |
| static inline u8 *oob_fdm_ptr(struct nand_chip *nand, int sect)
 | |
| {
 | |
| 	return nand->oob_poi + sect * NFI_FDM_SIZE;
 | |
| }
 | |
| 
 | |
| static inline u8 *oob_ecc_ptr(struct mt7621_nfc *nfc, int sect)
 | |
| {
 | |
| 	struct nand_chip *nand = &nfc->nand;
 | |
| 
 | |
| 	return nand->oob_poi + nand->ecc.steps * NFI_FDM_SIZE +
 | |
| 		sect * (nfc->spare_per_sector - NFI_FDM_SIZE);
 | |
| }
 | |
| 
 | |
| static inline u8 *page_data_ptr(struct nand_chip *nand, const u8 *buf,
 | |
| 				int sect)
 | |
| {
 | |
| 	return (u8 *)buf + sect * nand->ecc.size;
 | |
| }
 | |
| 
 | |
| static int mt7621_ecc_wait_idle(struct mt7621_nfc *nfc, u32 reg)
 | |
| {
 | |
| 	u32 val;
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = readw_poll_timeout(nfc->ecc_regs + reg, val, val & ECC_IDLE,
 | |
| 				 ECC_ENGINE_TIMEOUT);
 | |
| 	if (ret) {
 | |
| 		pr_warn("ECC engine timed out entering idle mode\n");
 | |
| 		return -EIO;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mt7621_ecc_decoder_wait_done(struct mt7621_nfc *nfc, u32 sect)
 | |
| {
 | |
| 	u32 val;
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = readw_poll_timeout(nfc->ecc_regs + ECC_DECDONE, val,
 | |
| 				 val & (1 << sect), ECC_ENGINE_TIMEOUT);
 | |
| 	if (ret) {
 | |
| 		pr_warn("ECC decoder for sector %d timed out\n", sect);
 | |
| 		return -ETIMEDOUT;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void mt7621_ecc_encoder_op(struct mt7621_nfc *nfc, bool enable)
 | |
| {
 | |
| 	mt7621_ecc_wait_idle(nfc, ECC_ENCIDLE);
 | |
| 	ecc_write16(nfc, ECC_ENCCON, enable ? ENC_EN : 0);
 | |
| }
 | |
| 
 | |
| static void mt7621_ecc_decoder_op(struct mt7621_nfc *nfc, bool enable)
 | |
| {
 | |
| 	mt7621_ecc_wait_idle(nfc, ECC_DECIDLE);
 | |
| 	ecc_write16(nfc, ECC_DECCON, enable ? DEC_EN : 0);
 | |
| }
 | |
| 
 | |
| static int mt7621_ecc_correct_check(struct mt7621_nfc *nfc, u8 *sector_buf,
 | |
| 				    u8 *fdm_buf, u32 sect)
 | |
| {
 | |
| 	struct nand_chip *nand = &nfc->nand;
 | |
| 	u32 decnum, num_error_bits, fdm_end_bits;
 | |
| 	u32 error_locations, error_bit_loc;
 | |
| 	u32 error_byte_pos, error_bit_pos;
 | |
| 	int bitflips = 0;
 | |
| 	u32 i;
 | |
| 
 | |
| 	decnum = ecc_read32(nfc, ECC_DECENUM);
 | |
| 	num_error_bits = (decnum >> (sect << ERRNUM_S)) & ERRNUM_M;
 | |
| 	fdm_end_bits = (nand->ecc.size + NFI_FDM_SIZE) << 3;
 | |
| 
 | |
| 	if (!num_error_bits)
 | |
| 		return 0;
 | |
| 
 | |
| 	if (num_error_bits == ERRNUM_M)
 | |
| 		return -1;
 | |
| 
 | |
| 	for (i = 0; i < num_error_bits; i++) {
 | |
| 		error_locations = ecc_read32(nfc, ECC_DECEL(i / 2));
 | |
| 		error_bit_loc = (error_locations >> ((i % 2) * DEC_EL_ODD_S)) &
 | |
| 				DEC_EL_M;
 | |
| 		error_byte_pos = error_bit_loc >> DEC_EL_BYTE_POS_S;
 | |
| 		error_bit_pos = error_bit_loc & DEC_EL_BIT_POS_M;
 | |
| 
 | |
| 		if (error_bit_loc < (nand->ecc.size << 3)) {
 | |
| 			if (sector_buf) {
 | |
| 				sector_buf[error_byte_pos] ^=
 | |
| 					(1 << error_bit_pos);
 | |
| 			}
 | |
| 		} else if (error_bit_loc < fdm_end_bits) {
 | |
| 			if (fdm_buf) {
 | |
| 				fdm_buf[error_byte_pos - nand->ecc.size] ^=
 | |
| 					(1 << error_bit_pos);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		bitflips++;
 | |
| 	}
 | |
| 
 | |
| 	return bitflips;
 | |
| }
 | |
| 
 | |
| static int mt7621_nfc_wait_write_completion(struct mt7621_nfc *nfc,
 | |
| 					    struct nand_chip *nand)
 | |
| {
 | |
| 	u16 val;
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = readw_poll_timeout(nfc->nfi_regs + NFI_ADDRCNTR, val,
 | |
| 				 FIELD_GET(SEC_CNTR, val) >= nand->ecc.steps,
 | |
| 				 NFI_CORE_TIMEOUT);
 | |
| 
 | |
| 	if (ret) {
 | |
| 		pr_warn("NFI core write operation timed out\n");
 | |
| 		return -ETIMEDOUT;
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static void mt7621_nfc_hw_reset(struct mt7621_nfc *nfc)
 | |
| {
 | |
| 	u32 val;
 | |
| 	int ret;
 | |
| 
 | |
| 	/* reset all registers and force the NFI master to terminate */
 | |
| 	nfi_write16(nfc, NFI_CON, CON_FIFO_FLUSH | CON_NFI_RST);
 | |
| 
 | |
| 	/* wait for the master to finish the last transaction */
 | |
| 	ret = readw_poll_timeout(nfc->nfi_regs + NFI_MASTER_STA, val,
 | |
| 				 !(val & MASTER_STA_MASK), NFI_RESET_TIMEOUT);
 | |
| 	if (ret) {
 | |
| 		pr_warn("Failed to reset NFI master in %dms\n",
 | |
| 			NFI_RESET_TIMEOUT);
 | |
| 	}
 | |
| 
 | |
| 	/* ensure any status register affected by the NFI master is reset */
 | |
| 	nfi_write16(nfc, NFI_CON, CON_FIFO_FLUSH | CON_NFI_RST);
 | |
| 	nfi_write16(nfc, NFI_STRDATA, 0);
 | |
| }
 | |
| 
 | |
| static inline void mt7621_nfc_hw_init(struct mt7621_nfc *nfc)
 | |
| {
 | |
| 	u32 acccon;
 | |
| 
 | |
| 	/*
 | |
| 	 * CNRNB: nand ready/busy register
 | |
| 	 * -------------------------------
 | |
| 	 * 7:4: timeout register for polling the NAND busy/ready signal
 | |
| 	 * 0  : poll the status of the busy/ready signal after [7:4]*16 cycles.
 | |
| 	 */
 | |
| 	nfi_write16(nfc, NFI_CNRNB, CB2R_TIME | STR_CNRNB);
 | |
| 
 | |
| 	mt7621_nfc_hw_reset(nfc);
 | |
| 
 | |
| 	/* Apply default access timing */
 | |
| 	acccon = ACCTIMING(ACCCON_POECS_DEF, ACCCON_PRECS_DEF, ACCCON_C2R_DEF,
 | |
| 			   ACCCON_W2R_DEF, ACCCON_WH_DEF, ACCCON_WST_DEF,
 | |
| 			   ACCCON_RLT_DEF);
 | |
| 
 | |
| 	nfi_write32(nfc, NFI_ACCCON, acccon);
 | |
| }
 | |
| 
 | |
| static int mt7621_nfc_send_command(struct mt7621_nfc *nfc, u8 command)
 | |
| {
 | |
| 	u32 val;
 | |
| 	int ret;
 | |
| 
 | |
| 	nfi_write32(nfc, NFI_CMD, command);
 | |
| 
 | |
| 	ret = readl_poll_timeout(nfc->nfi_regs + NFI_STA, val, !(val & STA_CMD),
 | |
| 				 NFI_CORE_TIMEOUT);
 | |
| 	if (ret) {
 | |
| 		pr_warn("NFI core timed out entering command mode\n");
 | |
| 		return -EIO;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mt7621_nfc_send_address_byte(struct mt7621_nfc *nfc, int addr)
 | |
| {
 | |
| 	u32 val;
 | |
| 	int ret;
 | |
| 
 | |
| 	nfi_write32(nfc, NFI_COLADDR, addr);
 | |
| 	nfi_write32(nfc, NFI_ROWADDR, 0);
 | |
| 	nfi_write16(nfc, NFI_ADDRNOB, 1);
 | |
| 
 | |
| 	ret = readl_poll_timeout(nfc->nfi_regs + NFI_STA, val,
 | |
| 				 !(val & STA_ADDR), NFI_CORE_TIMEOUT);
 | |
| 	if (ret) {
 | |
| 		pr_warn("NFI core timed out entering address mode\n");
 | |
| 		return -EIO;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void mt7621_nfc_cmd_ctrl(struct mtd_info *mtd, int dat,
 | |
| 				unsigned int ctrl)
 | |
| {
 | |
| 	struct mt7621_nfc *nfc = nand_get_controller_data(mtd_to_nand(mtd));
 | |
| 
 | |
| 	if (ctrl & NAND_ALE) {
 | |
| 		mt7621_nfc_send_address_byte(nfc, dat & 0xff);
 | |
| 	} else if (ctrl & NAND_CLE) {
 | |
| 		mt7621_nfc_hw_reset(nfc);
 | |
| 		nfi_write16(nfc, NFI_CNFG,
 | |
| 			    FIELD_PREP(CNFG_OP_MODE, CNFG_OP_CUSTOM));
 | |
| 		mt7621_nfc_send_command(nfc, dat);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int mt7621_nfc_dev_ready(struct mtd_info *mtd)
 | |
| {
 | |
| 	struct mt7621_nfc *nfc = nand_get_controller_data(mtd_to_nand(mtd));
 | |
| 
 | |
| 	if (nfi_read32(nfc, NFI_STA) & STA_BUSY)
 | |
| 		return 0;
 | |
| 
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| static void mt7621_nfc_select_chip(struct mtd_info *mtd, int chipnr)
 | |
| {
 | |
| 	struct mt7621_nfc *nfc = nand_get_controller_data(mtd_to_nand(mtd));
 | |
| 
 | |
| 	nfi_write16(nfc, NFI_CSEL, 0);
 | |
| }
 | |
| 
 | |
| static void mt7621_nfc_wait_pio_ready(struct mt7621_nfc *nfc)
 | |
| {
 | |
| 	int ret;
 | |
| 	u16 val;
 | |
| 
 | |
| 	ret = readw_poll_timeout(nfc->nfi_regs + NFI_PIO_DIRDY, val,
 | |
| 				 val & PIO_DIRDY, NFI_CORE_TIMEOUT);
 | |
| 	if (ret < 0)
 | |
| 		pr_err("NFI core PIO mode not ready\n");
 | |
| }
 | |
| 
 | |
| static u32 mt7621_nfc_pio_read(struct mt7621_nfc *nfc, bool br)
 | |
| {
 | |
| 	u32 reg, fsm;
 | |
| 
 | |
| 	/* after each byte read, the NFI_STA reg is reset by the hardware */
 | |
| 	reg = nfi_read32(nfc, NFI_STA);
 | |
| 	fsm = FIELD_GET(STA_NFI_FSM, reg);
 | |
| 
 | |
| 	if (fsm != STA_FSM_CUSTOM_DATA) {
 | |
| 		reg = nfi_read16(nfc, NFI_CNFG);
 | |
| 		reg |= CNFG_READ_MODE | CNFG_BYTE_RW;
 | |
| 		if (!br)
 | |
| 			reg &= ~CNFG_BYTE_RW;
 | |
| 		nfi_write16(nfc, NFI_CNFG, reg);
 | |
| 
 | |
| 		/*
 | |
| 		 * set to max sector to allow the HW to continue reading over
 | |
| 		 * unaligned accesses
 | |
| 		 */
 | |
| 		nfi_write16(nfc, NFI_CON, CON_NFI_SEC | CON_NFI_BRD);
 | |
| 
 | |
| 		/* trigger to fetch data */
 | |
| 		nfi_write16(nfc, NFI_STRDATA, STR_DATA);
 | |
| 	}
 | |
| 
 | |
| 	mt7621_nfc_wait_pio_ready(nfc);
 | |
| 
 | |
| 	return nfi_read32(nfc, NFI_DATAR);
 | |
| }
 | |
| 
 | |
| static void mt7621_nfc_read_data(struct mt7621_nfc *nfc, u8 *buf, u32 len)
 | |
| {
 | |
| 	while (((uintptr_t)buf & 3) && len) {
 | |
| 		*buf = mt7621_nfc_pio_read(nfc, true);
 | |
| 		buf++;
 | |
| 		len--;
 | |
| 	}
 | |
| 
 | |
| 	while (len >= 4) {
 | |
| 		*(u32 *)buf = mt7621_nfc_pio_read(nfc, false);
 | |
| 		buf += 4;
 | |
| 		len -= 4;
 | |
| 	}
 | |
| 
 | |
| 	while (len) {
 | |
| 		*buf = mt7621_nfc_pio_read(nfc, true);
 | |
| 		buf++;
 | |
| 		len--;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void mt7621_nfc_read_data_discard(struct mt7621_nfc *nfc, u32 len)
 | |
| {
 | |
| 	while (len >= 4) {
 | |
| 		mt7621_nfc_pio_read(nfc, false);
 | |
| 		len -= 4;
 | |
| 	}
 | |
| 
 | |
| 	while (len) {
 | |
| 		mt7621_nfc_pio_read(nfc, true);
 | |
| 		len--;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void mt7621_nfc_pio_write(struct mt7621_nfc *nfc, u32 val, bool bw)
 | |
| {
 | |
| 	u32 reg, fsm;
 | |
| 
 | |
| 	reg = nfi_read32(nfc, NFI_STA);
 | |
| 	fsm = FIELD_GET(STA_NFI_FSM, reg);
 | |
| 
 | |
| 	if (fsm != STA_FSM_CUSTOM_DATA) {
 | |
| 		reg = nfi_read16(nfc, NFI_CNFG);
 | |
| 		reg &= ~(CNFG_READ_MODE | CNFG_BYTE_RW);
 | |
| 		if (bw)
 | |
| 			reg |= CNFG_BYTE_RW;
 | |
| 		nfi_write16(nfc, NFI_CNFG, reg);
 | |
| 
 | |
| 		nfi_write16(nfc, NFI_CON, CON_NFI_SEC | CON_NFI_BWR);
 | |
| 		nfi_write16(nfc, NFI_STRDATA, STR_DATA);
 | |
| 	}
 | |
| 
 | |
| 	mt7621_nfc_wait_pio_ready(nfc);
 | |
| 	nfi_write32(nfc, NFI_DATAW, val);
 | |
| }
 | |
| 
 | |
| static void mt7621_nfc_write_data(struct mt7621_nfc *nfc, const u8 *buf,
 | |
| 				  u32 len)
 | |
| {
 | |
| 	while (((uintptr_t)buf & 3) && len) {
 | |
| 		mt7621_nfc_pio_write(nfc, *buf, true);
 | |
| 		buf++;
 | |
| 		len--;
 | |
| 	}
 | |
| 
 | |
| 	while (len >= 4) {
 | |
| 		mt7621_nfc_pio_write(nfc, *(const u32 *)buf, false);
 | |
| 		buf += 4;
 | |
| 		len -= 4;
 | |
| 	}
 | |
| 
 | |
| 	while (len) {
 | |
| 		mt7621_nfc_pio_write(nfc, *buf, true);
 | |
| 		buf++;
 | |
| 		len--;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void mt7621_nfc_write_data_empty(struct mt7621_nfc *nfc, u32 len)
 | |
| {
 | |
| 	while (len >= 4) {
 | |
| 		mt7621_nfc_pio_write(nfc, 0xffffffff, false);
 | |
| 		len -= 4;
 | |
| 	}
 | |
| 
 | |
| 	while (len) {
 | |
| 		mt7621_nfc_pio_write(nfc, 0xff, true);
 | |
| 		len--;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void mt7621_nfc_write_byte(struct mtd_info *mtd, u8 byte)
 | |
| {
 | |
| 	struct mt7621_nfc *nfc = nand_get_controller_data(mtd_to_nand(mtd));
 | |
| 
 | |
| 	mt7621_nfc_pio_write(nfc, byte, true);
 | |
| }
 | |
| 
 | |
| static void mt7621_nfc_write_buf(struct mtd_info *mtd, const u8 *buf, int len)
 | |
| {
 | |
| 	struct mt7621_nfc *nfc = nand_get_controller_data(mtd_to_nand(mtd));
 | |
| 
 | |
| 	return mt7621_nfc_write_data(nfc, buf, len);
 | |
| }
 | |
| 
 | |
| static u8 mt7621_nfc_read_byte(struct mtd_info *mtd)
 | |
| {
 | |
| 	struct mt7621_nfc *nfc = nand_get_controller_data(mtd_to_nand(mtd));
 | |
| 
 | |
| 	return mt7621_nfc_pio_read(nfc, true);
 | |
| }
 | |
| 
 | |
| static void mt7621_nfc_read_buf(struct mtd_info *mtd, u8 *buf, int len)
 | |
| {
 | |
| 	struct mt7621_nfc *nfc = nand_get_controller_data(mtd_to_nand(mtd));
 | |
| 
 | |
| 	mt7621_nfc_read_data(nfc, buf, len);
 | |
| }
 | |
| 
 | |
| static int mt7621_nfc_calc_ecc_strength(struct mt7621_nfc *nfc,
 | |
| 					u32 avail_ecc_bytes)
 | |
| {
 | |
| 	struct nand_chip *nand = &nfc->nand;
 | |
| 	struct mtd_info *mtd = nand_to_mtd(nand);
 | |
| 	u32 strength;
 | |
| 	int i;
 | |
| 
 | |
| 	strength = avail_ecc_bytes * 8 / ECC_PARITY_BITS;
 | |
| 
 | |
| 	/* Find the closest supported ecc strength */
 | |
| 	for (i = ARRAY_SIZE(mt7621_ecc_strength) - 1; i >= 0; i--) {
 | |
| 		if (mt7621_ecc_strength[i] <= strength)
 | |
| 			break;
 | |
| 	}
 | |
| 
 | |
| 	if (unlikely(i < 0)) {
 | |
| 		pr_err("OOB size (%u) is not supported\n", mtd->oobsize);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	nand->ecc.strength = mt7621_ecc_strength[i];
 | |
| 	nand->ecc.bytes = DIV_ROUND_UP(nand->ecc.strength * ECC_PARITY_BITS, 8);
 | |
| 
 | |
| 	pr_debug("ECC strength adjusted to %u bits\n", nand->ecc.strength);
 | |
| 
 | |
| 	return i;
 | |
| }
 | |
| 
 | |
| static int mt7621_nfc_set_spare_per_sector(struct mt7621_nfc *nfc)
 | |
| {
 | |
| 	struct nand_chip *nand = &nfc->nand;
 | |
| 	struct mtd_info *mtd = nand_to_mtd(nand);
 | |
| 	u32 size;
 | |
| 	int i;
 | |
| 
 | |
| 	size = nand->ecc.bytes + NFI_FDM_SIZE;
 | |
| 
 | |
| 	/* Find the closest supported spare size */
 | |
| 	for (i = 0; i < ARRAY_SIZE(mt7621_nfi_spare_size); i++) {
 | |
| 		if (mt7621_nfi_spare_size[i] >= size)
 | |
| 			break;
 | |
| 	}
 | |
| 
 | |
| 	if (unlikely(i >= ARRAY_SIZE(mt7621_nfi_spare_size))) {
 | |
| 		pr_err("OOB size (%u) is not supported\n", mtd->oobsize);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	nfc->spare_per_sector = mt7621_nfi_spare_size[i];
 | |
| 
 | |
| 	return i;
 | |
| }
 | |
| 
 | |
| static int mt7621_nfc_ecc_init(struct mt7621_nfc *nfc)
 | |
| {
 | |
| 	struct nand_chip *nand = &nfc->nand;
 | |
| 	struct mtd_info *mtd = nand_to_mtd(nand);
 | |
| 	u32 avail_ecc_bytes, encode_block_size, decode_block_size;
 | |
| 	u32 ecc_enccfg, ecc_deccfg;
 | |
| 	int ecc_cap;
 | |
| 
 | |
| 	nand->ecc.options |= NAND_ECC_CUSTOM_PAGE_ACCESS;
 | |
| 
 | |
| 	nand->ecc.size = ECC_SECTOR_SIZE;
 | |
| 	nand->ecc.steps = mtd->writesize / nand->ecc.size;
 | |
| 
 | |
| 	avail_ecc_bytes = mtd->oobsize / nand->ecc.steps - NFI_FDM_SIZE;
 | |
| 
 | |
| 	ecc_cap = mt7621_nfc_calc_ecc_strength(nfc, avail_ecc_bytes);
 | |
| 	if (ecc_cap < 0)
 | |
| 		return ecc_cap;
 | |
| 
 | |
| 	/* Sector + FDM */
 | |
| 	encode_block_size = (nand->ecc.size + NFI_FDM_SIZE) * 8;
 | |
| 	ecc_enccfg = ecc_cap | FIELD_PREP(ENC_MODE, ENC_MODE_NFI) |
 | |
| 		     FIELD_PREP(ENC_CNFG_MSG, encode_block_size);
 | |
| 
 | |
| 	/* Sector + FDM + ECC parity bits */
 | |
| 	decode_block_size = ((nand->ecc.size + NFI_FDM_SIZE) * 8) +
 | |
| 			    nand->ecc.strength * ECC_PARITY_BITS;
 | |
| 	ecc_deccfg = ecc_cap | FIELD_PREP(DEC_MODE, DEC_MODE_NFI) |
 | |
| 		     FIELD_PREP(DEC_CS, decode_block_size) |
 | |
| 		     FIELD_PREP(DEC_CON, DEC_CON_EL) | DEC_EMPTY_EN;
 | |
| 
 | |
| 	mt7621_ecc_encoder_op(nfc, false);
 | |
| 	ecc_write32(nfc, ECC_ENCCNFG, ecc_enccfg);
 | |
| 
 | |
| 	mt7621_ecc_decoder_op(nfc, false);
 | |
| 	ecc_write32(nfc, ECC_DECCNFG, ecc_deccfg);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mt7621_nfc_set_page_format(struct mt7621_nfc *nfc)
 | |
| {
 | |
| 	struct nand_chip *nand = &nfc->nand;
 | |
| 	struct mtd_info *mtd = nand_to_mtd(nand);
 | |
| 	int i, spare_size;
 | |
| 	u32 pagefmt;
 | |
| 
 | |
| 	spare_size = mt7621_nfc_set_spare_per_sector(nfc);
 | |
| 	if (spare_size < 0)
 | |
| 		return spare_size;
 | |
| 
 | |
| 	for (i = 0; i < ARRAY_SIZE(mt7621_nfi_page_size); i++) {
 | |
| 		if (mt7621_nfi_page_size[i] == mtd->writesize)
 | |
| 			break;
 | |
| 	}
 | |
| 
 | |
| 	if (unlikely(i >= ARRAY_SIZE(mt7621_nfi_page_size))) {
 | |
| 		pr_err("Page size (%u) is not supported\n", mtd->writesize);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	pagefmt = FIELD_PREP(PAGEFMT_PAGE, i) |
 | |
| 		  FIELD_PREP(PAGEFMT_SPARE, spare_size) |
 | |
| 		  FIELD_PREP(PAGEFMT_FDM, NFI_FDM_SIZE) |
 | |
| 		  FIELD_PREP(PAGEFMT_FDM_ECC, NFI_FDM_SIZE);
 | |
| 
 | |
| 	nfi_write16(nfc, NFI_PAGEFMT, pagefmt);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mt7621_nfc_attach_chip(struct nand_chip *nand)
 | |
| {
 | |
| 	struct mt7621_nfc *nfc = nand_get_controller_data(nand);
 | |
| 	int ret;
 | |
| 
 | |
| 	if (nand->options & NAND_BUSWIDTH_16) {
 | |
| 		pr_err("16-bit buswidth is not supported");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	ret = mt7621_nfc_ecc_init(nfc);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	return mt7621_nfc_set_page_format(nfc);
 | |
| }
 | |
| 
 | |
| static void mt7621_nfc_write_fdm(struct mt7621_nfc *nfc)
 | |
| {
 | |
| 	struct nand_chip *nand = &nfc->nand;
 | |
| 	u32 vall, valm;
 | |
| 	u8 *oobptr;
 | |
| 	int i, j;
 | |
| 
 | |
| 	for (i = 0; i < nand->ecc.steps; i++) {
 | |
| 		vall = 0;
 | |
| 		valm = 0;
 | |
| 		oobptr = oob_fdm_ptr(nand, i);
 | |
| 
 | |
| 		for (j = 0; j < 4; j++)
 | |
| 			vall |= (u32)oobptr[j] << (j * 8);
 | |
| 
 | |
| 		for (j = 0; j < 4; j++)
 | |
| 			valm |= (u32)oobptr[j + 4] << (j * 8);
 | |
| 
 | |
| 		nfi_write32(nfc, NFI_FDML(i), vall);
 | |
| 		nfi_write32(nfc, NFI_FDMM(i), valm);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void mt7621_nfc_read_sector_fdm(struct mt7621_nfc *nfc, u32 sect)
 | |
| {
 | |
| 	struct nand_chip *nand = &nfc->nand;
 | |
| 	u32 vall, valm;
 | |
| 	u8 *oobptr;
 | |
| 	int i;
 | |
| 
 | |
| 	vall = nfi_read32(nfc, NFI_FDML(sect));
 | |
| 	valm = nfi_read32(nfc, NFI_FDMM(sect));
 | |
| 	oobptr = oob_fdm_ptr(nand, sect);
 | |
| 
 | |
| 	for (i = 0; i < 4; i++)
 | |
| 		oobptr[i] = (vall >> (i * 8)) & 0xff;
 | |
| 
 | |
| 	for (i = 0; i < 4; i++)
 | |
| 		oobptr[i + 4] = (valm >> (i * 8)) & 0xff;
 | |
| }
 | |
| 
 | |
| static int mt7621_nfc_read_page_hwecc(struct mtd_info *mtd,
 | |
| 				      struct nand_chip *nand, uint8_t *buf,
 | |
| 				      int oob_required, int page)
 | |
| {
 | |
| 	struct mt7621_nfc *nfc = nand_get_controller_data(nand);
 | |
| 	int bitflips = 0, ret = 0;
 | |
| 	int rc, i;
 | |
| 
 | |
| 	nand_read_page_op(nand, page, 0, NULL, 0);
 | |
| 
 | |
| 	nfi_write16(nfc, NFI_CNFG, FIELD_PREP(CNFG_OP_MODE, CNFG_OP_CUSTOM) |
 | |
| 		    CNFG_READ_MODE | CNFG_AUTO_FMT_EN | CNFG_HW_ECC_EN);
 | |
| 
 | |
| 	mt7621_ecc_decoder_op(nfc, true);
 | |
| 
 | |
| 	nfi_write16(nfc, NFI_CON, FIELD_PREP(CON_NFI_SEC, nand->ecc.steps) |
 | |
| 		    CON_NFI_BRD);
 | |
| 
 | |
| 	for (i = 0; i < nand->ecc.steps; i++) {
 | |
| 		if (buf)
 | |
| 			mt7621_nfc_read_data(nfc, page_data_ptr(nand, buf, i),
 | |
| 					     nand->ecc.size);
 | |
| 		else
 | |
| 			mt7621_nfc_read_data_discard(nfc, nand->ecc.size);
 | |
| 
 | |
| 		rc = mt7621_ecc_decoder_wait_done(nfc, i);
 | |
| 
 | |
| 		mt7621_nfc_read_sector_fdm(nfc, i);
 | |
| 
 | |
| 		if (rc < 0) {
 | |
| 			ret = -EIO;
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		rc = mt7621_ecc_correct_check(nfc,
 | |
| 			buf ? page_data_ptr(nand, buf, i) : NULL,
 | |
| 			oob_fdm_ptr(nand, i), i);
 | |
| 
 | |
| 		if (rc < 0) {
 | |
| 			pr_warn("Uncorrectable ECC error at page %d step %d\n",
 | |
| 				page, i);
 | |
| 			bitflips = nand->ecc.strength + 1;
 | |
| 			mtd->ecc_stats.failed++;
 | |
| 		} else {
 | |
| 			if (rc > bitflips)
 | |
| 				bitflips = rc;
 | |
| 			mtd->ecc_stats.corrected += rc;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	mt7621_ecc_decoder_op(nfc, false);
 | |
| 
 | |
| 	nfi_write16(nfc, NFI_CON, 0);
 | |
| 
 | |
| 	if (ret < 0)
 | |
| 		return ret;
 | |
| 
 | |
| 	return bitflips;
 | |
| }
 | |
| 
 | |
| static int mt7621_nfc_read_page_raw(struct mtd_info *mtd,
 | |
| 				    struct nand_chip *nand, uint8_t *buf,
 | |
| 				    int oob_required, int page)
 | |
| {
 | |
| 	struct mt7621_nfc *nfc = nand_get_controller_data(nand);
 | |
| 	int i;
 | |
| 
 | |
| 	nand_read_page_op(nand, page, 0, NULL, 0);
 | |
| 
 | |
| 	nfi_write16(nfc, NFI_CNFG, FIELD_PREP(CNFG_OP_MODE, CNFG_OP_CUSTOM) |
 | |
| 		    CNFG_READ_MODE);
 | |
| 
 | |
| 	nfi_write16(nfc, NFI_CON, FIELD_PREP(CON_NFI_SEC, nand->ecc.steps) |
 | |
| 		    CON_NFI_BRD);
 | |
| 
 | |
| 	for (i = 0; i < nand->ecc.steps; i++) {
 | |
| 		/* Read data */
 | |
| 		if (buf)
 | |
| 			mt7621_nfc_read_data(nfc, page_data_ptr(nand, buf, i),
 | |
| 					     nand->ecc.size);
 | |
| 		else
 | |
| 			mt7621_nfc_read_data_discard(nfc, nand->ecc.size);
 | |
| 
 | |
| 		/* Read FDM */
 | |
| 		mt7621_nfc_read_data(nfc, oob_fdm_ptr(nand, i), NFI_FDM_SIZE);
 | |
| 
 | |
| 		/* Read ECC parity data */
 | |
| 		mt7621_nfc_read_data(nfc, oob_ecc_ptr(nfc, i),
 | |
| 				     nfc->spare_per_sector - NFI_FDM_SIZE);
 | |
| 	}
 | |
| 
 | |
| 	nfi_write16(nfc, NFI_CON, 0);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mt7621_nfc_read_oob_hwecc(struct mtd_info *mtd,
 | |
| 				     struct nand_chip *nand, int page)
 | |
| {
 | |
| 	return mt7621_nfc_read_page_hwecc(mtd, nand, NULL, 1, page);
 | |
| }
 | |
| 
 | |
| static int mt7621_nfc_read_oob_raw(struct mtd_info *mtd,
 | |
| 				   struct nand_chip *nand, int page)
 | |
| {
 | |
| 	return mt7621_nfc_read_page_raw(mtd, nand, NULL, 1, page);
 | |
| }
 | |
| 
 | |
| static int mt7621_nfc_check_empty_page(struct nand_chip *nand, const u8 *buf)
 | |
| {
 | |
| 	struct mtd_info *mtd = nand_to_mtd(nand);
 | |
| 	u8 *oobptr;
 | |
| 	u32 i, j;
 | |
| 
 | |
| 	if (buf) {
 | |
| 		for (i = 0; i < mtd->writesize; i++)
 | |
| 			if (buf[i] != 0xff)
 | |
| 				return 0;
 | |
| 	}
 | |
| 
 | |
| 	for (i = 0; i < nand->ecc.steps; i++) {
 | |
| 		oobptr = oob_fdm_ptr(nand, i);
 | |
| 		for (j = 0; j < NFI_FDM_SIZE; j++)
 | |
| 			if (oobptr[j] != 0xff)
 | |
| 				return 0;
 | |
| 	}
 | |
| 
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| static int mt7621_nfc_write_page_hwecc(struct mtd_info *mtd,
 | |
| 				       struct nand_chip *nand,
 | |
| 				       const u8 *buf, int oob_required,
 | |
| 				       int page)
 | |
| {
 | |
| 	struct mt7621_nfc *nfc = nand_get_controller_data(nand);
 | |
| 
 | |
| 	if (mt7621_nfc_check_empty_page(nand, buf)) {
 | |
| 		/*
 | |
| 		 * MT7621 ECC engine always generates parity code for input
 | |
| 		 * pages, even for empty pages. Doing so will write back ECC
 | |
| 		 * parity code to the oob region, which means such pages will
 | |
| 		 * no longer be empty pages.
 | |
| 		 *
 | |
| 		 * To avoid this, stop write operation if current page is an
 | |
| 		 * empty page.
 | |
| 		 */
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	nand_prog_page_begin_op(nand, page, 0, NULL, 0);
 | |
| 
 | |
| 	nfi_write16(nfc, NFI_CNFG, FIELD_PREP(CNFG_OP_MODE, CNFG_OP_CUSTOM) |
 | |
| 		    CNFG_AUTO_FMT_EN | CNFG_HW_ECC_EN);
 | |
| 
 | |
| 	mt7621_ecc_encoder_op(nfc, true);
 | |
| 
 | |
| 	mt7621_nfc_write_fdm(nfc);
 | |
| 
 | |
| 	nfi_write16(nfc, NFI_CON, FIELD_PREP(CON_NFI_SEC, nand->ecc.steps) |
 | |
| 		    CON_NFI_BWR);
 | |
| 
 | |
| 	if (buf)
 | |
| 		mt7621_nfc_write_data(nfc, buf, mtd->writesize);
 | |
| 	else
 | |
| 		mt7621_nfc_write_data_empty(nfc, mtd->writesize);
 | |
| 
 | |
| 	mt7621_nfc_wait_write_completion(nfc, nand);
 | |
| 
 | |
| 	mt7621_ecc_encoder_op(nfc, false);
 | |
| 
 | |
| 	nfi_write16(nfc, NFI_CON, 0);
 | |
| 
 | |
| 	return nand_prog_page_end_op(nand);
 | |
| }
 | |
| 
 | |
| static int mt7621_nfc_write_page_raw(struct mtd_info *mtd,
 | |
| 				     struct nand_chip *nand,
 | |
| 				     const u8 *buf, int oob_required,
 | |
| 				     int page)
 | |
| {
 | |
| 	struct mt7621_nfc *nfc = nand_get_controller_data(nand);
 | |
| 	int i;
 | |
| 
 | |
| 	nand_prog_page_begin_op(nand, page, 0, NULL, 0);
 | |
| 
 | |
| 	nfi_write16(nfc, NFI_CNFG, FIELD_PREP(CNFG_OP_MODE, CNFG_OP_CUSTOM));
 | |
| 
 | |
| 	nfi_write16(nfc, NFI_CON, FIELD_PREP(CON_NFI_SEC, nand->ecc.steps) |
 | |
| 		    CON_NFI_BWR);
 | |
| 
 | |
| 	for (i = 0; i < nand->ecc.steps; i++) {
 | |
| 		/* Write data */
 | |
| 		if (buf)
 | |
| 			mt7621_nfc_write_data(nfc, page_data_ptr(nand, buf, i),
 | |
| 					      nand->ecc.size);
 | |
| 		else
 | |
| 			mt7621_nfc_write_data_empty(nfc, nand->ecc.size);
 | |
| 
 | |
| 		/* Write FDM */
 | |
| 		mt7621_nfc_write_data(nfc, oob_fdm_ptr(nand, i),
 | |
| 				      NFI_FDM_SIZE);
 | |
| 
 | |
| 		/* Write dummy ECC parity data */
 | |
| 		mt7621_nfc_write_data_empty(nfc, nfc->spare_per_sector -
 | |
| 					    NFI_FDM_SIZE);
 | |
| 	}
 | |
| 
 | |
| 	mt7621_nfc_wait_write_completion(nfc, nand);
 | |
| 
 | |
| 	nfi_write16(nfc, NFI_CON, 0);
 | |
| 
 | |
| 	return nand_prog_page_end_op(nand);
 | |
| }
 | |
| 
 | |
| static int mt7621_nfc_write_oob_hwecc(struct mtd_info *mtd,
 | |
| 				      struct nand_chip *nand, int page)
 | |
| {
 | |
| 	return mt7621_nfc_write_page_hwecc(mtd, nand, NULL, 1, page);
 | |
| }
 | |
| 
 | |
| static int mt7621_nfc_write_oob_raw(struct mtd_info *mtd,
 | |
| 				    struct nand_chip *nand, int page)
 | |
| {
 | |
| 	return mt7621_nfc_write_page_raw(mtd, nand, NULL, 1, page);
 | |
| }
 | |
| 
 | |
| static int mt7621_nfc_ooblayout_free(struct mtd_info *mtd, int section,
 | |
| 				     struct mtd_oob_region *oob_region)
 | |
| {
 | |
| 	struct nand_chip *nand = mtd_to_nand(mtd);
 | |
| 
 | |
| 	if (section >= nand->ecc.steps)
 | |
| 		return -ERANGE;
 | |
| 
 | |
| 	oob_region->length = NFI_FDM_SIZE - 1;
 | |
| 	oob_region->offset = section * NFI_FDM_SIZE + 1;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mt7621_nfc_ooblayout_ecc(struct mtd_info *mtd, int section,
 | |
| 				    struct mtd_oob_region *oob_region)
 | |
| {
 | |
| 	struct nand_chip *nand = mtd_to_nand(mtd);
 | |
| 
 | |
| 	if (section)
 | |
| 		return -ERANGE;
 | |
| 
 | |
| 	oob_region->offset = NFI_FDM_SIZE * nand->ecc.steps;
 | |
| 	oob_region->length = mtd->oobsize - oob_region->offset;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static const struct mtd_ooblayout_ops mt7621_nfc_ooblayout_ops = {
 | |
| 	.rfree = mt7621_nfc_ooblayout_free,
 | |
| 	.ecc = mt7621_nfc_ooblayout_ecc,
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * This function will override the default one which is not supposed to be
 | |
|  * used for ECC syndrome based pages.
 | |
|  */
 | |
| static int mt7621_nfc_block_bad(struct mtd_info *mtd, loff_t ofs)
 | |
| {
 | |
| 	struct nand_chip *nand = mtd_to_nand(mtd);
 | |
| 	struct mtd_oob_ops ops;
 | |
| 	int ret, i = 0;
 | |
| 	u16 bad;
 | |
| 
 | |
| 	memset(&ops, 0, sizeof(ops));
 | |
| 	ops.oobbuf = (uint8_t *)&bad;
 | |
| 	ops.ooboffs = nand->badblockpos;
 | |
| 	if (nand->options & NAND_BUSWIDTH_16) {
 | |
| 		ops.ooboffs &= ~0x01;
 | |
| 		ops.ooblen = 2;
 | |
| 	} else {
 | |
| 		ops.ooblen = 1;
 | |
| 	}
 | |
| 	ops.mode = MTD_OPS_RAW;
 | |
| 
 | |
| 	/* Read from first/last page(s) if necessary */
 | |
| 	if (nand->bbt_options & NAND_BBT_SCANLASTPAGE)
 | |
| 		ofs += mtd->erasesize - mtd->writesize;
 | |
| 
 | |
| 	do {
 | |
| 		ret = mtd_read_oob(mtd, ofs, &ops);
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 
 | |
| 		if (likely(nand->badblockbits == 8))
 | |
| 			ret = bad != 0xFF;
 | |
| 		else
 | |
| 			ret = hweight8(bad) < nand->badblockbits;
 | |
| 
 | |
| 		i++;
 | |
| 		ofs += mtd->writesize;
 | |
| 	} while (!ret && (nand->bbt_options & NAND_BBT_SCAN2NDPAGE) && i < 2);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static void mt7621_nfc_init_chip(struct mt7621_nfc *nfc)
 | |
| {
 | |
| 	struct nand_chip *nand = &nfc->nand;
 | |
| 	struct mtd_info *mtd;
 | |
| 	int ret;
 | |
| 
 | |
| 	nand_set_controller_data(nand, nfc);
 | |
| 
 | |
| 	nand->options |= NAND_NO_SUBPAGE_WRITE;
 | |
| 
 | |
| 	nand->ecc.mode = NAND_ECC_HW_SYNDROME;
 | |
| 	nand->ecc.read_page = mt7621_nfc_read_page_hwecc;
 | |
| 	nand->ecc.read_page_raw = mt7621_nfc_read_page_raw;
 | |
| 	nand->ecc.write_page = mt7621_nfc_write_page_hwecc;
 | |
| 	nand->ecc.write_page_raw = mt7621_nfc_write_page_raw;
 | |
| 	nand->ecc.read_oob = mt7621_nfc_read_oob_hwecc;
 | |
| 	nand->ecc.read_oob_raw = mt7621_nfc_read_oob_raw;
 | |
| 	nand->ecc.write_oob = mt7621_nfc_write_oob_hwecc;
 | |
| 	nand->ecc.write_oob_raw = mt7621_nfc_write_oob_raw;
 | |
| 
 | |
| 	nand->dev_ready = mt7621_nfc_dev_ready;
 | |
| 	nand->select_chip = mt7621_nfc_select_chip;
 | |
| 	nand->write_byte = mt7621_nfc_write_byte;
 | |
| 	nand->write_buf = mt7621_nfc_write_buf;
 | |
| 	nand->read_byte = mt7621_nfc_read_byte;
 | |
| 	nand->read_buf = mt7621_nfc_read_buf;
 | |
| 	nand->cmd_ctrl = mt7621_nfc_cmd_ctrl;
 | |
| 	nand->block_bad = mt7621_nfc_block_bad;
 | |
| 
 | |
| 	mtd = nand_to_mtd(nand);
 | |
| 	mtd_set_ooblayout(mtd, &mt7621_nfc_ooblayout_ops);
 | |
| 
 | |
| 	/* Reset NFI master */
 | |
| 	mt7621_nfc_hw_init(nfc);
 | |
| 
 | |
| 	ret = nand_scan_ident(mtd, 1, NULL);
 | |
| 	if (ret)
 | |
| 		return;
 | |
| 
 | |
| 	mt7621_nfc_attach_chip(nand);
 | |
| 
 | |
| 	ret = nand_scan_tail(mtd);
 | |
| 	if (ret)
 | |
| 		return;
 | |
| 
 | |
| 	nand_register(0, mtd);
 | |
| }
 | |
| 
 | |
| static void mt7621_nfc_set_regs(struct mt7621_nfc *nfc)
 | |
| {
 | |
| 	nfc->nfi_regs = (void __iomem *)CKSEG1ADDR(NFI_BASE);
 | |
| 	nfc->ecc_regs = (void __iomem *)CKSEG1ADDR(NFI_ECC_BASE);
 | |
| }
 | |
| 
 | |
| void mt7621_nfc_spl_init(struct mt7621_nfc *nfc)
 | |
| {
 | |
| 	struct nand_chip *nand = &nfc->nand;
 | |
| 
 | |
| 	mt7621_nfc_set_regs(nfc);
 | |
| 
 | |
| 	nand_set_controller_data(nand, nfc);
 | |
| 
 | |
| 	nand->options |= NAND_NO_SUBPAGE_WRITE;
 | |
| 
 | |
| 	nand->ecc.mode = NAND_ECC_HW_SYNDROME;
 | |
| 	nand->ecc.read_page = mt7621_nfc_read_page_hwecc;
 | |
| 
 | |
| 	nand->dev_ready = mt7621_nfc_dev_ready;
 | |
| 	nand->select_chip = mt7621_nfc_select_chip;
 | |
| 	nand->read_byte = mt7621_nfc_read_byte;
 | |
| 	nand->read_buf = mt7621_nfc_read_buf;
 | |
| 	nand->cmd_ctrl = mt7621_nfc_cmd_ctrl;
 | |
| 
 | |
| 	/* Reset NFI master */
 | |
| 	mt7621_nfc_hw_init(nfc);
 | |
| }
 | |
| 
 | |
| int mt7621_nfc_spl_post_init(struct mt7621_nfc *nfc)
 | |
| {
 | |
| 	struct nand_chip *nand = &nfc->nand;
 | |
| 	int nand_maf_id, nand_dev_id;
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = nand_detect(nand, &nand_maf_id, &nand_dev_id, NULL);
 | |
| 
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	nand->numchips = 1;
 | |
| 	nand->mtd.size = nand->chipsize;
 | |
| 
 | |
| 	return mt7621_nfc_attach_chip(nand);
 | |
| }
 | |
| 
 | |
| void board_nand_init(void)
 | |
| {
 | |
| 	mt7621_nfc_set_regs(&nfc_dev);
 | |
| 	mt7621_nfc_init_chip(&nfc_dev);
 | |
| }
 |